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
7 changes: 7 additions & 0 deletions .agents/skills/work-queue/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
name: work-queue
description: Work one queued task off the project's agent queue, unattended.
disable-model-invocation: true
---

Get tickets to work on. Nobody will answer you: never ask, decide yourself. Take one queued task only. Commit your work on your branch and do not push; the run that started you publishes your branch when you stop. For you, committed counts as published: once the work is committed, close its ticket and mark the queue entry done. Before you stop, release any claim you still hold unless you closed the ticket. If nothing is queued, say so and stop. If this project has no ticketing system or no AI queue, show an error to the user and stop.
1 change: 1 addition & 0 deletions .claude/skills/work-queue
2 changes: 1 addition & 1 deletion packages/LOGIC.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The deliverables The Framework ships, one directory each. The product is the `fr
- **The `tickets` skill** (`skill-tickets/`) - the `@gemstack/skill-tickets` npm package: the project's tickets with their plans and claims on the `agent-data` branch, the `tickets` command that reads, writes, claims and closes them, and the skill text. Depends on `agent-data`.
- **The `queue` skill** (`skill-queue/`) - the `@gemstack/skill-queue` npm package: the agent queue on the `agent-data` branch, the `queue` command that reads it, adds an entry at a priority and takes one off, and the skill text. Depends on `agent-data`.
- **The `logs` skill** (`skill-logs/`) - the `@gemstack/skill-logs` npm package: the record of every run agents made on a project, on the `agent-data` branch, and the read-only `logs` command; the product writes every run through it. Depends on `agent-data`.
- **The `work-queue` command** (`skill-work-queue/`) - the `@gemstack/skill-work-queue` npm package: a command skill, one `SKILL.md` and no code, the job of working one queued task off the agent queue; it composes the capability skills of a project without naming one. One package per command; the daemon fires it by its slash command.
- **The `work-queue` command** (`skill-work-queue/`) - the `@gemstack/skill-work-queue` npm package: a command skill, one `SKILL.md` and no code, the job of working one queued task off the agent queue; it composes the capability skills of a project without naming one. One package per command; a project tracks its skill file under `.claude/skills/<command>`, and the daemon fires it by its slash command.
- **The Claude web bridge's extension** (`chrome-extension/`) - a Chrome extension, not an npm package: the far end of the Claude web bridge [5], reading claude.ai in a signed-in browser. Talks to the product over HTTP only.
- **The website** (`the-framework.ai/`) - the marketing site at https://the-framework.ai. Presents the product; shares no code with it.

Expand Down
1 change: 0 additions & 1 deletion packages/framework/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"dependencies": {
"@base-ui-components/react": "1.0.0-rc.0",
"@gemstack/agent-data": "workspace:*",
"@gemstack/skill-work-queue": "workspace:*",
"@gemstack/skill-branches": "workspace:*",
"@gemstack/skill-logs": "workspace:*",
"@gemstack/skill-queue": "workspace:*",
Expand Down
8 changes: 4 additions & 4 deletions packages/framework/src/auto-pm.LOGIC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Decides, once per look [1] and per project, whether the daemon may spend the account's quota [2] on work nobody asked for, and on what. Auto PM [3] either starts an agent [6] on the queued work [4] because the `agent-data` branch [11] moved, or, once a run found nothing queued, refills the agent queue [5] by firing the next routine [7] of a fixed rotation. The daemon reads no queue and names no skill: it reads the head of the branch, and it starts the queued work with one slash command, `/work-queue`, the command skill [22] shipped with The Framework. Every reason not to start is a sentence the daemon logs and the dashboard shows.
Decides, once per look [1] and per project, whether the daemon may spend the account's quota [2] on work nobody asked for, and on what. Auto PM [3] either starts an agent [6] on the queued work [4] because the `agent-data` branch [11] moved, or, once a run found nothing queued, refills the agent queue [5] by firing the next routine [7] of a fixed rotation. The daemon reads no queue and names no skill: it reads the head of the branch, and it starts the queued work with one slash command, `/work-queue`, the command skill [22] the project tracks as its own file. Every reason not to start is a sentence the daemon logs and the dashboard shows.

## Context

Expand Down Expand Up @@ -29,7 +29,7 @@ Decides, once per look [1] and per project, whether the daemon may spend the acc
[19] checkout: an agent's own working copy of the project: a git worktree under the project's `.branches/` directory, named as its branch.
[20] agent id: an agent's stable id, derived from the moment it started; it names the agent's checkout directory, its branch until the agent names it, and its run.
[21] holder: who a claim names: the agent's id when the daemon started the agent, else the branch the `tickets` command ran on.
[22] command skill: a skill file (`SKILL.md`) marked so that only a person or the daemon invokes it, whose body is the job's prompt, shipped as its own package; the daemon starts the agent with the skill's slash command and the coding agent's harness expands it.
[22] command skill: a skill file (`SKILL.md`) marked so that only a person or the daemon invokes it, whose body is the job's prompt, published as its own package and tracked by the project under `.claude/skills/<command>`; the daemon starts the agent with the skill's slash command and the coding agent's harness expands it.
[23] the heartbeat: one run on the queued work a day when nothing moved, the belt for a move the daemon missed.
[24] the chain: the queued work firing again as its run ends, because the run's own commits moved the branch; it ends with a run that moves nothing.

Expand Down Expand Up @@ -75,7 +75,7 @@ See `## Context`.

