Feature registry (source of truth)
Source of truth mapping every framework capability to (a) how a config-first
agent reaches it through mono-agent.config.json / env vars / the mono-agent
CLI, and (b) where the mono-agent-composer skill documents it. When a feature
is added to any package, add a row here and update the skill references.
Coverage legend
| Code | Meaning |
|---|---|
config | Declarable in mono-agent.config.json; an env override exists only where one is listed (-- means JSON-only / no env form) |
cli | Reached through a mono-agent CLI flag/command |
auto | Always active when the app runs; needs no declaration |
code | Programmatic escape hatch only (startMonoAgentApp options / lower-level packages) — intentional |
dev | Development/test-time tooling, not part of a running agent |
Env precedence everywhere: process env > mono-agent.config.json > built-in defaults.
Removal deadlines and permanent legacy-reader decisions live in the canonical
deprecation tracker.
Runtime (@mono-agent/agent-runtime, @mono-agent/runtime-adapter)
Section titled “Runtime (@mono-agent/agent-runtime, @mono-agent/runtime-adapter)”| Feature id | What it is | Coverage | Config / entry point |
|---|---|---|---|
runtime.prompt-cache-diagnostics | Metadata-only request fingerprints in run artifacts and offline token-weighted cache summary | config | providers.piNative.promptCacheDiagnostics (MONO_AGENT_PI_PROMPT_CACHE_DIAGNOSTICS), default false; measurement |
runtime.multi-backend | Pi runtime with 15+ provider backends (OpenAI, OpenAI-Codex, Copilot, Anthropic, OpenRouter, Ollama, LM Studio, OpenCode-through-Pi, …) — model refs use <provider>:<model> syntax (split at first : only) | config | runtime.model (MONO_AGENT_MODEL), e.g. openai-codex:gpt-5.6-terra, openai-codex:gpt-5.6-sol, opencode-go:kimi-k2.6 |
runtime.fallback-models | Ordered, uncapped backup routes on retryable provider failure. Each canonical entry owns optional exact effort (omission = provider default). The primary’s first attempt owns the provider session; retries and backups are stateless and record failover history. A retry/backup answer rotates a coordinated durable epoch; the next turn cold-reseeds. Warm failover has only the current message and failed-attempt snapshot, without earlier conversation history; see fallback sessions. A route change is operator-visible: an activity line while the run is in flight (chat channels and TUI, subject to activity hints) and one attribution line appended to the final answer, which also reaches cron/webhook notify payloads. A NOTHING_TO_REPORT turn stays suppressed | config | runtime.fallbacks[]: {model,effort?} (MONO_AGENT_FALLBACKS_JSON), repeated mono-agent init --fallback [--fallback-effort] |
runtime.retry | Same-model retries before the chain advances: the primary route re-runs the whole logical turn after transient provider failures (overloaded/rate-limited/timeout/network/5xx/terminated) with doubling backoff. Deterministic context_limit and provider_auth still advance immediately; cancellation and mid-turn safety failures never retry. Retries drop the provider session, emit provider_retry_started (rendered as a distinct activity line so the backoff stall is not mistaken for a hang), and append their own failoverHistory entry with retryIndex. A retry that recovers on the configured route adds no attribution note — the run’s identity is unchanged | config | runtime.retry.{primaryAttempts,backoffMs,maxBackoffMs} (MONO_AGENT_RETRY_*, default 2 attempts / 1000ms / 15000ms), per-route runtime.fallbacks[].attempts |
runtime.effort | Exact primary/per-fallback reasoning effort on supporting providers; omitted canonical route effort uses provider default | config | runtime.effort, runtime.fallbacks[].effort (MONO_AGENT_EFFORT, MONO_AGENT_FALLBACKS_JSON): none/minimal/low/medium/high/xhigh/max/ultra, constrained by model metadata. Reasoning-capable models map ultra to LOW; models without reasoning use OFF. max degrades to xhigh unless the resolved model advertises it. mono-agent doctor validates effort against the model’s advertised levels and warns, naming the nearest supported level, when a configured value is outside that set. Ranking above max only prevents keyword downgrade |
runtime.effort-keywords | Per-turn effort escalation from message trigger phrases: think → high, extra think/extrathink → xhigh, ultra think/ultrathink → max. Escalation-only vs the configured/per-trigger effort (strict increase, never a downgrade); Pi preserves model-advertised native max and otherwise clamps to xhigh; keywords stay in the message text; the resolved effort surfaces in run_config with overridden: true | auto | Always active on message-bearing turns; trigger list exported as EFFORT_KEYWORD_TRIGGERS from @mono-agent/config |
runtime.permission-mode | Tool-permission posture for the Pi runtime (validated and forwarded, not consumed by the Pi runtime itself) | config | runtime.permissionMode (MONO_AGENT_PERMISSION_MODE): default/plan/acceptEdits/bypassPermissions |
runtime.max-turns | Optional turn cap per run; omitted or 0 means unlimited | config | runtime.maxTurns (MONO_AGENT_MAX_TURNS) |
runtime.workspace | Working directory for runtime tools | config | runtime.workspace (MONO_AGENT_WORKSPACE) |
runtime.provider-sessions | Continuous provider session per conversation with idle eviction. Daily rollover can bucket conversation ids by date on every channel except the console (TUI + web), whose threads already carry a reader-owned session boundary; rolloverNotice is an adapter-visible, default-off one-line prelude on the first turn of a new bucket, without a new IPC path or provider-resume behavior change | config | runtime.session.mode + runtime.session.idleTimeoutMs + runtime.session.rollover + runtime.session.rolloverTimezone + runtime.session.rolloverNotice (MONO_AGENT_SESSION_MODE, MONO_AGENT_SESSION_IDLE_TIMEOUT_MS, MONO_AGENT_SESSION_ROLLOVER, MONO_AGENT_SESSION_ROLLOVER_TIMEZONE, MONO_AGENT_SESSION_ROLLOVER_NOTICE) |
runtime.concurrency | Admission/execution bounds for in-flight runs. Per-channel, not a single global cap: the app builds one harness per channel and each holds its own limiter, so these values bound each channel independently. With N enabled channels the effective ceiling is N× the configured value (e.g. maxConcurrentRuns: 4 across 3 channels allows up to 12 simultaneous provider runs). maxConcurrentRuns caps how many runs execute against the provider at once; maxPendingRuns caps how many runs may be admitted before the expensive provider step. Queued follow-ups on a warm session hold no slot. Scope note: these bounds cover the harness run path (which begins at responder.respond). Channel adapters (Slack/Telegram) do per-conversation admission + attachment downloads before that boundary, so cross-conversation transport download IO is not covered by these bounds (per-file byte caps + timeouts apply instead); adapter queues are drained/aborted on /cancel and stop | config | concurrency.maxConcurrentRuns (MONO_AGENT_CONCURRENCY_MAX_CONCURRENT_RUNS), concurrency.maxPendingRuns (MONO_AGENT_CONCURRENCY_MAX_PENDING_RUNS) |
runtime.local-providers | Ollama / LM Studio / OpenAI-compatible local model providers | config | providers.local[] (MONO_AGENT_LOCAL_PROVIDERS_JSON or MONO_AGENT_LOCAL_PROVIDER_*): id, type, baseUrl, apiKey/apiKeyEnv, models with capabilities/pricing |
runtime.pi-credentials | Pi credential resolution for OAuth/account providers and API-key providers such as OpenCode-Go | config | providers.piAuthPath (MONO_AGENT_PI_AUTH_PATH), default ~/.pi/agent/auth.json; opencode-go may use OPENCODE_API_KEY during setup |
runtime.providers | Declares which providers the agent supports, widening model selection to each provider’s advertised catalog (capped at maxAdvertisedModels, default 100 per provider). Every other key of the providers object is a provider id; the reserved keys piAuthPath, piNative, and local configure Pi auth, transport, and the legacy array form. ollama and lmstudio are zero-config autodiscovered via localhost | config | providers.<providerId>.{type, baseUrl, apiKey/apiKeyEnv, enabled, models, maxAdvertisedModels, trustPublicUrl} plus providers.{local[], piAuthPath, piNative.*} (MONO_AGENT_PROVIDERS_JSON, MONO_AGENT_PI_AUTH_PATH, MONO_AGENT_PI_*) |
runtime.pi-native-tuning | Host-authoritative Pi transport selection + transport retries + crash-safe durable session storage. Runs report the normalized requested mode as pi_transport_requested. With the default history store, piSessionsRoot uses a random history-owned epoch and transcript revision, separate bounded dirty fence, cross-process conversation lock, affirmative provider JSONL+directory sync, atomic clean history commit, and revision-checked warm handles. Fenced/missing/legacy/host-only/unsynced state rotates before resume; explicit invalidation durably removes rejected JSONL. Custom stores without the provider-turn transaction stay in-memory | config | providers.piNative.transport (MONO_AGENT_PI_TRANSPORT: auto/sse/websocket/websocket-cached, default auto), providers.piNative.piMaxRetries (MONO_AGENT_PI_MAX_RETRIES, 0-8, default 2), providers.piNative.maxRetryDelayMs (MONO_AGENT_MAX_RETRY_DELAY_MS, default 60000), providers.piNative.piSessionsRoot (MONO_AGENT_PI_SESSIONS_ROOT, e.g. .mono-agent/sessions; unset = in-memory) |
runtime.tool-parallelism | Safe parallel Pi tool scheduling: independent read-only built-ins may overlap only when the offered tool set contains no stateful/mutating or MCP tool. Pi 0.85 exposes only a global harness setting, so the presence of Write/Edit/Bash/Exec/NodeRepl or any MCP tool serializes the whole batch. A code-defined host can also force every tool sequential. User steering remains independently one-at-a-time | code | runtimeOptions.piToolExecutionMode: "safe-parallel" | "sequential" (default safe-parallel); deprecated piToolParallelismMode aliases warn and map forward |
runtime.web-research | Run-scoped WebSearch/WebFetch controller: auto tries explicitly configured Ollama Web Search, configured loopback SearXNG, one serialized ChatGPT-subscription Codex app-server search using structured sources only, then deterministic DuckDuckGo/Startpage fallback; named providers remain strict. Search provides exact operators, domain/relevance gates, reciprocal-rank fusion, canonical URLs, provider deferral with absolute retry time, and a configurable hard budget of four actual provider requests per logical run by default. Cache hits, coalesced followers, cooldown skips, and quota skips consume no budget. WebFetch adds deterministic charset handling, local Defuddle/Readability/Turndown extraction, JSON/feed/PDF/text parsing, bounded redirects/bodies/headers/retries, and opt-in isolated agent-browser rendering. Primary queries precede supplied alternates; search/fetch deadlines are bounded, Codex preserves a 10% quota reserve, and WebFetch line slices reuse a run-scoped document cache. Results are marked untrusted and successful searches share a process-wide bounded cache | config + auto | tools.web.coordination (MONO_AGENT_WEB_COORDINATION, default process, opt-in host admission/cooldowns/quota state with `mono-agent web-control status |
runtime.webfetch-retry | WebFetch retries transient network/body-stream failures and HTTP 408/425/429/5xx up to twice with bounded backoff/Retry-After handling so the model does not burn reasoning rounds re-fetching | auto | Built into the WebFetch tool |
runtime.context-compaction | The pi bridge drives adaptive compaction through a one-shot session_before_compact hook around AgentHarness.compact(): proactive before a turn at a model-window-derived trigger, plus reactive recovery with exactly one re-prompt only after the rebuilt context preview proves positive reduction. Rejected previews are not persisted. Numeric provider limits and generic failed-request estimates produce learned ceilings; persistent overflow is context_limit and is eligible for configured route fallback | config + provider | runtime.compaction.{enabled,triggerRatio,keepRecentTokens,summaryMaxTokens,minSavingsTokens,fixedOverheadEnabled,contextWindowOverride} (MONO_AGENT_COMPACTION_*); omitted scalar budgets adapt to the effective model window |
runtime.tool-bloat-guard | 256KB tool-output truncation with framed head/tail retention for text-only overflow and best-effort, run-specific artifact persistence | auto | Built in; raw saved blocks land under artifacts.dir/tool-output/<runId>/, are not JSONL replay, and their run directories follow artifacts.retention.{maxAgeDays,maxCount,dryRun} |
runtime.cost-tracking | Per-run usage/cost/cache metrics + events; Pi catalog estimates use Pi’s native request-wide pricing tiers and cache-write rates | auto | Recorded in JSONL artifacts |
runtime.builtin-tools | Read/Write/Edit/Glob/Grep/Bash/Exec/NodeRepl/WebFetch/WebSearch. Exec is direct argv; Bash is clean non-interactive shell execution; both preserve bounded partial output and structured failure metadata | config | Gated by tools.allowedTools / tools.disallowedTools |
agent-app.process-jobs | Opt-in Pi-native Exec/Bash background execution with no new tool: the schema gains optional background only for an available host controller, with request lineage diagnostics even at exhaustion (default depth 4, configurable ceiling 64). Background-only wake_on_completion defaults true; false keeps terminal card updates without waking. Exact sentinel-only replies suppress delivery while narration and rich parts remain visible; ambiguous wake receipts settle as unknown and never auto-replay, while a confirmed web follow-up receipt means the exact turn was durably admitted and does not wait for its later model outcome. Before store creation, a bounded monotonic generation registry retains every state root across disable/removal/A-to-B/restart; its strict single-file directory has a bounded same-filesystem sibling recovery namespace that requests inspect read-only and only mutation-locked root registration or clear-sessions preflight may repair. Configured app surfaces protect both registry control directories plus every retained lexical/canonical root under real Pi/SRT and reject any reachable non-Pi route before provider work. The JSON-only unsafe trusted-host posture requires explicit sandbox-off plus all-Pi routes, suppresses both ProcessJobs and clear-sessions synthetic SRT, and warns that ProcessJobs state plus the operator secret are model-accessible; registry failure, attestation, leases, reply private roots, and provider-zero route gates remain authoritative. Cooperative canonical-root ownership and true-settlement generation leases serialize official local hosts and store mutation, but do not claim resistance to hostile same-UID code. The owner-private store persists exact origin/run boundaries, redacted metadata, bounded output artifacts, queue/runtime clocks, state, and orthogonal wake settlement; all retained roots must be disjoint from every restart --clear-sessions purge root. Only exact Slack, Telegram, and web-console origins can wake; wakes are ordinary normal-tool/history turns with unforgeable host-owned chain depth. Slack and Telegram use serialized, same-message, exact-origin lifecycle updates with one bounded terminal fallback; busy pre-turn admission stays durably pending without spending the wake-attempt budget. POSIX process groups support descendant timeout/cancel and incarnation-matched restart cleanup; every nonterminal record becomes interrupted at restart, with no survival claim. Any later store failure closes admission, publishes live/doctor degradation, and retains bounded snapshot truth where safe. Operator API/CLI and durable web cards expose strict secret-free projections | config + cli + auto | processJobs.{enabled,unsafeAllowUnprotectedState,stateDir,maxConcurrent,maxActivePerConversation,maxQueued,maxRuntimeMs,maxQueueAgeMs,maxOutputBytes,previewChars,maxChainDepth,retention.{maxRecords,maxAgeMs,artifactMaxBytes}}; mono-agent jobs list|get|cancel [--agent] [--json]; unsupported on Windows |
agent-app.monitors | Opt-in streaming class of the process-job substrate exposing two Pi-native built-ins, Monitor and MonitorStop, registered only when a host controller is injected. A monitor keeps a command alive after the turn returns, treats each stdout line as one event, coalesces lines within coalesceMs, and wakes the exact originating conversation per batch plus once at the end, steering an in-flight run or queuing its own turn and holding no provider slot while it waits. Command preparation, workdir rules, environment cleaning, sandbox seam, and output redaction are shared with Bash rather than governed by a separate command allowlist. Tool policy defaults to wake_on: batch, dedupe: none, and min_wake_interval_ms: 0; optional consecutive batch suppression and an interval floor prevent unnecessary inference, while exit-only sends one terminal wake with a bounded tail. monitors.maxWakeIntervalMs defaults/caps at 300000 and the receipt reports the effective interval. Terminal wakes bypass suppression; durable suppression and follow-up/steered/unknown disposition counters are exposed by CLI/web. Capacity is counted independently of processJobs.*; monitors reuse that feature’s protected private-state root, registration proof, and origin binding and therefore require processJobs.enabled. At most one wake per monitor is in flight, a pending batch is bounded in lines and bytes with oldest-drop accounting reported to the model, and a sustained firehose is stopped with rate_limited plus one terminal wake. A provably pre-dispatch refusal re-offers identical content under a fresh sequence; ambiguous or possibly delivered batches are never replayed. Restart marks every live monitor interrupted, terminates the incarnation-matched process group, and owes exactly one recovery wake without ever re-running a model-authored command. The fenced event envelope states that the turn is host-raised rather than a user reply and that event text is untrusted data. Availability is Telegram, Slack, and existing user-created web conversations; web uses the owner-private notification ingress plus ordinary assistant wake turns, not a Monitor card. Cron, webhook, web:new, TUI-direct, and A2A never receive the tools | config + cli | monitors.{enabled,maxActive,maxActivePerConversation,maxRuntimeMs,persistentMaxRuntimeMs,coalesceMs,maxWakeIntervalMs,maxBatchLines,maxBatchBytes,maxLineBytes,maxChainDepth,rateLimit.{windowMs,maxLinesPerWindow,sustainedWindows}}; mono-agent monitors list|get|cancel [--agent] [--json]; requires processJobs.enabled; unsupported on Windows |
runtime.structured-output | JSON-schema-enforced output on every Pi runtime route | code | runtimeOptions.outputSchema via harness options |
runtime.subagents | The Agent built-in deploys up to maxConcurrent independent subagents per turn on the pi runtime. Each runs an isolated turn with its own prompt, tool boundary, and optional model, and returns its final answer plus a bounded per-tool-call activity log; every child tool call streams live to the TUI and web console as <profile>▸<tool>. The agent can either pick a configured profile or author a specialized subagent at call time (systemPrompt + name + tools), whose requested tools are capped by subagents.inline.allowedTools — defaulting to the parent agent’s own built-ins. Subagents are read-only by default, never receive Agent/AskUser/channel-send tools, cannot widen the sandbox, and cannot spawn subagents. Under context.skillDisclosure: "index" a subagent also inherits the parent’s skill index and ReadSkill (bodies on demand, never inlined); "disallowedTools": ["ReadSkill"] opts a profile out | config | subagents.{enabled,maxConcurrent,maxPerTurn,maxTurns,timeoutMs,definitions[],inline{enabled,allowedTools}} (MONO_AGENT_SUBAGENTS_JSON) plus Agent in tools.allowedTools; both halves are required |
runtime.live-input | In-flight user guidance on every Pi runtime route with separate native acceptance, exact owned-operation transcript consumption, and permanent uncertainty when delivery may have happened. Stable unique IDs permit replay only after proved rejection; anonymous and duplicate identities fail closed. Confirmed host settlement creates one safe-preview Steered activity with result Consumed by current run; consumption is not provider receipt or adherence. Only a message from the run’s own physical thread can steer it, so two Slack threads sharing one conversation never steer each other and an inbound message never steers a cron/proactive run | auto + code | Built into ordinary Slack, Telegram, and web-console turns through AgentResponder.offerLiveInput; custom hosts can supply runtimeOptions.liveInput |
runtime.live-input-ownership | Optional host-only ready/closed ownership events bind one admitted host operation to its actual harness run id without exposing that callback as a channel or model capability. The targeting-capable operator holds bounded exact-turn waiters and detaches them on close, mismatch, timeout, disconnect, or stop, so delayed guidance cannot reach a successor. The web console’s single UUID-bearing Send path uses that proof for active input, otherwise records a visible next-turn reason; its schema-23 submission ledger makes same-payload replay idempotent and browser recovery read-only | auto + code | AgentResponder.liveInputOwnership; responder.respond({ ...request, onLiveInputOwnership }, stream); operator capabilities.liveInputTargeting.version: 1; web POST /api/v1/threads/:id/submissions and GET /api/v1/threads/:id/submissions/:submissionId |
runtime.approval-gates | Human-in-the-loop tool approval (risk tiers, timeout, always-allow) | code | createMonoRuntime({ onToolApprovalRequest, toolRiskTiers, approvalDefaultRiskTier, approvalTimeoutMs, approvalAlwaysAllowTools }) — needs a host UI to answer; config posture is runtime.permissionMode |
runtime.custom | Any MonoRuntimeLike implementation | code | startMonoAgentApp({ runtime }) or await createConfiguredAgentResponder({ runtime }) (see composition) |
Sandbox (@mono-agent/runtime-adapter)
Section titled “Sandbox (@mono-agent/runtime-adapter)”| Feature id | What it is | Coverage | Config / entry point |
|---|---|---|---|
sandbox.mode | Native SRT wrapping for Pi-owned commands vs off | config | sandbox.mode (MONO_AGENT_SANDBOX_MODE) |
sandbox.network-policy | Enforced none / localhost / domain allowlist / all. all keeps filesystem enforcement with open egress via the SRT library-entry launch (managed or explicit node+cli only; bare binaries fail closed). Bare wildcard, IPv6 literals, paths, and port syntax stay rejected because pinned SRT cannot represent them exactly | config | sandbox.network.mode, sandbox.network.allowlist (MONO_AGENT_SANDBOX_NETWORK, MONO_AGENT_SANDBOX_NETWORK_ALLOWLIST) |
sandbox.filesystem-scopes | readable/writable roots + deny-write globs (root defaults to workspace; .env*, .git/config, .git/hooks/** denied by default). A managed worker additionally reopens only its launch-proof-verified active agent-app closure as an app-owned read-only root; request policy cannot remove it, and parent/historical closures are not admitted | config + auto | sandbox.readableRoots, sandbox.writableRoots, sandbox.denyWrite (MONO_AGENT_SANDBOX_READABLE_ROOTS, MONO_AGENT_SANDBOX_WRITABLE_ROOTS, MONO_AGENT_SANDBOX_DENY_WRITE); verified managed launch context |
sandbox.fallback | fail-closed vs unsafe-host-process when srt is unavailable | config | sandbox.fallback (MONO_AGENT_SANDBOX_FALLBACK), sandbox.unsafeAllowHostProcess (MONO_AGENT_SANDBOX_UNSAFE_ALLOW_HOST_PROCESS) |
sandbox.monotonic-merge | Request-scoped policies can only tighten, never widen | auto | Harness merges configured + request policies |
sandbox.managed-srt | macOS private-cache installation of exact pinned SRT with lock/hash/tree verification, atomic repair, and real filesystem/network enforcement proof. Runtime revalidates the managed tree per launch; corrupt managed state never falls back to PATH | cli + auto | mono-agent sandbox status [--json]|setup|check; automatic managed resolution for native Pi sandbox |
Context, skills, memory (@mono-agent/agent-harness, memory-*)
Section titled “Context, skills, memory (@mono-agent/agent-harness, memory-*)”| Feature id | What it is | Coverage | Config / entry point |
|---|---|---|---|
agent.public-name | Public display identity used as a default for human-facing trace/A2A labels only; never paths, service ids, session keys, or provider identity | config | agent.name (MONO_AGENT_NAME); guided mono-agent init / non-interactive --name |
context.identity | Identity markdown loaded into every prompt | config | context.identityPath (MONO_AGENT_IDENTITY_PATH) |
context.soul | Optional secondary voice/guardrail doc | config | context.soulPath (MONO_AGENT_SOUL_PATH) |
context.history | Conversation history assembly (owner-only durable store) | auto | 64 messages per exact conversation id independent of runtime.maxTurns; aggregate committed defaults 256 MiB / 10,000 conversations / 365 inactive days plus an independent 256 MiB live-stage cap; staged atomic publication, immediate markerless-stage recovery, non-destructive abort, deterministic post-commit pruning, and owner-only cross-process conversation/root locks; custom store via code (createConfiguredAgentResponder({ historyStore })) |
skills.selected-activation | Explicitly selected skills loaded from <skillsRoot>/<name>/SKILL.md | config | context.skillsRoot, context.selectedSkills (MONO_AGENT_SKILLS_ROOT, MONO_AGENT_SELECTED_SKILLS) |
skills.byte-capping | Per-skill instruction byte cap (default 48000) | config | context.skillMaxBytes (MONO_AGENT_SKILL_MAX_BYTES) |
memory.lite | FTS keyword recall plus a run-id-keyed, fsynced completed-turn intake that projects a canonical daily host observation. No external deps (SQLite bundled); no Ollama required | config | memory.mode: "lite", memory.path, memory.maxBytes, memory.writeMode |
memory.embeddings-config | Exclusive Ollama, LM Studio, or OpenAI semantic provider. Guided Journal/BuJo setup chooses Ollama or LM Studio, uses provider-native typed discovery, proves one real finite vector, and records service root/model/dimension plus optional apiKeyEnv. Manual authoring fallback cannot bypass first-run readiness; there is no cross-provider failover | config + cli | memory.embeddings.{provider,endpoint,model,dim,apiKeyEnv} (MONO_AGENT_MEMORY_EMBEDDINGS_*); guided mono-agent init |
memory.journal | Strict no-chat-LLM tier: fsynced completed-turn intake, case-preserving normalized-content hash dedupe, canonical lexical projection, bounded/background batched semantic indexing, and hybrid BM25+vector RRF recall. Embedding latency/failure is off the terminal-reporting critical path; restartable backlog/intake accounting is observable | config | memory.mode: "journal", memory.path, memory.maxBytes, memory.embeddings.{provider,endpoint,model,dim,apiKeyEnv} (MONO_AGENT_MEMORY_EMBEDDINGS_*) |
memory.bujo | Strict curated tier: fsynced run-id intake, immutable raw audit outside recall, then restartable capture using one contract-explicit, exactly validated memory/graph extraction plus at most one exact batch reconcile call (ADD/UPDATE/SUPERSEDE/NOOP), precise associations, explicit one-hop tool recall, static canonical salience, and projection-only consolidation. Provider/model failure retries durably; invalid or partial output never succeeds. Its selected embeddings service is independent from its explicit agent-host or Ollama capture LLM; no silent downshift | config | memory.mode: "bujo", memory.path, memory.embeddings.{provider,endpoint,model,dim,apiKeyEnv}, memory.llm.{provider,model,endpoint} — see docs/memory/index.md |
memory.backend-supermemory | Optional, explicitly installed external memory backend. @mono-agent/memory-supermemory proxies the same MemoryStore and MemoryRecall contracts to a local or hosted Supermemory service. The strong persistCompletedTurn path awaits a run-keyed remote upsert and propagates admission failure; service extraction/indexing remains asynchronous, while legacy append/schedule calls remain best-effort. load(conversationId, query?) searches the query or falls back to the conversation id. Hosted use sends completed-turn text and recall queries off-machine. The package is lockstep-versioned but remains outside the default app install | config | memory.backend: "supermemory", memory.writeMode, memory.supermemory.{baseUrl,apiKey,apiKeyEnv,container,timeoutMs,exposeMcpServer} (MONO_AGENT_MEMORY_BACKEND, MONO_AGENT_MEMORY_SUPERMEMORY_*); install the exact matching @mono-agent/memory-supermemory version — see docs/memory/backends-comparison.md |
memory.bujo-replay-projection | Owner-only root .replay-projection-v1.json is exact metadata-only canonical authority for BuJo capture thread edges, supersession lifecycle/edges, and migration-forget terminal timestamps. Strict health requires exact SQLite parity and reports orphaned sidecar publication temps as temporary_artifacts; plausible raw SQLite replay state is RED and missing+nonempty legacy state is never auto-blessed. Rebuild fingerprints/preserves the sidecar, and replay changes retire only a rollback in the BuJo source domain. A stopped managed generation or unmanaged legacy BuJo DB can be bound once through explicit TOFU when its SQLite family is owner-only. Multiple disjoint capture intents/receipts and at most one migration marker can attest an exact replay subset; mutable pending capture and migration are mutually exclusive, while completed receipts may coexist. Immediate rebuild completes mutable work without repeating provider work, then uses configured embeddings for the replacement generation before restart | auto + cli | mono-agent memory adopt-replay --json, then mono-agent memory rebuild --json; Lite/Journal do not consume the sidecar and reject replay-owned DB state |
memory.bujo-consolidation | Auto-scheduled projection-only pass: refresh index.md, keep future-log.md empty, and report the exact-normalized duplicate-group count. It never decays salience or automatically supersedes/rewrites canonical memory. In-app scheduler; no external cron needed. Override the five-field UTC cadence (hashed H fields are rejected) or disable it | config | memory.consolidation.{enabled,cron} (default 0 */2 * * *); env MONO_AGENT_MEMORY_CONSOLIDATION_CRON, MONO_AGENT_MEMORY_CONSOLIDATION_ENABLED |
memory.bujo-cli | Removed. The standalone memory-bujo bin and its env-var-driven <root> workflow are gone. Memory maintenance runs config-aware through mono-agent memory <subcommand> from the agent folder — rebuild/rollback are config-aware and recall became search. Legacy index and reflect have no one-for-one scheduled replacement: the in-app scheduler calls only projection-only store.consolidate(). migrate was a historical v1→v2 workflow and has no current CLI replacement | cli | Removed; use mono-agent memory <subcommand> — see docs/reference/deprecations.md and docs/memory/validation-and-cli.md |
memory.preview-cli | Config-aware memory operator surface: stats, daily previews, search, top, detailed local audit, strict health, metadata-only intake inspect/retry/resolve, safe side-by-side rebuild/rollback, explicit metadata-only legacy BuJo replay adoption, and explicit reversible BuJo forget plans. It remains available when memory.recallTool.enabled is false; preview shares the live resolver for backend, Supermemory-container, embeddings, and credentials while bypassing only the live-tool gate. Forget prepare is canonical-source-only and writes a content-free owner-private plan for at most 32 ids; stopped-store apply owns the authoritative writer lease and durable sibling recovery fence, creates an fsync-verified sibling backup, uses durable migration-forget semantics, and rebuilds the managed generation. Failure auto-restores; process death blocks writers until recovery resumes; explicit restore atomically consumes the snapshot without a third copy and refuses to erase any later durable file. Plain audit consumes a coalesced runtime heartbeat and may include configured paths/source locations. Rebuild refuses live writers, validates exact schema/payload/vector/graph/replay/source identity, atomically activates a versioned generation under SQLite writer and exact manifest-temp fences, and retains rollback only as a fresh immutable online backup with exact canonical-source parity plus a WAL-visible logical integrity commitment. Divergent legacy or outgoing indexes are preserved but never advertised as safe rollback | cli | mono-agent memory stats|today|show <date>|search <query>|top|audit|inspect [id]|retry [id]|resolve <id> <reason>|rebuild|rollback|adopt-replay|forget prepare|apply|restore|export|import prepare|apply|restore [--json] [--config <path>] [--env-file <path>]; --strict is audit-only, --limit is stats/search/top-only, and the bundle flags are export/import-only |
memory.bundle-transfer | Portable memory bundles for backup, machine migration, and seeding one agent from another. export writes an owner-only directory holding manifest.json plus a root-shaped source/ tree of dated daily/*.md, graph.jsonl, and the exact replay projection; managed indexes, capture intake/outbox, and derived projections are excluded. Export is read-only, needs no writer lease and no stopped agent, and proves consistency with a before/after canonical fingerprint plus a re-derivation from the copied bytes, retrying a racing write before failing closed. import prepare verifies bundle ownership/mode, strict manifest and digest, tree and canonical fingerprints, and rejects CRLF sources, then writes a content-free owner-private plan binding a merge digest. Merge dedupes by exact bullet bytes (so re-import is idempotent), fails closed on a differing id by default, never renames ids, keeps imported bullets in dated daily files, prefers this store’s entity metadata, set-unions relations/associations with verbatim provenance, and delegates every replay lifecycle conflict to the replay authority. Derived legacy-name-match association drift on non-imported memories is reported, and removals require an explicit flag. import apply needs a stopped agent, recomputes and re-binds the merge digest, makes an fsync-verified sibling backup, writes daily then graph then replay, and rebuilds — re-embedding under this agent’s provider, so a different embedding model or dimension is accepted. Import backups join the forget retention sweep; the undo is import restore, not rollback | cli | mono-agent memory export --bundle <dir> [--include-extras] [--allow-pending]; memory import prepare --bundle <dir> --plan <file> [--on-conflict fail|skip] [--entity-conflict target|source] [--accept-derived-association-drift]; memory import apply --plan <file>; memory import restore --backup <dir> |
memory.strict-health | Provider-free, snapshot-coherent health over managed identity, SQLite/FTS/vector/canonical—including exact BuJo replay projection—and rollback-source parity, durable intake/outbox, temporary artifacts, and runtime metadata. Unattested replay lifecycle/edges are canonical_mismatch, even when structurally plausible. Fresh owned work is in_progress; due ownerless work becomes work_stalled after a private 90-second grace. Exact schema v1 publishes only backend/mode, one closed status, ISO check time, closed issue codes, and eight counts; never paths, filenames, ids, text, payloads, raw errors, or extras. healthy/in_progress/not_configured exit 0; degraded/unhealthy/unknown exit 1; misuse exits 2 | cli + auto | mono-agent memory audit --strict --json; cached steady-state trace projection at least 30 seconds after the prior completion, plus one forced post-start/reload refresh |
memory.intake-ops | Payload-free completed-turn intake inspection plus stopped-store recovery. Inspect exposes only stable ids/state/timing/attempt/failure categories. Retry makes dead/delayed work due; resolve explicitly abandons one item as operator_resolved, preserves permanent duplicate protection, and refuses a retained semantic plan. Mutations verify no live matching agent and acquire the memory writer lease | cli | mono-agent memory inspect [<id>]; memory retry [<id>]; memory resolve <id> <reason-slug> |
memory.validate | mono-agent validate dynamically checks built-in native-module availability and managed identity before provider probes. Journal/BuJo always require a valid manifest; only Lite may remain unmanaged. Missing/corrupt metadata and active/configured tier/provider/model/dimension mismatch are errors with stop/rebuild/revalidate remediation. Ollama and LM Studio use their own typed catalogs and real embedding endpoints; one non-empty finite vector must match configured dimension. Missing declared LM Studio auth and operational provider failures are waiting; no probe crosses providers. --json emits one prose/ANSI-free {ok,sections,...} object and exits 0 iff ok | cli | mono-agent validate [--consumer] [--config] [--json] |
memory.write-mode | How the host persists each completed turn: disabled; append-host-summary; or capture. The built-in backend uses fsynced run-id admission before Lite/Journal daily or BuJo raw-audit projection; built-in capture additionally requires BuJo and admits the full turn before curation. Supermemory awaits a run-keyed remote upsert for either enabled mode, with full capture text present only in capture, then performs service-owned asynchronous extraction. Strong admission failure is explicitly degraded; legacy stores without persistCompletedTurn retain the best-effort append/schedule compatibility path | config | memory.writeMode (MONO_AGENT_MEMORY_WRITE_MODE); built-in capture requires memory.mode: "bujo", while Supermemory accepts capture independently of compatibility mode |
memory.per-turn-capture | Built-in BuJo capture: durably admit the run before terminal reporting, then perform restartable serialized curation with exactly one contract-explicit, strictly validated extraction and at most one strict batch-reconcile chat call. Reconcile prompts require exact per-action objects: ADD index/action only, NOOP plus supplied target, and UPDATE/SUPERSEDE plus target/replacement text. The strict parser never fills or coerces model values; invalid ranges, identifiers, references, or partial output retry as a whole. Precise associations and run-derived ids only; provider failure retries, a timed-out stop leaves pending work, and post-commit crash replay cannot duplicate semantic facts or publish unattested lifecycle/thread edges | config | Built-in backend with memory.writeMode: "capture" (MONO_AGENT_MEMORY_WRITE_MODE=capture) and memory.mode: "bujo"; Supermemory’s service-owned capture is documented by memory.backend-supermemory |
memory.recall-tool | Auto-provisioned targeted read-only MemoryRecall, default on for every configured backend and direct configured responder. Automatic/tool recall share one store/cache; automatic injection is direct-fact-only and capped at five hits / 8 KB. BuJo explicit tool recall may expand exactly one graph hop. Unqualified current/last-message queries bypass durable lookup and use active conversation history; broad explicit-period retrospectives route to MemoryJournal when available; interrupted-work requests route to RunHistory {} first when available, including from an empty recall result | config | config.memory.recallTool.enabled (MONO_AGENT_MEMORY_RECALL_TOOL_ENABLED, explicit false opts out of both explicit memory-read tools without changing automatic recall) |
memory.journal-browse | App-owned read-only request-scoped MemoryJournal for broad retrospectives over explicit inclusive local dates in a required IANA zone. Lite, Journal, and BuJo affirm local support over curated canonical daily/index records; BuJo raw audit observations and dropped records are excluded, while lifecycle/supersession state and safe relative provenance remain visible. Supermemory is unsupported with no search fallback or fake empty result. A first call spans at most 31 days and freezes at most 1,000 entries / 2 MiB; at most four run-local snapshots, 25 entries / 8 KiB per page, 2 KiB text per entry, and request-private authenticated cursors bound to the exact issued offset keep enumeration stable. Results distinguish empty, incomplete, failed, disabled, and unsupported states, are labelled untrusted curated summaries, and direct exact claims to RunHistory/SessionHistory | config + auto | Shares config.memory.recallTool.enabled; additionally policy-gated by MemoryJournal, mcp__mono-agent-memory-journal__MemoryJournal, or mcp__mono-agent-memory-journal__* (MONO_AGENT_ALLOWED_TOOLS, MONO_AGENT_DISALLOWED_TOOLS). No new config key or provider call |
memory.remember-tool | Agent-callable Remember that durably stores one explicitly stated fact: deterministic, append-only, and taking no chat LLM, so a success means the fact is already recallable rather than queued for curation. The text is stored NFKC-normalized, trimmed, and whitespace-collapsed to one line, and the tool echoes back exactly what was written. Bujo backend only (all three tiers) and writable stores only; the supermemory backend never advertises the capability, so a configured memory block does not by itself grant a write surface. Writes are idempotent across partial failure: the bullet id derives from the content hash and an unindexed canonical bullet is completed rather than duplicated, including after a UTC date rollover. Text carrying a configured credential value, a well-known token shape (OpenAI, GitHub classic and fine-grained, Slack bot and app-level, AWS key ids, bearer/basic, Telegram, case-insensitive), a credential assignment, or terminal/bidi controls is rejected and nothing is written; the scan uses the host-resolved environment and compares in one Unicode domain — defense in depth, not a guarantee. A canonical write whose index failed reports durable-but-unindexed and asks for an identical retry rather than a reword. Explicitly forgotten facts and root-level legacy date layouts are refused rather than mishandled | config | config.memory.rememberTool.enabled (MONO_AGENT_MEMORY_REMEMBER_TOOL_ENABLED, explicit false opts out); allowlist-gated, so a restrictive tools.allowedTools must name Remember and deny wins |
memory.llm-timeout | Per-call timeout for the in-app memory LLM used by per-turn capture. A timeout now reports agent-host memory LLM timed out after <ms>ms (provider too slow or unavailable) instead of a generic cancelled | config | memory.llm.timeoutMs (MONO_AGENT_MEMORY_LLM_TIMEOUT_MS, 1000–600000, default 60000) |
memory.custom-store | Any MemoryStore implementation | code | await createConfiguredAgentResponder({ memory }) (async since the lazy-backend change; see composition) |
Tools & MCP (@mono-agent/agent-harness)
Section titled “Tools & MCP (@mono-agent/agent-harness)”| Feature id | What it is | Coverage | Config / entry point |
|---|---|---|---|
tool-policy.allow-all | Omitted / ["*"] allowedTools = all tools (the default); explicit [] = none on runtimes that enforce it. Guided init discloses shell/file/web/channel effects and reconfirms allow-all when unsandboxed. The programmatic harness safety net with no policy is failClosedToolPolicy() | config | Default tools.allowedTools (MONO_AGENT_ALLOWED_TOOLS) |
tool-policy.allowlist / tool-policy.denylist | Tool allow/deny (deny wins where enforceable; overlap rejected). A wildcard anywhere in allowedTools means allow-all. The Pi runtime reports tool_policy: "projected" on every route. Custom structural bridges without the field should be treated conservatively. Pi-native cannot deny external MCP tools, whose server declaration remains the boundary | config | tools.allowedTools, tools.disallowedTools (MONO_AGENT_ALLOWED_TOOLS, MONO_AGENT_DISALLOWED_TOOLS) |
tool-policy.mcp-servers | MCP servers (stdio/sse/http) from a JSON file; inlined for SDK runtimes, path forwarded for CLI runtimes | config | tools.mcpConfigPath (MONO_AGENT_MCP_CONFIG_PATH) → mcp.json |
agent-app.rich-replies | Request-scoped rich reply production with one shared 20-part budget: PublishReplyFile descriptor-copies a confined generated file into owner-private, integrity-bound storage through an atomic staged publication, while Pi-native MCP tool results can register the exact declared MCP App UI resource for the web console. Slack uses its external upload flow, Telegram uses sendDocument, and both retain a safe human fallback until upload confirmation; OpenAI-compatible, webhook, A2A, and cron/verbatim output is never mutated. MCP App bridge connections are LRU/idle bounded; exact-origin resource reads, host-intersected CSP, nonce/source/invocation binding, rate limits, rotating audits, redacted confirmation previews, and a double opaque-origin iframe sandbox bound the executable UI | config + auto | PublishReplyFile is available under allow-all; a restrictive tools.allowedTools must name it. MCP Apps advertise only when every possible runtime route supports the Pi-owned bridge. Storage retention follows artifacts.retention.maxAgeDays (MONO_AGENT_ARTIFACT_RETENTION_MAX_AGE_DAYS); no new config key |
agent-app.durable-continuations | Origin-bound asynchronous results: selected stdio/loopback-HTTP MCP services claim during the originating request, submit one immutable later payload, and the host performs tool-free synthesis plus native thread delivery. Interactive claims use a bounded immutable snapshot prepared before origin commit and activated only after commit; legacy, missing, or corrupt snapshots use a fixed zero-model fallback. Detached services can use only named host routes. Continuation state, retry/dead-letter/unknown outcomes, and receipts remain separate from the originating run and from local process jobs | config + code | tools.continuationServers (MONO_AGENT_CONTINUATION_SERVERS), continuations.{enabled,host,port,stateDir,namedRoutes,detachedServices}; app handle exposes health/list/captured-text/retry/cancel/resolve and mono-agent continuations is the authenticated operator CLI |
agent-app.run-history-tool | App-owned read-only request-scoped RunHistory tool over normalized local run artifacts. Empty input lists settled prior runs, including cancelled/interrupted work; query matches sanitized trigger/user input plus summary metadata without reading event JSONL, ranked by matched terms (full matches win outright; ranked partial matches only when none matched fully, flagged by matchedAllTerms: false) and, when exactly one run matches, hydrating that run’s compact overview through the same bounded event read and safe projection as inspect; runId returns a compact overview; runId + cursor returns bounded timeline pages. Explicit list/search/inspect actions and run_id remain compatible. Results provide tool-authored guidance, terminal status, exact next-call arguments, and a run-scoped SessionHistory handoff for cancelled/interrupted evidence. Daily rollover buckets are ignored for logical-conversation scope, while current/running runs and unrelated conversations/threads remain excluded. System prompts, reasoning, recalled memory/turn context, raw artifact paths, and nested RunHistory result bodies are excluded. Ordinary filesystem spans are sanitized in place to [host-path] plus a bounded non-sensitive suffix, so surrounding commands, tool results, and assistant diagnostics remain visible; credentials and private run-artifact content are still omitted, and absolute roots or private run paths never survive. Structured projected values first pass through the shared observability redactor: non-numeric values under sensitive-looking object keys are redacted; numeric values under matched keys are retained; free text is not content-scanned or scrubbed. RunHistory then applies an additional projection sanitizer to object keys as well as string values, with deterministic collision-safe key disambiguation. In that second pass, numeric values under credential, private_key, and bearer can remain visible; numeric values under apiKey, token, client_secret, password, authorization, and cookie are redacted. Assignment-shaped password or secret prose is content-scanned and replaced with the diagnostic or tool-result omission sentinel. An optionally quoted assignment value is exempt only when its complete value is exactly [redacted]; any prefix or suffix is omitted. Evidence is page-bounded, incomplete-input-marked, and labelled untrusted | auto | No new config key. Auto-available under allow-all on MCP-capable routes; a restrictive tools.allowedTools must include RunHistory (run_history is a deprecated policy alias). Deny policy can remove it; direct OpenCode/MCP-incompatible routes suppress it |
agent-app.session-history-tool | Canonical managed-tool lifecycle persistence plus the sibling read-only request-scoped SessionHistory tool. The harness incrementally fsyncs securely pre-bounded, redacted, host-path-opaque invocation and terminal-result pairs to an owner-only SQLite sidecar independent of successful-turn history commit; stable conversation/run/call ids and writer-assigned per-run sequences make recovery idempotent, and a dangling start closes as interrupted without rerun. Client publication waits at most 250 ms: an accepted write still queued or syncing is emitted as immutable deferred metadata and reconciled before bounded run finalization, while only a definitive writer rejection is failed; after the run, SessionHistory is authoritative for committed rows. Eight terminal states, failure taxonomy, truncation metadata, opaque artifact availability limited to validated regular files under the run-specific tool-output root, isolated-run flags, retention/tombstones, and distinct unresolved fail-soft incidents survive restart and compaction; identical failed retries deduplicate and only the matching tool phase or canonical run-binding retry resolves lifecycle write/conflict incidents. Cold provider reseed receives bounded neutralized text while true warm resume omits history; automatic corrupt-store enrichment emits a structured warning and continues, but explicit search/get/stats fail closed. search provides text/tool/state/run/time filters, validated opaque cursors, at most 10 bounded previews, and trusted navigation that distinguishes invocation recordId from terminal resultRecordId; exact result/invocation reads preserve isolated access and use 8 KiB chunks, while get navigation supplies each bounded cursor continuation. Current-run and foreign-conversation records stay opaque, isolated/proactive runs are opt-in, nested history-tool bodies are omitted, and all evidence is untrusted. Neither history tool resumes provider state, replays tools, reruns work, or guarantees continuation | auto | No new config key. Auto-available under allow-all on request-scoped MCP-capable routes; a restrictive tools.allowedTools must include SessionHistory (session_history policy alias accepted). Deny policy can remove it. Direct OpenCode/ACP persist and cold-project records but suppress the tool and doctor reports unsupported_route; RunHistory’s pre-existing direct-ACP routing is unchanged. Logical-session reset removes all matching daily message/tool buckets; restart --clear-sessions purges all provider/message/tool continuity with separate counts |
agent-app.web-conversation-title | App-owned request-scoped SetConversationTitle tool for agent-maintained semantic titles in ordinary interactive web threads. The host grants title-write authority only when the request source, logical web:<threadId> conversation, active turn, and automatic writable title agree. The strict tool normalizes one title up to 80 characters and guides the model to name the conversation as a whole, refining it whenever a better whole-thread name emerges rather than tracking the current step. Its result stays a proposal: the host applies the title out of band and may decline it. The web service consumes only a successful structured result from the exact active turn, conditionally updates SQLite, emits normal thread invalidations, and retains the call in collapsed Activity without adding a chat message. Trigger/archived threads are ineligible; a user rename permanently wins, including races | auto | No new config key. Auto-available under allow-all on compatible routes; a restrictive tools.allowedTools must include SetConversationTitle, and deny wins. Any configured or accepted direct OpenCode route suppresses it. If unavailable or unused, the first user message remains the fallback title |
agent-app.adapter-send-tools | App-owned MCP tools for sending through already-enabled Slack and Telegram adapters: SlackSendMessage, TelegramSendMessage (optional non-blocking reply_options), and TelegramSendFile (document or photo via a kind param), plus one structured blocking AskUser tool across web, Slack, and Telegram. Successful message sends idempotently record the exact confirmed text in destination history without turning a later history failure into a false delivery failure. Strict producing-conversation TelegramSendFile removes model-owned destination input, derives and allowlist-checks the host-bound Telegram chat, and omits its raw id from the tool result | config | Auto-available under allow-all (the default) once the channel is enabled; a specific tools.allowedTools needs the exact tool names, plus valid slack.* / telegram.* adapter config in either case; existing adapter allowlists remain the destination boundary. Native sandbox networking must separately admit slack.com, the Telegram API/custom apiRoot host, and the configured interaction-bridge host for message history and ask tools; validate names missing hosts |
interaction.bridge | App-owned bridge for receipt-confirmed adapter-send history, structured blocking AskUser interactions, and run-scoped MCP progress. It defaults to 127.0.0.1; keep the host loopback because non-loopback values are not rejected. AskUser accepts one to five questions, each with two or three described options, optional multi-select, and custom replies; web submits all questions in one form while Slack/Telegram advance native buttons sequentially. Its default 10-minute wait can be disabled explicitly with interaction.askUser.timeoutMs: null (env none), leaving the ask pending until answer, cancellation, or app stop. It auto-starts for configured Slack/Telegram send tools, when AskUser is allowed, when the interaction block or an interaction env override is configured, or when interaction.progress.enabled resolves true and tools.mcpRequestContextServers names an opted project MCP server. Master bridge credentials remain host-owned; send-tool children receive run-and-channel-scoped history capabilities, while opted project stdio MCP children receive separate run-scoped progress capabilities | config + auto | interaction.bridge.{host,port}, interaction.askUser.timeoutMs, interaction.progress.enabled, tools.mcpRequestContextServers (MONO_AGENT_INTERACTION_BRIDGE_HOST, MONO_AGENT_INTERACTION_BRIDGE_PORT, MONO_AGENT_ASK_USER_TIMEOUT_MS, MONO_AGENT_PROGRESS_ENABLED, MONO_AGENT_MCP_REQUEST_CONTEXT_SERVERS) |
tool-policy.filesystem-roots | Extra filesystem roots for managed Read/Write/Edit/Glob/Grep when sandbox.mode is off. Read and write roots are explicit; every write root is also readable. Lexical and realpath containment reject traversal and symlink escapes. Native sandbox roots remain authoritative when sandboxing is enabled | config | tools.filesystem.{readableRoots,writableRoots} (MONO_AGENT_FILE_TOOL_READABLE_ROOTS, MONO_AGENT_FILE_TOOL_WRITABLE_ROOTS) |
agent-app.blocking-ask-history | Answered or expired blocking AskUser interactions commit a compact, untrusted-labelled questions/outcome/answers transcript into the logical producer’s assistant history copy; cancelled asks are not journaled. Described options are retained when the bound permits; an oversized newest valid entry may omit option descriptions so its questions, outcome, and answers remain whole. Cold/stateless provider replay therefore retains the out-of-band exchange even when the physical interaction destination differs. The delivered final message and long-term memory capture stay unchanged; non-blocking TelegramSendMessage.reply_options taps remain separate next turns | auto | Interaction bridge plus configured harness history commit; no config key |
Channels (@mono-agent/*-adapter, composed by @mono-agent/agent-app)
Section titled “Channels (@mono-agent/*-adapter, composed by @mono-agent/agent-app)”Built-in channels are independent JSON sections: telegram, slack,
webhook, openaiApi, cron, and tui. External channel packages are
declared under channels.plugins[], resolved by package name, and must return
the same ChannelDriver shape from @mono-agent/agent-contracts. The current
cataloged channel extras are @mono-agent/a2a-adapter and
@mono-agent/whatsapp-adapter, and @mono-agent/messenger-adapter. Most channels are opt-in via an enabled flag
(default off); the tui operator surface defaults on so local
operator tools can discover running agents without per-agent edits. A channel
that is off reports disabled; an enabled channel with incomplete config
reports waiting_for_config. Either way it never blocks the rest. Adapter
fields can also have MONO_AGENT_<CHANNEL>_* env vars.
| Feature id | What it is | Coverage | Config section / keys |
|---|---|---|---|
telegram.long-polling | Telegram bot via long polling, with a self-healing runner (the Slack slack.socket-mode analog). Allowed groups can use a local mention-only trigger boundary: native @mentions of the bot and replies to its messages run, while unrelated group conversation is ignored before ask/media/live-input admission. On a genuine crash an auto-restart monitor recreates the runner with exponential backoff (500ms doubling → 30s cap, reset after a 30s stability window); transient getUpdates errors self-retry in-runner first; the Bot API HTTP timeout is capped (50s client / 30s long-poll) so a half-open socket fails fast instead of hanging ~8 min. A poll-liveness watchdog force-restarts a silently-deaf runner that stops delivering updates without crashing. A transient poll crash reports the channel degraded and self-recovers to running once stable. Startup deleteWebhook is bounded (5s, best-effort). The crash-restart/retry loop is on by default and code-only | config | telegram.enabled, telegram.botToken (MONO_AGENT_TELEGRAM_BOT_TOKEN), telegram.allowedChatIds or telegram.allowAllChats; telegram.groupMode (MONO_AGENT_TELEGRAM_GROUP_MODE) and telegram.stripMentionText (MONO_AGENT_TELEGRAM_STRIP_MENTION_TEXT); telegram.pollWatchdogMs (MONO_AGENT_TELEGRAM_POLL_WATCHDOG_MS); telegram.transport.ipFamily (MONO_AGENT_TELEGRAM_IP_FAMILY) |
telegram.interactive | Built-in /model and /effort inline menus select only the configured primary/fallback catalog, with in-memory per-chat state reset by default or process restart; incompatible effort is cleared on model change, different-model turns are isolated, and public proactive notify keeps configured defaults. The same command menu can include config-driven command→prompt dispatch (built-in /start /help /cancel /model /effort reserved). UX adds lifecycle reactions (👀 working / 👍 done / 👎 error), sequential native AskUser buttons with typed custom replies and multi-select Done, non-blocking TelegramSendMessage.reply_options, outbound files via TelegramSendFile, and quiet-hours silent proactive notifications. | config+code | Runtime menus and AskUser presentation are app wiring; custom telegram.commands[] ({command, description, prompt?}), telegram.reactions (MONO_AGENT_TELEGRAM_REACTIONS), telegram.quietHours ({start, end, timezone}); AskUser, TelegramSendMessage, and TelegramSendFile via tools.allowedTools when policy is restrictive |
telegram.transcription | Opt-in auto-transcription of inbound Telegram audio (voice notes, audio files, and round-video video_notes — the latter also newly accepted as inbound attachments, downloaded as video/mp4). After download, the bytes are POSTed as multipart/form-data (file, model, optional language) to an OpenAI-compatible /v1/audio/transcriptions endpoint (e.g. a local WhisperKit server) and the transcript is inlined into the attachment text the model sees on the current turn. Failures degrade to the previous saved-file behavior with a one-line note — the run never fails on transcription errors. Per-call bound timeoutMs (default 120s), independent of the attachment download timeout. Off by default (no endpoint → no calls) | config | telegram.transcription.endpoint (MONO_AGENT_TELEGRAM_TRANSCRIPTION_ENDPOINT), telegram.transcription.model (MONO_AGENT_TELEGRAM_TRANSCRIPTION_MODEL, required with endpoint), telegram.transcription.language (MONO_AGENT_TELEGRAM_TRANSCRIPTION_LANGUAGE), telegram.transcription.timeoutMs (MONO_AGENT_TELEGRAM_TRANSCRIPTION_TIMEOUT_MS) |
slack.socket-mode | Slack Socket Mode bot with a self-healing reconnect loop (mirrors Telegram #105/#108). A built-in heartbeat watchdog (on by default: 30s ping probe / 90s silence budget) detects and force-recycles a silently half-open socket so the connection self-heals after host sleep or a network blip. Non-graceful exits use terminate-first teardown (avoids too_many_websockets orphans); reconnect backoff has jitter on by default (ratio 0.2) and only resets after a connection stays open past a stability window (not per-connect). Slack’s warning/refresh_requested reasons take a graceful no-backoff path. A non-graceful connection loss reports the channel degraded (responder kept alive) and returns to running on recovery. Socket Mode also routes workspace-registered /<bot>-model and /<bot>-effort commands; names derive from validated auth.test.user, shared-channel choices are channel-wide, thread-local mention commands override them, and Block Kit model identifiers render literally without emoji expansion. Optional native-message unfurl controls apply to interactive replies and normal-stream cron notifications while preserving Slack defaults when omitted | config + code | slack.enabled, slack.botToken, slack.appToken, slack.allowedChannelIds or slack.allowAllChannels, slack.botUserIds, slack.mentionTextAliases, slack.stripMentionText, slack.unfurlLinks (MONO_AGENT_SLACK_UNFURL_LINKS), slack.unfurlMedia (MONO_AGENT_SLACK_UNFURL_MEDIA). Runtime slash commands require Slack app registration plus the commands bot scope but no mono-agent config key. When unset, preserves one readable authenticated self-mention marker; true restores legacy full stripping and false keeps raw mention forms. Each omitted unfurl setting leaves its chat.postMessage request field absent. Socket Mode resilience is operator-tunable (all optional integers, ms; omit to use the default): slack.heartbeatIntervalMs (MONO_AGENT_SLACK_HEARTBEAT_INTERVAL_MS, default 30000), slack.heartbeatTimeoutMs (MONO_AGENT_SLACK_HEARTBEAT_TIMEOUT_MS, default 90000; 0 disables the watchdog), slack.reconnectInitialBackoffMs (MONO_AGENT_SLACK_RECONNECT_INITIAL_BACKOFF_MS, default 500), slack.reconnectMaxBackoffMs (MONO_AGENT_SLACK_RECONNECT_MAX_BACKOFF_MS, default 30000), slack.reconnectStabilityMs (MONO_AGENT_SLACK_RECONNECT_STABILITY_MS, default 30000), slack.reconnectStartupGraceMs (MONO_AGENT_SLACK_RECONNECT_STARTUP_GRACE_MS, default 10000 — quietly retries a lingering prior-process socket instead of flagging degraded), slack.drainDeadlineMs (MONO_AGENT_SLACK_DRAIN_DEADLINE_MS, default 5000 — backstop after a watchdog terminate) |
slack.speaker-names | Each inbound Slack turn carries who sent it, so the agent can address people by name in a shared channel. Slack events expose only a user ID — which doubles as a DM channel ID and is therefore treated as a host-only delivery target, never prompt content — so the adapter resolves the sender’s display name and handle through users.info behind a bounded in-process cache (500 entries, 30-minute TTL, 5-minute negative TTL, concurrency 3). Strictly best-effort and on by default: a missing_scope failure logs once and latches the lookup off for the process, a rate limit or deleted profile is negative-cached, and every failure mode leaves the turn unnamed rather than failing it. A resolved name is durable — it becomes the stored conversation turn’s speaker label and the memory-capture label — so it is separately switchable | config | slack.resolveUserNames (MONO_AGENT_SLACK_RESOLVE_USER_NAMES, default true). Requires the users:read bot scope |
channels.surface-awareness | Every Slack and Telegram turn tells the agent which surface it is on — DM vs. shared channel vs. group, the surface’s human name, its channel/chat id, and the per-message character budget with what happens to a longer answer. Behaviour legitimately differs by surface (a Slack channel run wakes only on app_mention, a DM run does not; a channel has several readers, a DM has one), and the agent could previously only guess. Kind and id are always stated; the Slack name needs conversations.info behind a bounded 200-entry cache that latches off on missing_scope. The budget is read from the transport’s own limit so it cannot drift. Cron, webhook, and CLI turns disclose no surface and their Session block is byte-identical to before. Interactive console turns (web console, terminal TUI; metadata.source web/tui) are classified separately: the block names the console and states the thread’s own conversation id verbatim (e.g. web:<threadId>) so host-side tools and operator commands that bind work to the thread can be given it exactly; it is still not a delivery target and no other web:* thread is reachable through it. Ids are model-visible by design — the thread ts, reply-target conversation id, callback URLs, delivery tokens, and the platform user id all remain host-only, but a deployment running SlackSendMessage with allowAllChannels can post to any channel id the model has seen | auto (Slack name: config) | Always on; slack.resolveChannelNames (MONO_AGENT_SLACK_RESOLVE_CHANNEL_NAMES, default true) adds the Slack channel name and requires channels:read/groups:read |
slack.thread-context | The agent receives what was said before it was triggered, so a mention deep in a thread is answerable. An in-thread trigger reads that thread (conversations.replies); a top-level channel mention or DM reads recent history (conversations.history). The result becomes the shared contract’s precedingMessages, which the harness renders as a bounded, fenced, explicitly untrusted transcript that is never written to durable history or memory. Slack is the first channel to produce it. Built around Slack’s May-2025 cap of roughly 1 read/minute and 15 objects for non-Marketplace apps: exactly one request per admitted turn, no retries, no pagination, and a per-channel cooldown breaker seeded from Retry-After. Because Slack’s docs contradict themselves on which end limit truncates, the replies path asks for a page anchored at the trigger and verifies the trigger came back — an unanchored page yields no transcript rather than a misleading one. The whole phase (read plus name resolution) is raced against one deadline, so a client that ignores the abort signal still cannot delay the turn. Own posts are excluded by user id and app id; other apps’ messages are included and labelled isBot | config | slack.threadContext.{enabled,maxMessages,requestLimit,timeoutMs,includeBotMessages} (MONO_AGENT_SLACK_THREAD_CONTEXT_*; defaults true/15/15/4000/true, maxMessages capped at the harness’s 30). Requires channels:history / groups:history / im:history / mpim:history for the conversation types you allow |
slack.ask-user | Structured AskUser renders one Block Kit question at a time with two or three option buttons, Other for a custom thread reply, and Done for multi-select. Answers resume the same model run; stale actions expire and the Slack allowlist remains authoritative | auto + code | No Slack-specific config; allow AskUser when using a restrictive tool policy and enable Slack Interactivity over Socket Mode |
slack.shortcuts | JSON-configured global/message Slack shortcuts. An exact callback ID runs its bound prompt as a proactive turn; destinations remain allowlist-bound, message shortcuts retain their source thread unless redirected, and optional acknowledgement/result threading gives immediate feedback | config | slack.shortcuts[]: {callbackId, prompt, channelId?, ackText?, threadReply?} (JSON-only; no environment-variable form) |
slack.app-home | Opt-in App Home view with an optional Markdown header and prompt-running buttons. The adapter publishes on app_home_opened; button actions use the same allowlisted proactive path as shortcuts, and publish failures are best-effort | config | slack.homeTab: {enabled?, headerText?, buttons?:[{actionId, label, prompt, channelId?, ackText?, threadReply?}]} (enabled defaults to false; buttons defaults to []; JSON-only; no environment-variable form) |
channel.plugins | Config-loaded external channel packages. The app resolves channels.plugins[].package by name, calls createChannelDriver(options) (or the package default export), and treats the returned object as a normal ChannelDriver. Missing packages, malformed exports, invalid inline config, and ID collisions report waiting channel sections instead of crashing. Loading seam only: no registry, version negotiation, or hooks beyond ChannelDriver. | config | channels.plugins[]: { package, id?, label?, config? } |
whatsapp.baileys | WhatsApp via Baileys socket (QR login; auth state in .mono-agent/whatsapp-auth) loaded as an external channel plugin | config | channels.plugins[].package: "@mono-agent/whatsapp-adapter", plugin config.{enabled,allowedChatJids,allowAllChats,groupMode,botJids,mentionTextAliases,stripMentionText}. When unset, defaults to true only when mentionTextAliases is non-empty; botJids alone does not enable stripping, so otherwise it defaults to false. |
messenger.graph | Facebook Messenger via a signed Meta webhook and the Send API, loaded as an external channel plugin | config | channels.plugins[].package: "@mono-agent/messenger-adapter", plugin config.{enabled,allowedUserIds,allowAllUsers,host,port,webhookPath,apiVersion,allowNonLoopback,proactiveMessagingType,proactiveTag}; secrets MONO_AGENT_MESSENGER_PAGE_ACCESS_TOKEN, MONO_AGENT_MESSENGER_APP_SECRET, MONO_AGENT_MESSENGER_VERIFY_TOKEN. |
webhook.http-invoke | HTTP POST invocation, sync or async with status polling; multiple named endpoints on one port, each with an optional prompt (pre-instructions prepended to the request text), optional per-endpoint model/effort and maxRunMs overrides, and optional native notification of the successful final answer. Optional static bearer auth covers invoke and status routes, uses timing-safe token comparison, and removes sensitive request headers before responder metadata; every non-loopback bind requires explicit opt-in plus a key. The request body may also set model/effort per call (request wins over endpoint config) — see runtime.per-trigger-model. A per-run watchdog (webhook.maxRunMs, see webhook.run-watchdog) bounds each run so a hung run can’t hold its conversation slot forever (matters most for async runs with no client disconnect) | config | webhook.enabled, host, port, path, prompt, notify, notifyConversationId, model, effort, defaultMode (sync/async), allowNonLoopback, apiKey (MONO_AGENT_WEBHOOK_API_KEY), retentionMs, maxStoredRequests, maxRunMs (MONO_AGENT_WEBHOOK_MAX_RUN_MS); multiple endpoints via webhook.endpoints[] (name/path/mode/prompt/enabled/notify/notifyConversationId/model/effort/maxRunMs) or webhook.dir *.md files (also MONO_AGENT_WEBHOOK_ENDPOINTS_JSON, MONO_AGENT_WEBHOOK_DIR, MONO_AGENT_WEBHOOK_MODEL, MONO_AGENT_WEBHOOK_EFFORT); request body { text, conversationId?, mode?, model?, effort?, metadata? } |
openai-api.chat-completions | OpenAI-compatible /v1/models + /v1/chat/completions (SSE streaming, optional bearer key; session continuity via X-OpenWebUI-Chat-Id/X-Conversation-Id headers with latest-message extraction). Non-loopback validation and startup require both explicit opt-in and a key; wildcard starts expose concrete loopback/private-LAN/Tailscale base URLs instead of an unusable 0.0.0.0 client URL | config | openaiApi.enabled, host, port, basePath, allowNonLoopback, apiKey, modelId |
a2a.provider | A2A provider with Agent Card discovery, JSON-RPC + REST, streaming, optional bearer, configurable request-body limit, and opt-in durable logical-dispatch idempotency | config | channels.plugins[].package: "@mono-agent/a2a-adapter", plugin config.enabled (MONO_AGENT_A2A_ENABLED, canonical — legacy config.provider.enabled still honored, root wins when both set), config.provider.{host,port,publicBaseUrl,allowNonLoopback,requireBearer,bearerToken,maxRequestBytes,idempotency.{namespace,stateDir,retentionMs,maxRecords}}, config.agent.{name,description,version,providerOrganization,providerUrl}, config.skill.{id,name,description,tags} |
a2a.consumer | Calling remote A2A agents (discovery + sendMessage), including capability-negotiated stable logical dispatch keys | config+code | plugin config.consumer.{remoteAgentUrls,defaultRemoteAgentUrl,bearerToken,timeoutMs} holds the settings; invoke with sendA2AMessage({idempotencyKey}) or createA2AConsumerResponder({idempotencyKeyForRequest}) |
tui.stream-endpoint | Loopback NDJSON operator endpoint used by mono-agent tui and the web console: structured AgentStreamEvent kinds (thinking, tool args/progress/results/timing, usage, cost, provider lifecycle/failover, warnings), explicit cancel, additive attachment/attachment-only turn input advertised by /v1/info, legacy authenticated verbatim-history append advertised by capabilities.historyAppend, and distinct canonical context import advertised only by capabilities.contextImport = {version:1,maxTextBytes:32768}. Import accepts an exact bounded {text,idempotencyKey} body, rejects whitespace-only values without trimming accepted opaque values, atomically retains fixed provenance plus assistant context, invalidates provider state, and never runs a model; retained pairs are bounded retry receipts, while reset/deletion removes that proof. Failures use stable context_import_conflict, context_import_unsupported, or sanitized context_import_failed codes with bounded reasons. Structured pending/submission routes for AskUser are advertised by capabilities.askUser, exact bounded terminal-ask lookup by capabilities.askById, and agent-authoritative cron overview/history/config/action routes by capabilities.cron without changing wire schema 1. Event frames have a strict 256 KiB UTF-8 NDJSON cap: above it, assistant-thought/tool-call payload fields are reduced and remeasured, while another oversized variant or a reducible event whose minimal metadata/invariant form still does not fit becomes a bounded oversized_event marker. Other frame kinds are unaffected. Default-on loopback operator surface ("tui": {"enabled": false} opts out); its baseUrl is published to the trace-source manifest for discovery | config | tui.{enabled,host,port,basePath,allowNonLoopback,apiKey} (MONO_AGENT_TUI_*; port 0 = ephemeral) |
acp.worklab-bridge | ACP v1 core-session profile over stdio: sanitized versioned source discovery; authoritative agent-owned workspace binding with advisory client cwd; durable source-bound session resume; empty client MCP/additional-root contract; text/resource-link prompts; structured text/thought/tool/usage/cost updates; cancellation; unused client filesystem/terminal capabilities; and AskUser form elicitation with strict answer validation. It does not advertise load, attachments, or general client-supplied MCP support, and therefore does not claim full general ACP v1 Agent conformance | cli+code | mono-agent bridge acp --discover; mono-agent bridge acp --source-id <id> [--require-tool-environment]; discoverAcpBridgeAgents() from @mono-agent/web |
cron.scheduled-prompts | Cron jobs invoking the responder on exactly five positional fields (minute hour day-of-month month day-of-week), UTC by default; seconds fields and macros such as @daily are rejected. Hashed H fields are stable because the job id is their seed. Jobs are timezone-aware and overlap-skipping, may override model/effort (see runtime.per-trigger-model), and may deliver successful non-empty answers through native Telegram/Slack notification or an explicit web:new console thread. Every firing has durable agent-owned identity, total ordering, state, and bounded history. The web console reads that state through the operator capability and shows a read-only cadence/next-run header; retained operator API run-now/runtime enable controls require an operator API key plus explicit opt-in, are idempotent and audited, never rewrite config, and can make an overlapping scheduled firing record skipped_overlap. A notify-enabled all-models failure can send a rate-limited notice to an explicit notifyConversationId. Guided init validates the expression inline and scaffolds cron/digest.md only after acceptance | config | cron.jobs[]: {id, enabled, expression, timezone, prompt, conversationId, maxRunMs, notify, notifyConversationId, notifyFailureCooldownHours, model, effort} plus cron.operatorActions.enabled (MONO_AGENT_CRON_OPERATOR_ACTIONS_ENABLED, default false); jobs also load from MONO_AGENT_CRON_JOBS_JSON, single-job MONO_AGENT_CRON_*, or one *.md file per job in cron.dir / MONO_AGENT_CRON_DIR (default cron/) — sources merge, duplicate ids error |
channel.native-notify | Opt-in per cron job / webhook endpoint via notify: true. When the run succeeds with non-empty final text, the agent’s final answer is delivered verbatim to the destination conversation — posted as-is with no second LLM turn — and recorded to history so a user’s reply resumes with it in context (a top-level Slack post records against the thread it opens, not the channel, so replies under two posts stay two conversations). Exact web:new creates a new assistant-only console thread per distinct result, marked CRON/WEBHOOK, only after durable agent history succeeds; it does not change the selected thread. Web delivery is idempotent and best-effort through an owner-private loopback ingress: one five-second attempt, no retry/outbox. The harness auto-injects guidance on notify turns (the agent is told its final reply is delivered verbatim and how to stay silent); the operator just writes the prompt. To stay silent (“nothing to report”), the agent produces empty final text or replies with the reserved sentinel NOTHING_TO_REPORT (matched trimmed and case-insensitively, either as the whole answer or as its final line — never as a substring) — then no notification is sent. A model that narrates before the marker is suppressed too, and logs a warning so the off-contract answer stays visible. Destination resolution: explicit notifyConversationId if set; otherwise inferred only when there is exactly one notify-capable (Telegram/Slack) candidate (seen conversations + adapter allowlist). Web is explicit-only and never inferred; other web:* values are rejected. Fallback inference is refreshed once immediately before each firing or invocation; that route snapshot binds both request.replyTo and final delivery. Webhook retains the selected route privately and reconstructs the completion request, so responder mutation cannot redirect, suppress, or inject delivery. With 0 or 2+ candidates delivery is skipped with a warning — it never guesses. Cron model-exhaustion failure notices are stricter: they require explicit notifyConversationId, never infer a destination, and use notifyFailureCooldownHours (default 6) for per-job rate limiting. Defaults stay off: a job/endpoint without notify delivers nothing. Cross-channel fan-out (one trigger notifying multiple/other conversations) is not built in — compose it from multiple cron jobs (each with its own notifyConversationId) or a skill. | config | Configured per cron job or webhook endpoint with notify and optional notifyConversationId; cron jobs may also set notifyFailureCooldownHours. Telegram/Slack destinations are bounded by their owning allowlists; web:new requires the running local web service. Env: MONO_AGENT_CRON_NOTIFY / MONO_AGENT_CRON_NOTIFY_CONVERSATION_ID / MONO_AGENT_CRON_NOTIFY_FAILURE_COOLDOWN_HOURS and the webhook equivalents (default off) |
runtime.per-trigger-model | Per-cron-job, per-webhook, Telegram per-chat, and Slack conversation-scoped override of runtime model/effort. Telegram and Slack expose only configured primary/fallback choices; Slack uses native Block Kit selectors plus exact mention-message commands. Slack direct-message selections span subsequent new DM threads, while public/private shared-channel selections are thread-local. A different-model request is ephemeral/isolated while same-model and effort-only requests retain the shared session. Effort narrowing is model-aware: reasoning-capable models map ultra to LOW; models without reasoning use OFF; max degrades to xhigh unless the resolved model advertises it. Ranking above max only prevents keyword downgrade. mono-agent doctor validates effort against the model’s advertised levels and warns, naming the nearest supported level, when a configured value is outside that set. | config+code | Cron jobs[].{model,effort} / MONO_AGENT_CRON_MODEL/_EFFORT; webhook endpoints[].{model,effort} / MONO_AGENT_WEBHOOK_MODEL/_EFFORT + request body {model,effort}; Telegram built-ins /model and /effort; Slack mention-message built-ins @agent /model and @agent /effort (neither channel has a config key). Effort enum none/minimal/low/medium/high/xhigh/max/ultra subject to model support. |
cron.run-watchdog | The cron channel aborts a run that does not settle within ~20 minutes and reclaims its slot, so a wedged run can’t starve every future tick (skip-on-overlap only guards a still-running prior tick). Per-job; an aborted run is recorded with interrupted status. Separately, destination-resolver promises are raced against the run signal, so replace/stop can reclaim resolver-held runs even without maxRunMs and late settlement is ignored | config+code | jobs[].maxRunMs or maxRunMs frontmatter overrides the default 1200000; programmatic callers can set startCronAdapter({ maxRunMs }) as the adapter-level fallback |
webhook.run-watchdog | Wall-clock bound per webhook run, at parity with cron.run-watchdog: races the full run pipeline (including notify-destination resolution and the responder) against the effective maxRunMs, aborts the request signal, and reclaims the conversation slot even if in-flight work never settles — a hung run (esp. async, with no client disconnect) can’t hold its slot forever. Run work that settles after the abort cannot produce a successful result (mirrors cron). Each endpoint resolves its bound independently, so one endpoint can use a tighter or looser value without changing siblings. Separately, destination-resolver promises are raced against the request signal, so disconnect/stop reclaim resolver-held slots even with the watchdog disabled and late settlement cannot start a responder or emit another result | config | webhook.endpoints[].maxRunMs overrides webhook.maxRunMs (MONO_AGENT_WEBHOOK_MAX_RUN_MS), then the app default is 1200000 (20 min); 0 explicitly disables (min 0 / max 86400000), including at endpoint scope |
channel.final-only-delivery | Telegram & Slack default to withholding answer deltas until the final answer while showing a working indicator. Tool starts may still use one transient activity message; set stream.finalOnly: false to restore live interim answer streaming. (The OpenAI-compatible /v1/chat/completions endpoint still streams token-by-token for clients like Open WebUI.) | code | Adapter stream.finalOnly (default true for telegram/slack); substrate option ResilientMessageStream({ finalOnly }) + ChannelTransport.indicateActivity() |
channel.transient-tool-activity | Interactive inbound Telegram/Slack turns expose tool starts in one cumulative message. On completion both adapters post the final answer separately and then best-effort delete the progress message, so cleanup failure cannot duplicate or lose the answer. Common tools use friendly emoji/action mappings; ReadSkill renders only its selected name as 📚 Reading "<skill>", read-only memory recall is preview-free 🧠 Recalling memory, memory writes remain 🧠 Updating memory, and ordinary file reads remain 📖 Reading. One allowlisted scalar preview is normalized, secret-redacted, and capped at 40 Unicode code points; paths preserve their filename and commands preserve both ends. Adjacent duplicate lines collapse as (×N). Subagent child tools remain grouped while running, then each group collapses independently at its first terminal event: every child line is removed while count/duration remain, with an optional secret-redacted one-line Result or Reason capped at 120 Unicode code points. Later completion bookends may enrich but never re-expand a terminal group. Reasoning and answer deltas stay hidden. Proactive turns suppress the message, and acknowledged cancellation best-effort deletes a still-transient status before retaining one Cancelled. acknowledgement | code | ResilientMessageStream({ finalOnly: true, showHints: true }); adapters force showHints: false for proactive delivery; both transports post final answers separately before optional ChannelTransport.delete() cleanup |
channel.stream-tuning | Status text, edit debounce, max message chars, welcome/help/error texts per chat channel | code | Adapter stream/messages options via custom channel drivers (createTelegramChannelDriver etc.) |
channel.custom | Bespoke transports | config+code | Implement ChannelDriver from @mono-agent/agent-contracts (neutral, dependency-free; ChannelId is any string) and either expose it from a package loaded by channels.plugins[] or pass it via startMonoAgentApp({ drivers }); @mono-agent/agent-app re-exports the host-bound aliases + BUILTIN_CHANNEL_IDS |
Observability & operator surfaces
Section titled “Observability & operator surfaces”| Feature id | What it is | Coverage | Config / entry point |
|---|---|---|---|
observability.jsonl-artifacts | Per-run event JSONL + summaries with finite terminal-run retention. Non-numeric values under sensitive-looking object keys are redacted; numeric values under matched keys are retained; retained free text is scanned for a closed set of high-confidence credential shapes. Event strings are capped at 4,096 bytes by default. The recorder separately replaces empty events + a running summary at start, schedules best-effort running checkpoints after 25 new events or five seconds from the first uncheckpointed event, and queues the complete terminal snapshot after any scheduled checkpoint. Every boundary replaces events before summary; there is no append, fsync, power-loss, or cross-file transaction guarantee. A crash can preserve the last successful prefix while losing the unscheduled or failed-write tail, and stale reconciliation reports only persisted data. Agent runs live at the top of artifacts.dir; memory-maintenance runs live under artifacts.dir/memory/ with their own aggressive retention, while legacy mixed directories still read back explicitly. The agent retention sweep also bounds tool-output/ run directories by path mtime/count, cleans recordless orphans, and keeps directories mapped from running or uncertain summaries. | config | artifacts.dir, artifacts.retention.{maxAgeDays,maxCount,dryRun}, artifacts.memoryRetention.{maxAgeDays,maxCount,dryRun} (MONO_AGENT_ARTIFACT_*) |
observability.latency-attribution | Per-turn provider_bridge_latency event (provider+tool+IO time vs harness overhead) and per-tool tool_timing events (execution_ms); MCP tool results carry mcp_call_duration_ms. Lets traces separate model-reasoning time from tool/MCP time | auto | Emitted into the run JSONL artifacts |
observability.trace-registry | Host publishes a heartbeat manifest so dashboards discover running agents; a config-local registry also best-effort mirrors into the global ~/.mono-agent/trace-sources registry. Optional typed memoryHealth uses a completion-based >=30-second steady-state cache plus one forced post-start/reload refresh, a backend-discriminated closed contract, serialized terminal writes, and its own freshest checkedAt; hostile extras are dropped and a contradictory newest snapshot is preserved as unknown, never stale green. Stale+dead manifests are pruned automatically | config + auto | traceability.{registryDir,sourceId,sourceLabel,heartbeatMs,staleAfterMs,globalDiscovery} (MONO_AGENT_TRACE_*); manifest memoryHealth |
observability.phoenix-exporter | Additive best-effort OTLP/HTTP protobuf export of each run lifecycle to Phoenix as a SEMANTIC timeline: streaming assistant deltas coalesce into one “Assistant thoughts”/“Assistant message” span, and a tool’s tool_use+tool_timing+tool_result events merge by tool_use_id into one TOOL span (input=args, output=result). Spans carry OpenInference semantics (openinference.span.kind AGENT/LLM/TOOL/CHAIN, or memory for memory-maintenance runs, input.value/output.value) and route to a named project via openinference.project.name (defaults to the trace source label/id). Deterministic per-run ids make re-export idempotent. Metadata-only by default; includeSensitiveData: true exports substantive payloads and caps strings. Non-numeric values under sensitive-looking object keys are redacted; numeric values under matched keys are retained; free text is not content-scanned by default. The default-off contentPatternRedaction option replaces a closed set of high-confidence credential shapes in retained outbound text independently of the local recorder’s always-on credential-shape scan. Failures are bounded by timeout and never change the run outcome or suppress JSONL writes. Transport lives in @mono-agent/observability/otel (via @opentelemetry/otlp-transformer); start/status show the endpoint, validate POSTs an empty protobuf to confirm export-compatibility (not just reachability) | config | observability.exporters[]: {type:"phoenix", endpoint, projectName, includeSensitiveData, contentPatternRedaction, headers, timeoutMs} (MONO_AGENT_OBSERVABILITY_EXPORTERS JSON array) |
observability.backfill | Retroactively export already-recorded run artifacts (run-*.summary.json + run-*.events.jsonl) to Phoenix with their historical timestamps, reusing the live OTLP mapping. Deterministic ids make re-runs overwrite rather than duplicate | cli | mono-agent backfill (--run <id> | --all) [--since <iso>] [--until <iso>] [--dry-run] |
observability.artifact-audit | Read-only structural audit over recorded run summaries: parse failures, recognized status histogram, recognized production failure-kind histogram, unrecognized values, stale running summaries, and failure-kind rates. It scans the whole artifact directory and never rewrites stale summaries | cli / code | auditRecordedRuns(artifactDir, { staleAfterMs }); mono-agent runs audit [--artifacts <path> | --consumer <path>] [--json] |
observability.artifact-metrics | Read-only aggregate metrics over recorded run summaries: status counts/rates, failure-kind rates, durationMs p50/p90/p99/max, and total/average cost from cost.cumulativeUsd, cost.totalUsd, or usage.cost_usd. Supports time windows and grouping by model, best-effort channel, or failure kind; scans the whole artifact directory and never contacts exporters | cli / code | summarizeRecordedRunMetrics({ artifactDir, since, until, groupBy }); mono-agent runs report [--artifacts <path>] [--since <iso>] [--until <iso>] [--by model|channel|failureKind] [--json] |
observability.rich-traces | Root run spans carry roll-up attributes — llm.model_name/mono.agent.model, llm.token_count.* (incl. prompt-cache read/write), mono.agent.cost_usd, mono.agent.duration_ms, and (only with includeSensitiveData) the system prompt (llm.input_messages.0.*, capped at 32KB and content-scanned only when contentPatternRedaction is enabled). Model-backed memory runs additionally export openinference.span.kind="memory" + mono.agent.memory.operation (extract/reconcile-batch; historical distill/reconcile/entities and legacy manual reflect/migrate remain readable); channel runs stay AGENT. (Key-based redaction skips numeric values so token counts survive export.) | auto | Included in every successfully exported run; the system prompt is gated by observability.exporters[].includeSensitiveData |
observability.stale-run-reconciliation | At startup the host rewrites any run summary left at running by a crashed prior process to interrupted (failureKind process_death); fire-and-forget, runs in the background, never gates readiness. interrupted maps to an ERROR span in Phoenix | auto | reconcileStaleRunArtifacts() run once at startup over artifacts.dir |
observability.runs-health | validate, status, and start --foreground read the local artifact directory and print a per-failure-kind breakdown — each known kind gets a label, explanation, and Next: remediation step. Known kinds: context_limit, usage_limit, process_death, interrupted, cancelled (+ variants cancelled_user/cancelled_stale/cancelled_shutdown/cancelled_signal), provider_unavailable, provider_unavailable_exhausted, runtime_error, session_not_found, session_busy, exception. Terminal statuses (cancelled/interrupted/failed) infer a display kind; unknown open-set kinds fall back to a generic “inspect the artifact summary and logs” hint | cli / code | describeRunFailureKind(...) / KNOWN_RUN_FAILURE_KINDS (exported from @mono-agent/observability); the runs-health section of mono-agent validate / status |
observability.fleet-loaded-code | On supported POSIX/macOS hosts, root builds take an exclusive ignored lock before clearing the old marker, then atomically publish an owner-only marker with full source SHA/state, Node/ABI, completion instant, deterministic deploy-output digest, and a separate digest of the installed root/workspace dependency topology, modes, and bytes (including native addons) only after packages, required CLI/TUI executable-mode finalization, and output sync succeed. The read-only fleet checker can require an exact discovered label set; converts only canonical filename-matched plists through absolute closed system probes; accepts a separate closed legacy shape or the exact current allowlisted /usr/bin/env -i producer shape; and fingerprints/revalidates each plist after all expensive probes. Initial/final launchctl print observations bind the loaded program, structured arguments, cwd, origin plist, and PID to that persisted definition, while the running executable device/inode and cwd are checked independently. With copied managed runtimes, --repo selects the source checkout and a read-only initial/final attestation requires the canonical content-addressed cache path, valid v4 marker and complete closure manifest, exact source/cache execution-closure parity including configured plugins, and the unchanged install-time filesystem identities of package entries, links, and every resolution-path directory inside the private install root; canonical ancestors above it must remain owner-private. The checker also requires an absent build lock, a clean current checkout on both reads, the full per-instance expected SHA, current output/dependency-digest parity, runtime identity, and a process start after both build completion and the conservative finalized-runtime boundary, then performs a global final launchd state pass after all rows. Failures are path-free, closed, and secret-safe in the loaded column. Unsupported hosts build normally without publishing deploy proof | dev | pnpm run build; node scripts/fleet-green-check.mjs [--dry-run] [--repo <deploy-checkout>] [--expect-labels <csv>] [--expect-sha <full-sha>] |
tui.chat | pi-tui operator console: live chat with structured stream-event insight (collapsed-expandable thinking, tool panels with args/progress/result/duration, usage/cost/failover status bar), recorded-run replay from the artifact dir, source-annotated config view, running-instance picker. Remote event frames have a strict 256 KiB UTF-8 NDJSON cap: assistant-thought/tool-call payload fields are reduced and remeasured, while other oversized variants or a reducible event with an oversized minimal form become bounded oversized_event markers; other frame kinds are unaffected. Replay contains only sensitive-key-redacted, credential-scanned events whose strings are capped at 4,096 bytes by default and that reached the latest successful start, incremental, or terminal recorder boundary; an unscheduled or failed-write tail and payload truncated before persistence cannot be recovered. Connects remotely to any running agent via the trace-source registry + tui.stream-endpoint, or embeds in-process against an AgentResponderLike | cli | mono-agent tui [--agent <label|sourceId>] [--conversation <id>]; low-level mono-agent-tui [--responder <file> | --url <baseUrl>] [--config <path>] (ships with @mono-agent/tui) |
web.console | Always-on assistant-ui browser operator console: derives its header, tab title, and installed-PWA identity from the OS hostname unless a persisted --name <label> overrides it; --name - restores the hostname default; offers four explicit curated shell/accent themes while preserving shared content/status/danger semantics; auto-discovers agents, uses a fixed compact/expanded rail, hides unpinned discovered-but-offline agents by default while keeping pinned/selected sources visible, and omits registry-departed sources while retaining their source-bound history and pins for restoration. It keeps owner-private SQLite threads/messages/quotes/attachments. Stable source-qualified cron routes fold bounded, keyset-paginated cross-run history into one read-only channel with a quiet human-language cadence/next-run header and originating-session/artifact links in the feed. The header shows viewer-local absolute next time or concise removed/disabled/unavailable state; operator config-view and confirmed control APIs remain available separately. Missing cron capability renders cached channels read-only with unknown live state; the console never derives next-run locally. A running structured AskUser call renders all one-to-five questions in one atomic form with described choices, Other/custom replies, and multi-select controls, then resumes the same model run. Cards reconcile by exact interaction id through one bounded backoff poller, retain terminal outcomes and compact answer summaries, and become non-actionable when expired, cancelled, evicted, or offline. Selected rendered message text can be persisted as one same-thread quote and supplied upstream as Markdown blockquote context without rewriting authored transcript text. An explicit per-origin bell enables durable, standards-based Web Push for completed responses, blocking AskUser, and failed/cancelled/interrupted runs; its SQLite outbox uses bounded retries, exact-thread foreground acknowledgement with the origin-stored subscription id, secret-free APIs, and a redacted plain-text preview. Browsers without confirmed push keep the hidden/unfocused page fallback. Turns continue across browser disconnects; reasoning/tools stream while provider/runtime telemetry stays out of the transcript and per-conversation usage accumulates in the context display. A browser subscribes its open conversation to message.delta frames (append/set/truncate against a per-message sequence) on the shared SSE stream and answers every other change with a rate-limited hint and a conditional read, so a streaming turn costs what it produced and an unchanged conversation costs a 304. Transcripts are shaped at the service boundary — non-allowlisted telemetry parts keep their position but lose their data, and tool arguments, results, and subagent reports over 4,096 characters ship as digest-stamped previews — with the whole part fetched on demand from the message and tool-call routes and ?full=1 disabling shaping for one read. The browser keeps its last eight conversations, one listing row per agent view it has opened, and an agent-list snapshot in origin-local IndexedDB so a cold start draws before the first response arrives; restored rows stay stale until confirmed, a hydration sweep bounds the store by age, count, and departed agents, and Clear cached data in the command palette removes all of it without touching the service store. A browser-local data mode (Auto/Lean/Full) with a session byte-and-rate meter governs picture and MCP App loading, page sizes, delta paint batching, and poll cadence; responses are compressed with per-route cache policy, and the prompt-mode service worker precaches the shell and stages a new build until the console is idle or the operator taps Reload now. Supports per-conversation model/effort/cancel plus SQLite-persisted per-agent defaults for new web conversations with one-click config revert and browser-picked transport-neutral attachments. Every run projects bounded requested/attempted/executed route attribution, classified fallback/retry history, and Pi’s effective effort into assistant messages when the run model differs from the current selection, while fallback warnings always remain visible there and the header carries no run attribution; subagents keep independent attribution. Standalone process-job and Monitor wakes re-read the conversation snapshot, while active-run steering retains its existing route. Agent settings additionally proxies ephemeral no-store provider-auth status and login sessions to capability-advertising agent hosts; operator requests are keyless when the endpoint has no API key and otherwise retain its bearer, exact-origin checks protect every browser auth route, and credentials stay outside web storage and thread history. One active turn per thread; different threads run concurrently. Threads archive/unarchive and remain permanently source-bound; configured cron channels can only be archived, while archived tombstones may be deleted without losing delivery idempotency receipts. Whole-store reset is explicit. Its managed macOS worker is paired with an attested helper-only log controller: wake-only worker checks, 5 MiB active files plus three retained generations, stopped-writer rotation, and durable failure/recovery status. Binds 0.0.0.0:5050 by default with no application auth, so the trusted LAN/tailnet is the security boundary; --loopback narrows access. Conflict-safe Tailscale Serve HTTPS never replaces another handler | cli | `mono-agent web [start |
Execution & composition (agent-harness, agent-orchestrator, agent-app)
Section titled “Execution & composition (agent-harness, agent-orchestrator, agent-app)”| Feature id | What it is | Coverage | Config / entry point |
|---|---|---|---|
app.cli-init | Non-destructive capability scaffold. Bare TTY init delegates to the readiness-proven wizard, collects public name plus exact IDENTITY.md → ## Role text, labels whether the identity will be created or preserved, and generates the managed mono-agent-memory project skill. On macOS success it starts the background agent and prints the manual edit → validate → restart → ordinary tui continuation; any flag or non-TTY invocation is scaffold-only and starts nothing. Other platforms print their manual start continuation. Repeated canonical fallbacks carry exact effort; the legacy CLI --fallback-models flag was removed (the JSON/env compatibility inputs remain). --auth runs supported Pi provider auth/preflight before writing, and --dry-run never launches commands | cli | mono-agent init [--name] [--model] [--effort] [--fallback <ref> [--fallback-effort <provider-default|level>]]... [--auth] [--dry-run] [...] |
app.cli-setup | Bare TTY mono-agent init: public agent naming and exact Role destination/text; searchable guided Pi (Anthropic, GitHub Copilot, OpenAI Codex, OpenCode-Go), live Codex, and local catalogs; distinct catalog/credential/verified states; uncapped fallbacks with per-model effort; Escape-back/Ctrl-C-exit state machine; concrete creation review; supported Pi OAuth/OpenCode-Go key handoff; managed SRT setup; and one strict sequential no-tool call per selected route (90s cloud/240s local each). The complete selected-capability configuration is staged against effective created-or-preserved files before real/potentially billed route calls, deferring only waiting credentials to exact live proof. Configuration failures show the capability detail and seed repair directly at the implicated section when unambiguous instead of unrelated auth/model recovery. Configuration validation can be interrupted before route calls; final validation can be interrupted before files are written; both recover through resume/restart/edit/cancel. Verified routes resume only while the whole ordered route-and-effort fingerprint is unchanged; changing any route/effort or credentials invalidates all route-plan proofs. On macOS, only a complete report may create/refresh launchd, wait up to 60 seconds for a fresh durable startup-completion proof, and open remote configuration against the authoritative background responder. Readiness failure preserves files, skips chat, attempts ownership-proven worker/helper cleanup, and reports if stopped state remains unproven. Off macOS, no process/readiness claim or conversational configuration is attempted | cli | mono-agent init (no flags, on a TTY; setup alias) |
app.secure-secret-persistence | Masked selected secrets are never printed. Existing non-empty dotenv values are preserved. A shell-only selected secret does not skip the durable prompt: the entered value must match every exported/persisted copy, then a missing dotenv value is persisted after canonical-parent ownership/writeability checks, current-user single-link .env/.gitignore validation, Git/symlink/parser/round-trip checks, transaction-artifact ignore rules, external owner-only locking, 0600 pathname no-clobber promotion, and post-validation/pre-start rechecks. Group/world write access is removed from the ignore guard. Pathname competitors remain; detected claimed-inode writes are retained at a reported recovery path. Tracked, hard-linked, foreign-owned, malformed, conflicting, unsafe, unrepresentable, stale-lock, or Windows cases fail closed | cli | Guided mono-agent init; InitMonoAgentFolderResult.changes, .secretPersistence, .identityRole, compatibility .secretsPersisted |
app.provider-auth | Pi OAuth/API-key credentials plus an ephemeral web-console status, re-authentication controller, and explicit live checks. Status covers only effective primary/fallback, agent-host memory, and enabled static trigger routes; present/expired/missing/keyless not_applicable remain separate from successful live-request verification and recent process-local auth/unavailable warnings. OK requires a retained successful live observation; static presence is Not verified. The additive providerAuth.checks.version: 1 capability exposes start, poll, and cancel actions for one bounded no-fallback request per displayed provider, with partial secret-free results and no provider traffic from passive status reads. Checks can consume quota and refresh OAuth credentials. GitHub Copilot and OpenAI Codex use Pi 0.85.1 native device code; Anthropic uses authorization URL plus validated pasted redirect/code; provider API-key prompts are masked. No --device-auth flag exists. A valid repeated login replaces the active session after validation; stale continuations are fenced, and a credential transaction that cannot drain safely within two seconds fails the fresh session closed. Live checks remain a separate explicit conflict. Browser and CLI results preserve siblings through the same owner-only locked no-clobber Pi-store transaction; ambient keys are never copied. Sessions and submitted values never enter web SQLite, threads, run history, logs, or browser persistence. The operator routes are keyless when the endpoint has no API key and otherwise require its bearer. This surface covers providers.piAuthPath, not Codex CLI ~/.codex/auth.json | cli | mono-agent auth login <provider> [--pi-auth-path] [--api-key-stdin]; guided setup/repair; Agent settings when the agent advertises the provider-auth capability; operator POST /v1/provider-auth/checks, GET /v1/provider-auth/checks/:checkId, and DELETE /v1/provider-auth/checks/:checkId plus corresponding web proxy routes |
app.cli-presets | List the built-in setup presets or show a preset’s generated config, .env.example, scaffolded files, and validation checklist. Five core presets (starter, telegram-assistant, slack-bot, local-private, code-sandbox); optional plugins own their setup assets; the deprecated mono-agent recipes … alias was removed. --json emits {ok,presets} for list and {ok,preset,configJson,envExample,files,checklist} for show <id> (unknown id → {ok:false,error}, exit 1) | cli | mono-agent presets list | show <id> [--json] |
app.cli-no-tools-guardrail | The no-tools trap is surfaced, not silent: allow-all (the default) reports All tools allowed, while an explicit empty tools.allowedTools: [] reports waiting on Pi. The wizard’s supported zero-tool path warns loudly. validate/doctor also flag unknown tool names with a case-insensitive “did you mean” hint (pi drops unknown names) and cross-check adapter send tools against enabled channels (send-tool-without-channel → waiting; channel-without-send-tool → non-fatal hint) | cli | Part of mono-agent validate / doctor; the tools step of mono-agent init |
app.cli-config | Source-annotated resolved-config view: every core section field-by-field with its value’s origin ([env]/[json]/[default]), followed by every channel section with the same per-field provenance (secrets shown only as set/unset) composed from the adapters’ exported field registries, JSON-secret placement warnings, and the channel status summary. Read-only. --json emits one ANSI-free {ok,config,channels,channelStatus,warnings} object with secrets redacted (never raw values); a missing/malformed config emits {ok:false,error} and exits 1 | cli | mono-agent config [--config] [--env-file] [--json] |
app.cli-validate | Per-section config report (core, runtime provenance, runtime routes, provider credentials, context, memory, tools, sandbox, observability, runs health, managed launchd-log active/retained/total byte inventory plus last inspection/wake count/last outcome/cooldown deadline, secret placement, every channel), optionally against a downstream consumer folder read-only. Runtime provenance identifies the CLI producing the report by its full managed closure id plus sanitized install metadata only after validating its private marker, freshly recomputed installed closure, and coherent current closure manifest, or reports dev (unmanaged); this is not a separately running daemon attestation. The launchd-log section validates metadata only and never chmods or rotates; malformed/unsafe monitor status is unavailable rather than trusted. JSON mode emits exactly one top-level {ok:boolean,...} object without ANSI/prose and exits 0 iff ok. mono-agent doctor is an alias | cli | mono-agent validate [--consumer] [--config] [--env-file] [--json] (alias: mono-agent doctor) |
app.provider-credentials-check | validate resolves every referenced primary, fallback, agent-host memory.llm, and enabled static webhook/cron model credential (disabled entries are ignored; dynamic request-body overrides are runtime-checked). Each Pi runtime ref must resolve through an enabled providers.local entry or an exact Pi built-in catalog model. A local provider’s declared apiKeyEnv must resolve, while a provider with no key declaration remains intentionally keyless. Built-in Pi credentials are checked against the Pi auth store; a missing/expired OAuth provider is waiting with a provider-specific mono-agent auth login <provider> hint, while an unresolvable runtime model is an error. Static liveness:false validation, including start preflight, launches no process and marks the CLI version unverified | cli | Part of mono-agent validate; Pi credentials resolve against providers.piAuthPath, custom models through providers.local[] |
app.secret-placement-check | validate (and mono-agent config) emit a non-fatal [WARN] (status waiting, never error) when a secret-marked field is resolved from the committed mono-agent.config.json rather than .env, naming the MONO_AGENT_* var to move it to. Covers core secrets (memory.embeddings.apiKey, memory.supermemory.apiKey) and every channel credential (telegram.botToken, slack.botToken/slack.appToken, openaiApi.apiKey, A2A plugin config.provider.bearerToken/config.consumer.bearerToken) via the adapters’ exported field registries. Advisory only — never blocks start, never prints the secret value; the section is omitted when no secret is JSON-sourced | cli | Part of mono-agent validate / mono-agent config |
app.cli-start | Start traceability plus every configured channel. The default macOS launchd service survives logins (auto-restart on crash) until stop; after readiness the worker performs an overlap-guarded bounded log inventory immediately and every five minutes. The immediate pass is observational with a five-minute wake floor; only safe oversized/permission-repairable per-agent logs can wake the separate no-KeepAlive, /dev/null helper, never shared-only repair or pending intent/journal/preparation state. Wake suppression is monotonic 5/10/20/40/60 minutes and rechecks stop immediately before kickstart. The helper retains RunAtLoad and recurs hourly at SHA-256(canonical main label)[0..3] unsigned-big-endian modulo 60; every login helper retains recovery coverage but its lightweight entry waits the same hash modulo 120 seconds before PID authentication, locking, attestation, or heavy import, dispersing heavy work without an admission lock. Start/restart unload helper then worker, prove all observed PIDs dead, recover or commit per-agent journaled bounded tails in that stopped window under the per-agent then shared-chain locks, atomically replace each owner-private plist, and load helper then worker. Scheduled maintenance atomically advances a separate per-agent lifecycle intent through stopping before bootout, stopped after observed-PID death, and restoring before bootstrap; it clears the intent only after exact-plist plus live-worker proof. Helper-load failure prevents worker start; worker-load failure unloads the helper. Its private managed runtime copies the exact already-resolved dependency closure, including config-selected channel/Supermemory plugin packages, without npm/lifecycle execution; the complete source digest and a relative path/type/mode/content-hash installed manifest are bound to the runtime marker. Launchd enters Node through env -i with only the reviewed operational allowlist, and start/restart resolve config and registries from the worker’s durable dotenv-plus-operational environment. Every foreground worker holds one owner-only canonical per-config lifetime lease across HOME/path aliases and PID reuse; managed startup freezes the attested config, Identity, optional Soul, and external MCP authority file into private read-only inputs before app/channel loading while traces retain the canonical config path. Managed readiness has a separate 60-second budget and requires a durable lifecycle-completion marker plus current launchd PID, snapshot, memory/channel, and optional TUI proof; later trace reasons do not revoke it. Timeout or readiness-read failure attempts the same proven stop/removal path and reports explicitly when stopped state is uncertain. Existing loaded helpers adopt the calendar schedule and lightweight entry only on per-agent restart; launchctl print is authoritative. --foreground is the blocking cross-platform path and cannot coexist with the managed worker (-f is logs-only and errors on start) | cli | mono-agent start [--config] [--env-file] [--foreground] |
app.managed-runtime-publication | macOS start/restart and scheduled recovery keep the current healthy worker loaded during closure materialization and publish a crash-recoverable owner-private per-label barrier that makes KeepAlive respawns wait through replacement-plist commit. Concurrent consumers coalesce behind the shared PID/incarnation-aware runtime install lock for up to five minutes. The new plists carry one path-free finalized-runtime proof covering the marker, public CLI, and lightweight maintenance entry; the worker validates canonical layout, both executable fingerprints, marker/manifest fingerprints, and the launch boundary before taking its lifetime lease or loading config | cli + auto | Internal managed LaunchAgent lifecycle under ~/.mono-agent/locks/ and ~/.mono-agent/runtimes/agent-app/ |
app.managed-launchd-recovery | The no-KeepAlive helper retains a login RunAtLoad pass and recurs hourly at a deterministic canonical-main-label hash minute, or is requested after the worker’s five-minute metadata scan satisfies the exact safe per-agent predicate and cooldown. Every agent remains eligible: before PID inspection, locking, attestation, or heavy import, its lightweight entry waits SHA-256(canonical main label)[0..3] unsigned-big-endian modulo 120 seconds. This disperses login heavy work over 0–119 seconds without an admission lock; worker-requested wakes inherit the same maximum delay. The idle/shared-only/pending-artifact path starts no helper and cannot invoke config validation, snapshot capture, or runtime reconciliation. Its plist targets an attested lightweight sibling entry. After dispersion that leaf authenticates the exact launchd-owned PID and takes the per-agent lifecycle lock without waiting before attestation or importing the controller graph; same-agent losers exit before expensive work. The winner verifies the entry against the shared runtime proof, dynamically imports the heavy controller, reconstructs the durable environment, captures a fresh keyed snapshot, strictly parses the loaded main definition, and checks its managed-runtime launch proof against the original controller CLI’s inert version/digest. A healthy pass neither runs full structural validation nor traverses the immutable closure; only proven drift or inactivity runs full validation and managed-runtime installation/verification under the per-agent lock. After installation and read-only rechecks, recovery takes the per-account shared-chain lock without waiting only around shared/stopped-writer mutation; contention defers successfully, healthy log-only work takes it separately, and readiness waiting occurs after its release. Mutable source is never executed. Recovery keeps the old worker serving, stops only main, refreshes both plists, bootstraps main, and waits up to 60 seconds for readiness. Failure preserves helper plus definitions for the next hourly retry; recurring drift/shared-only repair latency is at most 60 minutes between logins. Missing source falls back to the helper’s private closure for recovery without an upgrade/downgrade claim; explicit stop latches the monitor, unloads helper, and removes both definitions plus monitor status, preventing resurrection | auto | Installed per-config macOS maintenance LaunchAgent; owner-private status and lock state under ~/.mono-agent/; no public flags |
app.cli-stop | Stop the background instance for this config by unloading scheduled log maintenance first, then the worker; remove both LaunchAgent definitions only after both jobs and observed PIDs are proven gone (macOS background mode) | cli | mono-agent stop [--config] [--env-file] |
app.cli-status | Report a managed instance as running only when the cached trace PID is alive and exactly matches launchd’s current PID. An inactive/mismatched cached trace emits ok:false, exit 1, pid:null, stopped health, and rewrites cached running channels to stopped while omitting stale transport/endpoint facts | cli | mono-agent status [--config] [--env-file] [--json] |
app.cli-logs | Print (and optionally follow) the background instance’s active log files. Automatic stopped-writer maintenance retains .1 through .3, capped at 5 MiB each; validate / doctor reports exact active and retained bytes for safely inspected files and marks unsafe or unreadable inventory unavailable, read-only | cli | mono-agent logs [--config] [--env-file] [--follow|-f] [--lines <n>] |
app.cli-restart-clean | Restart the background instance (starts it if stopped). --clear-sessions first purges persisted Pi sessions, active conversation history, and ACP session authorizations, so the agent neither resumes nor replays pre-reset chat context and old ACP ids are revoked. Durable memory under memory.path and recorded run artifacts are untouched; missing stores are no-ops | cli | mono-agent restart [--config] [--env-file] [--clear-sessions] |
app.managed-project-skills | Every generated agent selects the versioned mono-agent-memory skill with index disclosure. Hash/version drift is reported. A retired mono-agent-configure selector is ignored at runtime and reported as non-fatal waiting by validation, while any other missing selected skill remains an error. Project update authenticates the canonical owner-only non-symlink directory chain, then removes a legacy manifest entry and deletes its file only when manifest ownership and exact bytes still match; modified/colliding copies fail closed. Active updates and retirement share one lock, backup, compare-and-swap, and rollback transaction | cli + config | context.skillsRoot, context.selectedSkills, context.skillDisclosure; mono-agent install-skill --project --check|--update |
app.docs-mcp-companion | Version-matched, offline hybrid semantic/BM25 search plus guided reading over public docs and authoritative composer references. Search returns 2–3k section excerpts; read expands stable targets to anchored windows up to 10k with resolved internal links and exact non-overlapping continuation actions. Runtime retrieval is read-only and network-free | cli + code | mono_agent_docs({action: "search", query, limit?, scope?}); mono_agent_docs({action: "read", target}); mono-agent-docs://chunk/{chunkId}; @mono-agent/docs-mcp |
app.cli-install-skill | Copy the composer skill into ~/.claude/skills / ~/.agents/skills and pair the exact-version mono-agent-docs MCP server by default, or check/update managed project skills. Missing harnesses are reported; unknown same-name entries fail closed; managed MCP and skill changes roll back together. Project check reports active ready/missing/stale/modified/collision plus legacy retired-managed/retired-missing/retired-modified/retired-collision; ok requires all active skills ready and no retirement pending. Project update retires only an exact manifest-owned legacy file inside the same lock/backup/rollback transaction | cli | mono-agent install-skill [--target claude|codex|both] [--force] [--no-docs-mcp]; mono-agent install-skill --project (--check [--json]|--update) |
app.cli-web | Operate the always-on web console. Bare web is read-only status/help. start/restart publish and load the paired worker/helper definitions on macOS and safely claim a free Tailscale Serve HTTPS port (443, otherwise 8443–8499); stop unloads helper first, removes both definitions after death proof, and removes only its owned route; run is foreground/cross-platform and never gains rotation authority; logs follows only active service logs by name; reset requires both jobs stopped, both plists absent, the lifecycle lock, and --all --yes. status distinguishes active maintenance from abandoned recovery; routine due is informational while unsafe or unproven maintenance stays nonzero. Defaults to LAN-reachable 0.0.0.0:5050 and evergreen; --loopback means 127.0.0.1 and conflicts with --host; --theme accepts evergreen, ocean, plum, or terracotta on start/restart/run, persists through managed restarts, and appears in status. No application authentication | cli | `mono-agent web [start |
app.env-file | .env auto-load (exported shell vars win) | cli | automatic; --env-file <path> to override |
harness.failure-handling | Explicit failure objects (never fake success) | auto | Built into every run |
harness.external-summary-safety | Private recorder artifacts may retain the compiled systemPrompt; every harness response uses ExternalRunSummary without that field, including early failures, and webhook sync/async/status/callback boundaries sanitize untrusted responder metadata again | auto | AgentHarnessResponse.metadata.summary; webhook response/status surfaces |
harness.request-runtime-options | Per-request runtime option extensions | code | createConfiguredAgentResponder({ runtimeOptionsForRequest }) |
orchestrator.ask-collaborator | Request-scoped MCP tool delegating to named collaborator responders (loopback and ephemeral port by default; guarded non-loopback/fixed-port options; call caps; per-collaborator timeout). Non-loopback exposure has no package-owned bearer authentication and requires a caller-owned trust/authentication boundary | code | createCollaboratorToolRuntimeExtension + runtimeOptionsForRequest (see programmatic multi-agent docs) |
Maintenance rules
Section titled “Maintenance rules”- A new option in any package is not done until it has a row here, a
MonoAgentConfig/channel-config surface (or an explicitcode/devjustification), and coverage in the composer skill references. - The composer skill files that must stay in sync:
packages/agent-app/skills/mono-agent-composer/SKILL.md,references/config-blueprint.md,references/feature-coverage.md,references/discovery-questions.md,references/package-map.md,references/validation.md, andreferences/playbooks.md(when a preset or capability module changes). - The published documentation site under
docs/(built with Astro Starlight inwebsite/and deployed on Vercel — seewebsite/README.mdfor the build/sync/deploy workflow and the Astro/Starlight version pins) is the reader-friendly projection of this registry. When a feature row changes, also update its prose page underdocs/<area>/, the scannabledocs/reference/feature-matrix.md, and — if a preset or capability module is affected — the matchingdocs/playbooks/<slug>.md,docs/reference/presets.md, and the composer’sreferences/playbooks.md. This file (docs/reference/feature-registry.md) remains the canonical source of truth.