personal vibeslop fork of connectome
README.md

connectome-host #

connectome-host runs long-lived agents. A recipe — one JSON file — describes an agent: its name, model and provider, memory strategy, MCP servers and modules. The host keeps that agent running: in a terminal, as a headless daemon under systemd or launchd, or as one process in a supervised fleet, with a browser console for whoever looks after it.

Most agents it runs are residents: agents with a name and a history who live on Discord or Zulip for months, remembering in their own voice — sometimes continuing a conversation that began on claude.ai, in Claude Code or in Codex. Most of the host's work goes into what that takes: memory that folds instead of truncating, prompt caches that stay warm at half-million-token contexts, honest cost and subscription-quota accounting, an operator who can see and repair a live context, and ways to bring an agent in from somewhere else. It started as a knowledge-mining TUI, and those recipes still ship (what we set out to build, and what became of it).

Built on the Connectome stack: @animalabs/agent-framework + @animalabs/context-manager + @animalabs/chronicle + @animalabs/membrane.

What people run on it #

Use Start here
A resident — a persistent agent on Discord (or other surfaces), headless on a VPS, with autobiographical memory docs/AGENT-ONBOARDING.md, docs/DEPLOYMENTS.md
A continued conversation — bring a claude.ai conversation, a Claude Code session or a Codex rollout across and keep going docs/claudeai-evacuation.md, docs/claude-code-ingest.md, scripts/import-codex-rollout.ts
Historical Fleet orchestration — archived conductor/child workflow, not currently exposed tools docs/fleet-protocol.md, recipes/TRIUMVIRATE-SETUP.md
Knowledge mining — standalone miner / reviewer / clerk recipes; historical pipeline design recipes/SETUP.md, docs/LIBRARY-PIPELINE.md
A workbench — a plain assistant with tools in your terminal Quick start below

All the documentation is indexed in docs/README.md; how the host is put together is in ARCHITECTURE.md.

Quick start #

# Prerequisites: Bun and Node.js 22.13+, plus credentials
export ANTHROPIC_API_KEY=sk-ant-...     # or ANTHROPIC_AUTH_TOKEN (Claude subscription)

bun install                                   # installs tools deps and builds web UI
bun src/index.ts                              # generic assistant (or the last recipe you loaded)
bun src/index.ts recipes/mock-test.json       # zero-cost offline smoke run, no credentials needed
bun src/index.ts path/to/recipe.json          # load a recipe
bun src/index.ts https://example.com/r.json   # recipe from URL

Bun loads .env automatically; .env.example lists the common variables.

Built-in tools #

ToolsModule is enabled by default and exposes bare bash, read, write, edit, grep, glob, ast_grep, ast_edit, search, fetch, browser, terminal, and repl. Default groups are native, web, browser, terminal, and repl; acp is opt-in. No tools MCP registration is needed.

Configure modules.tools directly:

{
  "modules": {
    "tools": {
      "groups": ["native", "web", "browser", "terminal", "repl", "acp"],
      "workspaceRoot": "/path/to/workspace",
      "stateDir": "/path/to/private-state",
      "artifactDir": "/path/to/private-state/tool-results",
      "webCacheDir": "/path/to/private-state/web-cache",
      "antigravityAuthFile": "/path/to/private-auth.json",
      "acpExecutable": "omp",
      "acpPermissionPolicy": "deny"
    },
    "acp": {
      "workspaceRoot": "/path/to/workspace",
      "stateDir": "/path/to/private-state",
      "permissionPolicy": "deny"
    }
  }
}

Set modules.tools: false to disable the built-in suite. Filesystem tools read and write disk directly; there are no workspace mount or Fleet tools exposed. Use absolute paths outside the configured workspace. Keep credentials, browser profiles, interpreter state, artifacts, and broker databases out of source control.

Without explicit paths, the host uses its launch directory as workspaceRoot and the current session store as stateDir. The ACP completion observer inherits the built-in suite's workspace, state, executable and permission settings unless overridden in modules.acp.

The ACP group exposes coding delegation; the optional modules.acp observer reconciles broker completions. Configure matching workspace/state and permission authority for both. ACP is not a replacement for the historical Fleet semantic orchestration workflow.

Root postinstall installs the independent tools/ dependencies automatically. npm run tools:install remains a repair/manual installation command and is useful for standalone MCP clients:

npm run tools:install
node tools/src/server.mjs --groups native,web,browser,terminal,repl
# Optional browser download (or set CONNECTOME_BROWSER_EXECUTABLE):
npm --prefix tools run browser:install

Node.js 22.13+ is required for independent tool supervisors and Node-only tests. Python sessions require Python 3 (override with CONNECTOME_PYTHON_EXECUTABLE). Browser tools and terminal screenshots require Chrome/Chromium. See tools/README.md for platform prerequisites and standalone configuration. The npm tarball includes tool sources and package/lock metadata, not dependencies or private state. If lifecycle scripts were skipped, approve and run the root postinstall before using the installed host. In a source checkout, run host tests with bun test (all existing host suites under test/); run the independent Node-only suites with npm --prefix tools test.

Share a fresh source export or npm pack tarball without .git. Ignore rules and package allowlists exclude current private logs/configuration/state; they do not remove personal records already present in earlier Git commits.

Recipes #

A recipe is a JSON file that configures everything specific to one agent:

{
  "name": "My Agent",
  "description": "What this agent does",
  "agent": {
    "name": "researcher",
    "model": "claude-opus-4-6",
    "timezone": "America/Los_Angeles",
    "systemPrompt": "You are a ...",
    "maxTokens": 16384
  },
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["path/to/server.js"],
      "env": { "API_KEY": "${MY_SERVER_API_KEY}" }
    }
  },
  "modules": {
    "subagents": true,
    "tools": { "workspaceRoot": "./notes" }
  },
  "sessionNaming": {
    "examples": ["Thread Archaeology", "Pipeline Debug"]
  }
}