#### Business logic

A project whose branch moved [15] starts one agent [6] told `/work-queue`, the command skill [22] shipped as the `@gemstack/skill-work-queue` package and linked into every checkout the daemon makes (`daemon-runtime.ts`), unattended [16], with its handoff [18] at `merge`, since what it implements has already been triaged onto the queue where a human could have vetoed it. One agent per move, however high the concurrency cap: the next start needs the branch to move again, which the agent's own commits do. The start spends the move and the rotation's turn alike: whether the queue wants refilling is for this run to find out. A commit the daemon wrote itself is not a move: the run's record the daemon writes at teardown, a routine lock [10] it takes or drops, a claim [9] it mints for a plan agent all carry the trailer, on this machine and on every other machine running the daemon, so a record never starts the next run and two daemons on one branch never fire empty runs at each other's records. A person's writes from the dashboard — queue an entry, release a claim — carry no trailer and are moves: a person asking for work is exactly what should start a run.
A project whose branch moved [15] starts one agent [6] told `/work-queue`, the command skill [22] the project tracks as its own file, in every checkout by itself, unattended [16], with its handoff [18] at `merge`, since what it implements has already been triaged onto the queue where a human could have vetoed it. One agent per move, however high the concurrency cap: the next start needs the branch to move again, which the agent's own commits do. The start spends the move and the rotation's turn alike: whether the queue wants refilling is for this run to find out. A commit the daemon wrote itself is not a move: the run's record the daemon writes at teardown, a routine lock [10] it takes or drops, a claim [9] it mints for a plan agent all carry the trailer, on this machine and on every other machine running the daemon, so a record never starts the next run and two daemons on one branch never fire empty runs at each other's records. A person's writes from the dashboard — queue an entry, release a claim — carry no trailer and are moves: a person asking for work is exactly what should start a run.

### The chain, and after an empty run the rotation

Expand Down Expand Up @@ -280,7 +280,7 @@ Before deciding, every agent this loop started on the project and has not yet be

#### Business logic

