From a11c899f14c5a13ab35b90cd81b4d1b6f588d2ed Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sun, 18 Oct 2026 23:22:39 -0700 Subject: [PATCH] "discarding" --- docs/operations.md | 6 +++--- mac/Sources/Byre/Store.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/operations.md b/docs/operations.md index 502a95b..f2610d2 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -57,7 +57,7 @@ Agents find closed tasks with the `search_closed_tasks` MCP tool, which task age `sqlite3 byre.db "SELECT id, discard_state, discarding_by, discard_session FROM tasks WHERE discard_state != '' AND status = 'active'"` lists discards in progress. Confirming Discard in the UI points the right pane away from the task at once, while its summary turn runs. -A Task's `merging` is true while `merging_by` or `finalizing` is set, and `discarding` while the task is active with `discard_state` set. Clearing `merging_by` without moving it to `finalizing`, or clearing `finalizing` without closing the task, emits `task.merge_ended` (not drawn); that, `task.merge_started`, `task.discard_started` and `task.discarded` reload the project's tasks in the UI, so the task switcher and the Project tab show the task as **Merging**, or **Working** while discarding (see "Task state" under the task switcher). +A Task's `merging` is true while `merging_by` or `finalizing` is set, and `discarding` while the task is active with `discard_state` set. Clearing `merging_by` without moving it to `finalizing`, or clearing `finalizing` without closing the task, emits `task.merge_ended` (not drawn); that, `task.merge_started`, `task.discard_started` and `task.discarded` reload the project's tasks in the UI, so the task switcher and the Project tab show the task as **Merging** or **Discarding** (see "Task state" under the task switcher). ### Task state in the store @@ -67,7 +67,7 @@ Subagent count: Claude Code's subagents can run in the background past the `turn `review_requests` covers the gap between a turn's end and its review starting. `turnEnded` emits `turn.ended` inside `EndTurn`'s transaction, which also sets the session idle and, when the turn's snapshot will be reviewed (no discard or merge pending, and no subagents running), adds one to `review_requests`, so the task goes from `running` to `review_pending` in the transaction that publishes the turn's summary. The snapshot is requested after that transaction. Each review request ends exactly once with `EndReviewRequest`, which subtracts one: after `afterSnapshot` returns (by then the review row is `running`, so the task is `reviewing`, or it was skipped or never started, so it is `idle`), or when the request is dropped because a newer one replaced it or the task's snapshots were closed. On startup `recoverReviews` zeroes every task's `review_requests` (`ClearReviewRequests`), since the requests in flight died with the process. -The UI's labels (see "Task state" under the task switcher) read `state` for **Working** and **Reviewing**; `task.state` reloads the project's tasks. `sqlite3 byre.db "SELECT id, title, state, review_requests FROM tasks WHERE status = 'active'"` shows them. +The UI's labels (see "Task state" under the task switcher) read `state` for **Working**, **Reviewing** and **Discarding**; `task.state` reloads the project's tasks. `sqlite3 byre.db "SELECT id, title, state, review_requests FROM tasks WHERE status = 'active'"` shows them. `POST /api/tasks/{id}/merge-in` (the transcript's **Sync from main** button, labelled with the default branch) brings the default branch into the task branch through the agent: it sends `mergeInPrompt` (`internal/agents/mergein.go`) as a `byre` message to the task's session, or starts a session with it when the task has none. The prompt has the agent run `git merge --no-ff --no-commit`, resolve conflicts, regenerate generated files rather than hand-merging them, check the build and tests, and leave the merge uncommitted; byre's end-of-turn commit concludes it as a merge commit. If resolving the conflicts took substantive changes rather than combining both sides as written, the prompt tells the agent to stop there and end its turn describing them; the turn's snapshot then gets its review like any other (see "Reviews"). byre doesn't inspect the merge itself: whether a change is substantive, and whether to stop, is the agent's call. On a Merge turn the prompt adds that byre squashes and pushes when the turn ends, and tells the agent to call the `stop_merge` MCP tool first if it stops (see below). The prompt ends by showing the task's current merge title and asking the agent to set a better one with `set_merge_title` if it no longer describes all the work on the branch. It is refused (409) while a session is `running`. @@ -254,7 +254,7 @@ Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and The task switcher (⌘K, or the project ▸ task picker centred in the right pane's top row) lists active tasks with their task state and open inbox item count (the Project tab shows the same state), live while it is open. Tasks with inbox items come first; within each group, the task least recently visited comes first (never-visited tasks before all others, the right pane's own task after all others, ties in the server's order: projects by name, tasks newest first), so ⌘K Enter repeated cycles through the tasks. Visit times are SwiftData `VisitRecord`s synced through iCloud (`docs/macos.md`, "Persistence"), stamped when a task is selected and again when it is left. After the tasks come the projects, one row each; picking one points the right pane at the project with no task, on the Project tab. Typing filters both and adds a last row that creates a task with the typed text as its title, in the right pane's project (the first project when there is none); Enter picks the highlighted row. Text before a first colon that starts at least one project's name (ignoring case, no spaces) picks those projects instead: `by: fix` lists only their tasks whose titles contain "fix", their project rows that match, and one row per project creating the task titled "fix" there. When no project name starts with it, the whole text is the title, so "Fix: login" stays as typed. A create row shows the user's default model (see "Agent sessions"), the model the composer offers first for the new task's first message; the task itself starts with no session. Below each create row, a "Start in …" row creates the same task and starts its agent on the default model with the title as its first message, through `POST /api/projects/{id}/tasks` with `prompt` set to the title. Picking a task points the right pane at it on the Questions tab when it has open inbox items, otherwise on the Transcript tab, scrolled to its latest summary when that is unread (see below and "Lead agent"). -**Task state.** The switcher and the Project tab show one label per task in place of its raw session state, the first that applies (`taskState` in `mac/Sources/Byre/Store.swift`), with a dot and a tooltip saying why: **Needs your answer** (an open `question` inbox item), **Merging** (`merging` or `merging_into_parent`), **Merge held** (`merge_queued_at`), **Merge after review** (`merge_after_review`), **Reviewing** (the task's `state` is `review_pending` or `reviewing`), **Working** (`running` or `discarding`; see "Task state in the store" for both), **Idle** (anything else, including no session). The Project tab, in its task list and TODO items, draws a spinner in place of the dot for **Reviewing**, **Working** and **Merging**. The label follows the tasks and inbox state the app already holds, so it needs no extra fetch. +**Task state.** The switcher and the Project tab show one label per task in place of its raw session state, the first that applies (`taskState` in `mac/Sources/Byre/Store.swift`), with a dot and a tooltip saying why: **Needs your answer** (an open `question` inbox item), **Merging** (`merging` or `merging_into_parent`), **Merge held** (`merge_queued_at`), **Merge after review** (`merge_after_review`), **Reviewing** (the task's `state` is `review_pending` or `reviewing`), **Working** (`running`), **Discarding** (`discarding`; see "Task state in the store" for both), **Idle** (anything else, including no session). The Project tab, in its task list and TODO items, draws a spinner in place of the dot for **Reviewing**, **Working**, **Discarding** and **Merging**. The label follows the tasks and inbox state the app already holds, so it needs no extra fetch. **Unread summaries.** A Task's `summary_seq` is the seq of its latest `turn.ended` event with `summary: true` (0 for none), read through the partial index `events_summary`, so the app knows of unread summaries without loading transcripts; a `turn.ended` with `summary: true` on the global stream raises it in the app. The switcher's and the Project tab's task rows show a **new summary** state label while `summary_seq` is above the seq the user has read (`unreadSummary` in `mac/Sources/Byre/Store.swift`), except on tasks of a workflow run, which close too soon for their summaries to be read. Picking such a row (in the switcher, only when the task has no open inbox items) points the right pane at the task's Transcript and scrolls it to that turn's final message (`Store.jump`, applied by the transcript once the event is drawn). Read state is per user on the server: `summary_reads` holds one highest-read seq per user and task. `GET /api/me/summary-reads` returns the caller's, for active tasks, as a map from task id to seq, and `PUT /api/tasks/{taskId}/summary-read` with `{seq}` raises it, emitting `summary.read` (`task_id`, `seq`) owned by the user only when it went up, so every Mac the user has byre open on clears the label. A summary counts as read only when the summary turn's end row is on screen in a transcript while that server's window is the key window of the active app (`summaryVisible` in `mac/Sources/Byre/Chat.swift`), so selecting the task on another tab leaves it unread. `sqlite3 byre.db 'SELECT * FROM summary_reads'` shows the read seqs. diff --git a/mac/Sources/Byre/Store.swift b/mac/Sources/Byre/Store.swift index e5bd525..71d34de 100644 --- a/mac/Sources/Byre/Store.swift +++ b/mac/Sources/Byre/Store.swift @@ -22,7 +22,7 @@ private let workingTitles: [ByreTask.StatePayload: (label: String, title: String .running: ("Working", "The agent is mid-turn or its subagents are running"), .reviewPending: ("Reviewing", "A review of the turn's work is about to start"), .reviewing: ("Reviewing", "A review is running"), - .discarding: ("Working", "Discarding"), + .discarding: ("Discarding", "The agent is writing the task summary, then byre removes the task"), ] @MainActor