From 52b2ca78aed7950af27d4865aee65da781514a99 Mon Sep 17 00:00:00 2001 From: T Date: Sun, 26 Apr 2026 11:09:37 -0600 Subject: Initial work, and name change to pantograph --- docs/phase-4.md | 64 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) (limited to 'docs/phase-4.md') diff --git a/docs/phase-4.md b/docs/phase-4.md index 268467a..af68cca 100644 --- a/docs/phase-4.md +++ b/docs/phase-4.md @@ -2,27 +2,27 @@ ## Goal -Persistent session storage as an append-only event log. Every event in a session — messages, which provider/model handled each request — is recorded to disk as it happens. On load, awl replays the event log to fully rebuild conversation state. Sessions survive process restarts and can be reviewed later. +Persistent session storage as an append-only event log. Every event in a session — messages, which provider/model handled each request — is recorded to disk as it happens. On load, pantograph replays the event log to fully rebuild conversation state. Sessions survive process restarts and can be reviewed later. ## Deliverable -A session persistence system where awl saves and resumes conversations. At the end of this phase, you can: +A session persistence system where pantograph saves and resumes conversations. At the end of this phase, you can: -- Start `awl`, hold a conversation, and find the session saved to disk. -- Restart `awl --resume` and continue the conversation exactly where you left off. -- Restart `awl --resume ` to resume a specific session. +- Start `panto`, hold a conversation, and find the session saved to disk. +- Restart `panto --resume` and continue the conversation exactly where you left off. +- Restart `panto --resume ` to resume a specific session. - Inspect the JSONL session file to see every event that occurred, including which provider/model handled each turn. -- Have awl recover gracefully from a crash — the corrupted trailing line is removed, and the session loads from the last valid entry. +- Have pantograph recover gracefully from a crash — the corrupted trailing line is removed, and the session loads from the last valid entry. ## What is usable at the end | Capability | How to exercise it | |---|---| -| Auto-save session | Start `awl`, converse, quit; session is in `~/.local/share/awl/sessions/` | -| Resume most recent | `awl --resume` — continues the most recent session for the current working directory | -| Resume specific session | `awl --resume ` — opens the session with the given ID (or unique prefix) | -| List sessions | `awl sessions` — lists sessions for the current working directory | -| Crash recovery | Kill `awl` mid-turn; restart with `--resume`; corrupted trailing line is removed, session loads from last valid entry | +| Auto-save session | Start `panto`, converse, quit; session is in `~/.local/share/panto/sessions/` | +| Resume most recent | `panto --resume` — continues the most recent session for the current working directory | +| Resume specific session | `panto --resume ` — opens the session with the given ID (or unique prefix) | +| List sessions | `panto sessions` — lists sessions for the current working directory | +| Crash recovery | Kill `panto` mid-turn; restart with `--resume`; corrupted trailing line is removed, session loads from last valid entry | ## What is explicitly out of scope @@ -40,12 +40,12 @@ A session persistence system where awl saves and resumes conversations. At the e Sessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with a `type` field. Entries form a tree structure via `id`/`parentId` fields, enabling in-place branching in future phases without creating new files. -The event log is the authoritative record of a session. It captures everything that happened — not just the conversation content, but the full context of how it happened (which model was active, when it changed, etc.). On load, awl rebuilds all state from the log alone. +The event log is the authoritative record of a session. It captures everything that happened — not just the conversation content, but the full context of how it happened (which model was active, when it changed, etc.). On load, pantograph rebuilds all state from the log alone. ### File Location ``` -~/.local/share/awl/sessions//_.jsonl +~/.local/share/panto/sessions//_.jsonl ``` Where `` is the working directory with `/` replaced by `--`. This groups sessions by project directory, making it easy to find sessions for a given project. @@ -54,7 +54,7 @@ The base directory respects `XDG_DATA_HOME`, falling back to `~/.local/share` if Example: ``` -~/.local/share/awl/sessions/--Users-travis-Code-awl--/2026-04-25T17-40-15-990Z_019dc5ba-53f6-71a5-ab8f-b1f8709c2572.jsonl +~/.local/share/panto/sessions/--Users-travis-Code-pantograph--/2026-04-25T17-40-15-990Z_019dc5ba-53f6-71a5-ab8f-b1f8709c2572.jsonl ``` ### Crash Recovery @@ -75,7 +75,7 @@ First line of the file. Metadata only — not part of the tree (no `id`/`parentI "version": 1, "id": "019dc5ba-53f6-71a5-ab8f-b1f8709c2572", "timestamp": "2026-04-25T17:40:15.990Z", - "cwd": "/Users/travis/Code/awl", + "cwd": "/Users/travis/Code/pantograph", "provider": "anthropic", "model": "claude-sonnet-4-20250514" } @@ -181,11 +181,11 @@ A message in the conversation. The `message` field contains role, content blocks } ``` -Note that tool results are content blocks on a user message, consistent with awl's internal model (and Anthropic's wire format). This differs from pi's approach of giving `toolResult` its own message role — in awl, tool results are content blocks that happen to live on user messages, just as they do in the Conversation model. +Note that tool results are content blocks on a user message, consistent with pantograph's internal model (and Anthropic's wire format). This differs from pi's approach of giving `toolResult` its own message role — in pantograph, tool results are content blocks that happen to live on user messages, just as they do in the Conversation model. ### Content Block Serialization -The on-disk content block types correspond directly to awl's internal `ContentBlock` tagged union: +The on-disk content block types correspond directly to pantograph's internal `ContentBlock` tagged union: | Internal type | On-disk `type` | Fields | |---|---|---| @@ -196,8 +196,8 @@ The on-disk content block types correspond directly to awl's internal `ContentBl Notable decisions: -- **`input` is a string, not a parsed object.** Consistent with awl's internal model where tool input is stored as raw JSON bytes. The on-disk format does not parse or interpret tool schemas. -- **`content` in `toolResult` is a string.** Consistent with awl's model where tool result content is a single `TextualBlock`. If structured content (e.g., images) is needed later, the format can evolve. +- **`input` is a string, not a parsed object.** Consistent with pantograph's internal model where tool input is stored as raw JSON bytes. The on-disk format does not parse or interpret tool schemas. +- **`content` in `toolResult` is a string.** Consistent with pantograph's model where tool result content is a single `TextualBlock`. If structured content (e.g., images) is needed later, the format can evolve. - **`thinking` stores full thinking content.** The event log records everything that happened. Thinking blocks are never omitted. ### Assistant message metadata @@ -279,7 +279,7 @@ The tree structure is defined now so that the on-disk format supports branching ## Rebuilding State from the Log -On resume, awl fully rebuilds conversation state by replaying the event log: +On resume, pantograph fully rebuilds conversation state by replaying the event log: 1. **Read the file line by line.** Parse each line as a JSON object. 2. **Crash recovery.** If the last line doesn't parse as a complete JSON object, delete it from the file. @@ -299,7 +299,7 @@ The Conversation stores only `role` and `content` per message — the data neede ### Handling incomplete turns on resume -If the session was interrupted mid-turn, the log may contain an assistant message with `ToolUse` blocks but no corresponding user message with `ToolResult` blocks. On resume, awl does **not** automatically re-execute the tools. The conversation is rebuilt as-is and presented to the user. The agent loop waits for user input — the user decides how to proceed. +If the session was interrupted mid-turn, the log may contain an assistant message with `ToolUse` blocks but no corresponding user message with `ToolResult` blocks. On resume, pantograph does **not** automatically re-execute the tools. The conversation is rebuilt as-is and presented to the user. The agent loop waits for user input — the user decides how to proceed. --- @@ -313,7 +313,7 @@ If the session was interrupted mid-turn, the log may contain an assistant messag A single agent turn with tool calls appends multiple rounds of these entries. -**File creation.** The session file is created when the session is initialized (header written). If `awl` starts and the user immediately quits without the session being initialized, no file is created. +**File creation.** The session file is created when the session is initialized (header written). If `pantograph` starts and the user immediately quits without the session being initialized, no file is created. **No explicit save command.** Persistence is automatic. Every event is written to disk as it happens. @@ -510,7 +510,7 @@ SessionInfo = struct { ### Modified files - **`agent.zig`** — Receives a `SessionManager` reference. After each message is assembled, calls `appendMessage()` with the current provider/model (user messages get stamped; assistant/system messages pass null). The agent loop is now a producer of session entries. -- **`config.zig`** — Session directory path added. Default: `$XDG_DATA_HOME/awl/sessions/` (falling back to `~/.local/share/awl/sessions/` if `XDG_DATA_HOME` is unset). Overridable via `AWL_SESSION_DIR` environment variable. +- **`config.zig`** — Session directory path added. Default: `$XDG_DATA_HOME/panto/sessions/` (falling back to `~/.local/share/panto/sessions/` if `XDG_DATA_HOME` is unset). Overridable via `PANTO_SESSION_DIR` environment variable. - **`main.zig`** — `--resume` and `--resume ` flags. `sessions` subcommand. On startup with `--resume`, load session, rebuild conversation, continue agent loop. Without `--resume`, create a new session. --- @@ -522,7 +522,7 @@ SessionInfo = struct { Resume the most recent session for the current working directory. If no sessions exist, create a new one. ``` -awl --resume +panto --resume ``` ### `--resume ` @@ -530,7 +530,7 @@ awl --resume Resume a specific session by its ID (or a unique prefix of the ID). Errors if no session matches, or if the prefix is ambiguous. ``` -awl --resume 019dc5ba +panto --resume 019dc5ba ``` ### `sessions` subcommand @@ -538,7 +538,7 @@ awl --resume 019dc5ba List sessions for the current working directory. Shows session ID (short), creation time, and message count. ``` -awl sessions +panto sessions ``` Output: @@ -564,12 +564,12 @@ Output: ### Integration test (manual) -- Start `awl`, hold a multi-turn conversation with tool calls, quit +- Start `panto`, hold a multi-turn conversation with tool calls, quit - Inspect the JSONL file — verify entries are present and well-formed -- Run `awl --resume` — verify conversation continues from where it left off +- Run `panto --resume` — verify conversation continues from where it left off - Change the model mid-session, send a prompt, verify the user message entry in the log carries the updated `provider`/`model` fields -- Kill `awl` mid-turn (e.g., Ctrl+C during streaming), run `awl --resume` — verify session loads from last valid entry -- Run `awl sessions` — verify sessions are listed with correct metadata +- Kill `pantograph` mid-turn (e.g., Ctrl+C during streaming), run `panto --resume` — verify session loads from last valid entry +- Run `panto sessions` — verify sessions are listed with correct metadata --- @@ -577,6 +577,6 @@ Output: 1. **Entry ID generation**: 8-char hex IDs give ~4 billion possibilities. Collision probability within a single session is negligible, but should we verify uniqueness against existing entries, or trust the randomness? 2. **Timestamp precision**: ISO 8601 with millisecond precision is sufficient. Zig's `std.time` provides nanosecond precision — we format to milliseconds. -3. **File locking**: If two `awl` processes try to append to the same session file simultaneously, lines could interleave and corrupt the file. Should we use `flock` for mutual exclusion? Or is this not worth worrying about in phase 4? +3. **File locking**: If two `pantograph` processes try to append to the same session file simultaneously, lines could interleave and corrupt the file. Should we use `flock` for mutual exclusion? Or is this not worth worrying about in phase 4? 4. **Large session files**: A long coding session can produce thousands of entries and megabytes of JSONL. Replaying the entire log on every resume could become slow. Should we cache the rebuilt Conversation state (e.g., as a checkpoint line at the end of the file) and only replay new entries on subsequent loads? Or defer this optimization? -5. **Session directory creation**: Should `awl` create the session directory tree eagerly on startup, or lazily when the first session is created? +5. **Session directory creation**: Should `pantograph` create the session directory tree eagerly on startup, or lazily when the first session is created? -- cgit v1.3