Repository navigation
v0.10.0
0.10.0
Minor Changes
-
5ea2f11: Control can start, prompt and cancel any agent that speaks the Agent Client Protocol (CONTROL.md §6,
apimode; ADAPTERS.md "Agents driven through ACP"). The daemon runs the agent's own CLI as a child on the launch line the ACP registry publishes (Gemini CLI, OpenCode, goose, Copilot CLI, Cursor, Droid, Devin, Kilo, Qwen Code, Cline and twenty more, 2026-10-22), is its JSON-RPC client, keeps the process for the next message, cancels withsession/cancel, reopens an idle session withsession/loadwhere the agent can, and answers its permission requests from the machine's mode (safe refuses and the reply says what; auto allows). Only an agent already installed is ever run (on PATH, or whereSESSIONPIPE_ACP_<HARNESS>points); the hello advertises the ones found. The pairing request and every hello list at most 20 harnesses, the schema's limit (Claude Code, Codex and Antigravity first, then the agents in the registry's order), and theirmodesincludeapiwhenever an ACP agent is listed. -
7b65074: A Claude Code home that runs on another API now has an account. Such a home has no claude.ai login, and its
settings.jsonsetsenv.ANTHROPIC_BASE_URL(Kimi K3 on a Modal endpoint, GLM on Z.ai). Its events carryaccount_id: api-<16 hex>, made from the endpoint's origin and the same on every machine that uses it. Control's pair and hello name it by the endpoint's host, inaccounts[].name, a new optional field. Before this, its sessions carried no account, so a board filed them with the harnesses that report none (a person's Kimi sessions sat in "No account" beside 1,331 Codex ones). Only that one settings key is read, never the token or the headers beside it. -
27c4194: Control: device keys (CONTROL.md §2, §3.2).
sessionpipe-core/controlverifies a device key's signature ({cred, sig}, raw r‖s oversessionpipe.device.v1⏎<purpose>⏎<rp id>⏎<challenge>) for a grant, a confirm or a pairing proof, chosen by the machine's own record of the key;keyUsablecloses key fields and checks a device key's rules (type: "device",alg -7,store: "hardware",uv: "each-use", id = SHA-256 of the SPKI);verifyCommandrefuses akey.addwhose key could never be used (bad_fields).verifyAssertiontakes the purpose. New exportsDEVICE_CONTEXT,deviceMessage,deviceKeyId,KEY_TYPES,DEVICE_SIG_FIELDS,DeviceSignature,DeviceGrant. The daemon advertiseskey_types: ["passkey","device"]when pairing and in every hello, stores a device key whole (from a pairing or akey.add), andcontrol keysshows each key's kind. 26 new shared vectors (111–136). -
abcba7f: Control: a third machine mode,
inherit, so sessions you message run the way your own sessions on that machine run.sessionpipe control mode inherit(or--mode inheritoncontrol pairandconnect, or the whole word at pairing's question) passes no permission flag, so each harness starts every turn in its own default there: Claude Code in its settings'permissions.defaultMode(a resume too, and through the Agent SDK too), Codex as itsconfig.toml'sapproval_policyandsandbox_modesay, Antigravity with no flag, as in safe. No mode passes a bypass flag. Before this, a machine whose Claude Code settings turn auto mode off (permissions.disableAutoMode) had no mode that fit:autoran each messaged session in Claude Code's default mode, which refused every command that needed approval, andsaferefuses them by design. Schemas:control-pairandcontrol-hellomodegainsinherit. -
531b391: Control: a machine runs in
inheritmode only with receivers that know it. A receiver that predatesinheritshows such a machine as safe, while its sessions run with no permission flag. A receiver now says it shows the mode as what it is withcontrol.inherit: truein its well-known file (HTTP.md §1, CONTROL.md §2), andsessionpipe control pair --mode inherit(andconnect --mode inherit) refuses, before anything is sent, a receiver that doesn't: "… doesn't know inherit mode yet; it would show this machine as safe while the sessions you message run with no permission flag".sessionpipe control mode inheritchecks every paired receiver the same way and keeps the mode it had when one doesn't know it, or can't be read. Schema:well-knowncontrol.inherit(optional boolean). Conformance:delivery/23-control-inherit. -
fc0fedd: Two Node-only subpaths in
sessionpipe-core, so a receiver of the person's own (a board on their machine) runs as a service with the daemon's code rather than a copy of it.sessionpipe-core/service: the launchd agent and the systemd user unit for any process (serviceFiles,installServiceanduninstallService, each with a dry run that answers the files and commands and touches nothing, andensureLinger).sessionpipe-core/version:compareVersions,isNewer,parseVersion,UPDATE_EXITandsupervise, the parent that restarts a process onto new code where no service manager will. The root export is unchanged and still runs in a Worker and a browser. The control daemon now uses both; its service is the same, except that the unit'sExecStartquotes every argument. -
2865e0d: For receivers. What a 0.10.0 machine sends and accepts, and how to adopt it while some machines still run 0.9.0 (CHANGELOG.md, "For receivers (0.10.0)", has the detail).
- Closed command field set. Each command kind carries only its own fields; any other field inside the signed
cmdis refusedbad_fields, by the verifier and on the machine (0.9.0 ignored it). Keep your own data besidecmd, never in it. mode: "inherit"in pairing requests and hellos: no permission flag, so a start that names no level runs as the person's own sessions there do, permission checks off included where their settings say so. Show it asinherit, never as safe or auto. A 0.10.0 machine pairs in inherit, or switches to it, only with a receiver whose well-known file sayscontrol.inherit: true; it tells everyone else's person "… doesn't know inherit mode yet; it would show this machine as safe". Set the flag once you show the mode truthfully and accept it in/pairand/hello(conformancedelivery/23).- Refusal code
modeis nowpermission, plusrefusedmodelandfailedmodel. Map both codes; a 0.9.0 machine still saysmode. - Hello:
start(per harness:permissionsamongread,safe,edits,auto, capped by the mode; the defaultpermission, which may beinherit;models,any_model),prompt_files: true, and nohome: a start'scwdmay be~or~/…instead.harnesseslists at most 20, andmodesincludeapiwhen an ACP agent is listed. - Acks: a start's
takenanddeliveredcarryapplied(permissionmay beinherit). Showapplied, never what was asked. - Events:
session.attended,turn.ended.background,turn.started.submitted_byandinjected,session.launched_by, andaccount_idapi-<16 hex>for a Claude Code home on another API, named inaccounts[]bynamewith noemail. All optional, tier 0. - ACP agents (gemini-cli, opencode, goose, cursor, copilot-cli, droid and more) appear in
harnessesand in acksessionrefs, acked in modeapi; they take no start options. - Device keys: an app that can't run WebAuthn signs with a hardware-held P-256 key,
{id, alg: -7, spki, type: "device", store: "hardware", uv: "each-use"}, its signature{cred, sig}(plusstrin a grant) where an assertion would stand; a 0.10.0 machine sayskey_types: ["passkey","device"]when pairing and in every hello. To accept them, store and carry the key whole (all six fields) in a pairing's and akey.add'skey, pass a{cred, sig}grant, confirm or pairing proof through untouched, and saycontrol.signing.key_types: ["passkey","device"]in your well-known file. They are added only as passkeys are: at the terminal's pairing code, or by akey.adda trusted key signed. A receiver that doesn't know them keeps working, passkeys only. - While machines run 0.9.0: send
permissionormodelonly where the last hello'sstartadvertises them (0.9.0 ignores them silently);fileson apromptonly withprompt_files: true(0.9.0 refuses them); a~folder only to a machine whose hello hasstart; neverinheritas a command's level; a device key only for a machine whose last hello listsdeviceinkey_types(0.9.0 would store it as a passkey and refuse its signatures); read an absentattended,background,submitted_by,injectedorlaunched_byas unknown, never as false, 0 or a person.
- Closed command field set. Each command kind carries only its own fields; any other field inside the signed
-
a6f8bb7: A control
promptcan carryfiles, as astartcan (CONTROL.md §3.1, §6prompt), to a machine whose hello saysprompt_files: true. The daemon fetches and checks them as for a start, writes them to<cwd>/.sessionpipe/files/<session>/<nonce>/(the command's signed nonce, never the receiver's unsigned message id), and appends the same "Attached files" block before the message is handed over in any mode: a resume, a fork, a held SDK session, an ACP agent (api), a waiter or a Stop. A file that fails is ackedfailedwith codefileand the message is not delivered. A receiver no longer needs to put a link to an image into the message's text. -
d51fd31:
session.attendedsays whether a person attends the session: an optional tier-0 boolean in the harness's own word, absent when the harness does not say (unknown, neverfalse). Claude Code fills it fromCLAUDE_CODE_SESSION_ATTENDED, counting a control turn and a session with a link (its Remote Control bridge, the desktop app) as attended: every headless run says0, a Remote Control session included, and aclaude -pstarted inside one inheritsCLAUDE_CODE_ENVIRONMENT_KIND=bridge, so the link is what tells them apart. The hook carriesCLAUDE_CODE_SESSION_ATTENDEDandSESSIONPIPE_CONTROL_JOBinto the job, and the worker takes those andCLAUDE_CODE_HOST_SESSION_IDfrom the job alone, so a worker another session's hook spawned no longer lends a swept job its own. Codex, Gemini CLI, Antigravity and Devin leave it absent. -
fae77a5:
session.launched_by: the session that started this one, as<harness>:<id>, read fromSESSIONPIPE_LAUNCHED_BYin the hook's environment (PROTOCOL.md §3). A script that runsclaude -pfor a session sets it, and a receiver can group the run under its launcher without reading it as a subagent. A subagent keeps itsparent_idand gets nolaunched_by. -
bd49ef7: A control
startcan say how much the new session may do and which model it runs (CONTROL.md §2, §3.1, §5 step 8, §6 Options; ADAPTERS.md "Start options").permissionis one of four levels,read,safe,editsandauto, with nothing aboveauto;modelis the harness's own id, passed as one argument. The machine'smodeis the ceiling, and a start above it, or at a level the harness has no flag for, is refusedpermissionbefore anything is taken or run (the daemon's oldmodecode). On a machine ininheritmode a start that names no level runs with no permission flag, as the person's own sessions there do, and one that names a level may name onlyreadorsafe, since sessionpipe can't rank the person's own default;inheritis never a level a command names. The hello advertises what each harness takes here (start, capped by the mode: Claude Code's levels from its--help, Codex's models from its own cache; a harness's default may beinherit). A start'scwdmay be~or~/…, read under the daemon user's home, so a receiver can start a session in a folder an event reported without the machine ever telling it its home. A start's level and model are recorded with the session, and every later resume or fork runs at the narrower of that and the machine's mode then (at mostsafeon an inherit machine for a session that named a level). Thetakenanddeliveredacks of a start carryapplied(itspermissionmay beinherit); a model the harness rejects endsfailedwith codemodel. An ACP agent takes neither option: a start that names one is refused, never run without it. Each command kind now has a closed set of fields, so a field a machine doesn't know is refused rather than ignored.sessionpipe control statusshows the options per harness. A start whose text begins with a dash no longer reachesclaude -pas a flag. -
d7269b4:
turn.endedcarriesbackground: how much background work the harness reports still in flight as the turn ends, each piece of which wakes the session when it finishes. Claude Code's Stop hook lists it asbackground_tasks(in every Stop recorded since 2.1.258): a background shell command, a monitor, a subagent, a workflow. A session that ended its turn waiting on a two-hour background command read as finished on its receiver, under "Last 24 hours", until the command woke it. Tier 0, a count only: never what that work runs. -
f8770b9:
turn.startedcan now say who started the turn: two optional tier-0 fields,submitted_by(the harness's own word for who submitted the prompt, forwarded verbatim and never mapped onto a vocabulary of the spec — a different vocabulary fromsession.started.source) andinjected(truewhen the prompt text is a block the harness wrote rather than words a person typed). Claude Code fillssubmitted_byfromUserPromptSubmit.sourcewhen a payload carries that key — its hook schema documentsuser·sdk·system·loop_wakeup·schedule_wakeup·poll_eventand says a payload may omit it while the field rolls out, and no build through 2.1.304 sends it — and setsinjectedfrom a closed list of its own prompt wrappers (<task-notification>,<cross-session-message …>,<system-reminder>,<local-command-stdout>,<command-…>), so a task notification, a peer message or a slash-command expansion is distinguishable from a person typing.injected: truesays only that the prompt is not the person's words, never that no person caused the turn: a Talk orsessionpipe waitdelivery arrives inside a<task-notification>. Both fields are absent when the harness does not say, and an absent field is unknown, never "the person".sessionpipe tailprints both. Codex, Gemini CLI, Antigravity and Devin leave both absent.