@autodidacticon/orchestrator 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -2
- package/dist/src/agent/api.js +14 -0
- package/dist/src/agent/api.js.map +1 -0
- package/dist/src/agent/mcp-shim.js +104 -0
- package/dist/src/agent/mcp-shim.js.map +1 -0
- package/dist/src/agent/seal.js +28 -0
- package/dist/src/agent/seal.js.map +1 -0
- package/dist/src/agent/server.js +96 -0
- package/dist/src/agent/server.js.map +1 -0
- package/dist/src/brief/compose.js +121 -0
- package/dist/src/brief/compose.js.map +1 -0
- package/dist/src/brief/remediate.js +14 -0
- package/dist/src/brief/remediate.js.map +1 -0
- package/dist/src/brief/template.js +35 -0
- package/dist/src/brief/template.js.map +1 -0
- package/dist/src/brief/validate.js +3 -0
- package/dist/src/brief/validate.js.map +1 -0
- package/dist/src/config.js +157 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/crash.js +68 -0
- package/dist/src/crash.js.map +1 -0
- package/dist/src/entry.js +21 -0
- package/dist/src/entry.js.map +1 -0
- package/dist/src/graph/diff.js +54 -0
- package/dist/src/graph/diff.js.map +1 -0
- package/dist/src/graph/load.js +129 -0
- package/dist/src/graph/load.js.map +1 -0
- package/dist/src/graph/model.js +15 -0
- package/dist/src/graph/model.js.map +1 -0
- package/dist/src/graph/query.js +275 -0
- package/dist/src/graph/query.js.map +1 -0
- package/dist/src/graph/source.js +43 -0
- package/dist/src/graph/source.js.map +1 -0
- package/dist/src/health/budgets.js +49 -0
- package/dist/src/health/budgets.js.map +1 -0
- package/dist/src/health/progress.js +43 -0
- package/dist/src/health/progress.js.map +1 -0
- package/dist/src/health/stuck.js +58 -0
- package/dist/src/health/stuck.js.map +1 -0
- package/dist/src/import/mapping.js +209 -0
- package/dist/src/import/mapping.js.map +1 -0
- package/dist/src/index.js +37 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/launch/launch.js +118 -0
- package/dist/src/launch/launch.js.map +1 -0
- package/dist/src/launch/recovery.js +115 -0
- package/dist/src/launch/recovery.js.map +1 -0
- package/dist/src/loop/core.js +2 -0
- package/dist/src/loop/core.js.map +1 -0
- package/dist/src/loop/orchestrator.js +1508 -0
- package/dist/src/loop/orchestrator.js.map +1 -0
- package/dist/src/loop/schedule.js +143 -0
- package/dist/src/loop/schedule.js.map +1 -0
- package/dist/src/loop/tick.js +419 -0
- package/dist/src/loop/tick.js.map +1 -0
- package/dist/src/main.js +252 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/operator/cli.js +275 -0
- package/dist/src/operator/cli.js.map +1 -0
- package/dist/src/operator/mcp-server.js +341 -0
- package/dist/src/operator/mcp-server.js.map +1 -0
- package/dist/src/operator/mcp.js +31 -0
- package/dist/src/operator/mcp.js.map +1 -0
- package/dist/src/operator/routes.js +383 -0
- package/dist/src/operator/routes.js.map +1 -0
- package/dist/src/runner/claude-code.js +82 -0
- package/dist/src/runner/claude-code.js.map +1 -0
- package/dist/src/runner/command.js +24 -0
- package/dist/src/runner/command.js.map +1 -0
- package/dist/src/runner/inproc.js +43 -0
- package/dist/src/runner/inproc.js.map +1 -0
- package/dist/src/runner/runner.js +2 -0
- package/dist/src/runner/runner.js.map +1 -0
- package/dist/src/runner/selection.js +20 -0
- package/dist/src/runner/selection.js.map +1 -0
- package/dist/src/runner/subprocess.js +137 -0
- package/dist/src/runner/subprocess.js.map +1 -0
- package/dist/src/runs/ancestry.js +34 -0
- package/dist/src/runs/ancestry.js.map +1 -0
- package/dist/src/runs/lifecycle.js +240 -0
- package/dist/src/runs/lifecycle.js.map +1 -0
- package/dist/src/runs/resolve.js +217 -0
- package/dist/src/runs/resolve.js.map +1 -0
- package/dist/src/runs/runDir.js +174 -0
- package/dist/src/runs/runDir.js.map +1 -0
- package/dist/src/state/sqlite-warning.js +30 -0
- package/dist/src/state/sqlite-warning.js.map +1 -0
- package/dist/src/state/sqlite.js +384 -0
- package/dist/src/state/sqlite.js.map +1 -0
- package/dist/src/state/status.js +53 -0
- package/dist/src/state/status.js.map +1 -0
- package/dist/src/state/store.js +15 -0
- package/dist/src/state/store.js.map +1 -0
- package/dist/src/state/transitions.js +94 -0
- package/dist/src/state/transitions.js.map +1 -0
- package/dist/src/toolchain/branch.js +19 -0
- package/dist/src/toolchain/branch.js.map +1 -0
- package/dist/src/toolchain/detect.js +115 -0
- package/dist/src/toolchain/detect.js.map +1 -0
- package/dist/src/toolchain/tools.js +56 -0
- package/dist/src/toolchain/tools.js.map +1 -0
- package/dist/src/tracker/comments.js +48 -0
- package/dist/src/tracker/comments.js.map +1 -0
- package/dist/src/tracker/effects.js +117 -0
- package/dist/src/tracker/effects.js.map +1 -0
- package/dist/src/tracker/policy.js +36 -0
- package/dist/src/tracker/policy.js.map +1 -0
- package/dist/src/verify/fake.js +29 -0
- package/dist/src/verify/fake.js.map +1 -0
- package/dist/src/verify/job.js +2 -0
- package/dist/src/verify/job.js.map +1 -0
- package/dist/src/verify/verifier.js +166 -0
- package/dist/src/verify/verifier.js.map +1 -0
- package/dist/src/workspace/fake.js +71 -0
- package/dist/src/workspace/fake.js.map +1 -0
- package/dist/src/workspace/git-workspace.js +132 -0
- package/dist/src/workspace/git-workspace.js.map +1 -0
- package/dist/src/workspace/git.js +113 -0
- package/dist/src/workspace/git.js.map +1 -0
- package/dist/src/workspace/workspace.js +2 -0
- package/dist/src/workspace/workspace.js.map +1 -0
- package/hosts/claude-code/.claude-plugin/plugin.json +9 -0
- package/hosts/claude-code/.mcp.json +9 -0
- package/hosts/claude-code/bin/orchestrator.mjs +134 -0
- package/hosts/claude-code/hooks/hooks.json +16 -0
- package/hosts/claude-code/hooks/session-end.mjs +96 -0
- package/hosts/claude-code/hooks/session-start.mjs +114 -0
- package/hosts/claude-code/package-lock.json +1247 -0
- package/hosts/claude-code/package.json +7 -0
- package/hosts/claude-code/skills/import/SKILL.md +92 -0
- package/hosts/claude-code/skills/review/SKILL.md +48 -0
- package/hosts/claude-code/skills/run/SKILL.md +185 -0
- package/hosts/claude-code/skills/work/SKILL.md +73 -0
- package/hosts/claude-code/workflows/work.js +104 -0
- package/package.json +60 -4
package/README.md
CHANGED
|
@@ -1,3 +1,158 @@
|
|
|
1
|
-
#
|
|
1
|
+
# orchestrator
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An orchestrator that runs many coding agents in parallel from a dependency graph of tasks against one repository. Dependencies gate admission. Each agent works in its own git worktree and reports through a local API. Humans resolve blockers and review milestones. A task is done only after its sealed work has been rebased, checked, and published to the integration branch. The orchestrator itself is a static TypeScript daemon with named hook points for model-backed judgment, designed to converge to the same outcome after a crash at any point. Task graphs are imported from Linear or Jira through the session's own tracker connector, and progress is mirrored back to the tracker.
|
|
4
|
+
|
|
5
|
+
## Running locally
|
|
6
|
+
|
|
7
|
+
Prerequisites: Node 22.13 or newer (the store uses Node's built-in SQLite), git, and, for real agents, the Claude Code CLI (`claude`) logged in. Scripted agents need nothing else. Start from a Claude Code session with the plugin, or from a terminal with the CLIs.
|
|
8
|
+
|
|
9
|
+
### From a Claude Code session
|
|
10
|
+
|
|
11
|
+
Install the plugin from this repository's marketplace. Nothing else needs to be installed or on PATH:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
claude plugin marketplace add autodidacticon/orchestrator
|
|
15
|
+
claude plugin install orchestrator@orchestrator
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
From a checkout instead, run `npm install && npm run build` once, then `claude --plugin-dir /path/to/orchestrator/hosts/claude-code`.
|
|
19
|
+
|
|
20
|
+
Then use the skills: `import` turns a Linear or Jira epic into the graph with the session's own tracker connector, `run` starts the daemon, has the session's own subagents execute the launches through a bundled workflow, and monitors (questions, reviews, stuck tasks, proposals, tracker mirroring), `work` adds workers from another session or switches the mode, and `review` judges a milestone's pinned commit. On `start` the daemon detects the repository's check command, integration branch, and the tools agents may run, and the skill reports them with their sources; when nothing supplies a check command, the skill looks in `CLAUDE.md`, the README, and the root manifests and sets it through `configure`, or asks. No `orchestrator.yaml` is needed; the terminal section below describes the resolution order and the overrides. The plugin's launcher (`hosts/claude-code/bin/orchestrator.mjs`) runs the package from the plugin's own `node_modules` after a marketplace install (Claude Code installs the published package `@autodidacticon/orchestrator` pinned in `hosts/claude-code/package.json`), from the checkout when loaded with `--plugin-dir`, and through `npx` as a fallback.
|
|
21
|
+
|
|
22
|
+
Who executes launches is `runner.mode`: `pull` parks each prepared launch until a worker claims it, and is what a run started from the plugin defaults to, with the `run` skill's workflow claiming launches for visible session subagents; `spawn` has the daemon run headless Claude Code agents, and is the default for `orchestrator-ctl start` from a terminal, where no session is attached; `mixed` parks for `runner.claimWindowMs` (default 60 s) and then spawns. `runner.mode` in `orchestrator.yaml`, `orchestrator-ctl mode <mode>`, or `start` with `mode` are explicit choices that the plugin default never overrides. A claimed launch has no process: its lease is renewed by the worker's API calls and by progress in the worktree, and `release` hands it back (untouched: no attempt spent; with `--keep`, or `keep: true` on the tool, uncommitted work is committed on the launch branch, the branch is kept, and the task returns to unstarted at no cost with an `interrupted` context in its next brief, up to `daemon.maxKeptReleases` times, default 3).
|
|
23
|
+
|
|
24
|
+
### From a terminal
|
|
25
|
+
|
|
26
|
+
Build the package and put its executables on PATH:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
git clone git@github.com:autodidacticon/orchestrator.git && cd orchestrator
|
|
30
|
+
npm install
|
|
31
|
+
npm run build
|
|
32
|
+
npm link # puts orchestrator, orchestrator-ctl, orchestrator-mcp, orchestrator-mcp-shim on PATH
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
1. No configuration file is needed. At every start the daemon reads the repository root (never subdirectories) and detects the check command from the first matching manifest in a fixed order (a `Makefile` with a `test` target, then `package.json` with a real `test` script using the package manager named by `packageManager` or the lockfile, then `Cargo.toml`, `go.mod`, `pyproject.toml`/`setup.cfg`/`pytest.ini` with uv or poetry when their lockfile exists, `Gemfile`, `build.gradle(.kts)`, `pom.xml`, `mix.exs`, `*.sln`/`*.csproj`), the integration branch (`origin/HEAD` when that branch exists locally, else the checked-out branch, else `main`), and the tools spawned agents may run (`Bash(git:*)` and the orchestrator tools, plus the test, run, install, and exec commands of every toolchain found: `npm test`, `npm run`, `npm install`, `npm ci`, `npx`, `node`, `cargo`, `go`, `pytest`, `python`, `pip`, `uv`, `poetry`, `make`, and their counterparts). Every value is resolved in one order, an explicit argument over `orchestrator.yaml` over the branch pinned for the run (integration branch only) over detection over the built-in default, and records where it came from. Arguments are durable overrides for the run: `orchestrator-ctl start --set checkCommand="make check" --set capacity=3` (also `integrationBranch`, `allowedTools` as a JSON array or one rule per flag, `runnerCommand`, `model`, `effort`), and `orchestrator-ctl config --check-command ... --capacity ... --allow "Bash(docker:*)" --runner-command ... --model ... --effort ... --clear <key>` on a running daemon; `orchestrator-ctl config` alone prints every key with its value and source, the detection record, and `needsConfig`. The same facts are in `status` (`config`, `toolchain`, `needsConfig`) and in `config.json` in the run directory. When nothing supplies a check command, `needsConfig` names it and submitted work is held, without spending an attempt, until one is set. The integration branch is pinned when the run first starts: a re-pointed `origin/HEAD` or a different checked-out branch never moves it, and an explicit different branch is refused once the run holds launches (start from a fresh run directory to change it). The derived allow list lets spawned agents fetch and run packages, including their install scripts, with the user's credentials; `runner.claude.deriveTools: false` with an explicit `runner.claude.allowedTools`, or a `deny` rule in the project's `.claude/settings.json`, narrows it. Which agents get the list is the runner mode: it is filled into the `{allowedTools}` slot of the default `runnerCommand` for launches the daemon spawns (`spawn`, and `mixed` after the claim window), while a launch claimed by a session worker runs under that session's own permissions (see the previous section). Workers run `claude-opus-5-5` at `medium` effort by default (`runner.claude.model` and `runner.claude.effort`, overridable the same way); a task chooses its own with `model` and `effort` in its payload, which the import fills from `model:<name>` and `effort:<level>` labels on the issue. The choice is fixed when a launch is prepared, filled into the `{model}` and `{effort}` slots of the default `runnerCommand` for spawned agents, carried in the claim response for session workers, and shown per launch in `status`. An existing `orchestrator.yaml` keeps working unchanged: keys it sets resolve with source `file`, and a `runnerCommand` without the `{allowedTools}`, `{model}`, or `{effort}` slots runs verbatim. The file is optional for everything else too:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
integrationBranch: main
|
|
39
|
+
checkCommand: npm test # run on every rebased submission before it is published
|
|
40
|
+
capacity: 2 # agents in parallel
|
|
41
|
+
runner:
|
|
42
|
+
claude:
|
|
43
|
+
allowedTools: ["Bash(docker:*)"] # appended to the derived list for spawned agents
|
|
44
|
+
deriveTools: true # false: only Bash(git:*), the orchestrator tools, and the rules above
|
|
45
|
+
model: claude-opus-5-5 # worker model; a task's `model` payload key or `model:<name>` label overrides it
|
|
46
|
+
effort: medium # low, medium, high, xhigh, or max; a task's `effort` key or `effort:<level>` label overrides it
|
|
47
|
+
scheduling:
|
|
48
|
+
order: [criticalPath, priority, fifo] # admission order; ties keep graph file order
|
|
49
|
+
scopes: true # never run two tasks whose declared scopes overlap
|
|
50
|
+
budgets:
|
|
51
|
+
maxLaunchMs: 7200000 # 2 h per launch
|
|
52
|
+
maxRunCostUsd: 20 # pause admission when the run has spent this much
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
2. Start the daemon for that repository. It records run data under `~/.orchestrator/runs/<id>/` and prints the graph file path:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
cd /path/to/your/repo
|
|
59
|
+
orchestrator-ctl start # {"started": true, "url": "http://127.0.0.1:...", "graphPath": "~/.orchestrator/runs/<id>/graph.yaml", ...}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
3. Write the task graph to that `graphPath`. The daemon re-reads it on change. Every task needs a description and acceptance criteria, or it is blocked as under-specified:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
nodes:
|
|
66
|
+
- id: T1
|
|
67
|
+
title: Add the schema migration
|
|
68
|
+
description: Create the users table migration and the model.
|
|
69
|
+
acceptanceCriteria: [migration applies cleanly, model has unit tests]
|
|
70
|
+
deps: []
|
|
71
|
+
- id: T2
|
|
72
|
+
title: Expose the users API
|
|
73
|
+
description: Add GET and POST /users on top of T1's model.
|
|
74
|
+
acceptanceCriteria: [endpoints documented, integration test passes]
|
|
75
|
+
deps: [T1]
|
|
76
|
+
scope: ["src/api/**", "package.json"] # optional: path globs the work stays within
|
|
77
|
+
priority: 1 # optional: lower is more urgent
|
|
78
|
+
- id: MVP
|
|
79
|
+
kind: milestone
|
|
80
|
+
gated: true # a human reviews and accepts the pinned commit
|
|
81
|
+
deps: [T2]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
4. Operate the run from the same terminal:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
orchestrator-ctl status # sets by state, stall roots, open questions, stuck tasks, deferred tasks, cost, config, toolchain, needsConfig
|
|
88
|
+
orchestrator-ctl config [--check-command <cmd>] [--allow <rule>] [--capacity <n>] [--clear <key>] # show or override the configuration
|
|
89
|
+
orchestrator-ctl answer T1 "use sqlite" # answer an agent's question
|
|
90
|
+
orchestrator-ctl logs T1 # the agent's captured output
|
|
91
|
+
orchestrator-ctl nudge T1 "commit what you have"
|
|
92
|
+
orchestrator-ctl accept MVP --sha <sha> --version 1 # or: reject MVP --note "..."
|
|
93
|
+
orchestrator-ctl reopen T1 [--cascade]
|
|
94
|
+
orchestrator-ctl proposals | accept-proposal <id> | reject-proposal <id> --note "..."
|
|
95
|
+
orchestrator-ctl pause | resume [--override-budget]
|
|
96
|
+
orchestrator-ctl mode pull | claim w1 | release <launchId> --reason "..."
|
|
97
|
+
orchestrator-ctl stop [--reap] # graceful: agents keep running and are adopted on the next start
|
|
98
|
+
orchestrator-ctl runs # every run under ~/.orchestrator
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The daemon exits on its own after `daemon.idleExitMs` (default 10 min, 0 disables) with nothing live and no session attached (`daemon.exitWhileAttached: true` lets it exit while one is), records why in `daemon.json` (`runs` shows `exit` for a daemon that is gone, or nothing after a crash), and any `orchestrator-ctl` command or plugin tool revives it on demand; only an explicit `stop` is final until the next `start`. `status` reports `idle` and `attachments`. To run the daemon in the foreground instead, `orchestrator --repo .` (SIGTERM stops it gracefully). To keep everything inside one directory, use a config with explicit paths and `orchestrator --config orchestrator.yaml` with `repoPath`, `graphPath`, and `dataDir` set; then pass `--data-dir` to `orchestrator-ctl`.
|
|
102
|
+
|
|
103
|
+
By default agents are headless Claude Code sessions (`runnerCommand`, with `{allowedTools}`, `{model}`, and `{effort}` filled at spawn), each in its own git worktree with the brief on stdin and the agent API as an MCP server. For a dry run without model calls, point `runnerCommand` at the scripted agent: `node --import <repo>/node_modules/tsx/dist/loader.mjs <repo>/test/agents/scripted.ts {briefJson}`, and give each task a `behavior` such as `commitAndSubmit`, `blockAndWait:<question>`, or `fail`.
|
|
104
|
+
|
|
105
|
+
### Tests
|
|
106
|
+
|
|
107
|
+
`npm test` runs everything that needs no model: unit tests, slices with real subprocess agents, git, and the HTTP API, and the crash-injection suite in `test/crash`, which is the project gate. Three opt-in tests use a real model: `ORCH_E2E_CLAUDE=1 npm test -- test/e2e/claude` runs three Claude Code agents, `ORCH_E2E_CLAUDE_PLUGIN=1 npm test -- test/e2e/plugin` has a Claude Code session operate a run through the plugin, and `ORCH_E2E_CLAUDE_WORK=1 npm test -- test/e2e/work` has a session execute a run in pull mode with two workers through the work skill.
|
|
108
|
+
|
|
109
|
+
## Behaviour
|
|
110
|
+
|
|
111
|
+
A task is done once its sealed work has been rebased onto the integration branch, checked, and published as one squash commit with `Orchestrator-Task` and `Orchestrator-Launch` trailers; a gated milestone then waits for a human to accept its pinned commit. The daemon converges to the same outcome after a crash at any named point, and surviving agents are adopted on restart.
|
|
112
|
+
|
|
113
|
+
Tracker sync. After an import that names its tracker, the daemon derives tracker-bound effects from its own transitions into a durable outbox, in the same transaction as the transition: an issue goes in progress on its first launch, done when its work is published (or, with `tracker.doneAt: milestoneAccepted`, when its gated milestone is accepted), cancelled on cancel; the parent issue follows its first starting child and closes when the milestone is accepted. Comments are posted where a human is needed: an agent's question, a block with its reason, a milestone ready for review, accepted, or rejected (`tracker.commentOnDone` and `tracker.commentOnFail` add completion and failure comments, both off by default). The daemon never talks to a tracker: the `run` skill drains the outbox after every `wait` with the session's Linear or Atlassian tools, mapping states by type, and acknowledges each effect (`outbox`, `ack` tools; `GET /v1/outbox`, `POST /v1/outbox/:id/{ack,nack}` routes; `orchestrator-ctl outbox | ack`). Every comment ends with a marker line, `— orchestrator run <id> · event <n>`, so a drainer can recognise its own comments and never posts one twice. A human reply under a question comment is relayed as the answer (`tracker.answersFromComments`, default on); the first answer wins. Config keys: `tracker.enabled` (`auto` | `always` | `never`), `maxAttempts`, `parentInProgress`, `doneAt`, `commentOnDone`, `commentOnFail`, `answersFromComments`. Status reports `tracker`, `outboxPending`, and `trackerErrors`. Manual check against a sandbox workspace: import an epic with the `import` skill in a Claude Code session connected to Linear or Jira, run it with the `run` skill, and watch the child issues move and the question comment appear; this is not part of `npm test`.
|
|
114
|
+
|
|
115
|
+
The daemon's life. A session attaches through the plugin's operator server, which registers its process ancestry with the daemon on first contact and ends the attachment when its transport closes; the plugin's `SessionEnd` hook posts the ending session's ancestry as well, and the daemon ends any attachment whose own process has died. A claim made through a session carries its attachment, and when the attachment ends the daemon releases that launch with `keep` on the next tick: uncommitted work becomes a work-in-progress commit on the launch branch, the worktree goes, the branch stays, the task returns to `unstarted` with `attempts` unchanged and an `interrupted` context (prior launch, branch, head, the WIP commit) in its next brief, counted in `interruptions` and bounded by `daemon.maxKeptReleases`. With nothing live, no verifier job pending, no cascade pending, no request in flight, and no session attached, the daemon exits after `daemon.idleExitMs`, writes an exit record, and is revived by the next operator call; the plugin's `SessionStart` hook names runs whose daemon is alive or whose last exit left parked launches, open questions, a milestone under review, or a stall. Releasing a new version: bump the version in `package.json`, `hosts/claude-code/.claude-plugin/plugin.json`, and the pin in `hosts/claude-code/package.json`; `npm publish` (which builds); regenerate `hosts/claude-code/package-lock.json` with `npm install --package-lock-only` in that directory against the published version; then push.
|
|
116
|
+
|
|
117
|
+
Scheduling. Ready tasks are admitted in the order of `scheduling.order`, a chain of stages applied as one stable sort (default `[criticalPath, priority, fifo]`): `criticalPath` puts the task with the longest chain of tasks behind it first, so the chain that gates the milestone starts first; `priority` reads the task's `priority` as a number, lower first, missing last (the import passes the tracker's number through, dropping Linear's 0); `fifo` is graph file order, which also breaks any remaining tie. Waiting tasks whose answer has arrived are resumed before anything is ordered; `order: [fifo]` restores the v1 order. A task may declare `scope`, a list of path globs (in the graph file, or under a `Scope` heading in an imported description, one glob per bullet; `options.scopeHeading` renames it). While a task is running, waiting, submitted, or has a claimable launch, no task whose scope overlaps it is launched: two globs overlap when the static prefix of one (the leading path segments before any `*`, `?`, `[`, `{`) is a prefix of the other's, so `src/**` holds `src/api/x.ts`, `src/api/**` and `src/ui/**` run together, and `**` or `*` overlaps everything. Held-back tasks appear as `deferred: [{ id, reason: scopeOverlap, with }]` in status and as a count in the digest; they launch on the first tick after the blocker publishes. `scheduling.scopes: false` turns the filter off; a malformed `scope` fails graph validation.
|
|
118
|
+
|
|
119
|
+
Health. The daemon renews a launch's lease every tick while its process is alive, so a quiet agent is never failed for silence; lease expiry applies only to launches the runner cannot observe. Progress is a worktree fingerprint (head plus status and diff), any API call, or a changed heartbeat text. A launch idle past `health.stuckAfterMs` (default 15 min) is flagged (`stuck` in status and the digest, a `stuck` notice in its inbox, event `launch.stuck`) and idle past `health.stuckFailAfterMs` (default 45 min) is reaped and failed with reason `stuck`, spending an attempt; either threshold at 0 disables that step. `orchestrator-ctl nudge <task> <message>` (also the `nudge` tool) queues a message for the agent's inbox. Budgets: `budgets.maxLaunchMs` (default 2 h) fails an overlong launch with reason `timeBudget`, waiting time excluded; `budgets.maxRunCostUsd` (default 0, unlimited) pauses admission with `pausedReason: costBudget` once the run's cost, read from Claude Code's final JSON per launch, reaches the ceiling, and `resume` is refused until `orchestrator-ctl resume --override-budget` (or `resume` with `overrideBudget`) raises the ceiling by one budget. When a task is published, every other running agent gets a `baseMoved` notice naming the new head and the files it touched, and may rebase its branch onto it; the verifier rebases from the merge base so such branches integrate cleanly (`health.baseMovedNotice` turns the notice off).
|
|
120
|
+
|
|
121
|
+
Graph mutation. Milestones hold no work, so they never block a reopen: reopening a done task resets every done or submitted milestone downstream (`milestone.reset`), which is reviewed again at its next version once the revised work is published, and dependency edits to such milestones are compatible reloads that reset them the same way. A reopen whose target has started task dependents is refused with the blockers; `orchestrator-ctl reopen <task> --cascade` (or `reopen` with `cascade: true`) records a plan, returns it as `reset: [{ id, from }]`, and the next tick kills running dependents, drops queued verification, and returns done dependents and the target to unstarted with budgets reset. Nothing on the integration branch is reverted: each relaunch is briefed with a revision context naming the prior launch, branch, published commit, and report, and publishes a further trailer commit. Agents may put structured `proposals` (title, description, acceptance criteria, optional deps, scope, labels) in their submit report; they are held pending (`proposals` in status and the digest) until `accept-proposal <id> [--node-id <tracker key>]` appends a task node depending on the proposer, joining its gated milestone when `proposals.joinMilestone` is true (default), or `reject-proposal <id> --note`. A compatible reload that edits a running task's payload sends its agent a `payloadChanged` inbox notice (`graph.payloadChangedNotice`, default true); the pinned brief stays and the next launch uses the new payload.
|
|
122
|
+
|
|
123
|
+
## Contents
|
|
124
|
+
|
|
125
|
+
### Requirements
|
|
126
|
+
|
|
127
|
+
Current specs in `openspec/specs/`, synced from the archived changes `add-orchestrator-v1`, `add-session-operator`, `add-tracker-sync`, `add-health-monitoring`, `add-graph-mutation`, `add-pull-runner`, `add-scheduling-policy`, `replace-sqlite-driver`, `add-daemon-lifecycle`, `add-toolchain-discovery`, and `add-model-selection`:
|
|
128
|
+
|
|
129
|
+
- Capability specs
|
|
130
|
+
- [task-graph](openspec/specs/task-graph/spec.md): loading, validation, external nodes, readiness, milestones, stall detection, revisions, reload compatibility, revocability through milestones and the cascade plan, critical path length
|
|
131
|
+
- [task-lifecycle](openspec/specs/task-lifecycle/spec.md): states, transitions by actor, admission, counters, leases, retry, reopen guard, cascade reopen, milestone reset and review, cancellation
|
|
132
|
+
- [agent-protocol](openspec/specs/agent-protocol/spec.md): the brief, the API, deduplication, sealing, no-op and acceptance checks, blocking and resume, fallback, fencing, MCP façade, brief validation, the check command and tool rules in the brief
|
|
133
|
+
- [integration-pipeline](openspec/specs/integration-pipeline/spec.md): worktrees, the verifier and the hold while no check command is configured, publication, conflicts, foreign commits, milestone tags
|
|
134
|
+
- [crash-recovery](openspec/specs/crash-recovery/spec.md): durable records, recovery per boundary, adoption, fencing, sweeps, crash points, invariants, store transactions
|
|
135
|
+
- [operator-control](openspec/specs/operator-control/spec.md): operator routes shared by the CLI and the MCP server, status, run directories, daemon lifecycle, idle exit, exit record and revival, session attachments, the configuration route and start arguments, event stream, review bundle, graph import, pause and resume, shutdown, logs
|
|
136
|
+
- [operator-mcp](openspec/specs/operator-mcp/spec.md): the operator MCP server a host session drives: tool set, wait, start with configuration arguments, configure, review
|
|
137
|
+
- [graph-import](openspec/specs/graph-import/spec.md): normalized issue records, mapping rules including the `model:` and `effort:` selector labels, acceptance criteria and scope extraction, priority normalisation, import outcome
|
|
138
|
+
- [host-integration](openspec/specs/host-integration/spec.md): package entrypoints, per-host wrappers, the plugin launcher and session hooks, skills and how they report the toolchain and resolve `needsConfig`, no native dependencies, quiet SQLite binding
|
|
139
|
+
- [tracker-sync](openspec/specs/tracker-sync/spec.md): the durable outbox of tracker effects, effect kinds and marker, mirroring policy, done semantics, the drainer contract
|
|
140
|
+
- [health-monitoring](openspec/specs/health-monitoring/spec.md): process liveness, progress signals, stuck detection and escalation, the diagnosis hook, launch and run budgets, usage accounting, the base-moved notice
|
|
141
|
+
- [pull-runner](openspec/specs/pull-runner/spec.md): runner modes and the claim window, claim and release with keep (the claim carrying the launch's model and effort), session attachments, worker identity and idempotent re-claim, access modes with remote reserved
|
|
142
|
+
- [scheduling-policy](openspec/specs/scheduling-policy/spec.md): the admission ordering chain and its stages, declared scopes, the overlap rule, the scope filter at admission
|
|
143
|
+
- [toolchain-discovery](openspec/specs/toolchain-discovery/spec.md): resolution order and provenance, toolchain detection and the package manager, integration branch detection and pinning, derived allowed tools, runner command composition with the allow-list, model, and effort slots, the resolved configuration record, runtime overrides and start-only keys
|
|
144
|
+
- [model-selection](openspec/specs/model-selection/spec.md): the run default worker model and effort with provenance, per-task selectors and their validation, resolution fixed at preparation, delivery to spawned agents and claimed workers, visibility to operators
|
|
145
|
+
- Archived change `add-orchestrator-v1` (the v1 runtime): [Proposal](openspec/changes/archive/2026-10-12-add-orchestrator-v1/proposal.md), [Design](openspec/changes/archive/2026-10-12-add-orchestrator-v1/design.md), [Tasks](openspec/changes/archive/2026-10-12-add-orchestrator-v1/tasks.md)
|
|
146
|
+
- Archived change `add-session-operator` (operate from a Claude Code session): [Proposal](openspec/changes/archive/2026-10-12-add-session-operator/proposal.md), [Design](openspec/changes/archive/2026-10-12-add-session-operator/design.md), [Tasks](openspec/changes/archive/2026-10-12-add-session-operator/tasks.md)
|
|
147
|
+
- Archived change `add-tracker-sync` (mirror the run to the tracker): [Proposal](openspec/changes/archive/2026-10-12-add-tracker-sync/proposal.md), [Design](openspec/changes/archive/2026-10-12-add-tracker-sync/design.md), [Tasks](openspec/changes/archive/2026-10-12-add-tracker-sync/tasks.md)
|
|
148
|
+
- Archived change `add-health-monitoring` (liveness, stuck detection, budgets): [Proposal](openspec/changes/archive/2026-10-12-add-health-monitoring/proposal.md), [Design](openspec/changes/archive/2026-10-12-add-health-monitoring/design.md), [Tasks](openspec/changes/archive/2026-10-12-add-health-monitoring/tasks.md)
|
|
149
|
+
- Archived change `add-graph-mutation` (transparent milestones, cascade reopen, proposals): [Proposal](openspec/changes/archive/2026-10-12-add-graph-mutation/proposal.md), [Design](openspec/changes/archive/2026-10-12-add-graph-mutation/design.md), [Tasks](openspec/changes/archive/2026-10-12-add-graph-mutation/tasks.md)
|
|
150
|
+
- Archived change `add-pull-runner` (workers claim launches; the work skill and workflow): [Proposal](openspec/changes/archive/2026-10-13-add-pull-runner/proposal.md), [Design](openspec/changes/archive/2026-10-13-add-pull-runner/design.md), [Tasks](openspec/changes/archive/2026-10-13-add-pull-runner/tasks.md)
|
|
151
|
+
- Archived change `add-scheduling-policy` (ordering chain, scopes, deferrals): [Proposal](openspec/changes/archive/2026-10-13-add-scheduling-policy/proposal.md), [Design](openspec/changes/archive/2026-10-13-add-scheduling-policy/design.md), [Tasks](openspec/changes/archive/2026-10-13-add-scheduling-policy/tasks.md)
|
|
152
|
+
- Archived change `replace-sqlite-driver` (Node's built-in SQLite, no native dependency): [Proposal](openspec/changes/archive/2026-10-13-replace-sqlite-driver/proposal.md), [Design](openspec/changes/archive/2026-10-13-replace-sqlite-driver/design.md), [Tasks](openspec/changes/archive/2026-10-13-replace-sqlite-driver/tasks.md)
|
|
153
|
+
- Archived change `add-daemon-lifecycle` (visible session subagents by default, idle exit and revival, session attachments, kept release, self-contained plugin, hooks): [Proposal](openspec/changes/archive/2026-10-13-add-daemon-lifecycle/proposal.md), [Design](openspec/changes/archive/2026-10-13-add-daemon-lifecycle/design.md), [Tasks](openspec/changes/archive/2026-10-13-add-daemon-lifecycle/tasks.md)
|
|
154
|
+
- Archived change `add-toolchain-discovery` (the check command, integration branch, and spawned agents' allowed tools detected from the repository with provenance, durable overrides through `configure` and `--set`, no `orchestrator.yaml` required): [Proposal](openspec/changes/archive/2026-10-13-add-toolchain-discovery/proposal.md), [Design](openspec/changes/archive/2026-10-13-add-toolchain-discovery/design.md), [Tasks](openspec/changes/archive/2026-10-13-add-toolchain-discovery/tasks.md)
|
|
155
|
+
|
|
156
|
+
- Archived change `add-model-selection` (workers run `claude-opus-5-5` at `medium` by default; a task names its own model or effort by payload or by `model:`/`effort:` labels; the choice is fixed per launch and carried to spawned agents and session workers): [Proposal](openspec/changes/archive/2026-10-17-add-model-selection/proposal.md), [Design](openspec/changes/archive/2026-10-17-add-model-selection/design.md), [Tasks](openspec/changes/archive/2026-10-17-add-model-selection/tasks.md)
|
|
157
|
+
|
|
158
|
+
No change is active. The next planned piece of work, not yet proposed, is the usage and log loss when a launch is reaped on report.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent API schemas. The contract is the operations and their semantics; the
|
|
3
|
+
* transport is loopback HTTP (server.ts) with an MCP façade (mcp-shim.ts).
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export { failBodySchema, questionSchema, submitBodySchema } from '../loop/orchestrator.js';
|
|
7
|
+
export const AGENT_OPS = ['heartbeat', 'report', 'block', 'inbox', 'log'];
|
|
8
|
+
export const fallbackFileSchema = z.object({
|
|
9
|
+
launchId: z.string(),
|
|
10
|
+
seq: z.number().int().nonnegative(),
|
|
11
|
+
kind: z.enum(['submit', 'fail', 'block']),
|
|
12
|
+
body: z.record(z.unknown()),
|
|
13
|
+
});
|
|
14
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/agent/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3F,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAU,CAAC;AAEnF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACnC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;CAC5B,CAAC,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP façade: a stdio MCP server exposing the five agent operations as tools
|
|
4
|
+
* and forwarding them to the daemon's HTTP API with the launch token from the
|
|
5
|
+
* environment. Retries through daemon restarts for the retry window.
|
|
6
|
+
*
|
|
7
|
+
* Env: ORCH_API, ORCH_LAUNCH_ID, ORCH_TOKEN, ORCH_RETRY_WINDOW_MS (default 120000).
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { isEntrypoint } from '../entry.js';
|
|
11
|
+
export function envFromProcess(env = process.env) {
|
|
12
|
+
const api = env['ORCH_API'];
|
|
13
|
+
const launchId = env['ORCH_LAUNCH_ID'];
|
|
14
|
+
const token = env['ORCH_TOKEN'];
|
|
15
|
+
if (!api || !launchId || !token)
|
|
16
|
+
throw new Error('ORCH_API, ORCH_LAUNCH_ID and ORCH_TOKEN are required');
|
|
17
|
+
return { api, launchId, token, retryWindowMs: Number(env['ORCH_RETRY_WINDOW_MS'] ?? 120_000) };
|
|
18
|
+
}
|
|
19
|
+
/** Forward one operation, retrying connection failures with backoff for the retry window. */
|
|
20
|
+
export async function forward(env, op, body, method = 'POST') {
|
|
21
|
+
const url = `${env.api}/v1/launches/${env.launchId}/${op}`;
|
|
22
|
+
const deadline = Date.now() + env.retryWindowMs;
|
|
23
|
+
let delay = 100;
|
|
24
|
+
for (;;) {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(url, {
|
|
27
|
+
method,
|
|
28
|
+
headers: { authorization: `Bearer ${env.token}`, 'content-type': 'application/json' },
|
|
29
|
+
body: method === 'POST' ? JSON.stringify(body) : undefined,
|
|
30
|
+
});
|
|
31
|
+
return { status: res.status, body: await res.json() };
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (Date.now() + delay > deadline)
|
|
35
|
+
throw err;
|
|
36
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
37
|
+
delay = Math.min(delay * 2, 2000);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export const TOOL_NAMES = ['heartbeat', 'report', 'block', 'inbox', 'log'];
|
|
42
|
+
export async function startShim(env) {
|
|
43
|
+
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
44
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
45
|
+
const server = new McpServer({ name: 'orchestrator-agent-api', version: '0.2.0' });
|
|
46
|
+
const text = (r) => ({
|
|
47
|
+
content: [{ type: 'text', text: JSON.stringify({ status: r.status, ...(typeof r.body === 'object' && r.body ? r.body : { body: r.body }) }) }],
|
|
48
|
+
isError: r.status >= 400,
|
|
49
|
+
});
|
|
50
|
+
server.registerTool('heartbeat', { description: 'Renew the lease and report progress; a changed progress text counts as progress for stuck detection.', inputSchema: { progress: z.string().optional() } }, async (args) => text(await forward(env, 'heartbeat', args)));
|
|
51
|
+
server.registerTool('report', {
|
|
52
|
+
description: 'Final report. kind=submit requires a committed, clean worktree; kind=fail gives up. seq must increase per call.',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
seq: z.number().int().nonnegative(),
|
|
55
|
+
kind: z.enum(['submit', 'fail']),
|
|
56
|
+
summary: z.string().optional(),
|
|
57
|
+
interfaces: z.array(z.string()).optional(),
|
|
58
|
+
decisions: z.array(z.string()).optional(),
|
|
59
|
+
deviations: z.array(z.string()).optional(),
|
|
60
|
+
followUps: z.array(z.string()).optional(),
|
|
61
|
+
tests: z.array(z.string()).optional(),
|
|
62
|
+
acceptance: z.array(z.object({ criterion: z.string(), evidence: z.string() })).optional(),
|
|
63
|
+
noChanges: z.object({ justification: z.string() }).optional(),
|
|
64
|
+
proposals: z
|
|
65
|
+
.array(z.object({ title: z.string(), description: z.string(), acceptanceCriteria: z.array(z.string()), deps: z.array(z.string()).optional(), scope: z.array(z.string()).optional(), labels: z.array(z.string()).optional() }))
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Follow-up tasks you discovered; a human accepts them into the graph. followUps stays free text.'),
|
|
68
|
+
tried: z.string().optional(),
|
|
69
|
+
why: z.string().optional(),
|
|
70
|
+
whatWouldUnblock: z.string().optional(),
|
|
71
|
+
},
|
|
72
|
+
}, async (args) => text(await forward(env, 'report', args)));
|
|
73
|
+
server.registerTool('block', {
|
|
74
|
+
description: 'Ask a human a question and wait for the answer. Returns {answer} or {timeout:true}.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
seq: z.number().int().nonnegative(),
|
|
77
|
+
kind: z.enum(['needsDecision', 'needsAccess', 'impossible', 'dependencyCancelled', 'outOfScope', 'insufficientSpec', 'milestoneRejected']),
|
|
78
|
+
message: z.string(),
|
|
79
|
+
options: z.array(z.string()).optional(),
|
|
80
|
+
},
|
|
81
|
+
}, async (args) => text(await forward({ ...env, retryWindowMs: Math.max(env.retryWindowMs, 24 * 3600_000) }, 'block', args)));
|
|
82
|
+
server.registerTool('inbox', { description: 'Fetch pending notices from the orchestrator: stuck (no progress observed), nudge (a message from the operator), baseMoved (the integration branch advanced; head and files). Check after long steps.', inputSchema: {} }, async () => text(await forward(env, 'inbox', {}, 'GET')));
|
|
83
|
+
server.registerTool('log', { description: 'Append a log line to the launch log.', inputSchema: { text: z.string(), seq: z.number().int().optional() } }, async (args) => text(await forward(env, 'log', args)));
|
|
84
|
+
await server.connect(new StdioServerTransport());
|
|
85
|
+
}
|
|
86
|
+
export const USAGE = `orchestrator-mcp-shim: MCP façade for an agent launch.
|
|
87
|
+
|
|
88
|
+
orchestrator-mcp-shim
|
|
89
|
+
|
|
90
|
+
A stdio MCP server exposing heartbeat, report, block, inbox, and log, forwarded
|
|
91
|
+
to the daemon with the launch token. Env: ORCH_API, ORCH_LAUNCH_ID, ORCH_TOKEN,
|
|
92
|
+
ORCH_RETRY_WINDOW_MS. The orchestrator writes this into each launch's mcp.json.
|
|
93
|
+
`;
|
|
94
|
+
const isEntry = isEntrypoint(import.meta.url);
|
|
95
|
+
if (isEntry && (process.argv.includes('--help') || process.argv.includes('-h'))) {
|
|
96
|
+
process.stdout.write(USAGE);
|
|
97
|
+
}
|
|
98
|
+
else if (isEntry) {
|
|
99
|
+
startShim(envFromProcess()).catch((err) => {
|
|
100
|
+
process.stderr.write(`${String(err)}\n`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=mcp-shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-shim.js","sourceRoot":"","sources":["../../../src/agent/mcp-shim.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Criteria are compared after dropping markdown code spans and emphasis, a
|
|
3
|
+
* leading list marker such as the brief's "3.", and trailing punctuation,
|
|
4
|
+
* collapsing whitespace, and ignoring case, so an agent that quotes a criterion
|
|
5
|
+
* loosely still covers it. See specs/agent-protocol "Acceptance evidence".
|
|
6
|
+
*/
|
|
7
|
+
export function normaliseCriterion(text) {
|
|
8
|
+
return text
|
|
9
|
+
.replace(/[`*_~]/g, '')
|
|
10
|
+
.replace(/\s+/g, ' ')
|
|
11
|
+
.trim()
|
|
12
|
+
.replace(/^(?:\d+[.)]|[-+])\s*/, '')
|
|
13
|
+
.replace(/[.;:,]+$/, '')
|
|
14
|
+
.toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
export function checkSubmission(input) {
|
|
17
|
+
if (input.sealedSha === input.baseSha && !input.noChanges) {
|
|
18
|
+
return { ok: false, reason: 'emptySubmission', message: 'no commits ahead of base and no noChanges declaration' };
|
|
19
|
+
}
|
|
20
|
+
// A criterion is covered by its normalised text or by its 1-based position in the brief's list.
|
|
21
|
+
const covered = new Set(input.covered.map(normaliseCriterion));
|
|
22
|
+
const uncovered = input.criteria.filter((c, i) => !covered.has(normaliseCriterion(c)) && !covered.has(String(i + 1)));
|
|
23
|
+
if (uncovered.length) {
|
|
24
|
+
return { ok: false, reason: 'missingAcceptanceEvidence', message: `no evidence for: ${uncovered.join('; ')}`, uncovered };
|
|
25
|
+
}
|
|
26
|
+
return { ok: true };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=seal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seal.js","sourceRoot":"","sources":["../../../src/agent/seal.ts"],"names":[],"mappings":"AAiBA;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI;SACR,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;SACtB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE;SACN,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC;SACnC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,WAAW,EAAE,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QAC1D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,uDAAuD,EAAE,CAAC;IACpH,CAAC;IACD,gGAAgG;IAChG,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtH,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,OAAO,EAAE,oBAAoB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC;IAC5H,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopback HTTP server for agents and operators.
|
|
3
|
+
* POST /v1/launches/:id/{heartbeat,report,block,log}
|
|
4
|
+
* GET /v1/launches/:id/inbox
|
|
5
|
+
* Operator routes are registered by operator/routes.ts.
|
|
6
|
+
*/
|
|
7
|
+
import http from 'node:http';
|
|
8
|
+
export class ApiServer {
|
|
9
|
+
orch;
|
|
10
|
+
server;
|
|
11
|
+
routes = [];
|
|
12
|
+
port = 0;
|
|
13
|
+
constructor(orch) {
|
|
14
|
+
this.orch = orch;
|
|
15
|
+
this.server = http.createServer((req, res) => void this.dispatch(req, res));
|
|
16
|
+
this.server.keepAliveTimeout = 5_000;
|
|
17
|
+
this.addRoute('POST', /^\/v1\/launches\/([^/]+)\/(heartbeat|report|block|log)$/, async (req, body, [launchId, op]) => {
|
|
18
|
+
const token = bearer(req);
|
|
19
|
+
return this.orch.agentCall(launchId, token, op, body);
|
|
20
|
+
});
|
|
21
|
+
this.addRoute('GET', /^\/v1\/launches\/([^/]+)\/inbox$/, async (req, _body, [launchId]) => {
|
|
22
|
+
return this.orch.agentCall(launchId, bearer(req), 'inbox', {});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
addRoute(method, pattern, handler) {
|
|
26
|
+
this.routes.push({ method, pattern, handler });
|
|
27
|
+
}
|
|
28
|
+
async listen(port = 0) {
|
|
29
|
+
await new Promise((resolve, reject) => {
|
|
30
|
+
const onError = (err) => reject(err);
|
|
31
|
+
this.server.once('error', onError);
|
|
32
|
+
this.server.listen(port, '127.0.0.1', () => {
|
|
33
|
+
this.server.off('error', onError);
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
this.port = this.server.address().port;
|
|
38
|
+
const url = `http://127.0.0.1:${this.port}`;
|
|
39
|
+
this.orch.apiBaseUrl = url;
|
|
40
|
+
return url;
|
|
41
|
+
}
|
|
42
|
+
async close() {
|
|
43
|
+
await new Promise((resolve) => this.server.close(() => resolve()));
|
|
44
|
+
this.server.closeAllConnections();
|
|
45
|
+
}
|
|
46
|
+
async dispatch(req, res) {
|
|
47
|
+
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
48
|
+
for (const r of this.routes) {
|
|
49
|
+
if (r.method !== req.method)
|
|
50
|
+
continue;
|
|
51
|
+
const m = r.pattern.exec(url.pathname);
|
|
52
|
+
if (!m)
|
|
53
|
+
continue;
|
|
54
|
+
// A probe is a liveness check that must not count as activity. See specs/operator-control "Idle exit".
|
|
55
|
+
const probe = req.method === 'GET' && url.pathname === '/v1/status' && url.searchParams.get('probe') === '1';
|
|
56
|
+
const attachment = req.headers['x-orch-attachment'];
|
|
57
|
+
const attachmentId = typeof attachment === 'string' && attachment ? attachment : undefined;
|
|
58
|
+
this.orch.requestBegan();
|
|
59
|
+
try {
|
|
60
|
+
const body = req.method === 'POST' || req.method === 'PUT' ? await readJson(req) : Object.fromEntries(url.searchParams.entries());
|
|
61
|
+
const out = await r.handler(req, body, m.slice(1));
|
|
62
|
+
// The operation stands; the caller learns its attachment is unknown and registers again. See specs/operator-control "Session attachments".
|
|
63
|
+
const headers = attachmentId && !this.orch.isLiveAttachment(attachmentId) ? { 'x-orch-attachment': 'unknown' } : undefined;
|
|
64
|
+
send(res, out.status, out.body, headers);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
send(res, 500, { error: String(err) });
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
this.orch.requestEnded(probe);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
send(res, 404, { error: 'not found' });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function bearer(req) {
|
|
78
|
+
const h = req.headers.authorization ?? '';
|
|
79
|
+
return h.startsWith('Bearer ') ? h.slice(7).trim() : '';
|
|
80
|
+
}
|
|
81
|
+
function send(res, status, body, headers = {}) {
|
|
82
|
+
const text = JSON.stringify(body ?? {});
|
|
83
|
+
res.writeHead(status, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(text), ...headers });
|
|
84
|
+
res.end(text);
|
|
85
|
+
}
|
|
86
|
+
async function readJson(req) {
|
|
87
|
+
const chunks = [];
|
|
88
|
+
for await (const c of req)
|
|
89
|
+
chunks.push(c);
|
|
90
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
91
|
+
if (!text.trim())
|
|
92
|
+
return {};
|
|
93
|
+
const parsed = JSON.parse(text);
|
|
94
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/agent/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export function acceptanceCriteriaOf(payload) {
|
|
2
|
+
const raw = payload['acceptanceCriteria'];
|
|
3
|
+
if (Array.isArray(raw))
|
|
4
|
+
return raw.map(String);
|
|
5
|
+
if (typeof raw === 'string' && raw.trim())
|
|
6
|
+
return raw.split('\n').map((s) => s.replace(/^[-*]\s*/, '').trim()).filter(Boolean);
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
export function composeBrief(input, opts) {
|
|
10
|
+
const { launch, node } = input;
|
|
11
|
+
const criteria = acceptanceCriteriaOf(node.payload);
|
|
12
|
+
const json = {
|
|
13
|
+
launchId: launch.launchId,
|
|
14
|
+
taskId: node.id,
|
|
15
|
+
token: launch.token,
|
|
16
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
17
|
+
worktree: launch.worktree,
|
|
18
|
+
branch: launch.branch,
|
|
19
|
+
baseSha: launch.baseSha,
|
|
20
|
+
graphRev: launch.graphRev,
|
|
21
|
+
integrationBranch: input.integrationBranch,
|
|
22
|
+
checkCommand: input.checkCommand,
|
|
23
|
+
allowedTools: input.allowedTools,
|
|
24
|
+
retryWindowMs: input.retryWindowMs,
|
|
25
|
+
fallbackPath: opts.fallbackPath,
|
|
26
|
+
attempt: launch.attempt,
|
|
27
|
+
launchNo: launch.launchNo,
|
|
28
|
+
task: node.payload,
|
|
29
|
+
acceptanceCriteria: criteria,
|
|
30
|
+
dependencies: input.depReports.map((d) => ({
|
|
31
|
+
taskId: d.taskId,
|
|
32
|
+
summary: d.report ? String(d.report.body['summary'] ?? '') : undefined,
|
|
33
|
+
report: d.report?.body,
|
|
34
|
+
cancelled: d.cancelled,
|
|
35
|
+
external: d.external,
|
|
36
|
+
})),
|
|
37
|
+
context: input.carry,
|
|
38
|
+
};
|
|
39
|
+
const title = String(node.payload['title'] ?? node.id);
|
|
40
|
+
const description = String(node.payload['description'] ?? '');
|
|
41
|
+
const lines = [];
|
|
42
|
+
lines.push(`# Task ${node.id}: ${title}`, '');
|
|
43
|
+
if (description)
|
|
44
|
+
lines.push(description, '');
|
|
45
|
+
lines.push('## Acceptance criteria', '');
|
|
46
|
+
if (criteria.length)
|
|
47
|
+
criteria.forEach((c, i) => lines.push(`${i + 1}. ${c}`));
|
|
48
|
+
else
|
|
49
|
+
lines.push('- (none declared)');
|
|
50
|
+
lines.push('', '## Definition of done', '');
|
|
51
|
+
lines.push(`- Commit all work on branch \`${launch.branch}\` in this worktree. Uncommitted or untracked files make your submission be rejected with 409 and the file list; commit and resubmit.`, `- Never merge into, push to, or check out \`${input.integrationBranch}\`. You may rebase your launch branch onto it when a baseMoved notice says it advanced; the orchestrator integrates your branch after verification.`, input.checkCommand
|
|
52
|
+
? `- Run the check command before submitting: \`${input.checkCommand}\`.`
|
|
53
|
+
: '- No check command is configured for this repository yet; the operator is being asked. Run the project\'s documented test suite before submitting.', '- If the task needs no changes, say so explicitly with `noChanges: { justification }` in your report.', '- Your report must give evidence for every acceptance criterion above: one `acceptance` entry per criterion, whose `criterion` quotes the criterion text (markdown and case do not matter) or gives its number.', '- Terminate as soon as your report is accepted.', '');
|
|
54
|
+
lines.push('## Workspace', '');
|
|
55
|
+
lines.push(`- Worktree: \`${launch.worktree}\``, `- Branch: \`${launch.branch}\``, `- Base commit: \`${launch.baseSha}\``, `- Integration branch: \`${input.integrationBranch}\``);
|
|
56
|
+
if (input.allowedTools?.length) {
|
|
57
|
+
const bash = input.allowedTools.filter((r) => r.startsWith('Bash('));
|
|
58
|
+
lines.push(`- If the orchestrator spawned you, these Bash rules are in force: ${bash.map((r) => `\`${r}\``).join(', ')}. A worker holding a claimed launch runs under its own permissions instead.`, '- Run commands from the worktree root without `cd`: every part of a compound command must match a rule on its own. When you need a command these rules do not cover, ask with a `needsAccess` question naming the command rather than retrying it.');
|
|
59
|
+
}
|
|
60
|
+
lines.push('- Notices may arrive in your inbox (the `inbox` operation): `stuck` when no progress has been observed for a while, `nudge` with a message from the operator, `baseMoved` with the new integration `head` and the `files` that changed, and `payloadChanged` when your task\'s description was edited (re-read the brief file). Check the inbox after long steps.', '');
|
|
61
|
+
lines.push('## Dependencies', '');
|
|
62
|
+
if (!input.depReports.length)
|
|
63
|
+
lines.push('- none');
|
|
64
|
+
for (const d of input.depReports) {
|
|
65
|
+
if (d.external) {
|
|
66
|
+
lines.push(`- **${d.taskId}**${d.external.title ? ` (${d.external.title})` : ''} is outside this run and is ${d.external.done ? 'done' : 'not done'}. Its work was not produced by an agent here; look at the repository for what it delivered.`);
|
|
67
|
+
}
|
|
68
|
+
else if (d.cancelled) {
|
|
69
|
+
lines.push(`- **${d.taskId}** was cancelled: ${d.cancelled.reason}. Its work does not exist; plan accordingly.`);
|
|
70
|
+
}
|
|
71
|
+
else if (d.report) {
|
|
72
|
+
const b = d.report.body;
|
|
73
|
+
lines.push(`- **${d.taskId}**: ${String(b['summary'] ?? '')}`);
|
|
74
|
+
for (const key of ['interfaces', 'decisions', 'deviations']) {
|
|
75
|
+
const arr = b[key];
|
|
76
|
+
if (Array.isArray(arr) && arr.length)
|
|
77
|
+
lines.push(` - ${key}: ${arr.map(String).join('; ')}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
lines.push(`- **${d.taskId}**: no completion report available; see \`git log ${input.integrationBranch}\`.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
lines.push('');
|
|
85
|
+
if (input.carry && Object.keys(input.carry).length) {
|
|
86
|
+
lines.push('## Context from previous attempts', '');
|
|
87
|
+
const c = input.carry;
|
|
88
|
+
if (c['priorFailure'])
|
|
89
|
+
lines.push(`- Previous attempt failed: ${JSON.stringify(c['priorFailure'])}`);
|
|
90
|
+
if (c['verifierOutput'])
|
|
91
|
+
lines.push('- Verifier output:', '', '```', String(c['verifierOutput']).slice(0, 8000), '```');
|
|
92
|
+
if (c['conflict']) {
|
|
93
|
+
const k = c['conflict'];
|
|
94
|
+
lines.push(`- Your previous branch \`${k.previousBranch ?? ''}\` conflicted on rebase in: ${(k.files ?? []).join(', ')}. Tasks published since your base: ${(k.since ?? []).join(', ') || 'none'}. Rebase that branch or start over from the new base.`);
|
|
95
|
+
}
|
|
96
|
+
if (c['interrupted']) {
|
|
97
|
+
const k = c['interrupted'];
|
|
98
|
+
lines.push(`- Your previous launch \`${k.priorLaunchId}\` was interrupted (${k.reason}); this is interruption ${k.interruptions} for this task. Its branch \`${k.priorBranch}\` holds the work so far at \`${k.headSha}\`${k.wipCommit ? `, and commit \`${k.wipCommit}\` is a work-in-progress commit of changes it had not committed` : ''}. Continue from that branch by merging or cherry-picking it onto your base, or inspect it and start over${k.wipCommit ? '; squash the work-in-progress commit before submitting' : ''}.`);
|
|
99
|
+
}
|
|
100
|
+
if (c['question'] || c['answer'])
|
|
101
|
+
lines.push(`- Question asked earlier: ${JSON.stringify(c['question'])}`, `- Human answer: ${String(c['answer'] ?? '')}`);
|
|
102
|
+
lines.push('');
|
|
103
|
+
}
|
|
104
|
+
const revision = input.carry?.['revision'];
|
|
105
|
+
if (revision) {
|
|
106
|
+
lines.push('## Revision', '');
|
|
107
|
+
lines.push(revision.cause === 'upstreamReopened'
|
|
108
|
+
? `- This task is being revised because its upstream task \`${revision.upstream ?? ''}\` was reopened and its work may change.`
|
|
109
|
+
: '- This task was reopened for revision.');
|
|
110
|
+
lines.push(`- Your prior launch \`${revision.priorLaunchId}\` worked on branch \`${revision.priorBranch}\`${revision.priorPublishedSha
|
|
111
|
+
? ` and published commit \`${revision.priorPublishedSha}\`, which remains on \`${input.integrationBranch}\` and is contained in your base commit \`${launch.baseSha}\``
|
|
112
|
+
: ' and did not publish'}.`);
|
|
113
|
+
if (revision.priorReportSummary)
|
|
114
|
+
lines.push(`- Prior report: ${revision.priorReportSummary}`);
|
|
115
|
+
lines.push('- Revise the prior work rather than redoing it: build on what the base commit already contains, and commit the delta.', '');
|
|
116
|
+
}
|
|
117
|
+
lines.push('## Reporting', '');
|
|
118
|
+
lines.push(`- API: \`${input.apiBaseUrl}/v1/launches/${launch.launchId}/...\` with header \`Authorization: Bearer <token from brief.json>\`.`, '- Operations: heartbeat, report (kind submit|fail, with seq), block (a question; waits for the answer), inbox, log.', `- If the API fails for more than ${Math.round(input.retryWindowMs / 1000)}s, write your report to \`${opts.fallbackPath}\` and exit.`, '- If any call returns 401, stop immediately: your launch has been revoked.', '');
|
|
119
|
+
return { markdown: lines.join('\n'), json };
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../src/brief/compose.ts"],"names":[],"mappings":"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"}
|