Every string value may reference the environment as ${VAR} or ${VAR:-default}; a missing required variable fails the load, naming it. Values and combinations are validated at load time and fail loudly, naming the key. Unrecognized keys, though, are generally not rejected — a misspelled key (mcplServers, a leftover modules.files) is silently ignored, so check spelling against this README and src/recipe.ts.

agent.timezone is an IANA zone used only for times rendered to the agent. Chronicle and MCPL protocol timestamps remain epoch/UTC. If the recipe omits it, AGENT_TIMEZONE is used, then the process timezone.

Memory defaults: agent.strategy may be omitted entirely. The default is the autobiographical memory strategy with adaptive resolution, KV-stable folding (compile plans that preserve prompt-cache prefixes), compression by the agent's own model, and summaries voiced as the agent itself (summaryParticipant defaults to agent.name). Set a strategy block only to tune windows/budgets or opt into a different strategy type (frontdesk for channel-staffing agents, passthrough for none) — see docs/AGENT-ONBOARDING.md for sizing guidance on long-lived agents and docs/AGENT-MEMORY-GUIDE.md for how it works.

Prose routing #

Plain assistant text (anything the model writes that is not a tool call) is delivered by Agent Framework according to agent.proseRouting:

Mode Behavior
"locus" (default) Text is auto-published to the current locus — the channel that last woke the agent. Text emitted in a tool-call round is delivered live, as narration, unless that round also calls skip_reply or an explicit send tool.
"hybrid" Like locus, but a leading >>>destination envelope routes that segment elsewhere through the authorized channel resolver.
"explicit" Text must start with >>#channel / >>@person / >>skip_reply; unprefixed text is never delivered and bounces to a clipboard for a prefixed resend.
"disabled" Text is never auto-published. The only way anything reaches a channel is an explicit send tool (send_message, channel_publish, reply_message, send_dm, ...). Authored text stays in Chronicle and the turn-end [delivered] nothing receipt tells the agent how many segments were withheld.

Use "disabled" for agents that run multi-step tool tasks from a busy shared channel: in locus mode a stray one-line narration between two tool calls ("checking page 2") is published to that channel as an ordinary message, and the only mitigation is behavioral (never narrate in tool rounds, always end tool-only turns with skip_reply). With "disabled" the agent replies by calling a send tool, and nothing else leaks. Ephemeral subagents inherit the caller's mode.

agent.sameRoundThinkTextPolicy ("public" default, or "private") governs only text emitted beside a think() call in the same round. It does not cover tool-call rounds without think(); use proseRouting: "disabled" for that. The think policy can be inspected and switched at runtime through the agent's agent_settings tool and the web UI; proseRouting is fixed for the process lifetime.

{
  "agent": {
    "proseRouting": "disabled",
    "sameRoundThinkTextPolicy": "private"
  }
}

See Agent Framework's docs/disabled-prose-routing.md, docs/explicit-prose-routing.md, and docs/hybrid-prose-routing.md for the full semantics of each mode.

Recipe loading #

Command Behavior
bun src/index.ts Reuse last saved recipe, or start with generic default
bun src/index.ts <path> Load recipe from local file
bun src/index.ts <url> Fetch recipe from HTTP URL
bun src/index.ts --no-recipe Reset to default generic assistant

