Kin / Agents & automation
Local clients
kin --stdio hosts the ordinary interactive session for one local program that owns Kin's standard input and output — the same journal, workspace trust, tools, and questions as the terminal, projected as typed JSON lines. It is how an OS-native surface such as the Kinra OS Front Door carries a live Kin conversation…
Read as Markdownkin --stdio hosts the ordinary interactive session for one local program that owns Kin’s standard input and output — the same journal, workspace trust, conversation lease, tools, modes, and questions as the terminal, projected as typed JSON lines. It is how an OS-native surface such as the Kinra OS Front Door carries a live Kin conversation without a second harness, transcript, or authority model.
It is not headless mode. kin -p runs one unattended turn and turns every human decision into a needs-human exit; kin --stdio keeps the session open, streams, asks, waits for the person, and takes follow-ups in the same conversation. It is also not a network service: there is no socket or port, and the client is whichever process holds the pipes.
kin --stdio --workdir ~/code/project # a fresh conversation
kin --stdio --workdir ~/code/project --resume ID # exactly one saved conversation
The contract in one exchange
Every line is one JSON object. The client speaks first.
→ {"type":"hello","protocol":1,"client":"front-door","capabilities":{"open_url":true}}
← {"type":"welcome","protocol":1,"kin":"0.6.10","capabilities":["open_url"]}
← {"type":"session","session_id":"s_…","model":"…","workspace":"/home/…/project","posture":"…"}
← {"type":"ready"}
← {"type":"mode_changed","mode":"auto"}
→ {"type":"turn","id":"t1","text":"What does this project's release gate check?"}
← {"type":"accepted","of":"turn","id":"t1"}
← {"type":"thinking","active":true}
← {"type":"tool_call","id":"call_1","name":"read_file","arguments":"{\"path\":\"Taskfile.yml\"}"}
← {"type":"tool_result","id":"call_1","name":"read_file","result":"…","result_truncated":true}
← {"type":"stream_chunk","message_id":"m1","content":"The gate runs …"}
← {"type":"stream_done","message_id":"m1"}
← {"type":"done","reason":"stop"}
← {"type":"turn_ended","id":"t1"}
→ {"type":"close"}
← {"type":"accepted","of":"close"}
← {"type":"closed"}
The events between accepted and turn_ended are the ordinary event vocabulary — the same shapes the terminal renders — with a few fields bounded for the pipe.
Client messages
| Message | Fields | Meaning |
|---|---|---|
hello |
protocol (must be 1), client (≤64 chars, optional), capabilities (object of booleans, optional) |
Required first message. Declares the protocol and the handoffs the client can perform. Today the only capability is open_url. |
turn |
id (≤64 chars), text (≤32,000 chars), context (optional, ≤8 items) |
Start one root turn. The text is literal turn data — no ! shell passthrough and no / skill dispatch, exactly like an interactive launch prompt. |
cancel |
— | Interrupt the running turn, as ++escape++ does in the terminal. Refused as idle when nothing runs. |
resolve |
kind, id, and the answer |
Answer the exact pending blocking event: question (index, optional text), approval (outcome once/always/deny, optional note, and the display_signature the event carried), plan (index), proposal (decision apply/reject). |
handoff_result |
id, ok, detail (≤500 chars) |
Report what the environment did with a handoff. |
close |
— | End the session: the turn is cancelled, the journal closes, the lease is released, and the last line is closed. |
End of input, SIGTERM, SIGINT, and SIGHUP close the same way. Anything else is refused by name — never guessed at.
Attention context
A turn may carry a short list of facts about where the person’s attention was when they invoked Kin:
{"type":"turn","id":"t2","text":"Summarize this page for me.",
"context":[{"kind":"window","label":"Brave","value":"kinra-ai/kin — GitHub"},
{"kind":"workspace","label":"workspace","value":"2 on DP-5"}]}
kind is one of window, workspace, output, selection, path, or note; labels are capped at 80 characters and values at 500. Kin frames the whole block as untrusted data ahead of the request and says what it is: a window title or path is a hint about context, never proof of a file, a page’s content, or intent. Kin reads or asks before acting on it.
Host messages
Beside the ordinary events, the host writes a small set of its own:
| Message | Fields | Meaning |
|---|---|---|
welcome |
protocol, kin, capabilities |
The hello was accepted; the accepted capabilities follow. |
accepted |
of, id |
A turn, cancel, resolve, handoff_result, or close was taken. |
refused |
of, reason, detail |
A message was not taken: malformed, unknown, bounds, busy, idle, stale, closing, or protocol. The session is unaffected. |
turn_ended |
id |
The turn settled, whichever way — after done, interrupted, or an error. |
handoff |
id, kind, payload |
Kin asks the environment to perform one typed effect; answer with handoff_result. |
fatal |
reason, detail |
The host cannot serve — protocol, untrusted, lease, resume, backend, or host — and exits after this line. |
closed |
— | The last line before exit. |
One turn, exact answers, bounded lines
Only one turn runs at a time; a second turn is refused as busy until turn_ended. Cancel first if the new request replaces the old one.
A resolve is accepted only for the exact pending block of the stated kind. A wrong id, a wrong kind, a duplicate answer, or an answer after the turn settled is refused as stale and resolves nothing. An approval must echo the display_signature its event carried, so a stale review can never approve a changed call. Git credential and GitHub connection requests are forwarded but cannot be resolved over stdio; they belong to the terminal, and a client hands the conversation off (see below).
Client lines are limited to 64 KiB; an oversize or malformed line is refused and drained, and the stream continues. In the other direction, tool arguments and results are capped and flagged (arguments_truncated, result_truncated), image blocks are dropped, and reasoning, todos, tasks_changed, and tool_call_draft are not forwarded. Review bodies — approvals, plans, proposals — arrive complete.
Handoffs: how an action leaves prose
A client that declares open_url gives the session one extra tool. When Kin decides a page should open, it calls open_url with one exact http/https URL; the host parks a handoff on the client:
← {"type":"handoff","id":"ho_3","kind":"open_url","payload":{"url":"https://kinra.ai/kin"}}
→ {"type":"handoff_result","id":"ho_3","ok":true,"detail":"Brave"}
← {"type":"tool_result","id":"call_4","name":"open_url","result":"opened https://kinra.ai/kin in the person's browser (Brave)"}
The environment owns the effect: it decides how the page opens, may decline ("ok":false), and reports a fact, never authority over Kin. No page content comes back — reading stays with web_fetch. A handoff the environment never answers settles on the tool’s own timeout. Without the capability the tool does not exist, so a plain terminal Kin cannot open a browser. A client must never infer an action from a response; the typed handoff is the only door.
Trust, leases, and handing off to the terminal
A host cannot create workspace trust. Like a headless or resumed launch, it needs a persistent trust record for the workspace before any project material or inference loads; otherwise the first line is {"type":"fatal","reason":"untrusted",…} and the process exits 2. Trust the workspace once from an interactive kin there.
One conversation has one live client. --stdio --resume ID is refused (fatal with reason lease) while a terminal or another host owns that conversation, and the terminal’s --resume is refused while a host lives. To move a conversation to the terminal — for a Git credential prompt, a plan review the client does not render, or simply deeper work — the client sends close, waits for closed, and then launches kin --resume ID. The journal is the transcript; the client keeps at most the opaque session id.
Exit codes
| Code | Meaning |
|---|---|
0 |
The client closed the session or its end of the pipe. |
1 |
The host failed after the handshake (see the fatal line’s detail). |
2 |
The host could not start: bad hello, untrusted workspace, held lease, unknown resume target, or invalid model configuration. Argparse usage errors also exit 2. |
--stdio is mutually exclusive with -p, with --agent, and with a positional prompt; it composes with --workdir, --resume, --continue, --new, --no-save, --mode, and the model and preset flags.