Skip to content

Sessions

Every interactive run is persisted as a JSONL file under ~/.letscode/sessions/<cwd-hash>/<uuid>.jsonl. One JSON object per line, one line per MessageEnd event.

Continuation

letscode -c

Continues the most recent session for the current working directory. The conversation history is reloaded, the system prompt is recomputed (so newly added skills / tools are visible), and the agent picks up where it left off.

Since v0.7, resume prints a compact recap on startup: the total message count plus the last user turn and last assistant turn, both truncated to one line. Everything before that is still in state.messages for the model, just not re-rendered. To browse the full transcript use /tree; to write it out as HTML use /export.

Labels (since v0.7)

Each session has an optional human-readable label kept in a <uuid>.name sidecar next to the JSONL file (zero change to the JSONL format). /name shows the current label; /name my project sets it. Labels show up in /resume's listing and drive the default filename for /export.

Auto-naming: when the first user message is persisted and the session has no label, letscode derives one locally: the first line of the message, capped at four words / forty characters. The process is cheap, deterministic, and never overwrites an explicit /name.

Ephemeral runs

letscode --no-session "one-off question"

Skips persistence entirely. Use for piped pipelines, CI checks, or anything you don't want in the session history.

File format

The JSONL envelope:

{ "_v": 2, "msg": { "role": "user", "id": "...", "content": [...], "timestamp": ... } }
{ "_v": 2, "msg": { "role": "assistant", "id": "...", "content": [...], "tool_calls": [...], ... } }
{ "_v": 2, "msg": { "role": "tool", "id": "...", "tool_call_id": "...", "content": [...], ... } }
{ "_v": 2, "msg": { "role": "custom", "kind": "compaction", "payload": {...}, ... } }
  • _v: 2 is the format version. v1 files still load (entries get a synthesized parent_id chain).
  • parent_id lives on the envelope (not shown above for brevity). Entries form a tree even though the default UX is linear. Tree navigation shipped in v0.4: /tree (numbered chain), /fork [n] (branch a new session at entry n), /clone (duplicate and continue on the copy).

Custom messages

The role: "custom" entries are letscode-specific. They live in the transcript but are not sent to the LLM by default. The most common kind is compaction: when the context window is approached, the agent collapses older turns into a single summary CustomMessage(kind="compaction") and the built-in plugin renders it as a synthetic assistant message at LLM-call time.

Plugins can define their own CustomMessage.kind values and render them via the letscode_render_custom_message hook. See Extension model for the contract.

Locations

Path Purpose
~/.letscode/sessions/<cwd-hash>/<uuid>.jsonl Default session storage.
~/.letscode/sessions/<cwd-hash>/<uuid>.name Optional label sidecar (since v0.7).
~/.letscode/history prompt_toolkit input history.
~/.letscode/config.toml User configuration.
~/.letscode/catalog.toml Model catalog overlay (since v0.7).
~/.letscode/cache/update-check.json Cached PyPI update-check state (since v0.7).
~/.letscode/skills/ User-global skills directory.
~/.letscode/prompts/ User-global prompt templates (since v0.7).

The <cwd-hash> is a stable hash of the working directory's absolute path; sessions are scoped per project location, so -c continues the right conversation regardless of which repo you're in.