Collapse handoff turns in the transcript instead of streaming them

This commit is contained in:
Ian Gulliver
2026-10-15 10:30:24 -07:00
parent c777409dc1
commit e0d292fd75
7 changed files with 85 additions and 31 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ curl -H 'Tailscale-User-Login: you@example.com' -H 'Tailscale-User-Name: You' ht
Images: `POST /api/blobs` takes a raw image body (type sniffed from the bytes, PNG, JPEG, GIF or WebP only, at most 5 MiB, `maxBlobBytes` in `internal/server/blobs.go`) and returns its id; `GET /api/blobs/{id}` serves it. A message or session prompt may carry `images`, a list of blob ids (unknown ids are a 400); a message may then have empty text. byre reads each blob when the turn is sent and passes it to Claude as a base64 `image` content block before the text (`claude.Session.Send`). The `user.message` event records the ids as `images`, and a queued turn keeps them in `turn_queue.images`. In the composer, pasting or dropping an image adds a thumbnail with a × to remove it; images upload when the message is sent, and the transcript shows them on the user's message, each linking to the full image.
Compaction is vendor-agnostic: `POST /api/sessions/{id}/compact` (the transcript's **Compact** button, always shown and enabled only while the task has an idle session) sends the agent `handoffPrompt` (`internal/agents/compact.go`) as a `byre` message, asking for a handoff covering the goal, decisions and why, current state, dead ends, open questions and files. That turn files no inbox summary and isn't bounced for questions. When it ends normally, its `turn.ended` carries `handoff: true`, the session is completed, and a new session on the same task and model starts with `handoffPreamble` plus the handoff as its first message (actor `byre`). The context meter tracks the new session. Once the handoff turn ends, the transcript folds the request and everything the agent did in that turn into one collapsed note. If byre restarts during the handoff turn, the continuation is lost and the old session simply goes idle.
Compaction is vendor-agnostic: `POST /api/sessions/{id}/compact` (the transcript's **Compact** button, always shown and enabled only while the task has an idle session) sends the agent `handoffPrompt` (`internal/agents/compact.go`) as a `byre` message, asking for a handoff covering the goal, decisions and why, current state, dead ends, open questions and files. That turn files no inbox summary and isn't bounced for questions. When it ends normally, its `turn.ended` carries `handoff: true`, the session is completed, and a new session on the same task and model starts with `handoffPreamble` plus the handoff as its first message (actor `byre`). The context meter tracks the new session. The request's `user.message` carries `handoff: true`, and from it the transcript puts the request and everything the agent does in that turn into one collapsed note reading "writing a handoff…", without streaming the agent's text; when the turn ends its summary becomes the `turn.ended` note, or "handoff not written" if the turn ended without one. If byre restarts during the handoff turn, the continuation is lost and the old session simply goes idle.
Agents spawn parallel work with the `create_task` MCP tool (`internal/agents/tasks.go`): `title`, `prompt`, `base` and optional `model` (default: the caller's session model). It creates a task in the caller's project exactly as `POST /api/projects/{id}/tasks` does, and starts a session there with `prompt` as the first message (actor `agent`), created by the caller's session creator so commits use that user's git identity. `base` is the agent's choice: `default_branch` starts from the project's default branch, `this_task` from the caller's worktree `HEAD`, which leaves out uncommitted edits from the current turn. The tool returns the new task's id at once; the new session is independent of the caller.