- The queued work [4] is the command skill [22] `work-queue`: its name is the skill's, its prompt is the slash command `/work-queue`, its label "Work the queue" and its tooltip "Work one queued task off the agent queue, unattended." are written here, and it is declared as working the queue and as auto-merging its pull request. The skill file itself is the `SKILL.md` of the `@gemstack/skill-work-queue` package, which The Framework depends on.
- The queued work [4] is the command skill [22] `work-queue`: its name is the skill's, its prompt is the slash command `/work-queue`, its label "Work the queue" and its tooltip "Work one queued task off the agent queue, unattended." are written here, and it is declared as working the queue and as auto-merging its pull request. The skill file itself is the project's tracked `.claude/skills/work-queue/SKILL.md`; The Framework ships no skill file and depends on no skill package.
- Each preset-backed routine carries the preset's stable name, which is what the rotation's position and the switched-off list key on; the prompt rendered from the preset; and the preset's label and one-line tooltip, read off the preset so a relabeled preset relabels its routine and the sentence the launcher shows for a preset and the sentence the routines list shows for its routine are the same sentence.
- Only the maintenance routine carries a separate description line, "sweeping the codebase for maintenance work", because "Maintenance" names its preset rather than the work; the other routines' labels read as what they do, so their rows stay one line and their log lines say the label itself.
- The planning routine is declared as fanning out [14]; the triage routines each declare their routine lock [10].
Expand Down
2 changes: 1 addition & 1 deletion packages/framework/src/auto-pm.test.LOGIC.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ What the tests cover, for the Auto PM sweep's policy and loop. The loop is drive
- **The routine off, a move** - is the rotation's turn, and nothing works the queue.
- **An unreadable branch** - stands the project down naming the branch.
- **The log says a stand-down once** - the same stand-down over three looks is one log line; the report carries it every time.
- **The queued-work routine** - its prompt is the slash command of the `work-queue` skill file, which names itself, is invocable only by a person or the daemon, and tells the agent one task, commit without pushing, committed counts as published, say so and stop; it is the only routine that works the queue and the only one that auto-merges.
- **The queued-work routine** - its prompt is the slash command `/work-queue`; it is the only routine that works the queue and the only one that auto-merges.

**The rotation**

Expand Down
20 changes: 2 additions & 18 deletions packages/framework/src/auto-pm.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { strict as assert } from 'node:assert'
import { test } from 'node:test'
import { readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'
import {
autoPmDecision,
quotaHeadroom,
Expand Down Expand Up @@ -545,23 +542,10 @@ test('a stand-down is logged when it is news, not once a minute (#1774)', async
})

test('AUTO_PM_WORK_JOB fires the command skill by its slash command, and lands its own PRs (#1216/#1774)', () => {
// The prompt is the skill's name as a slash command; the agent's harness expands it. The skill
// file ships as its own package, and only a person or the daemon may invoke it.
// The prompt is the skill's name as a slash command; the agent's harness expands it from the
// project's own tracked skill file. The daemon holds no skill file to check: it names the command.
assert.equal(AUTO_PM_WORK_JOB.prompt, `/${WORK_QUEUE_SKILL_NAME}`)
assert.equal(AUTO_PM_WORK_JOB.works, true)
const pkg = dirname(createRequire(import.meta.url).resolve('@gemstack/skill-work-queue/package.json'))
const skill = readFileSync(join(pkg, 'SKILL.md'), 'utf8')
assert.match(skill, new RegExp(`^---\\nname: ${WORK_QUEUE_SKILL_NAME}\\n`))
assert.match(skill, /\ndisable-model-invocation: true\n/)
// What the agent is told: one task, commit but do not push, committed counts as published,
// release what it holds, say so and stop when nothing is queued.
assert.match(skill, /Take one queued task only/)
assert.match(skill, /do not push/)
assert.match(skill, /committed counts as published/)
assert.match(skill, /If nothing is queued, say so and stop/)
// Rom's note (#1774): a command assumes no capability; when it would be broken without one, it says so in capability words, never a skill's name.
assert.match(skill, /no ticketing system or no AI queue, show an error to the user and stop/)
assert.doesNotMatch(skill, /`tickets`|`queue`|npx/)
// The queued work implements entries whose triage a human could have vetoed, so its review
// happened before the agent. Every other job writes tickets/plans and has nothing to merge.
assert.equal(AUTO_PM_WORK_JOB.autoMerge, true)
Expand Down
8 changes: 4 additions & 4 deletions packages/framework/src/auto-pm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,10 @@ export const AUTO_PM_JOBS: readonly AutoPmJob[] = [
]

/**
* The command skill the daemon fires on the queued work (#1774): the `SKILL.md` of the
* `@gemstack/skill-work-queue` package, linked into every checkout the daemon makes, marked so
* that only a person or the daemon invokes it. Its prompt is the slash command; the agent's
* harness expands it.
* The command skill the daemon fires on the queued work (#1774): `work-queue`, a skill file the
* project tracks under `.claude/skills/<command>`, marked so that only a person or the daemon
* invokes it. Its prompt is the slash command; the agent's harness expands it. The daemon ships
* no skill file, links none into a checkout and depends on no skill package.
*/
export const WORK_QUEUE_SKILL_NAME = 'work-queue'

Expand Down
Loading
Loading