Skip to content

Connect Jcode (beta)

Guide (informative) · For: operators · Prereqs: Quickstart

Jcode joins a Cotal mesh as a lateral peer. The connector creates one private Jcode Harness API instance per seat, one Jcode session inside it, and exposes the normal cotal_* tool surface through Jcode’s documented stdio MCP configuration.

Beta means the supported path is deliberately narrow: a fresh private session, prompt injection, presence, managed start/stop, requested reasoning effort, and an attached TUI work. Features that do not preserve that private session’s mesh surface fail loud: --resume, exact-session continuation, --share-tools, --events, and connector --opt values are not supported.

The connector is seeded with the Cotal CLI. Jcode’s released Harness API bridge is a Unix-socket surface, so Windows is not supported. Managed seats run on Linux, macOS, and the BSDs. Install Jcode 0.78.1 or later from its GitHub release and make the binary available as jcode on PATH:

Terminal window
jcode version --json
cotal spawn --agent jcode

If an older Cotal installation is missing the connector, run cotal ext seed --repair (or cotal ext add @cotal-ai/connector-jcode). This connector intentionally uses the released binary’s api-bridge command; it does not require a Rust checkout.

Terminal window
cotal spawn --agent jcode
cotal spawn reviewer --agent jcode -d
cotal spawn --agent jcode --model gpt-5.6-sol --prompt "Review the current change."
COTAL_DEFAULT_AGENT=jcode cotal spawn

A detached seat is managed normally: cotal ps, cotal attach, and cotal stop control the same process the connector starts. In a terminal, Jcode opens on the managed session. With piped output it stays headless; set COTAL_JCODE_TUI=1 or COTAL_JCODE_TUI=0 in the environment of the process building the launch to override that choice. For a detached spawn, that is the manager’s environment.

Jcode’s stable integration surface is the Harness API: protocol-v1 NDJSON over a Unix socket. The connector launches a private instance with @1jehuang/jcode-sdk’s launchInstance() and attaches only to that instance’s own socket:

  • launchInstance() starts a private JCODE_HOME, runtime directory, daemon, and api-bridge; the connector holds the process handle first-hand and closes that instance with the Cotal seat. This gives each managed Cotal peer one owned session and prevents it from seeing or changing the operator’s live Jcode sessions.
  • Attaching to an operator-run jcode api-bridge shares the operator’s live session inventory. That is appropriate for a dashboard or editor integration, but not a managed Cotal seat: stop, prompt injection, and session selection could act on the operator’s work. The connector never attaches to an operator bridge.
  • A managed seat never updates its own binary. Jcode’s background updater restarts the process tree when it lands a release; that restart drops the seat’s TUI, which is the only connection the Jcode server counts as a client, and nothing re-attaches, so the server’s idle reaper takes the seat down five minutes later in the middle of a turn. The seat’s version is whatever is on PATH when you spawn it, and it stays that version for the seat’s life. Update deliberately, between seats, not under a running agent.

On a graceful stop and on a startup failure, the connector proves the private daemon tree is actually gone rather than trusting the SDK’s registry-keyed stop (which is a silent no-op when the servers.json socket path does not match verbatim): it reads the PIDs the private home itself records, sends a bounded SIGTERM, escalates survivors to an exact-PID SIGKILL, and reports a failed stop instead of a clean one if any recorded process survives. It never signals by name, so teardown can only ever reach the seat’s own tree.

A seat that dies without that teardown, from a manager restart or a kill past the grace window, leaves its Jcode server running. The server has a process group of its own and carries no COTAL_NAME, so a name-keyed reap does not reach it, and it holds the seat’s runtime directory until its own five-minute idle timer expires. Each launch records its identity nonce and its host process in the private home, and the seat’s next launch stops the tree that record names. The recorded host is the gate: while it is still alive the seat is still serving, so nothing is signalled and the second launch meets Jcode’s own runtime-directory lock instead.

The private Jcode home lives under <manager-workspace>/.cotal/jcode/. It is unique per space/name and is owner-only. Jcode’s own credential inheritance is used for the private instance, so provider logins work without copying its transcript/config tree into the seat. The spawned Jcode process does not inherit COTAL_* values or the Cotal launch-material pointer.