The loaded recipe is saved to $DATA_DIR/.recipe.json (mode 0600) in its unresolved form — ${VAR} references kept, a URL system prompt kept as the URL — and re-resolved against the current environment on each bare start, so rotated credentials take effect on restart. The exceptions are fleet children's recipe and extensions' path, which are stored resolved: keep secrets out of them, since a ${TOKEN} in a child-recipe URL lands in the snapshot as the token itself. Treat the data directory as sensitive anyway — it holds the agent's history, and a fleet parent currently persists its children's resolved env there (#162). A variable that has disappeared since fails the start loudly instead of falling back to the default recipe.

System prompt from URL #

If systemPrompt is an HTTP(S) URL (no spaces or newlines), it's fetched as plain text:

{
  "agent": {
    "systemPrompt": "https://example.com/prompts/researcher.md"
  }
}

MCP servers #

A recipe's mcpServers decides which servers the agent gets. An entry can define a server outright (command + args, or url for WebSocket), or name an id from mcpl-servers.json — a registry in the working directory (edited with /mcp add|remove|env). Registry servers are opt-in: one loads only when a recipe names its id. For a named id the registry supplies the command, args and env, and the recipe entry may carry only policy fields — no command or url needed: channelSubscription, toolPrefix, feature-set and tool toggles, reconnect settings, WebSocket transport, access, toolLifecycle and inheritEnv (RECIPE_OVERRIDABLE_SERVER_FIELDS in src/mcpl-config.ts). An id-only entry the registry doesn't define is a startup error. Changes take effect on restart.

Agents with modules.mcplAdmin can also deploy, restart and unload their own servers; those are kept in mcpl-servers.agent.json and load regardless of the recipe.

Per server, requestTimeoutMs raises the framework's JSON-RPC timeout (60 s by default) for slow tools, and agent.retry passes a Membrane retry policy through for flaky gateways.

Stdio servers do not inherit the host environment. A child gets agent-framework's allowlist (CHILD_ENV_ALLOWLIST: PATH, HOME, USER, LOGNAME, SHELL, TERM, LANG, LC_*, TZ, temp and XDG dirs, DISPLAY/WAYLAND_DISPLAY, TLS CA bundles, HTTP_PROXY/HTTPS_PROXY/NO_PROXY in either case, and the Windows system variables), then the entry's env, then the host's AGENT_TIMEZONE. agent-framework then adds its derived DISCORD_SUPPRESSED_REACTIONS_BASELINE when the child spawns, unless the entry's env sets one with tokens, or, for an inheritEnv entry, the host's own environment does. A server that needs a value from .env must declare it, e.g. "DISCORD_GUILD_ID": "${DISCORD_GUILD_ID}"; a variable left only in .env is unset for the server.

Operator-owned recipe/file entries can set inheritEnv: true to pass the full host environment, including credentials. Prefer explicit env entries for needed variables. Explicit recipe false overrides file true; omission preserves the file's policy. Agent-owned mcpl-servers.agent.json overlays strip inheritEnv, including when replacing an operator-defined server. Put a full-inheritance grant in the recipe or operator server file instead.

Check legacy configuration variables before enabling full inheritance. For Discord MCPL, an inherited DISCORD_SUPPRESS_REACTION_EMOJIS="" seeds an explicit empty suppression list when its configured filters file does not yet exist; that durable file then overrides the protective baseline. Remove an unintended stale variable before the first startup, or configure the intended suppression in the filters file. Likewise, a DISCORD_SUPPRESSED_REACTIONS_BASELINE with tokens in the host's environment becomes an inheriting child's whole baseline, in place of agent-framework's protective default. The framework logs when it does.

Feature sets and tool names #

An MCPL server declares feature sets, and each server entry chooses which of them to enable:

  • enabledFeatureSets omitted: every set the server declares is enabled, subject to the uses rule below.
  • enabledFeatureSets: [] in a recipe or in mcpl-servers.json: no set is enabled (deny-all). Only the agent's own mcpl-servers.agent.json reads an empty list as unset, since strict function calling makes some models send [] for "unspecified" (resolveOverlayEntry in src/mcpl-config.ts).
  • A * in a pattern matches exactly one dot-separated segment (memory.* matches memory.retrieval, not memory.a.b). disabledFeatureSets wins over enabledFeatureSets.
  • A set whose declaration has no uses, an empty one or an unrecognized value stays disabled whatever the lists say, as does one whose uses names a capability the server was not granted. Each such set is logged on stderr as [mcpl] <server>/<set> disabled: ….

The model sees an MCPL server's tools as mcpl--<serverId>--<tool>: that is agent-framework's default prefix, and the host sets none of its own. An entry's toolPrefix replaces the mcpl--<serverId> part. Tool-name patterns elsewhere in a recipe match that model-facing form, so toolClassOverrides keys and toolLifecycle tools narrowings are written mcpl--blender--*, not blender--*. Most host module tools use <module>--<tool> (for example history--search); built-in ToolsModule tools use bare names. enabledTools and disabledTools take bare server names.

Tool lifecycle and tool classes (MCPL RFC-007 / RFC-008) #

An MCPL server can follow the agent's calls to other tools: a desktop avatar picking up a prop while a shell command runs, or pointing where the agent clicks. It receives tools/lifecycle notifications (started, then completed / failed / aborted) and never tool results. Both permissions are off by default. A toolLifecycle block on the server's entry, in the recipe or in mcpl-servers.json, is the grant:

"mcpServers": {
  "avatar": {
    "command": "node",
    "args": ["avatar-mcpl.mjs"],
    "toolLifecycle": {
      "observe": {},
      "inputs": { "classes": "default" }
    }
  }
}
  • observe sends metadata (tool, class, provider, phase, duration). {} means every call. Narrow it with tools (patterns over model-facing names such as mcpl--cua--*, * = any run), classes, or conversations (agent names).
  • inputs sends argument fields, but only the fields the server asks for with tools/observe. It needs a tools or classes term to deliver anything ("default" = computer, shell, files, web, media, body). It never carries comms or unclassed tools' arguments. maxInputBytes bounds the payload (default 16 KiB).

A tool's class comes from, in order:

  1. the recipe's toolClassOverrides;
  2. this host's table of its own module tools (HOST_TOOL_CLASSES in src/tool-lifecycle-config.ts) or the framework's built-ins;
  3. the server's own _meta["mcpl/class"].

Third-party MCP servers never declare a class, so class them in the recipe:

"toolClassOverrides": {
  "mcpl--cua--*": ["computer"],
  "mcpl--blender--*": ["media"]
}

An unclassed tool is treated as the most restrictive class: observable that it ran, never what it was given.

To check what each tool ended up as, and which of the three sources decided it, run /tools [agent], open the web UI's MCP tab, or GET /debug/tool-classes. With mcplAdmin enabled, the agent's mcpl_list shows the same for each server's tools.

Servers an agent deploys for itself (mcpl-servers.agent.json) can never hold either permission. The overlay denies toolLifecycle and strips any toolLifecycle block, as it already does for context hooks and server-initiated inference. To let such a server observe, the operator moves it into the recipe. These settings take effect with an agent-framework that includes tool lifecycle (anima-research/agent-framework#199); older ones ignore them.

Included recipes #

Recipe What it is
recipes/mock-test.json Offline smoke test: mock provider, no credentials, loopback web UI
recipes/claude-export-revive.json Continue a conversation imported from claude.ai (guide)
recipes/triumvirate.json Historical conductor example; Fleet orchestration is not exposed by the current host (archive)
recipes/knowledge-miner.json Knowledge extraction from Zulip + GitLab + web search (guide)
recipes/knowledge-reviewer.json Critic pass and SME checklists over mined documents
recipes/clerk.json Frontdesk agent that answers from the mined library on a Zulip channel
recipes/zulip-miner.json Zulip-only knowledge extraction
recipes/mcpl-editor-test.json, webui-test.json, webui-fleet-test.json Development test recipes

The production residents' recipes live in their own install directories, not here; docs/AGENT-ONBOARDING.md shows the shape.

Claude subscription provider #

The default anthropic provider also runs on a Claude subscription (Pro/Max) instead of an API key. Install Claude Code, generate a long-lived OAuth token with claude setup-token, and export it as ANTHROPIC_AUTH_TOKEN:

export ANTHROPIC_AUTH_TOKEN=sk-ant-oat...

No recipe change is needed — any anthropic recipe works. When ANTHROPIC_AUTH_TOKEN is set it takes precedence over ANTHROPIC_API_KEY (requests never carry both). Connectome then sends the oauth-2025-04-20 beta header (merged with any agent.anthropicBetas) and prepends the Claude Code identity block the subscription endpoint requires ahead of the recipe's system prompt. Usage draws down the subscription's 5-hour and weekly windows rather than per-token billing; the TUI status line and WebUI show them. When the quota meter already has a reading that shows a spent window, a 429 parks the agent until the window resets instead of retrying; without a reading (e.g. the first 429 in a headless run with no viewer, or an unreadable usage endpoint) it follows the normal retry path.

A claude setup-token credential cannot be rotated by the host: when it expires or is rejected, the host raises an auth-rejected alert whose only action is pasting a new token. A credentials file with a refresh token (ANTHROPIC_OAUTH_CREDENTIALS_FILE) makes "Refresh token" available and warns 30 minutes before expiry. See the credential-alerts note under the ChatGPT provider and docs/subscription-transport.md.

OpenAI reasoning #

For openai-responses and openai-codex, primary-agent reasoning is configured under agent.responses. Omitted settings default to reasoningEffort: "high" and reasoningContext: "all_turns". Add the following to an existing recipe to request a higher effort:

{
  "agent": {
    "responses": {
      "reasoningEffort": "xhigh",
      "reasoningContext": "all_turns"
    }
  }
}

The host accepts none, low, medium, high, xhigh, or max for reasoningEffort, and current_turn or all_turns for reasoningContext. The selected model must support the requested effort. These settings apply to the primary agent; modules.retrieval has its own reasoning setting described below. Memory sizing and compression-model settings are covered in docs/AGENT-ONBOARDING.md.

ChatGPT subscription provider #

Install the Codex CLI, sign in with codex login, then select the subscription transport in a recipe:

{
  "agent": {
    "provider": "openai-codex",
    "model": "gpt-5.4",
    "codex": { "fastMode": false },
    "systemPrompt": "You are a helpful assistant."
  }
}

Connectome asks the Codex app-server to refresh the ChatGPT login and starts a device-code flow if needed. No OPENAI_API_KEY is used for this provider. Use /fast on or /fast off at runtime. Connectome requests Codex's Fast tier and warns if the service reports that it fell back to Standard; Fast mode consumes subscription credits at a higher rate when applied. Quota windows are shown the same way as for the Claude subscription. See docs/subscription-transport.md.

On any subscription credential (Anthropic OAuth or Codex) the host names what is wrong with it — spent quota, unreadable quota, expiring/expired/rejected token, pending login — as an ops alert with the actions it can run, shown on the TUI status bar / /auth, the WebUI alert strip and Health tab, and the fleet parent. Nothing rotates without an operator action. See docs/subscription-transport.md.

OpenAI-compatible endpoints (Ollama, vLLM, Together, Groq, NanoGPT, ...) #

Any server speaking the OpenAI chat-completions API works through the generic openai-compatible provider — the recipe names the endpoint and the model:

{
  "agent": {
    "provider": "openai-compatible",
    "baseUrl": "http://localhost:11434/v1",
    "model": "qwen3:32b",
    "systemPrompt": "You are a helpful assistant."
  }
}

The API key is read from OPENAI_COMPATIBLE_API_KEY only — deliberately no OPENAI_API_KEY fallback: baseUrl is recipe-controlled, and a real OpenAI credential must never be sent silently to an arbitrary endpoint. Local servers usually need none. agent.model is required — there is no default model for an arbitrary endpoint. Tool calls use the standard tool_calls format, so the endpoint must support function calling for tool-using recipes. Provider-side prompt caching and cache accounting depend on what the endpoint reports.

Other providers #

agent.provider Credentials Notes
bedrock AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (default us-west-2) Claude on AWS — including models retired from the first-party API. BEDROCK_BASE_URL routes through an inference gateway. Prompt caching is enabled per model where Bedrock supports it; agent.promptCaching overrides.
openai-responses OPENAI_API_KEY (OPENAI_BASE_URL optional) OpenAI Platform, Responses API.
openrouter OPENROUTER_API_KEY
mock none Echoes the last user message by default; scripted replies and timing are set under agent.mock (below). Calls are still logged.

Every provider's calls are logged to $DATA_DIR/llm-calls.<iso>.jsonl. agent.formatter: "anthropic-xml" with agent.prefillUserMessage reproduces classic prefill-style prompting for agents migrated from prefill-era bots.

Mock provider #

agent.provider: "mock" runs membrane's MockAdapter: the full host loop with no credentials and no provider spend. agent.mock configures it:

Key Default Meaning
echoMode true Reply [Echo] <last user message>.
defaultResponse membrane's canned text Reply when not echoing and the queue is empty. A non-empty string.
responseQueue none Replies returned in order, one per provider call, before the echo or defaultResponse takes over. Every call through the adapter takes one, including auxiliary calls such as compression and session naming. Non-empty strings.
completeDelayMs 10 Delay before a non-streamed reply, in ms. Agent turns stream; auxiliary calls take this path.
streamChunkDelayMs 5 Delay between streamed chunks, in ms. There is none before the first chunk.
streamChunkSize 10 Characters per streamed chunk. A positive integer.

A streamed reply of L characters takes about (ceil(L / streamChunkSize) - 1) × streamChunkDelayMs, so a slow agent turn is a long reply with small chunks. This one holds the first turn for about 2 s, which leaves time to send more events while it is in flight and see how they coalesce:

"agent": {
  "provider": "mock",
  "mock": {
    "echoMode": false,
    "responseQueue": ["first reply", "second reply"],
    "defaultResponse": "done",
    "streamChunkSize": 1,
    "streamChunkDelayMs": 200
  }
}

Unknown keys under agent.mock are reported in the recipe's unknown-key warning, like those at the other levels.

What it provides #

Keeping one agent alive

  • Autobiographical memory — context-manager folds older history into memories the agent writes in its own voice, fitted to a context budget the agent and operator can change at runtime (agent_settings, WebUI Settings); nothing is deleted from Chronicle
  • Prompt-cache economics — cache-stable folding, a 1-hour Anthropic cache by default (agent.cacheTtl), plus opt-in agent.cacheKeepalive: current-prefix Antigravity warming and validated Anthropic snapshot replay, with live Settings controls and distinct Health accounting. Implicit Google cache lifetime is not guaranteed. See transport safety.
  • Cost and quota — a billing-grade call ledger with a cache verdict per call; on subscriptions, quota windows instead of dollars and parking on a spent window
  • Attention — the wake gate (modules.wake, on) decides which events start a turn; agents edit their own rules. subscriptionGc (on) closes channels that flood context; channelMode (on) switches a channel between mentions-only and debounced ambient. See docs/ATTENTION-AND-GATING.md
  • Tools (modules.tools, on) — direct filesystem, shell, web, browser, terminal, and persistent repl tools with bare names. ACP is an opt-in group.
  • History (opt-in, modules.history) — the agent searches and extracts from its own uncompressed record. The object form { "semantic": { "url": … } } adds history--semantic_search: meaning-based search over raw messages and memories, through a shared embed service (startup fails if the installed agent-framework predates 0.20). The index namespace is always <prefix>/<session id>, the prefix defaulting to the agent name; token takes ${VAR}; plain http is refused outside loopback and the tailnet unless allowInsecureHttp: true; unknown keys fail the load. Sync sends the raw record — including the agent's private notes unless includePrivateTools: false — and /session delete does not remove the remote index
  • Shared instructions (opt-in, modules.instructions) — see below
  • Identity (opt-in, modules.identity) — the agent's own key-based identity, used to obtain access to services without credentials entering its context
  • Conversations (top-level conversations — deprecated, not recommended) — per-channel conversation forks spawned from a dormant trunk agent. Being retired (agent-framework#235): its 'mention' bind/trigger rule, the default for channels, reads metadata.mentioned, which discord-mcpl does not set, so on Discord channels an @-mention never binds or triggers a fork. Still works; the host logs a [deprecated] line at startup
  • Subconscious (top-level subconscious) — a secondary agent that can tune_out channels; code execution (top-level codeExecution) — a Python tool runner
  • Activity (modules.activity) typing indicators and a TTS relay (modules.ttsRelay) for voice clients
  • Extensions (top-level extensions) — local modules that register custom context strategies or framework modules

Looking after it

  • Web UI (modules.webui) — browser operator console, below
  • TUI, readline and batch modes — OpenTUI interactive terminal, --no-tui for a plain prompt, or piped stdin for a one-shot batch run
  • Headless mode — --headless: no terminal, JSONL over a Unix socket; how residents run under a supervisor
  • Time-travel — Chronicle-backed undo/redo, checkpoints, branch exploration; in the web UI, rolling back to a message and suppressing messages
  • Session management — isolated sessions with auto-naming
  • Ops alerts — compression quarantine, refusal streaks, inference exhaustion and more surface as ops:alert traces, logs/failures.log records, and an optional webhook (CONNECTOME_OPS_WEBHOOK)
  • Importers — claude.ai exports, Claude Code transcripts and Codex rollouts become sessions (guides above)

More agents

  • Historical Fleet orchestration — archived in docs/fleet-protocol.md; modules.fleet and Fleet agent tools are not exposed by the current host.
  • Subagent forking (opt-in, modules.subagents) — spawn/fork parallel in-process agents, shown in the fleet tree (Tab)
  • Persistent lessons (opt-in, modules.lessons) — knowledge store with confidence scores and tags. Automatic retrieval-injection of lessons into context (modules.retrieval) is a separate opt-in — it adds per-turn context churn and retrieval-model calls, so enable it only for agents that actually curate a lesson library

For openai-responses and openai-codex, an object-valued modules.retrieval can set reasoningEffort (none, minimal, low, medium, high, xhigh, or max) independently of the primary agent. Retrieval calls are independent one-shot requests, so there is no separate retrieval reasoning-context setting. When reasoningEffort is configured, model must also be set explicitly: the historical retrieval default is a Claude model and cannot be sent through an OpenAI adapter. Anthropic/Claude uses different native thinking controls and does not accept this OpenAI-shaped option.

For a single-pass local decision model, configure an HTTP decision endpoint:

{
  "modules": {
    "retrieval": {
      "model": "LiquidAI/d1-2B",
      "decisionUrl": "http://127.0.0.1:7345",
      "decisionThreshold": 0.5,
      "maxInjected": 5
    }
  }
}

The host POSTs the recent conversation and every eligible lesson to <decisionUrl>/v1/decide; concept extraction and substring matching are bypassed. The endpoint returns model, durationMs, relevantIds, and a probabilities object containing a finite P(yes) in [0,1] for every submitted lesson ID. Selection is inclusive at decisionThreshold (default 0.5), ordered by probability descending, then lesson confidence descending, before maxInjected is applied. Dynamic knowledge remains at afterUser, preserving the stable system-prefix cache. Successful empty decisions and tool-only continuations reuse the existing retrieval cache.

Use a trusted endpoint: requests contain conversation text and lesson content. Local HTTP has a five-second timeout. Connection errors, HTTP errors, timeouts, and malformed decisions warn and fall back to the existing three-step cloud pipeline. On Google Antigravity, that fallback uses gemini-3.8-flash-low with low reasoning, never the local model name; other providers use the configured primary model. No provider reasoning setting is needed for local decisions. The authenticated /debug/retrieval/view inspector distinguishes semantic evaluation from mechanical matching and shows probabilities, selected IDs, decision timing, and retained local errors when cloud fallback takes over.

Vault retrieval #

modules.vaultRetrieval independently retrieves knowledge from an existing QMD collection. It is disabled by default and does not require Lessons:

{
  "modules": {
    "vaultRetrieval": {
      "enabled": true,
      "command": "qmd",
      "collection": "notes",
      "minScore": 0.70,
      "maxChunks": 2,
      "timeoutMs": 3000
    }
  }
}

The example shows all defaults except enabled. Index the collection with QMD before enabling retrieval. Searches use structured lex: / vec: queries with --no-rerank --json, with CI removed from the child environment. QMD's no-rerank scores are rank-based, not calibrated similarity probabilities: on QMD 2.8.3, the default minScore: 0.70 admits only the top-ranked hit; maxChunks is an upper bound.

Set decisionUrl to a trusted d1-compatible service to semantically select the snippets instead of treating RRF rank as relevance:

{
  "decisionUrl": "http://127.0.0.1:7345",
  "decisionThreshold": 0.5,
  "candidateLimit": 4
}

These fields belong inside modules.vaultRetrieval. candidateLimit defaults to four and its effective minimum is maxChunks. The module fetches that many candidates without applying minScore, then POSTs them and the user message to /v1/decide. It selects inclusive P(yes) >= decisionThreshold (default 0.5), orders by probability, and injects at most maxChunks. Headers report these calibrated probabilities, so a low-RRF second-ranked note can be retained and a high-RRF but irrelevant first-ranked note can be rejected.

Set queryUrl (for example, http://127.0.0.1:7346) to use an independently managed, resident QMD SDK worker instead of loading the embedding model in a new CLI process per search. The worker accepts POST /v1/query with { query, collection, limit } and returns the same JSON result array as the CLI: [{ file, title, snippet, score }]. file uses a canonical qmd://collection/path URI. A failed, timed-out, or malformed worker response warns and falls back to the configured CLI. The host does not start or restart this optional service. Use a trusted endpoint: queries contain user text and results contain private vault knowledge.

The WebUI Lessons → Last Turn Retrieval section shows both modules' latest primary-agent decisions: evaluated counts, calibrated match probabilities, query/decision/total latency, cache reuse, and fallback/error states. Vault matches link to Obsidian. Legacy scores are explicitly labeled as rankings, not probabilities. The visible inspector refreshes after parallel context gathering and inference boundaries without polling; local/fleet snapshots remain scoped. Cached query/decision timings describe the original work, while total latency describes the latest gather.

Results are injected only at afterUser, never into the system prompt or stored conversation, preserving the stable prompt prefix. Searches run concurrently with other module context hooks, including Lessons; turn assembly still waits for the slowest hook. Results, including empty searches, are cached per agent and user message across tool continuations. New/edited messages or replacement contexts select a new cache entry; errors remain retryable. QMD failures and timeouts log a warning and omit vault context. A failed, timed-out, or malformed decision response warns and falls back to the original minScore filtering; fallback scores remain uncalibrated QMD ranks. timeoutMs bounds each worker HTTP, CLI, and decision attempt; a worker failure followed by CLI fallback can therefore take two query timeouts. Shutdown cancels in-flight requests without stale injections. On POSIX, CLI cancellation terminates the process group, including its model worker.

Shared instructions #

modules.instructions reads living instruction documents directly from disk and injects them into every agent's context on every turn, including ephemeral subagents. Edits take effect on the next turn; nothing is persisted to history. The existing single-file config remains supported: path instructions/AGENTS.md, position: "system", 32 KiB cap by default. Alternatively, configure an ordered files array (not combined with single-file options):

{
  "agent": { "systemPrompt": "" },
  "modules": {
    "instructions": {
      "files": [
        { "path": "orientation.md", "header": "", "includeInCompression": true },
        { "path": "instructions/AGENTS.md", "maxBytes": 32768, "includeInCompression": false }
      ]
    }
  }
}

Each entry requires path and independently supports header, maxBytes, position, and includeInCompression. header: "" preserves exact file text with no heading or leading separator. System files follow the base system prompt in list order; an empty base adds no leading newline. Relative paths use the configured workspace root (or recipe directory); absolute paths work too. Each file independently uses a bounded handle-based read and (realpath, mtimeMs, size) cache: unchanged files incur no content rereads. A missing/unreadable file fails open without hiding the other files, warning once per distinct error until recovery. No workspace mount, materialization, or synchronization tool is required: edit the disk source directly.

Compression identity: only position: "system" files are eligible. If any file explicitly sets includeInCompression, only entries set to true are included; an explicit false disables automatic inference for the list. With no explicit flags, files with header: "" or basename WAKE.md are selected. This includes a compact orientation without automatically adding the tool reference. The base agent.systemPrompt is retained. Empty/missing files clear their old contribution.

The host awaits InstructionsModule.bindAgentContext before publishing or seeding each resident, ephemeral or conversation context. Generic framework embedders can supply FrameworkConfig.bindAgentContext; the returned disposer runs when that context retires. ContextManager.setSystemPromptRefreshHook refreshes stat-cached identity before memory operations, so idle edits apply without requiring a primary turn. Compression and merges append the configured summarySystemPrompt; this separate memory-writing slot does not duplicate the primary instruction overlay or persist instructions to Chronicle.

Cache note: at position: "system" the block lives in every agent's prompt-cache prefix, so edits change that prefix. An enabled current-prefix warmer may seed it before the next turn, but retention/quota savings are not guaranteed. Curate in batches, or use afterUser for cache-cheap, lower-salience injection. Compared to lessons (modules.lessons): lessons are a structured, confidence-scored store with model-driven retrieval; instructions are ordered free-form curated documents, present verbatim up to their byte caps.

Prerequisites #

  • Bun runs the host; Node.js 20+ and npm build the web UI
  • No Rust toolchain: @animalabs/chronicle ships prebuilt native binaries
  • Credentials for one provider: an Anthropic API key, a Claude subscription OAuth token (claude setup-token), an OpenAI or OpenRouter key, AWS credentials for Bedrock, the Codex CLI signed in with ChatGPT — or none, for mock and keyless local endpoints

Install #

bun install      # or npm install; the postinstall step builds the web UI

Environment variables #

Variable Default Description
ANTHROPIC_API_KEY (required for anthropic unless ANTHROPIC_AUTH_TOKEN is set) Anthropic API key
ANTHROPIC_AUTH_TOKEN — Claude subscription OAuth token (claude setup-token); takes precedence over ANTHROPIC_API_KEY
ANTHROPIC_OAUTH_CREDENTIALS_FILE — JSON credentials file (accessToken + refreshToken + expiresAt, Claude Code's shape or flat); a refresh token makes the credential host-rotatable. Use a COPY, never ~/.claude/.credentials.json itself
ANTHROPIC_OAUTH_AUTO_REFRESH off 1/true: rotate on a 401 without an operator action
ANTHROPIC_BASE_URL Anthropic API Route Anthropic calls through a gateway
OPENAI_API_KEY, OPENAI_BASE_URL — openai-responses recipes
OPENAI_COMPATIBLE_API_KEY — Key for openai-compatible recipes (no OPENAI_API_KEY fallback by design); omit for local servers
OPENROUTER_API_KEY — openrouter recipes
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION region us-west-2 bedrock recipes
BEDROCK_BASE_URL — Route Bedrock calls through a gateway
CODEX_BINARY codex Codex CLI executable for openai-codex subscription auth
CODEX_HOME ~/.codex Codex credential/config directory
CODEX_BASE_URL ChatGPT Codex backend Optional subscription transport override
CODEX_GATE_TOKEN — openai-codex through an inference gate that holds the ChatGPT logins (gate's /codex leg): no Codex CLI or login on this host. Requires CODEX_BASE_URL pointing at the gate
MODEL from recipe, else claude-opus-4-6 (gpt-5.4 for openai-codex) Override model (wins over the recipe)
DATA_DIR ./data Session, recipe and log storage
AGENT_TIMEZONE process timezone Agent-facing clock when the recipe sets no agent.timezone
SLEEP_PRIVILEGED_FILE ./sleep-privileged.json Who may wake the agent during sleep
OBSERVERS_FILE $DATA_DIR/observers.json Web UI observer grants
IDENTITY_KEY_FILE, IDENTITY_HOME $DATA_DIR/identity-key.pem, id.animalabs.ai modules.identity
CONNECTOME_OPS_WEBHOOK — Webhook for ops alerts
COUNT_TOKENS_MODEL the agent's model Override the model used for exact token counts in the context-makeup panel
LLM_CALLS_FULL_PAYLOADS off 1/true: keep the full request on every llm-calls log entry (large)
GATE_TELEMETRY off 1: send x-gate-* telemetry headers — only when ANTHROPIC_BASE_URL is also set

Recipes may reference any further variables they need as ${VAR} (for example WEBUI_USERNAME / WEBUI_PASSWORD, ZULIP_CHANNEL, GITLAB_TOKEN in the shipped recipes).

Running #

bun src/index.ts                    # Interactive TUI
bun src/index.ts --no-tui           # Readline mode
echo "Hello" | bun src/index.ts     # Batch mode: run each line, then stop
bun src/index.ts <recipe> --headless                     # Daemon: JSONL IPC over $DATA_DIR/ipc.sock, no terminal
bun src/index.ts <recipe> --headless --exit-when-idle    # One-shot: exit once the agent goes idle
bun src/index.ts <recipe> --headless --socket-path <p>   # Custom socket path
bun --watch src/index.ts            # Dev mode

Put the recipe before other arguments: the first argument that doesn't start with -- is taken as the recipe. Headless mode, its files and its protocol are described in docs/fleet-protocol.md.

Whenever stdin is not a TTY and --headless is absent, the host runs in batch mode, with or without --no-tui: it reads stdin to EOF, runs each line, then stops the agent and closes its MCPL servers. reconnect: true does not bring them back, because an explicit close is not a failure. That includes a host started by a supervisor or nohup with stdin redirected, so anything meant to keep serving needs --headless. The host prints a [batch] line at the start and before the teardown. If the recipe enables the web UI, its server outlives the agent by design, so the process keeps running, with no agent data behind the page, until interrupted.

Web UI #

Enable it in the recipe. The host serves the SPA and its WebSocket protocol on port 7340 and binds 0.0.0.0 by default; a non-loopback bind refuses to start without basic-auth credentials, so either bind loopback for local use:

"modules": { "webui": { "host": "127.0.0.1" } }

or supply credentials for a remote deployment:

"modules": { "webui": { "basicAuth": { "username": "${WEBUI_USERNAME}", "password": "${WEBUI_PASSWORD}" } } }

Build the SPA bundle once with bun run build:web (also runs on install via postinstall). Deployment behind a reverse proxy, observer access and all endpoints are covered in docs/webui-deployment.md.

  • Chat with full interiority: thinking blocks, tool calls + results, live streaming, inline images
  • Sidebar: agent/fleet tree, lessons, MCPL servers (live status, tool classes and registry), workspace files, context makeup + compression coverage, Settings (live context budget with dry runs), Pins (protected ranges), Health (alerts, per-call stats, compression debt) — each can inspect any fleet child via the "inspecting:" selector
  • Live surgery: roll back to a message, suppress messages, quiesce/resume the host; every action is recorded in an operator log (docs/webui-live-surgery.md)
  • Header branch chip opens the Chronicle branch lineage tree (checkout from the UI)
  • Ops alerts (compression quarantine, refusal streaks, inference-exhausted) render as persistent banner rows
  • Usage panel: per-agent costs and a billing-grade call ledger with cache verdicts; quota windows on subscriptions
  • Health: linked seven-day Antigravity quota and call timelines, selectable interval breakdowns by workload or model/thinking, optional cached-token stacks and paged individual calls; observed quota deltas remain distinct from per-call charges
  • /healthz (health JSON for doctor/fleet tooling), /quota, /antigravity-history (health-scoped seven-day diagnostics), /curve (compression-curve visualization), /debug/context/* (docs/debug-context-api.md), /debug/tool-classes — authenticated like the rest of the surface, and answerable by a fleet child with ?scope=<child>
  • /debug/retrieval/view — operator-only per-run lesson selection viewer (see docs/retrieval-traces.md)
  • Read-only observer access via Ed25519 device keys with per-grant scopes; the agent can grant and revoke observers itself

For SPA development: cd web && bun run dev proxies the Vite dev server onto a locally running host.

Slash commands #

The same commands work in the TUI, readline mode, the web UI and over the headless socket.

Command Effect
/help List all commands
/quit, /q Exit (exports lessons if the lessons module is loaded; asks first if fleet children are running)
/recipe Show current recipe info
/status Show agent state, branch, queue depth
/usage Show session token usage and costs
/clear Clear this client's display (history and context are kept)
/lessons Show lesson library sorted by confidence
/export Export lessons to ./output/ (JSON + markdown)
/newtopic [context] Reset the head window for a new topic
/nudge [agent] Run inference on the current context without a new event
/puppet <tool> [json] Admin: execute a tool as the agent and store the call/result pair
/undo Revert to state before last agent turn
/redo Re-apply undone action
/checkpoint [name] Save current state (no name: list checkpoints; held for this session)
/restore [name] Restore to checkpoint
/branches List Chronicle branches and checkpoints
/checkout <name> Switch to branch
/branchto <msgId> Branch from a specific message
/history [n] Show recent state transitions
/find <text> Search messages for text
/mcp list List servers in mcpl-servers.json
/mcp add <id> <cmd> [args...] Add or overwrite a registry server (keeps its env)
/mcp remove <id> Remove a registry server
/mcp env <id> KEY=VALUE [...] Set env vars on a registry server
/tools [agent] Each tool's effective MCPL class and where it came from (recipe override, host table, server _meta, or unclassed)
/budget [tokens] Show/set stream token budget
/fast [on|off|status] Toggle Codex subscription Fast mode
/auth [status|refresh|login|recheck|token <tok>] Subscription credential state and operator actions (see docs/subscription-transport.md)
/session [list|new|switch|rename|delete] Session management; delete requires --confirm
/fleet [list|status|view|peek|stop|restart] Fleet children (see docs/fleet-protocol.md)

Commands that move the head (/undo, /redo, /checkout, /restore, /branchto, /newtopic) are refused while a turn is in flight.

TUI controls #

Key Action
Enter Send message or command
Alt+Enter / Ctrl+J Newline
Esc Interrupt agent (chat) / back (fleet/peek)
Tab / Ctrl+F Toggle fleet view (when subagents or a fleet are enabled)
Ctrl+B Send the running stream and sync subagents to the background
Ctrl+V Toggle verbose mode
Ctrl+C Exit (asks first if fleet children are running; press again to force)
@child message Send a line straight to a fleet child, bypassing the conductor

Fleet view (Tab):

Key Action
Up/Down Navigate tree
Enter/Right Expand/collapse
Left Collapse
p Peek the selected node's live stream — local subagents, fleet children, or a single agent/subagent inside a fleet child
Delete / Backspace Stop the selected subagent or fleet child
r Restart the selected fleet child

Architecture #

See ARCHITECTURE.md for how the host is built, and docs/README.md for every guide.

Dependencies #

Package Source Role
@animalabs/agent-framework npm Event-driven agent orchestration
@animalabs/context-manager npm Context window management and compression
@animalabs/chronicle npm Branchable event store (Rust + N-API, prebuilt)
@animalabs/membrane npm LLM provider abstraction
@opentui/core npm Terminal UI (Zig native core)