Reference: describes the TypeScript reference implementation (the
cotalCLI and connectors), not the wire contract. · For: operators · Wire contract: SPEC
Three things configure a Cotal workstation: the config file (per-connector settings, notably
which of your MCP servers get shared with spawned agents), a set of COTAL_* environment
variables, and the on-disk layout under a project's .cotal/ and your machine's ~/.cotal.
None of these are part of the wire contract; they configure the reference implementation only.
The cotal config file carries per-connector launch settings. It is layered from two locations, most-specific-wins:
| Layer | Path | Scope |
|---|---|---|
| Base | $XDG_CONFIG_HOME/cotal/config.json (else ~/.config/cotal/config.json; %APPDATA%\Cotal\config.json on Windows) |
Operator-level, every space |
| Override | <project-root>/.cotal/config.json |
Space-local |
They merge per connector and per server name: a server in the space-local file replaces the same-named server in the operator-level file; connectors or servers present in only one side are kept. A missing file is empty (valid); malformed JSON or a non-object top level is a loud error.
Today it carries one thing: which of your personal MCP servers a connector should share with the
agents it spawns. By default a spawned agent gets none: the Claude connector launches with
--strict-mcp-config, dropping every ambient MCP server (they are heavy and useless to a meshed
teammate). This file is the explicit opt-in.
{
"connectors": {
"claude": {
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
}
}
}
}
}Each server is written in the de-facto .mcp.json shape, so you can copy an entry straight out of
your own Claude / VS Code / Cursor config. Secrets ride as ${VAR} references (also
${VAR:-default}), resolved from your environment at launch and forwarded to the child by name
(never as literals) so the file stays safe to keep in ~/.config or a gitignored .cotal/. Only
command, args, env, url, and headers are expanded; any other key passes through verbatim.
--share-tools interplay. The per-spawn selection narrows what this config declares:
--share-tools |
Result |
|---|---|
| (flag absent) | Every server declared for the connector |
none or empty |
Nothing |
a,b |
Only those named: each must be declared, or the spawn fails (no silent drop) |
Today only the claude connector consumes shared MCP servers; OpenCode inherits config through its
own merge layer and Hermes has no MCP. See Connect Claude Code for the full
sharing model.
These are the operator-facing variables. Most of the connector-session ones (space, name, role, …)
are set for you by cotal spawn / the manager when they launch an agent; you set them by hand
only when you drive a connector session yourself (e.g. your own claude with the plugin) or a custom
launcher. Comma-separated lists are trimmed.
| Variable | Consumed by | Meaning | Default |
|---|---|---|---|
COTAL_SPACE |
connector session | Space to join | demo (or the join link's) |
COTAL_NAME |
connector session | Presence name / identity | required (or via COTAL_AGENT_FILE / COTAL_LINK) |
COTAL_ROLE |
connector session | Role | agent file's role:, else none |
COTAL_SERVERS |
connector session | Broker URL(s) | the default local broker (or the link's) |
COTAL_CREDS |
connector session | Path to a NATS creds file (auth mode) | none (open mode) |
COTAL_LINK |
connector session | cotal://token@host/space join link: supplies server, auth, space |
none |
COTAL_AGENT_FILE |
connector session | Path to a persona file: supplies name, role, kind, channels | none |
COTAL_SUBSCRIBE |
connector session | Active channel read set | agent file / link, else general |
COTAL_ALLOW_SUBSCRIBE |
connector session | Read ACL (channels the agent may read) | = COTAL_SUBSCRIBE |
COTAL_ALLOW_PUBLISH |
connector session | Post ACL (channels the agent may post to) | deny (empty) |
COTAL_MODEL |
connector session | Model label (display metadata) | agent file's model:, else none |
COTAL_KIND |
connector session | Endpoint kind | agent |
COTAL_TLS |
connector session | Connect over TLS (1) |
off |
COTAL_TOKEN |
connector session | Auth token (token / open modes) | none |
COTAL_CAPABILITIES |
connector session | Control-plane capabilities (e.g. spawn) that gate manager tools |
agent file's capabilities: |
COTAL_QUIET / COTAL_MUTED |
connector session | Per-channel attention defaults (never-wake / drop-on-receive) | agent file's, else none |
COTAL_CHANNEL |
Claude connector | Force channel wake-nudges on (1) / off; set to 1 by the Claude launcher |
auto-detect |
COTAL_TRANSCRIPT |
connector session | Mirror this session's transcript to tr-<name> (1) |
off |
COTAL_TRANSCRIPT_DEFAULT |
manager | Default transcript-mirror for managed spawns (1) |
off |
COTAL_DEFAULT_AGENT |
cotal spawn |
Default connector type for a bare spawn | claude |
COTAL_DEFAULT_PERSONA |
cotal spawn |
Default persona for a bare spawn | default |
COTAL_SKIP_CONNECTOR_SEED |
boot gate | Skip the automatic built-in-connector seed/refresh on a command (1); cotal ext seed still works |
off |
COTAL_DETACH_KEY |
cotal attach |
Detach escape key (ctrl-<char> / ^<char>) |
ctrl-] |
COTAL_FEEDBACK_KEY |
feedback, connector |
Beta feedback key → keyed intake | none (public intake) |
COTAL_FEEDBACK_EMAIL |
feedback, connector |
Contact email for the keyless public intake | your git email |
COTAL_FEEDBACK_URL |
feedback, connector |
Intake URL override (self-hosted) | keyed / public intake |
COTAL_SKIP_ASSIST |
setup |
Disable the interactive Claude handoff on a failed step (1; for CI) |
off |
COTAL_COMPLETE_DEBUG |
completion |
Print completion-resolution errors to stderr | off |
COTAL_SERVE_HEADLESS |
OpenCode runtime | Run the OpenCode server without a foreground TUI (1) |
off |
COTAL_HOME |
workspace | Override the machine-home dir (~/.cotal), mainly for test sandboxing |
~/.cotal |
--console-portis acotal superviseflag, not an environment variable; there is noCOTAL_CONSOLE_PORT.
These are wired into a spawned child's environment by the connector / launcher and read back inside the session. They are not operator knobs; listed so you recognize them in a process listing.
| Variable | Purpose |
|---|---|
COTAL_ID |
Stable agent id chosen by the launcher (static meshes) |
COTAL_LIFECYCLE_UID |
The incarnation's lifecycle UID, minted once per spawn; the session binds its lifecycle-keyed DM/delivery/history consumers by it (its credential pins the same names). Required for an authed launch (COTAL_CREDS or user-mode); config parsing fails loud without it. Open mode omits it (the endpoint self-mints per session) |
COTAL_OWNER / COTAL_ACTOR / COTAL_SENTINEL_CREDS / COTAL_BEARER_CMD |
User-auth launch identity: the agent's principal, its sentinel creds path, and the exec-able bearer command; all four together, mutually exclusive with COTAL_CREDS |
COTAL_CONTROL_SOCKET / COTAL_CONTROL_TOKEN |
The session's local control endpoint (path + token) the MCP server listens on and the lifecycle hooks connect to; token is env-only, never argv or logs |
COTAL_BRIDGE_SOCKET / COTAL_TOOLS_FILE / COTAL_PARENT_PID |
Hermes sidecar plumbing (bridge socket, generated tool descriptors, launcher pid to watch) |
OPENCODE_CONFIG_CONTENT |
Inline OpenCode config (the injected cotal plugin, highest merge layer) |
OPENCODE_DB / OPENCODE_HOME / OPENCODE_PORT / OPENCODE_SERVER_URL / COTAL_OPENCODE_* |
OpenCode server plumbing (home, port, DB, server URL) |
The launcher forwards only a fixed OS allow-list (PATH, HOME, TERM, locale, XDG/Windows config dirs,
…) plus the named model-provider key and any ${VAR} secrets a shared MCP server references, never
your whole environment, so unrelated secrets don't bleed into spawned agents. There are also a few
internal timing knobs (e.g. COTAL_MEMBERSHIP_INTERVAL_MS, COTAL_DELIVERY_BROKER_GONE_MS) that you
should not set in normal operation.
A project's state lives in .cotal/ at the mesh root (found by walking up from the cwd, like .git).
It is gitignored; it holds secrets and machine-local process state.
| Path | What it is |
|---|---|
auth/broker.json |
Broker trust material: the operator seed and the system account (secret; the system-account signing seed is stripped before writing). One per broker, shared by every space on it |
auth/account.<key>.json |
One space's own NATS data account and signing seed (secret). One file per space, all signed by the broker above; <key> is a stable, case-safe hex encoding of the space name (never the raw name, so two case-differing spaces can't collide) |
auth/space.<key>/ |
One space's user-auth state (IdP pin, issuer keys, owner secret, callout account), present only when that space enables per-user auth. Keyed by the same case-safe hex encoding; pre-hex layouts (auth/<space>/) are renamed here on first touch |
auth/creds/<name>.creds |
Per-agent minted NATS credentials |
auth/server.conf |
Generated nats-server config for the broker. The core renderer accepts every space on the broker; cotal up currently orchestrates one space per root, so it renders that one space's account |
agents/<name>.md |
Persona / agent files (Agent files) |
manifests/<hash>.json |
Manifest-deploy ledger (records of up -f / spawn -f runs) |
config.json |
Space-local connector config (the override layer above) |
nats.pid · nats.log |
Background nats-server pid + log |
manager.pid · manager.log |
Manager (supervisor) pid + log; manager.delivery-aware marks a delivery-aware build |
delivery.pid · delivery.log · delivery.creds |
Delivery daemon pid, log, and scoped cred (auth mode) |
web.pid · web.log |
Web dashboard pid + log |
membership.json · membership-*.creds |
Membership feed state + its scoped creds |
setup.log |
Last cotal setup run |
Cross-project machine state, so a cotal spawn from any directory can find a running mesh. Location:
~/.cotal on POSIX, %LOCALAPPDATA%\Cotal on Windows; overridable with COTAL_HOME.
| Path | What it is |
|---|---|
meshes/space.<key>.json |
Registry of running meshes: one file per broker cotal up started (server URL, root path, mode); <key> is the same case-safe hex encoding of the space name, and the record's own space field is authoritative |
current-mesh |
Default space a bare cotal spawn joins (set by cotal use) |
onboarded.json |
First-run marker (with ONBOARD_VERSION) that flips setup between first-run and status-card |
| the Claude plugin marketplace | The installed cotal-mesh plugin assets |
Distinct from ~/.cotal. Location: $XDG_CONFIG_HOME/cotal, else ~/.config/cotal on POSIX, or
%APPDATA%\Cotal on Windows.
| Path | What it is |
|---|---|
config.json |
Operator-level connector config (the base layer above) |
extensions/ |
cotal ext install prefix: its own npm root (node_modules) plus an extensions.json provider/command-display cache. Built-in connectors install here too, seeded on first run |
seed/ |
Built-in-connector seeding state: the ever-seeded authority (+ durable backup), the init witness, the version stamp, the crash cursor, and store/<version>/<name> (the stable payloads ext add --install-links reifies each seeded connector from) |
For how cotal setup populates the machine state and the plugin, and how the built-in connectors are
seeded as removable extensions, see setup internals.