Kiro
How Kiro maps onto the unified conversation model,
what the kiro-local adapter reads, and what Kiro’s files cannot supply.
For turning it on, see Kiro. For the words, see the Kiro glossary.
Where the data comes from
Kiro writes each session to disk as it runs. The adapter reads those files, as the Claude Code adapter reads Claude Code’s, and needs nothing from Kiro: no setting, no hook, no plugin.
Kiro has three agent engines, and only one is read.
| Engine | Used by | Where it writes | Read |
|---|---|---|---|
V3, @kiro/agent |
kiro-cli --v3, or kiro-cli with chat.agentEngine set to v3; Kiro Crew on its kas backend; the Kiro IDE |
~/.kiro/sessions/<workspace key>/sess_<id>/ |
yes |
| V2 | kiro-cli by default in 2.28.0 |
~/.kiro/sessions/cli/<id>.jsonl |
no: counted and reported |
V1, --classic |
kiro-cli --classic |
a SQLite database beside the login | no |
Measured with kiro-cli 2.28.0 and its engine @kiro/agent 0.66.26, and Kiro Crew 0.7.2 on it, on
one macOS machine: 86 sessions, 56 of them with records, and 3,101 records, from tasks run for the
purpose and from real use. Kiro Crew 0.8.0, which bundles kiro-cli 2.27.1 and engine 0.66.22, was
measured on the same machine after it updated itself: 13 sessions, 10 of them with records, and
2,069 records, from real use. All 10 were collected and verified.
The engine’s code was read where the files did not say enough, and each such point names it. The
IDE writes the same store; no IDE turn was measured.
A session is read when its header, session.json, says schemaVersion 1.0.0, the one schema
measured. A session that says another is counted and left, as the V2 sessions are, and both appear
in asz sources and once in asz collect:
not read : kiro-local: 24 session(s) of kiro-cli's V2 engine under sessions/cli; only the V3 engine's sessions are collected: start kiro-cli with --v3, or set chat.agentEngine to v3
What is read
One session directory holds:
| File | Read as |
|---|---|
session.json |
the session’s header, replaced whole by the engine, also between turns. It lands as a new snapshot when its bytes change, as an agent_meta file of the main stream. Its title names the conversation. |
messages.jsonl |
the main stream, main. Only appended to. |
sub-executions/<id>.jsonl |
one subagent’s stream, named by the file. Only appended to. |
tool-outputs/, snapshots/, publish*.cursor, session.json.tmp.* |
not read: a large tool output the engine spilled, the files it keeps for checkpoints, its upload offsets, and a header copy left by an interrupted replace |
A session with a header and no record is not collected until it has one. Kiro Crew opens sessions before it uses them: 30 of the 86 held nothing else.
The engine writes under the home directory and does not read KIRO_HOME, so the default source
root is ~/.kiro. The workspace key in the path is a digest of the session’s workspace paths. The
paths themselves are in the header, and the session filters
match them.
The mapping
| Model | Kiro |
|---|---|
| Conversation | the session id, sess_<uuid>; supplied, one session is one conversation |
| ExecutionStream | main for messages.jsonl; a child stream per sub-executions/<subSessionId>.jsonl |
| Run | executionId, one turn |
| Talk | a prompt and the turn it opened |
llm.call |
none: no record names the model request it came from |
message.external |
a user record |
message.assistant |
assistant with operationType Say |
thinking |
assistant with operationType Reasoning |
tool |
tool_call and tool_result, joined by toolCallId |
agent.call |
sub_agent_start |
agent.output |
the child’s last response, with sub_agent_complete, the response the parent received |
context.injection |
agent_note, session_start, steering_inclusion, and a user record the engine wrote |
epoch.boundary, epoch.summary |
tombstone, and assistant with operationType Summary |
error.api |
session_metadata with key displayError |
control.notice |
a turn_end whose stopReason is not end_turn, a ContextualHookInvoked, and a record type the adapter does not describe |
turn.duration |
usage_summary |
A prompt belongs to the turn after it
A turn is turn_start … turn_end, and every record inside names it by executionId. A
person’s prompt does not: none of the 210 user records carries one, nor does the note the
engine writes before each prompt. A message sent while a turn runs, which the engine marks
source: steer, carries none either.
Each belongs to the first turn named after it in the file: the prompt to its own turn, and a message sent during a turn to that turn. The rule reads line order, which the engine keeps. Its timestamps are not in line order: the note before a prompt is stamped later than the prompt.
A record whose turn is not in the file yet waits. A landed record is never written again, so a prompt landed before its turn starts would sit outside that turn for good. The adapter reads past what it is about to land, and lands the waiting records once the turn is named, which the engine does milliseconds later. A prompt whose turn never starts, because the engine stopped first, lands with no turn once the file has been still for five minutes.
Model output without a model call
The engine records what the model said and not which request said it. A turn lists its request ids
in usage_summary, and no record names one. Grouping records into calls, by turn or by record,
would draw call boundaries Kiro never recorded, so no llm.call is written. The agent’s text,
reasoning and tools are steps of the run.
Record ids
A record’s id in Session Data is the engine’s id, then #, then the first 12 hex digits of the
record’s SHA-256. The engine can give one id to two different records: when it asks twice to
approve one tool call, both requests carry one id and both answers another. In the 3,101 records, 46
ids did so, for 23 approvals, and no other id was reused. Assembly removes a repeated id as a copy, so the engine’s id
alone would drop the second record. The digest keeps the id the same for the same bytes, so a record
landed again after a crash is still removed as the copy it is.
Subagents
The engine writes sub_agent_start and sub_agent_complete in the parent, and the child’s records
in sub-executions/<subSessionId>.jsonl. No tool call in the parent names the spawn, so the start
record is the call: its id is the child’s subSessionId, and sub_agent_complete is its result.
The starts relation joins the call to the child’s stream, and result_of joins the child’s output
back to the call.
A child’s stream is named by its file. The child’s records carry the parent’s turn as
executionId, and most carry the child’s id as subExecutionId, but hook records in a child do
not.
Compaction
A tombstone takes the records before it out of the context the model is sent: kind
summarization after a compaction, checkpoint_revert after a return to a checkpoint. It starts a
new epoch. effectiveFromMessageId names where the dropped records begin, not the last record
kept, so the epoch names no continuation. The summary that follows is assistant with
operationType Summary, which the engine sends to the model as a person’s message, so it is input
here. Nothing joins it to its tombstone, so no summarizes relation is drawn.
The system prompt
session_start holds the system prompt the engine composed, and is named system_prompt, so a
server can withhold it. The engine writes it when the first
turn ends.
Kiro Crew sends its own system prompt as the first prompt of a session, in a user record: 24 of
24 Crew sessions measured begin so. It sends its rules again in the prompt that resumes a session.
Nothing in the record tells either from a prompt a person typed, so each is a message.external
and is not named system_prompt.
Messages sent during a turn
Three user records carry a message sent while a turn runs. The person’s message, source: steer, is a message.external of that turn. The engine’s text that carries it to the model,
marked syntheticUserMessageReason, is a context.injection of the same turn. The note that the
messages were consumed, steeringClearedIds, is kept and is no step. Kiro Crew also sends its own
notices this way, such as one that it blocked a tool call; those read as the person’s messages do.
Reasoning
A Reasoning record whose text is ... holds no reasoning: the engine wrote that in place of
reasoning it did not keep, and reads it as none itself. Its thinking step is unavailable. Of 338
reasoning records, 179 were so. The signature beside it is named in dropped.
Kiro Crew
Kiro Crew runs every chat through kiro-cli acp --agent-engine v3 when its agent.acp_backend is
kas, so its sessions are V3 sessions under the directory Crew works in. Measured with Crew 0.7.2:
| Crew does | In the store |
|---|---|
| a chat in the dashboard | one session over many prompts, in the directory the chat works in, by default Crew’s own |
kirocrew chat -m |
a new session for each message |
| a background job | a session under _bg in Crew’s workspace |
| a scheduled job | a session kept under cron_<job id> in Crew’s workspace |
a task’s steps, kirocrew run |
temporary sessions: 16 created and 14 removed in one run, lifetimes 1.4 to 358 seconds, median 68 |
A collector that reads every few minutes does not see most task steps: Crew frees the session and the engine removes it before the next read. A session found and then removed keeps what was landed. Kiro Crew issue #18988 proposes a setting that keeps these records for a while before Crew deletes them.
What this adapter cannot supply
| Not in the files | Why |
|---|---|
| tokens | the engine records credits per turn in usage_summary, and no token counts, so no token metric is derived |
| model calls | no record names its request; see above |
| the model per record | no record names the model that answered; the header names the one chosen |
| a permission decision as a step | pending_interaction and interaction_resolved land and are kept; the model has a step for them, and nothing draws it from Kiro yet |
| an interruption as a step | a turn the person cancelled ends with stopReason cancelled, which lands as a notice |
| a large tool output whole | the model is sent a preview, which is the result here; the rest is in tool-outputs, which is not read |
| a conversation over several sessions | Kiro Crew names no chain between the sessions of one chat |
| records the engine writes on recovery | they end their id in -synthetic; none was measured, and they land as what they are |