Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/agent-driver/src/actions-zip.SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Reads the files out of a zip archive, so an agent that ran on the `actions` run target can have its transcript recovered: GitHub only hands back a workflow run's uploaded artifact as a zip, and that download is the sole way to read what happened on a GitHub Actions runner. Only the shapes GitHub's artifact upload produces are supported — stored or deflated files, no encryption. Anything unrecognized is reported as a failure instead of yielding a partial archive, because a silently truncated transcript would read as an agent that said less than it actually did.
Reads the files out of a zip archive, so an agent that ran under the GitHub Actions driver can have its transcript recovered: GitHub only hands back a workflow run's uploaded artifact as a zip, and that download is the sole way to read what happened on a GitHub Actions runner. Only the shapes GitHub's artifact upload produces are supported — stored or deflated files, no encryption. Anything unrecognized is reported as a failure instead of yielding a partial archive, because a silently truncated transcript would read as an agent that said less than it actually did.

## Before modifying/creating SPEC.md files

Expand Down
4 changes: 2 additions & 2 deletions packages/agent-driver/src/actions.SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The driver for the `actions` run target: instead of running the coding-agent CLI on this device, it hands each turn to a GitHub Actions runner — dispatch the project's agent workflow, wait for the run to finish, then read back the transcript the run uploaded. To the caller this looks exactly like any other driver; only the tempo differs.
The GitHub Actions driver (`github-actions`): instead of running the coding-agent CLI on this device, it hands each turn to a GitHub Actions runner — dispatch the project's agent workflow, wait for the run to finish, then read back the transcript the run uploaded. To the caller this looks exactly like any other driver; only the tempo differs.

## User story

Expand Down Expand Up @@ -60,7 +60,7 @@ Dispatching a workflow returns no identifier for the run it creates. Each turn t

#### User story

The caller's UI shows an agent's tool calls and messages as an event stream, the same way for every run target.
The caller's UI shows an agent's tool calls and messages as an event stream, the same way for every driver.

#### Business logic

Expand Down
2 changes: 1 addition & 1 deletion packages/agent-driver/src/actions.test.SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
What the tests cover: an agent on the `actions` run target dispatches its workflow, keeps polling while the run is queued and in progress, and turns the run's uploaded transcript into a finished turn with the agent's final message, its session id, and its usage figures.
What the tests cover: an agent under the GitHub Actions driver dispatches its workflow, keeps polling while the run is queued and in progress, and turns the run's uploaded transcript into a finished turn with the agent's final message, its session id, and its usage figures.

- The prompt is dispatched with the system prompt framing in front of the task text, plus a per-turn correlation id that is the only way back to a run GitHub reports no identifier for; two agents never share a correlation prefix, so a freshly started process cannot latch onto another agent's workflow run.
- Each run is asked to push to the same branch for the whole agent, and every turn after the first is dispatched from the branch the previous run pushed, so later work builds on earlier work.
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-driver/src/index.SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The package's main entry point: the driver contract itself and the set of implementation ids, the three drivers that satisfy it (Claude Code, Codex, the `actions` run target), the fake driver, reading where the account's quota stands, the process engine a further CLI-backed driver would be built on, and the event-stream helper a driver implemented outside the package builds its events with. No business logic of its own. The Actions run target's internal archive reader is deliberately not offered, since nothing outside that driver uses it.
The package's main entry point: the driver contract itself and the set of implementation ids, the three drivers that satisfy it (Claude Code, Codex, GitHub Actions), the fake driver, reading where the account's quota stands, the process engine a further CLI-backed driver would be built on, and the event-stream helper a driver implemented outside the package builds its events with. No business logic of its own. The GitHub Actions driver's internal archive reader is deliberately not offered, since nothing outside that driver uses it.

## Before modifying/creating SPEC.md files

Expand Down
10 changes: 5 additions & 5 deletions packages/agent-driver/src/types.SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The user picks which coding agent does the work — Claude Code or Codex — and
## Business logic — TL;DR

- **The CLI is a black box** - the caller prompts it, lets its own loop run to completion, and then reads the code it produced. It never gates on which tool the agent reached for.
- **Every pass is a fresh prompt** - review, security, QA, UX: each is its own prompt with its own fresh context, and a role is framing added to the prompt rather than a separate kind of agent.
- **Every prompt is a fresh context** - each prompt the caller sends is its own turn with its own fresh context, and a role is framing added to the prompt rather than a separate kind of agent.
- **Capabilities are optional, never faked** - reading the account's quota, reading a file from the agent's workspace, and continuing a previous conversation are each offered only by drivers that can genuinely do them; a driver that cannot simply does not, and the product copes.
- **Events are for looking, not for deciding** - everything the agent streams reaches the caller's UI for visibility, and no control flow ever branches on it.
- **Spend, traffic light, and proportion are three different things** - what an agent spent, whether the account may still spend, and how much of the allowance is gone are kept strictly apart and never substituted for one another.
Expand All @@ -36,17 +36,17 @@ The contract is deliberately tiny: start an agent bound to a checkout, prompt it

