Run script/setup in new worktrees and give each task a port block
This commit is contained in:
+10
-1
@@ -18,6 +18,15 @@ State lives in the `byre` directory under Go's `os.UserConfigDir()` (on macOS, `
|
||||
|
||||
Schema changes are the `migrations` slice in `internal/store/store.go`: named entries, each with a short kebab-case name saying what it does. On open, byre applies, in slice order, every entry whose name isn't in the `migrations` table, each in its own transaction that also records the name. Each runs with foreign keys off, so a migration can rebuild a table other tables reference, and `PRAGMA foreign_key_check` must come back empty before it commits. byre is deployed from task branches that each append their own migrations, so after a merge a migration can land before others the database has already applied, and it still runs. Give a new migration a name no other branch will use, and make any data migration safe to run twice. `sqlite3 byre.db 'SELECT * FROM migrations'` lists what has been applied. A database from before the table existed is moved over on open: the first `PRAGMA user_version` entries are recorded as applied, and `user_version` is no longer read or written.
|
||||
|
||||
## Worktree setup and ports
|
||||
|
||||
Every task worktree is made by `createTask` (`internal/agents/tasks.go`): new tasks, `create_task`, `start_todo` and forks alike.
|
||||
|
||||
- **Ports.** Each active task holds a block of `store.PortBlock` ports starting at `tasks.port_base` (`port_base` on the Task). `Store.CreateTask` gives a new task the lowest block from `store.PortFirst` up that no active task holds, so a merged or discarded task's block is reused. The agent's `claude` process gets `PORT` (the first port, which most dev servers read on their own), and the session guidance names the range and says to run dev servers on it. `sqlite3 byre.db "SELECT id, port_base FROM tasks WHERE status = 'active'"` lists the blocks in use.
|
||||
- **Setup script.** If the new worktree has an executable `script/setup` (`setupScript` in `internal/agents/setup.go`), byre runs it in the background, in the worktree, with `PORT` added to byre's environment. Nothing in its interface is byre-specific: a script that copies untracked files such as `.env` from the project's checkout finds it as the parent of `git rev-parse --path-format=absolute --git-common-dir`. It is the project's own file, so a branch that changes it changes the setup of tasks started or forked from that branch. It is killed after `setupTimeout`. The task's transcript shows a `setup.started` note and a `setup.ended` note holding the last `setupOutputBytes` of its combined output (`output`), how long it took (`duration_ms`) and `error` when it failed.
|
||||
- **Waiting.** While setup runs, the task's turns are queued as if the caps were full (`Manager.settingUp`, checked in `turn` and `drain`), so the session shows `queued`; when setup ends, byre drains the queue. Task creation itself doesn't wait.
|
||||
- **Failure.** A failed setup doesn't stop the agent. The next prompt its session submits gets the failure and the output appended to the `UserPromptSubmit` guidance (`remindTaskGuidance`), and at that moment byre files the same text as a `session_error` inbox item. The setup state and failure live in memory, so a byre restart during setup leaves the queued turn to start without it and nothing reruns the script.
|
||||
|
||||
## Closing a task
|
||||
|
||||
A task ends `merged` or `discarded`; either way its sessions are stopped, its snapshot refs, worktree and branch are removed, its `open` inbox items become `dismissed` (resolved by whoever closed it), and its record and events remain. `GET /api/tasks/{id}/unmerged` reports what would be lost or merged: commits on the branch that aren't on the default branch, and files, lines and uncommitted paths that differ from the merge base. The UI's Merge and Discard buttons each ask a one-line confirmation and are disabled while the task's session is `running` or `queued`. Merge is also disabled until `unmerged` reports a commit or a changed file; the transcript fetches it when the task is opened and again shortly after each tool result or turn end.
|
||||
@@ -73,7 +82,7 @@ Agents don't commit; byre does. Each `user.message` event that starts a turn rec
|
||||
|
||||
byre chooses the Claude session ID (`vendor_session_id`) up front, so every session can be resumed. Processes start on demand: the first message after a restart resumes the session with `--resume`. Sessions are `running` from the moment a turn is sent until its `result` arrives; on startup, every session still marked `running` was cut off mid-turn, and byre resumes it and sends a message telling the agent to re-check the working tree. Stopping the server kills all agent processes. Merging or discarding a task kills its sessions and marks them `completed`.
|
||||
|
||||
Every session gets byre's guidance (`guidance` in `internal/agents/guidance.go`): ask questions with `ask_human`, end each turn with a final message that summarizes it (including every departure from the plan), put everything the human needs in a blocking question's context since that turn files no report, don't commit (byre commits each turn), and don't push, touch other branches or run git maintenance. A task started from a `TODO.md` item gets one more line naming the item and telling the agent to remove it from `TODO.md` in its worktree when the work is done (`taskGuidance`; see "TODO"). It is passed as `--append-system-prompt`, which Claude Code records on a session's first request and reuses on resume, so a running session keeps the text it started with; a `UserPromptSubmit` hook also attaches it as `additionalContext` to every user turn, which reaches existing sessions too.
|
||||
Every session gets byre's guidance (`guidance` in `internal/agents/guidance.go`): ask questions with `ask_human`, end each turn with a final message that summarizes it (including every departure from the plan), put everything the human needs in a blocking question's context since that turn files no report, don't commit (byre commits each turn), and don't push, touch other branches or run git maintenance. A task agent's guidance also names its port range (see "Worktree setup and ports"). A task started from a `TODO.md` item gets one more line naming the item and telling the agent to remove it from `TODO.md` in its worktree when the work is done (`taskGuidance`; see "TODO"). It is passed as `--append-system-prompt`, which Claude Code records on a session's first request and reuses on resume, so a running session keeps the text it started with; a `UserPromptSubmit` hook also attaches it as `additionalContext` to every user turn, which reaches existing sessions too.
|
||||
|
||||
A Stop hook (`bounceProseQuestion` in `internal/agents/guidance.go`) bounces a turn whose final message asks the human something in prose: if `ask_human` wasn't called during the turn and the final message (`last_assistant_message`) has a sentence ending in `?` outside code spans, code blocks, quotes and blockquotes, the stop is blocked once with an instruction to ask through `ask_human`. The per-turn flags reset when the next user message is sent. Claude Code reports the block as a "Stop hook error" notification; that is expected. An agent told explicitly to ask in plain text will refuse the bounce.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user