diff --git a/TODO.md b/TODO.md index ee755f1..ca23c66 100644 --- a/TODO.md +++ b/TODO.md @@ -2,8 +2,6 @@ - Merge finalization keeps no durable progress; cleanup runs before the task is closed `internal/agents/mergein.go:303–324` runs push → notes → fast-forward → `RemoveTask` → `MergeTask`. A crash or error after the push leaves an active task with no worktree; a retried Merge fails in `Snapshot`, or squashes to no change and closes with an empty `merge_commit`. Have the `finalizing` marker record the squash commit; on startup, a recorded commit that is an ancestor of origin/ finishes notes, fast-forward, removal and MergeTask idempotently, otherwise the marker is cleared and an error is filed. -- A failed auto-commit still advances the workflow from the old HEAD - A `CommitAll` error is only logged (`internal/agents/manager.go:838–841`) and `FinishStage` still runs (`:885–889`), so `RecordStage` stores the old `git.Head` (`internal/agents/workflows.go:349–356`), the next stage branches without the turn's work, and `discardStages` later deletes the worktree that held it. On commit failure, treat the turn as unfinished: skip FinishStage, the handoff and the summary, and file the error in the task's inbox. - Codex mid-turn delivery deletes input before the steer is accepted Queued inputs are deleted or marked delivered and recorded as `user.message` (`internal/agents/queue.go:119`, `:158`; `internal/agents/inbox.go:404`) before `Steer`, whose error is only logged (`internal/agents/codex.go:167–174`). Return the consumed ids from `deliverMidTurn`; when Steer fails, re-insert the inputs in order and clear `delivered_at` so the turn-end Deliver sends them. - Inbox answer validation isn't atomic with resolving the question diff --git a/docs/operations.md b/docs/operations.md index 6b022d5..863ec15 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -140,7 +140,7 @@ Compaction is vendor-agnostic: `POST /api/sessions/{id}/compact` (the transcript Task agents spawn parallel work as subtasks (see "Subtasks"). -Agents don't commit; byre does. Each `user.message` event that starts a turn records `head`, the worktree's `HEAD` when the turn started. When a turn ends, byre runs `git add --all` and commits if anything changed (`git.CommitAll`), as the session's creator, with the first sentence of the turn's final message as the message (`commitMessage` in `internal/agents/manager.go`); `turn.ended` then carries the new sha as `commit`. A failed commit is logged and the turn still ends. +Agents don't commit; byre does. Each `user.message` event that starts a turn records `head`, the worktree's `HEAD` when the turn started. When a turn ends, byre runs `git add --all` and commits if anything changed (`git.CommitAll`), as the session's creator, with the first sentence of the turn's final message as the message (`commitMessage` in `internal/agents/manager.go`); `turn.ended` then carries the new sha as `commit`. A failed commit files a `session_error` in the task's inbox and the turn ends unfinished: no summary, no handoff, no merge finalize, and a workflow stage neither finishes nor records its output, so the next stage never branches from a `HEAD` that lacks the turn's work. A session runs on one of two vendors, picked by the model it is created with (`workflow.VendorOf` in `internal/workflow/workflow.go`): `gpt-*` models run on Codex (`codex app-server`), every other model on Claude Code. The UI's model list is `models` in `web/src/task.ts`; a live session's picker offers only its own vendor's models, and the API refuses (400) a switch to the other vendor's model. Both vendors sit behind `agentProcess` (`internal/agents/process.go`), and both end a turn through the same `turnEnded`, so commits, turn summaries, snapshots, handoffs, merges and deliveries don't depend on the vendor. diff --git a/internal/agents/manager.go b/internal/agents/manager.go index 2edf57b..fb87506 100644 --- a/internal/agents/manager.go +++ b/internal/agents/manager.go @@ -871,13 +871,15 @@ func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error { m.mu.Unlock() finished := !res.isError && !interrupted && strings.TrimSpace(res.result) != "" - handoff := compacting && finished payload := res.payload sha, err := git.CommitAll(l.dir, commitMessage(res), l.gitName, l.gitEmail) if err != nil { slog.Error("commit turn", "session", l.id, "err", err) + m.fileError(l, fmt.Sprintf("byre couldn't commit the turn's work, so the turn counts as unfinished: %v", err)) + finished = false } + handoff := compacting && finished if sha != "" { payload["commit"] = sha }