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.
Install
Section titled “Install”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:
jcode version --jsoncotal spawn --agent jcodeIf 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.
Spawn it
Section titled “Spawn it”cotal spawn --agent jcodecotal spawn reviewer --agent jcode -dcotal spawn --agent jcode --model gpt-5.6-sol --prompt "Review the current change."COTAL_DEFAULT_AGENT=jcode cotal spawnA 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.
How it binds
Section titled “How it binds”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 privateJCODE_HOME, runtime directory, daemon, andapi-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-bridgeshares 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
PATHwhen 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 limits
Section titled “Model limits”--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:
cotal spawn --agent jcode --model gpt-5.6-sol --variant highWhich 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.
Security limits
Section titled “Security limits”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.