Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Each name links to its format documentation. Use the ID with `--from` and `--wit
| [Cowork](docs/formats/cowork.md) | `cowork` | Yes | Yes |
| [Grok CLI](docs/formats/grok.md) | `grok` | Yes | Yes |
| [Grok Bot](docs/formats/grok-bot.md) | `grok_bot` | Yes | Via local gateway |
| [Kimi Code](docs/formats/kimi.md) | `kimi` | Yes | No |
| [fx](docs/formats/fx.md) | `fx` | Yes | Yes |
| [Antigravity](docs/formats/antigravity.md) | `antigravity` | Yes | Yes |
| [Hermes Agent](docs/formats/hermes.md) | `hermes` | Yes | No |
Expand Down
8 changes: 7 additions & 1 deletion cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub mod mcp;
mod pager;
mod view;

pub const HARNESSES: &str = "harnesses: claude_code, claude_chat, chatgpt, codex, opencode, pi, campfire, cursor, cursor_desktop, grok, fx, hermes, \
pub const HARNESSES: &str = "harnesses: claude_code, claude_chat, chatgpt, codex, opencode, pi, campfire, cursor, cursor_desktop, grok, kimi, fx, hermes, \
amp, antigravity, simple, cowork, cowork_remote";

/// The `txcript` binary's command line.
Expand Down Expand Up @@ -1318,6 +1318,7 @@ mod style {
HarnessId::CursorDesktop => "\x1b[96m", // bright cyan
HarnessId::Grok => "\x1b[37m", // white
HarnessId::GrokBot => "\x1b[97m", // bright white
HarnessId::Kimi => "\x1b[38;5;141m", // violet
HarnessId::Fx => "\x1b[38;5;39m", // azure
HarnessId::Hermes => "\x1b[93m", // bright yellow
HarnessId::Amp => "\x1b[95m", // bright magenta
Expand Down Expand Up @@ -1919,6 +1920,11 @@ fn ensure_resumable_source(source: HarnessId, target: HarnessId) -> Result<(), S
"ChatGPT is pull-only: choose another --with harness; txcript never continues conversations in ChatGPT"
.to_string(),
)
} else if source == HarnessId::Kimi && target == HarnessId::Kimi {
Err(
"Kimi Code is read-only: choose another --with harness; txcript never writes undocumented Kimi session files"
.to_string(),
)
} else {
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions docs/formats/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ than none.
| [amp.md](amp.md) | Amp (Sourcegraph) | `src/harness/amp.rs` |
| [grok.md](grok.md) | Grok CLI (xAI) | `src/harness/grok.rs` |
| [grok-bot.md](grok-bot.md) | Grok Bot (desktop assistant) | `src/harness/grok_bot.rs` |
| [kimi.md](kimi.md) | Kimi Code (Moonshot) | `src/harness/kimi.rs` |
| [fx.md](fx.md) | fx (Vercel) | `src/harness/fx.rs` |
| [hermes.md](hermes.md) | Hermes Agent | `src/harness/hermes.rs` |
| [antigravity.md](antigravity.md) | Antigravity (Google) | `src/harness/antigravity.rs` |
Expand Down
103 changes: 103 additions & 0 deletions docs/formats/kimi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Kimi Code

Kimi Code CLI stores sessions under:

```text
~/.kimi-code/sessions/wd_<workspace>_<hash>/session_<uuid>/
state.json
agents/<agent-name>/wire.jsonl
```

`state.json` contains session metadata. Each agent has an append-only wire
log; `agents/main/wire.jsonl` is the main conversation. Other agent logs are
separate conversations and are not merged into the main transcript.

## Native representation

`txcript::harness::kimi::KimiSession` retains `state.json` as a JSON value and
the main wire log as a JSONL value list. Unknown Kimi event types remain in the
native body, so a native text load/render round trip does not discard events
that txcript does not understand.

The `createdAt` field is accepted as either an RFC3339 string or epoch
milliseconds. Kimi versions have emitted both forms.

### Session id and schema versions

`state.json` has two observed shapes. Schema version 2 (`"version": 2`) records
the session id in `id` and the working directory in `cwd`. Version 1 carries no
`version` marker, uses `workDir`, and has no id field at all. Kimi's own
`session_index.jsonl` calls the same value `sessionId`.

The id is therefore resolved in order: `sessionId`, `id`, then the
`session_<uuid>` segment of `agents.<name>.homedir` — an absolute path every
observed schema records. The store falls back to the session directory name.
The homedir fallback matters for `from_text` and the wasm parser, which see the
JSON without its path.

Discovery is gated on structure — a readable `state.json` plus
`agents/main/wire.jsonl` — never on the directory name, so a Kimi release that
renames its session directories still lists.

## Common projection

The following wire events become conversational blocks:

| Kimi event | Common representation |
| --- | --- |
| `context.append_message` with `role=user` | user text message |
| `content.part` with `part.type=text` | assistant text block |
| `content.part` with `part.type=think` | assistant thinking block |
| `tool.call` | assistant tool-use block |
| `tool.result` | user tool-result block |
| `context.undo` | rewinds the last `count` turns |

Tool IDs and arguments are retained. `isError` maps to the Common tool-result
error flag. A Kimi result note is appended to textual output so truncation or
permission annotations are not silently lost. Usage, timing, permission,
MCP-tool-snapshot, and step-bookkeeping events are not fabricated as messages.

### Rewound context

Kimi rewinds its context with `context.undo` after a failed or cancelled turn,
then re-sends the prompt as a fresh `turn.prompt`. Because `wire.jsonl` is
append-only, the rolled-back entries stay on disk. Replaying them would
resurrect prompts the user already retried — a session that hit ten provider
errors in a row reads back with the same prompt ten times — so the reader
applies the rewind.

`count` is measured in turns, not entries: one turn can append several
messages (a prompt plus injected reminders), and a single `count: 1` undo drops
all of them. A wire log with no `turn.prompt` markers falls back to entry
granularity.

## Lineage

`kimi fork` writes the source session's id to the forked session's
`state.json` as top-level `forkedFrom` (documented in Kimi Code's
`docs/en/configuration/data-locations.md`). txcript reads it into a Fork
`Meta.lineage`, at discovery as well as on load. Subagents are not sessions of
their own — they live under the same session's `agents/<name>/` — so there is
no spawn link to read. Writing `forkedFrom` back out is not implemented.

## Store capabilities

Kimi Code has no documented session import or deletion command. The txcript
Kimi store is therefore **read-only**:

- `list`, `query`, `view`, and `export` work;
- a Kimi session can be converted into any writable target harness;
- `save` and `delete` return an explicit read-only error;
- txcript never writes directly into `~/.kimi-code/sessions`.

The native Kimi resume command is `kimi --session <id>`. Cross-harness
continuation is supported; native Kimi continuation is refused because the
store cannot safely create a Kimi session without an official import contract.

## Provenance

The format is based on the Kimi Code CLI wire protocol observed in local
sessions and the public CLI interface documented by `kimi --help`. The wire
protocol is an implementation detail and may change between Kimi releases;
unknown events are retained to make the reader fail conservatively rather than
silently discarding native data.
3 changes: 2 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ writeFileSync("session.jsonl", convert(input, "codex", "claude_code"));
const common = JSON.parse(toCommon(input, "codex")); // { meta, messages }
const pi = fromCommon(JSON.stringify(common), "pi");

harnesses(); // ["claude_code","claude_chat","cowork_remote","chatgpt","codex","opencode","pi","campfire","cursor","cursor_desktop","grok","grok_bot","fx","hermes","amp","antigravity","simple","cowork"]
harnesses(); // ["claude_code","claude_chat","cowork_remote","chatgpt","codex","opencode","pi","campfire","cursor","cursor_desktop","grok","grok_bot","fx","hermes","kimi","amp","antigravity","simple","cowork"]
```

Text-in / text-out: `input` is the source harness's native session text and the result is the target's. Invalid harness names or unparseable input throw a JS `Error`.
Expand Down Expand Up @@ -257,6 +257,7 @@ const matches = JSON.parse(index.query(JSON.stringify({ pattern: "relay bug" }))
| `grok_bot` | agent-transcript JSONL (`role`/`message` envelopes) |
| `fx` | JSON bundle of the session directory's files |
| `hermes` | `hermes sessions export` JSON object |
| `kimi` | JSON object of the session's `state.json` and main `wire.jsonl` events |
| `amp` | `amp threads export` JSON |
| `antigravity` | JSON dump of the conversation database, protobuf blobs hex-encoded |
| `simple` | the [Simple](formats/simple.md) interchange JSON document |
Expand Down
Loading
Loading