Because the home is keyed by space and name, a seat respawned under the same space, name, and manager workspace lands in the same home, and the connector automatically continues the non-archived Jcode session there that was recorded for the seat’s working directory and holds the largest transcript, since that is the session carrying the memory a restart would otherwise throw away. A seat spawned under a fresh name keys a different home and starts with an empty transcript, so keep the same name when you want a replacement seat to continue where the previous one stopped. This automatic continuation is a relaunch of the seat’s own private session; it is separate from --resume, which names an outside session and stays unsupported. The short socket alias the connector derives from that home is reclaimed at every launch, so a name a stopped seat used stays launchable.

Connector diagnostics are written both to the spawning terminal and to an owner-only <private-home>/logs/connector-<timestamp>-<pid>.log, so a failed launch remains inspectable after the manager’s launch error scrolls away. Public startup failures stay scrubbed to allow-listed codes rather than arbitrary Harness API messages.

Credential mirroring is mandatory for managed Jcode seats: each launch atomically refreshes the allowlisted Jcode, provider-config, and external-login destinations, and removes a destination when its source login was removed. Cleanup addresses only that explicit inventory; transcripts, MCP configuration, logs, and other private-home state are untouched. Copy, mkdir, and removal walk the parents with O_NOFOLLOW, then publish, create, or unlink the leaf through the pinned parent rather than through a path the kernel re-walks. Replacing a walked directory with a symlink cannot write, create, or delete a namesake outside the private home.

Two mechanisms provide that pin. On Linux the leaf is named /dev/fd/<fd>/<name>, the openat and unlinkat equivalent Node does not expose, after /dev/fd/<fd>/. is proven to traverse. macOS mounts /dev/fd but has no subpath namespace under a descriptor, so there the connector pins the parent as the process working directory instead: a single-component name resolves from that directory’s inode and no ancestor is walked again. Entering by path is verified rather than trusted, because chdir takes a path: the entered directory’s inode must equal the inode of the descriptor opened a moment earlier, and a mismatch is refused by name. The working directory is restored on every exit, including the refusing ones. If neither pin is available, the connector throws a named error and mirrors nothing. Once a pin holds, ENOENT on a child means the mirror path is absent.

There is no credential-free opt-out today because the private instance must reproduce the operator’s current provider-login state rather than silently start with stale or partial authorization.

If a provider failure closes the private Harness API connection during a mesh-driven turn, the connector leaves that turn’s inbox batch unacknowledged and opens one bounded recovery window for a private replacement connection to the same session. A transient launch or attach failure retries inside that window, so a loaded host gets the same result as a fast one without creating an unbounded connector relaunch loop. The seat reports waiting while it reconnects, then redrives that unacknowledged batch only after the session attaches. Each failed replacement must be proven stopped before another launch. A permanent Harness refusal, including an invalid request, missing session, protocol mismatch, missing binary, or socket permission denial, ends the seat immediately; another launch cannot change it. An unprovable teardown, the recovery window expiring, or a second disconnect after a successful replacement also ends the seat. An unrecognized Harness SDK error code remains transient by default and retries inside the same bounded window; new permanent codes must be added to the explicit classifier and its exact-count regression.

Jcode currently supports stdio MCP servers. The connector writes only its own cotal entry to the private JCODE_HOME/mcp.json; it starts a stdio MCP bridge for that entry and relays its calls to the host’s one MeshAgent. The Jcode/MCP child receives a per-launch relay capability, but not the Cotal broker credential or its launch-material pointer. Jcode also overlays project .jcode/mcp.json, .mcp.json, and .claude/mcp.json; a managed launch refuses a workspace containing any of those files, because one could replace the cotal bridge or add tools that were not explicitly shared. Operator MCP configuration is isolated in the private home and project MCP configuration is not supported yet.

Before the seat joins the mesh, the host runs a mandatory Jcode turn that calls cotal_orientation. Jcode loads MCP tools asynchronously; its first turn can use the pre-MCP tool snapshot immediately before Jcode rebuilds that snapshot. The host repeats the identical proof once in that case. A second absence fails the launch, so a bridge that never comes up remains a loud failure rather than an agent that is present but mute. A managed Jcode seat has a three-minute bounded readiness window: first boot can download model material, start the MCP bridge, and wait through the provider-backed readiness turns. If that window expires, the launch is uncertain, not a failed or cleanup verdict; use cotal attach <name> or cotal ps to inspect it and do not stop it solely because the window elapsed. The host then waits for the mesh connection and presence bind to complete before it adds a no-reply notice that the bootstrap orientation predates the join and that a new orientation is live context. During a broker outage, it stays waiting and sends no connected notice.