#### Rationale

Two identities are kept apart. The **driver** is the user's choice — Claude Code or Codex. Each driver has a separate implementation for each place it can run, since running on a GitHub Actions runner or in a cloud session is a different mechanism from running on this device, and the run target is a fact about *where*, not about *which agent*. The contract fixes the set of implementation ids (`claude-code`, `codex`, `claude-web`, `github-actions`, `fake`), because an agent's record names the implementation that ran it and every reader of that record must agree on the vocabulary; mapping an id back to the user's choice is the caller's business, not the package's.
Two identities are kept apart. The **driver** is the user's choice — Claude Code or Codex. Each driver has a separate implementation for each place it can run, since running on a GitHub Actions runner or in a cloud session is a different mechanism from running on this device, and where it runs is a fact about *where*, not about *which agent*. The contract fixes the set of implementation ids (`claude-code`, `codex`, `claude-web`, `github-actions`, `fake`), because an agent's record names the implementation that ran it and every reader of that record must agree on the vocabulary; mapping an id back to the user's choice is the caller's business, not the package's.

### Every pass is a fresh prompt
### Every prompt is a fresh context

#### User story

An agent plans, then builds, then reviews its own work for quality, security, and user experience — and each of those passes should think about the task with a clear head rather than dragging the previous pass's context along.
The caller sends an agent several prompts in a row — a plan, then the build, then a review of its own work — and wants each to think about the task with a clear head rather than dragging the previous prompt's context along.

#### Business logic

Each pass is its own prompt, and a prompt is the unit of fresh context. Everything runs through the driver — there is no second execution path — and a role is delivered as framing prepended to the prompt rather than as a separate kind of agent. Framing exists at two levels: framing that applies to every prompt of an agent, and framing added for one prompt only.
A prompt is the unit of fresh context. Everything runs through the driver — there is no second execution path — and a role is delivered as framing prepended to the prompt rather than as a separate kind of agent. Framing exists at two levels: framing that applies to every prompt of an agent, and framing added for one prompt only.

The one deliberate exception is continuing a conversation: a prompt may ask to pick up the agent's previous turn instead of starting fresh, which is how a live chat message lands in the ongoing conversation with its full history, and how a finished agent is revived from the caller's UI with everything it already knew.

Expand Down
4 changes: 2 additions & 2 deletions packages/skill-branches/SKILL.SPEC.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
The skill's instructions: what an agent is told about living inside its own checkout, under the package's conventions. Given to every agent a caller starts in a checkout it created, and installable by a skills catalogue as `skills/branches.md`.
The skill's instructions: what an agent is told about living inside its own checkout, under the package's conventions. Found by every agent a caller starts in a checkout the package created, as a skill of its harness (`src/skill-links`).

## User story

- The user starts an agent from the dashboard and expects its work on a branch named after what it did, reviewable as a pull request, with nothing touched in the user's own checkout.
- The user starts an agent from the caller's UI and expects its work on a branch named after what it did, reviewable as a pull request, with nothing touched in the user's own checkout.

## Business logic — TL;DR

Expand Down
4 changes: 2 additions & 2 deletions packages/skill-branches/src/cli.test.SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ What the tests cover: every command of the command line against real git, and th