For a foreground launch, the TUI opens as soon as the session is ready, before the readiness turn, so it streams boot activity instead of leaving the terminal blank. Presence still begins only after the readiness proof passes. An inbound peer message then wakes a Harness API turn. A directed message that arrives while the Harness session is busy (a Cotal-owned run(), a TUI-owned turn, or an advisory idle pulse between tool rounds of a still-open Cotal-owned run) enters Jcode’s session-owned soft-interrupt queue. Ambient channel traffic stays buffered for the next turn. The host marks presence working while the session is busy, publishes activity naming automatic queue depth and age while anything remains uncommitted, and acknowledges every initial or soft-interrupted inbox id only after that containing turn succeeds. A failed Cotal-owned turn or private Harness replacement leaves those ids unacknowledged for mesh redelivery. cotal_inbox pulls only buffered quiet ambient from that host-owned queue; its shared optional peek argument is supported, so peek: true shows those messages without clearing them.

--model is passed to Jcode’s session-level Harness API model selector. Jcode validates the model against the active provider, then the connector reads runtime identity back and refuses startup if it is not the requested model; a seat is never allowed to join under a model label it did not receive.

Model startup refusals are named without exposing provider output: model_prefix_rejected means a provider/model value was supplied where the Harness API requires a bare id, model_refused means Jcode rejected that bare id, and model_mismatch means Jcode accepted the request but reported a different effective model. private_state names a different step: the seat’s private home, its credential mirror, or its short socket alias could not be prepared.

cotal models --agent jcode reads the declared catalog from the operator Jcode home’s config.toml: each provider with model_catalog = true, its [[providers.<name>.models]] ids, and any declared reasoning_efforts. This is the same config Jcode copies into a private managed instance. The command fails loud when the file is unreadable, malformed, or enables a catalog without model entries.

The listed effort tiers are declarations, not provider-verified capabilities. cotal models prints that caveat inline as variants (declared, not provider-verified) beside each configured tier list, so it cannot be missed by reading only the model rows. Providers can reject a tier the file names, so launch remains the authority: Jcode applies the requested value and a provider rejection ends the launch. --refresh does not turn this local declaration into a live probe.

The Harness API can set a requested effort but cannot read an effective effort back. Its runtime identity reports provider, model, and routes only; no reply or event carries the applied tier. Cotal therefore records the accepted request and does not relabel it as an observed effect.

--variant is the session’s reasoning effort, applied after the model and before the seat’s first turn, so a seat never serves a turn at an effort nobody chose. A persona’s variant: is the default and --variant overrides it, the same way model: and --model work:

Terminal window
cotal spawn --agent jcode --model gpt-5.6-sol --variant high

Which tiers exist depends on the provider and model. The connector does not carry a copy of those ladders: it passes the requested tier to Jcode, which validates it against the active model’s ladder. A rejected tier, or a model with no reasoning-effort surface, ends the launch rather than quietly starting the seat at another effort. The external observer/UI receives only the requested tier, effective model, fixed invalid_request provider code, and an accepted-tier ladder when it can be safely parsed; arbitrary provider rejection text stays private. Omit --variant to keep Jcode’s configured default.

If the mandatory readiness turn receives a provider invalid_request refusal for a model id or reasoning-effort value, the launch diagnostic names only the provider error code and rejected value. Other provider response text remains scrubbed, so an external observer/UI can correct connector-visible input without exposing private harness output.

The following fail loud before a new session is provisioned where the manager can preflight them, or at connector launch as a backstop:

  • Resume /continuation: a Cotal seat owns a new private Jcode instance. Reusing a session from an operator or another seat would violate that ownership boundary.
  • Tool sharing: Jcode resolves its MCP configuration from several global and project sources. The connector owns a private configuration containing only cotal, rather than claim a chosen subset can be safely merged.
  • Events: Jcode’s Harness API does not provide the durable structured rollout surface required by Cotal’s event plane.
  • Launch options: the connector does not map arbitrary flags/config into the Harness API.
  • Containers: the current deploy image does not bundle Jcode, so there is no containerized Jcode connector today.

The private home protects against accidental sharing and stale session selection; it is not an OS-user isolation boundary. A hostile process running as the same user can still read that user’s files or inspect another same-user process. Use OS/container isolation where peers must be mutually hostile.

The model can receive remote peer messages and Jcode is an autonomous coding harness. Treat its provider credentials, filesystem access, and network capability as the privileges of the OS user running the seat. Cotal’s spawn capability governs who may create a seat; it is not a sandbox for what a model can be persuaded to do after creation.