- **`create`** - makes the checkout on `agent-<id>`, links the parent's dependency directory in, hides `.branches/` from the project's git through the exclude file; works from inside another checkout; `--base` puts the branch on the stated commit.
- **Ids** - an id that could escape the branches directory is refused by `create` and `remove` with nothing created.
- **`name`** - renames from a subdirectory of the checkout, leaves no second branch, moves the `branches/` link to the new name without moving the checkout; the same name again is a no-op and another name renames again, dropping the old link; a name taken locally or only on the remote gets a numeric suffix that the caller reads back; a name outside `[a-z0-9-]+` is refused with the branch untouched; the project's main checkout is refused and keeps its branch.
- **`name`** - renames from a subdirectory of the checkout, leaves no second branch, moves the `.branches/` link to the new name without moving the checkout; the same name again is a no-op and another name renames again, dropping the old link; a name taken locally or only on the remote gets a numeric suffix that the caller reads back; a name outside `[a-z0-9-]+` is refused with the branch untouched; the project's main checkout is refused and keeps its branch.
- **`status`** - reads dirty before the agent commits, clean after, on the remote after a push; accepts an absolute or a relative path; refuses a leftover directory that is not a checkout.
- **`list`** - empty for a project with no checkouts; each checkout with the branch it is on now, renamed or not; a number per checkout with `--sizes`.
- **`remove`** - a dirty checkout is kept with the reason on stderr; a committed one is pushed, removed, and its link dropped; a second removal reports no checkout; with `--no-push` an unpushed checkout is kept and nothing reaches the remote, until someone pushes it by hand.
Expand All @@ -13,7 +13,7 @@ What the tests cover: every command of the command line against real git, and th
- **A git failure** - reported with git's own line, exit code 1.
- **The executable** - runs by name from the exported bin directory: JSON on stdout, the reason on stderr, the exit codes 0, 1 and 2.
- **Naming again** - asking again for the name the checkout already carries changes nothing, whether it got the plain name or a suffixed one, and whether or not its own branch has been pushed.
- **Reserved names** - `data` and any `agent-…` name are refused, the branch stays, and no phantom checkout appears in the listing.
- **A name spelled like a checkout directory** - `agent-<x>` as a session name is a name like any other: the branch becomes `agent-agent-<x>`, its link follows, and the link is never listed as a checkout.
- **The project from its layout** - a project that is itself a linked worktree gets its checkouts under its own directory, lists them from inside one of them, and its links follow a rename; the main checkout it was made from sees none of them.
- **Before the project is looked for** - a bad id is refused even outside a repository; `status <path>` on a directory outside a repository answers "not a checkout"; `attach` outside a repository is refused as such.
- **Not commands** - names of built-in object properties are not commands.
Expand Down
8 changes: 4 additions & 4 deletions packages/skill-branches/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,15 @@ export function nodeGitRunner(): GitRunner {
* Forgiving in one direction only: an unreadable / missing git reads as "no repo", which is the
* conservative answer for the caller that treats a repo's failure as fatal.
*/
export async function isGitRepo(cwd: string, agent: GitRunner = nodeGitRunner()): Promise<boolean> {
return agent(['rev-parse', '--is-inside-work-tree'], cwd)
export async function isGitRepo(cwd: string, git: GitRunner = nodeGitRunner()): Promise<boolean> {
return git(['rev-parse', '--is-inside-work-tree'], cwd)
.then(out => out.trim() === 'true')
.catch(() => false)
}

/** The root of the checkout `cwd` is in — an agent's own, from anywhere under it. Rejects outside a repo. */
export async function checkoutRoot(cwd: string, agent: GitRunner = nodeGitRunner()): Promise<string> {
return (await agent(['rev-parse', '--show-toplevel'], cwd)).trim()
export async function checkoutRoot(cwd: string, git: GitRunner = nodeGitRunner()): Promise<string> {
return (await git(['rev-parse', '--show-toplevel'], cwd)).trim()
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/skill-branches/src/index.SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The package's main entry point: it gathers the git runner, the naming and layout conventions, the checkout lifecycle, the checkout-as-an-agent-gets-it sequence, the dependency linking, the branch-name links, the reclaim rule, the command line and the executable's directory into one place for a caller to import. No business logic of its own. The naming conventions are also reachable on their own (`branch-names`), for code that runs in a browser and must not pull in git; and the skill's text (`SKILL.md`) is exported for a caller that puts it in an agent's prompt.
The package's main entry point: it gathers the git runner, the naming and layout conventions, the checkout lifecycle, the checkout-as-an-agent-gets-it sequence, the dependency linking, the branch-name links, the skill links, the reclaim rule, the command line and the executable's directory into one place for a caller to import. No business logic of its own. The naming conventions are also reachable on their own (`branch-names`), for code that runs in a browser and must not pull in git.

## Before modifying/creating SPEC.md files

Expand Down
6 changes: 3 additions & 3 deletions packages/skill-branches/src/worktree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ test('addWorktree appends the base ref when given', async () => {
assert.deepEqual(git.calls[0]?.args, ['worktree', 'add', '-b', 'b', worktreePath(REPO, 'run1'), 'origin/main'])
})

test('addWorktree rejects an unsafe run id before touching git (no traversal out of worktrees/)', async () => {
test('addWorktree rejects an unsafe agent id before touching git (no traversal out of .branches/)', async () => {
const git = recordingGit()
await assert.rejects(() => addWorktree(REPO, { agentId: '../evil', branch: 'b' }, git), /unsafe run id/)
await assert.rejects(() => addWorktree(REPO, { agentId: '../evil', branch: 'b' }, git), /unsafe agent id/)
assert.equal(git.calls.length, 0)
})

Expand Down Expand Up @@ -251,7 +251,7 @@ test('a run worktree reads dirty until the agent commits; the branch then outliv
}
})

test('agentBranchName names the branch after the run id (#736)', () => {
test('agentBranchName names the branch after the agent id (#736)', () => {
assert.equal(agentBranchName('2026-07-19T10-00-00-000Z'), 'agent-2026-07-19T10-00-00-000Z')
})

Expand Down
Loading
Loading