agents-relay 1.0.0 → 1.0.1

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 CHANGED
@@ -20,7 +20,7 @@ npx agents-relay status --repo OWNER/REPO --pr 12 --id job-1
20
20
  Jobs default to `fixed`, preserving the original submit-and-run behavior. Use
21
21
  `--mode autonomous` with `init` or `job create` to make reconciliation own
22
22
  planner progress. The durable status JSON and dashboard show the mode. An
23
- autonomous runtime needs `--planner-command`; it receives the objective/task
23
+ autonomous runtime uses the bundled `skills/agents-relay/agents/planner.agent.md` by default; `--planner-command` is an optional override
24
24
  snapshot as JSON on stdin and must return `{ "objective_status": "in_progress"|"satisfied", "assessment": "...", "next_tasks": [...] }`.
25
25
  Planner-created tasks use stable IDs and the existing parent/subtask tree, so
26
26
  retries and daemon restarts do not create duplicate work.
package/dist/cli.js CHANGED
@@ -263,7 +263,8 @@ async function runJobCommand(action, args) {
263
263
  });
264
264
  console.log(JSON.stringify({ repository: repo, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state }, null, 2));
265
265
  }
266
- export function runtime(store, args, bus) { const emit = (event) => { process.stderr.write(`${JSON.stringify(event)}\n`); }; const plannerCommand = arg(args, '--planner-command'); const modelRuntime = arg(args, '--codex', 'codex'); const planner = plannerCommand ? new CommandObjectivePlanner(plannerCommand) : new AgentObjectivePlanner(modelRuntime); return new Reconciler(store, { owner: arg(args, '--owner', `cli-${process.pid}`), maxConcurrent: Number(arg(args, '--concurrency', '4')), leaseMs: Number(arg(args, '--lease-ms', '300000')), adapters: [new ShellAdapter(), new CodexAdapter(modelRuntime), new ChatGptAdapter({ endpoint: arg(args, '--macbridge-url') || undefined, tokenFile: arg(args, '--macbridge-token-file') || undefined })], continuations: [new CodexThreadContinuation(modelRuntime), new CommandContinuation(), new WebhookContinuation()], planner, eventBus: bus, emit }); }
266
+ export function runtimePlanner(args) { const plannerCommand = arg([...args], '--planner-command'); const modelRuntime = arg([...args], '--codex', 'codex'); return plannerCommand ? new CommandObjectivePlanner(plannerCommand) : new AgentObjectivePlanner(modelRuntime); }
267
+ export function runtime(store, args, bus) { const emit = (event) => { process.stderr.write(`${JSON.stringify(event)}\n`); }; const modelRuntime = arg(args, '--codex', 'codex'); const planner = runtimePlanner(args); return new Reconciler(store, { owner: arg(args, '--owner', `cli-${process.pid}`), maxConcurrent: Number(arg(args, '--concurrency', '4')), leaseMs: Number(arg(args, '--lease-ms', '300000')), adapters: [new ShellAdapter(), new CodexAdapter(modelRuntime), new ChatGptAdapter({ endpoint: arg(args, '--macbridge-url') || undefined, tokenFile: arg(args, '--macbridge-token-file') || undefined })], continuations: [new CodexThreadContinuation(modelRuntime), new CommandContinuation(), new WebhookContinuation()], planner, eventBus: bus, emit }); }
267
268
  export async function createService(args) {
268
269
  const loaded = await storeFor(args);
269
270
  const upstream = eventBus(args);
package/dist/relayd.js CHANGED
@@ -11,7 +11,7 @@ import { RepositoryWorkerPool, aggregateManagedGitHubJobs } from './pool.js';
11
11
  import { Reconciler } from './reconciler.js';
12
12
  import { dashboardManagedJobs, GitHubStore, InMemoryStore, loadManagedGitHubPullRequestGraphql } from './store.js';
13
13
  import { githubAuthContext } from './github-auth.js';
14
- import { isEntrypoint, startService, SERVICE_DEFAULTS } from './cli.js';
14
+ import { isEntrypoint, runtimePlanner, startService, SERVICE_DEFAULTS } from './cli.js';
15
15
  import { codexAndZaiUsageRegistry } from './usage.js';
16
16
  import { defaultWorkspaceRoot, discoverWorkspaceRepositories } from './workspace.js';
17
17
  export const daemonHelp = `Agents Relay daemon runs the dashboard and worker pool.
@@ -64,7 +64,7 @@ export async function runDaemon(argv) {
64
64
  const trusted = auth.trustedAuthors;
65
65
  const concurrency = Number(value(argv, '--concurrency', '4'));
66
66
  const bus = value(argv, '--events') === 'nats' ? new NatsEventBus(value(argv, '--nats-url', 'nats://127.0.0.1:4222'), value(argv, '--subject-prefix', 'agents-relay.events.job')) : undefined;
67
- const makeReconciler = (store, maxConcurrent) => new Reconciler(store, { owner: `relayd-${process.pid}`, maxConcurrent, leaseMs: Number(value(argv, '--lease-ms', '300000')), adapters: [new ShellAdapter(), new CodexAdapter(value(argv, '--codex', 'codex')), new ChatGptAdapter({ endpoint: value(argv, '--macbridge-url') || undefined, tokenFile: value(argv, '--macbridge-token-file') || undefined })], continuations: [new CodexThreadContinuation(value(argv, '--codex', 'codex')), new CommandContinuation(), new WebhookContinuation()], eventBus: bus });
67
+ const makeReconciler = (store, maxConcurrent) => new Reconciler(store, { owner: `relayd-${process.pid}`, maxConcurrent, leaseMs: Number(value(argv, '--lease-ms', '300000')), adapters: [new ShellAdapter(), new CodexAdapter(value(argv, '--codex', 'codex')), new ChatGptAdapter({ endpoint: value(argv, '--macbridge-url') || undefined, tokenFile: value(argv, '--macbridge-token-file') || undefined })], continuations: [new CodexThreadContinuation(value(argv, '--codex', 'codex')), new CommandContinuation(), new WebhookContinuation()], planner: runtimePlanner(argv), eventBus: bus });
68
68
  const repositories = normalized.repository
69
69
  ? [normalized.repository]
70
70
  : (await discoverWorkspaceRepositories(normalized.workspaceRoot ?? defaultWorkspaceRoot())).map(item => item.repository);
package/dist/store.js CHANGED
@@ -99,20 +99,35 @@ function repositoryParts(repository) {
99
99
  }
100
100
  async function headHasNoAheadCommits(client, repository, base, head) {
101
101
  const path = `repos/${repository}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`;
102
- const comparison = await client.request(path);
103
- return Number(comparison.ahead_by) === 0;
102
+ try {
103
+ const comparison = await client.request(path);
104
+ return Number(comparison.ahead_by) === 0;
105
+ }
106
+ catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ if (/404|Not Found/i.test(message))
109
+ return true;
110
+ throw error;
111
+ }
104
112
  }
105
- async function bootstrapEmptyHeadCommit(client, repository, head) {
113
+ async function bootstrapEmptyHeadCommit(client, repository, base, head) {
106
114
  const { owner, name } = repositoryParts(repository);
107
- const data = await githubGraphql(client, 'query($owner:String!,$name:String!,$qualified:String!){repository(owner:$owner,name:$name){ref(qualifiedName:$qualified){target{... on Commit{oid tree{oid}}}}}}', { owner, name, qualified: `refs/heads/${head}` });
115
+ const data = await githubGraphql(client, 'query($owner:String!,$name:String!,$head:String!,$base:String!){repository(owner:$owner,name:$name){head:ref(qualifiedName:$head){target{... on Commit{oid tree{oid}}}} base:ref(qualifiedName:$base){target{... on Commit{oid tree{oid}}}}}}', { owner, name, head: `refs/heads/${head}`, base: `refs/heads/${base}` });
108
116
  const repo = data.repository;
109
- const ref = repo?.ref;
110
- const target = ref?.target;
117
+ const headRef = repo?.head;
118
+ const baseRef = repo?.base;
119
+ const target = (headRef?.target ?? baseRef?.target);
111
120
  const tree = target?.tree;
112
121
  const expectedHeadOid = typeof target?.oid === 'string' ? target.oid : '';
113
122
  const treeOid = typeof tree?.oid === 'string' ? tree.oid : '';
114
123
  if (!expectedHeadOid || !treeOid)
115
- throw new Error(`Unable to resolve head branch ${head}`);
124
+ throw new Error(`Unable to resolve head branch ${head} or base branch ${base}`);
125
+ if (!headRef) {
126
+ await client.request(`repos/${repository}/git/refs`, {
127
+ method: 'POST',
128
+ body: JSON.stringify({ ref: `refs/heads/${head}`, sha: expectedHeadOid }),
129
+ });
130
+ }
116
131
  const created = await client.request(`repos/${repository}/git/commits`, {
117
132
  method: 'POST',
118
133
  body: JSON.stringify({
@@ -167,7 +182,7 @@ export async function resolvePullRequest(client, repository, options) {
167
182
  if (!options.title)
168
183
  throw new Error('--title is required when creating a pull request');
169
184
  if (await headHasNoAheadCommits(client, repository, base, options.head)) {
170
- await bootstrapEmptyHeadCommit(client, repository, options.head);
185
+ await bootstrapEmptyHeadCommit(client, repository, base, options.head);
171
186
  const afterBootstrap = await find();
172
187
  if (afterBootstrap)
173
188
  return afterBootstrap;
@@ -192,7 +207,7 @@ export async function resolvePullRequest(client, repository, options) {
192
207
  return raced;
193
208
  const message = error instanceof Error ? error.message : String(error);
194
209
  if (/No commits between|no commits between/i.test(message)) {
195
- await bootstrapEmptyHeadCommit(client, repository, options.head);
210
+ await bootstrapEmptyHeadCommit(client, repository, base, options.head);
196
211
  const afterBootstrap = await find();
197
212
  if (afterBootstrap)
198
213
  return afterBootstrap;
package/package.json CHANGED
@@ -1,14 +1,29 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
- "bin": { "agents-relay": "dist/cli.js", "agents-relayd": "dist/relayd.js" },
6
+ "bin": {
7
+ "agents-relay": "dist/cli.js",
8
+ "agents-relayd": "dist/relayd.js"
9
+ },
7
10
  "scripts": {
8
11
  "build": "tsc -p tsconfig.json && chmod +x dist/cli.js dist/relayd.js",
9
12
  "test": "npm run build && node --test test/*.test.js",
10
13
  "dev": "tsx src/cli.ts"
11
14
  },
12
- "devDependencies": { "@types/node": "^22.10.2", "tsx": "^4.19.2", "typescript": "^5.7.2" },
13
- "optionalDependencies": { "nats": "^2.29.0" }
15
+ "devDependencies": {
16
+ "@types/node": "^22.10.2",
17
+ "tsx": "^4.19.2",
18
+ "typescript": "^5.7.2"
19
+ },
20
+ "optionalDependencies": {
21
+ "nats": "^2.29.0"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "skills",
26
+ "README.md",
27
+ "LICENSE"
28
+ ]
14
29
  }
@@ -1,91 +0,0 @@
1
- name: Build, test, and publish
2
-
3
- on:
4
- push:
5
- branches: [main]
6
-
7
- permissions:
8
- contents: write
9
-
10
- concurrency:
11
- group: npm-publish-main
12
- cancel-in-progress: false
13
-
14
- jobs:
15
- publish:
16
- runs-on: ubuntu-latest
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: actions/setup-node@v4
20
- with:
21
- node-version: 22
22
- registry-url: https://registry.npmjs.org
23
- - run: npm ci
24
- - run: npm test
25
- - run: chmod +x dist/cli.js dist/relayd.js
26
- - name: Select next package version
27
- id: version
28
- shell: bash
29
- run: |
30
- error_file="$(mktemp)"
31
- trap 'rm -f "$error_file"' EXIT
32
- if current="$(npm view agents-relay version --registry=https://registry.npmjs.org 2>"$error_file")"; then
33
- :
34
- elif grep -q 'E404' "$error_file"; then
35
- current=""
36
- else
37
- cat "$error_file" >&2
38
- exit 1
39
- fi
40
- for attempt in 1 2 3 4 5; do
41
- next="$(node scripts/npm-version.mjs "$current")"
42
- if npm view "agents-relay@$next" version --registry=https://registry.npmjs.org >/dev/null 2>&1; then
43
- current="$next"
44
- continue
45
- fi
46
- break
47
- done
48
- if npm view "agents-relay@$next" version --registry=https://registry.npmjs.org >/dev/null 2>&1; then
49
- echo 'Unable to find an unused npm patch version after five attempts' >&2
50
- exit 1
51
- fi
52
- package_version="$(node -p "require('./package.json').version")"
53
- if [ "$package_version" != "$next" ]; then
54
- npm version "$next" --no-git-tag-version --ignore-scripts
55
- else
56
- echo "package.json is already $next; publishing the initial version without rewriting it."
57
- fi
58
- echo "version=$next" >> "$GITHUB_OUTPUT"
59
- - name: Verify package contents
60
- run: |
61
- test -x dist/cli.js
62
- test -x dist/relayd.js
63
- npm pack --dry-run --json > pack.json
64
- node -e 'const pack = require("./pack.json")[0]; const bins = new Map(pack.files.map(file => [file.path, file.mode])); for (const file of ["dist/cli.js", "dist/relayd.js"]) { if (bins.get(file) !== 0o755) throw new Error(`${file} is not executable in the package`); }'
65
- - name: Publish package
66
- shell: bash
67
- run: |
68
- set -o pipefail
69
- publish_error="$(mktemp)"
70
- trap 'rm -f "$publish_error"' EXIT
71
- if npm publish --access public 2> >(tee "$publish_error" >&2); then
72
- exit 0
73
- fi
74
- if grep -Eq 'EPUBLISH|previously published|cannot publish over' "$publish_error" \
75
- && [ "$(npm view agents-relay version --registry=https://registry.npmjs.org 2>/dev/null || true)" = "${{ steps.version.outputs.version }}" ]; then
76
- echo "agents-relay@${{ steps.version.outputs.version }} is already published; treating this run as idempotent."
77
- exit 0
78
- fi
79
- exit 1
80
- env:
81
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
82
- - name: Tag published version
83
- shell: bash
84
- run: |
85
- tag="v${{ steps.version.outputs.version }}"
86
- if git ls-remote --exit-code --refs origin "refs/tags/$tag" >/dev/null 2>&1; then
87
- echo "$tag already exists; leaving the existing release tag in place."
88
- exit 0
89
- fi
90
- git tag "$tag"
91
- git push origin "refs/tags/$tag"
package/AGENTS.md DELETED
@@ -1,16 +0,0 @@
1
- # Working on Agents Relay
2
-
3
- Agents Relay is a generic, shareable async-agent runtime and skill.
4
-
5
- ## Principles
6
-
7
- - GitHub PR state is durable truth. Events are wake-up/observability signals, never durable truth.
8
- - Reconcile desired durable state into ephemeral worker executions.
9
- - One top-level objective maps to one PR; child tasks live inside that PR.
10
- - Agents may submit child tasks; task lineage is explicit.
11
- - Workers are replaceable and recoverable. Results and state must survive worker or daemon death.
12
- - Keep provider/model routing separate from roles and capabilities.
13
- - Prefer standard library and small dependencies; do not build a second database when GitHub already holds the durable state.
14
- - TypeScript must use explicit types and avoid `any`.
15
- - UI must remain usable without a frontend framework unless a framework becomes necessary.
16
- - Tests must cover state transitions, parser/serialization, reconciliation, and recovery-sensitive logic.
@@ -1,34 +0,0 @@
1
- # Agent Network enhancement
2
-
3
- ## Objective
4
-
5
- Evolve Agents Relay from task adapters into a minimal network of independently registered, result-owning agents while retaining GitHub PR state as durable truth and preserving shell/Codex/ChatGPT worker support.
6
-
7
- ## Scope shipped
8
-
9
- - Typed agent registration distinguishes identity, role/responsibility, boundaries, claimed capabilities, endpoint/runtime, availability, and routing metadata.
10
- - Trusted `agents-relay:agent:v1` markers persist registrations in the existing GitHub PR comment store; no second database is introduced.
11
- - `agent-register` and `agent-discover` are machine-driven CLI/core surfaces.
12
- - Discovery applies hard requirements first, then returns explainable evidence and a small policy ranking with optional exploration of unobserved agents.
13
- - Evidence models representative evaluations, historical outcomes, reliability inputs, latency, and cost without fabricating a universal score.
14
- - Documentation defines the single-orchestrator UX, durable task/lease/retry/reconciliation model, and a narrow future authenticated remote job/status contract.
15
-
16
- ## Non-goals
17
-
18
- This PR does not build a public marketplace, broad multi-owner control plane, unauthenticated HTTP endpoint, private-data/credential routing, isolation/billing/trust enforcement, or automatic final agent selection. Adapters remain execution runtimes; events remain live observability and never durable truth.
19
-
20
- ## Acceptance criteria
21
-
22
- - Existing task, scheduler, lease/recovery, continuation, dashboard, and ChatGPT adapter behavior remains green.
23
- - Agents can be registered and discovered from the same durable PR-backed store.
24
- - Discovery rejects unmet capabilities/runtime/trust/availability requirements and exposes evidence rather than an opaque score.
25
- - Registration and discovery work without a human web form.
26
- - The future remote surface is explicitly limited to authenticated create/submit and status operations, with intention-only/public jobs separated from private-data jobs.
27
-
28
- ## Validation
29
-
30
- Run `npm test`, `npm run build`, and `git diff --check`. The implementation adds registry/discovery tests and store round-trip/forgery tests while retaining the existing test suite.
31
-
32
- ## Future direction
33
-
34
- Add authenticated remote submission only when the host has a concrete identity and trust boundary. Then add durable remote task leases/heartbeats and recovery integration, richer evidence aggregation, policy-pluggable matching, and security/isolation gates for private jobs. Keep GitHub PR markers authoritative throughout.
@@ -1,120 +0,0 @@
1
- # Agents Relay v1 architecture
2
-
3
- ## Goal
4
-
5
- Provide a generic, shareable async agent job system where agents can submit durable child tasks, specialized workers execute them asynchronously, results are persisted, and the parent agent can continue when dependencies become ready.
6
-
7
- ## Core model
8
-
9
- - **GitHub pull request** — durable top-level job container and audit surface.
10
- - **Job marker comment** — durable job metadata and continuation state.
11
- - **Task marker comments** — append-friendly durable child-task records.
12
- - **Agents Relay daemon** — reconciler, scheduler, dispatcher, lease/recovery manager, continuation engine, and dashboard server.
13
- - **Agent adapters** — pluggable execution backends such as Codex and shell commands.
14
- - **Event adapter** — optional low-latency notification/wake channel; events never replace durable PR state.
15
- - **Periodic watchdog** — low-frequency recovery sweep that re-reads durable PR/job state to repair missed wakeups and recover stale execution state (30 minutes with webhook mode by default; 5 minutes without webhooks for compatibility).
16
- - **Artifact references** — large/private outputs stay outside GitHub comments; task records store references.
17
-
18
- ## Agent network slice
19
-
20
- An agent is a registered, result-owning worker/social role with a responsibility boundary. It is not the same thing as a skill (a callable capability) or an execution adapter (shell, Codex, or ChatGPT). Registration is machine-driven through the CLI/core API and is stored as a trusted `agents-relay:agent:v1` marker comment on the same PR.
21
-
22
- Registration records identity/name, role and responsibility, claimed capabilities, explicit boundaries/non-goals, endpoint/runtime, availability, and routing metadata. `agent-register` and `agent-discover` provide the smallest useful registry surface. Discovery applies hard filters first and returns observed evidence and reasons rather than a universal score. Evidence can include representative evaluations, outcomes, reliability, latency, and cost. `--explore` allows new/unobserved agents to be considered first; the orchestrator still owns the final choice.
23
-
24
- The registry does not yet execute remote agents or expose a public marketplace. A constrained ChatGPT/voice/mobile runtime should eventually use a small authenticated service surface with only `POST /jobs` (intention-only/public jobs) and `GET /jobs/:id`; credentials and private payloads must stay out of PR markers and events. Private-data/credential jobs, multi-owner trust, isolation, billing, and public discovery are future security-gated work.
25
-
26
- ## Execution modes
27
-
28
- Jobs persist `executionMode` as `fixed` or `autonomous`; missing mode in legacy
29
- markers is read as `fixed`. Fixed is the default and keeps the v1 submit flow
30
- unchanged. Autonomous jobs use the existing task records and parent hierarchy:
31
- when all currently runnable work is settled and no planner task is active,
32
- reconciliation appends one durable `kind: "planner"` task. The planner returns
33
- typed `objective_status`, `assessment`, and `next_tasks` values. Each
34
- `next_tasks` item becomes a normal child task of that planner task.
35
-
36
- Stable task IDs and append-if-same-definition behavior make planner retries
37
- idempotent. Leases, owner recovery, and completion decisions remain in the
38
- reconciler; planner output never directly marks a job complete.
39
-
40
- ## Reconciliation rule
41
-
42
- Every wake-up means only: "the durable state may have changed." The daemon reloads the PR and task records, derives desired actions, and reconciles actual execution state. It never advances a job solely because an event claimed something happened. Event-driven and periodic reconciles are single-flight within one process; a periodic sweep may create the next autonomous planner task only from durable settled state.
43
-
44
- ## State
45
-
46
- Job states:
47
-
48
- OPEN -> RUNNING -> WAITING|BLOCKED -> COMPLETED|FAILED|CANCELLED
49
-
50
- Task states:
51
-
52
- QUEUED -> READY -> RUNNING -> WAITING|BLOCKED -> SUCCEEDED|FAILED|CANCELLED
53
-
54
- A task becomes READY when all declared dependencies have succeeded and any approval gate is satisfied. Autonomous completion additionally requires a durable planner result of `satisfied`, every planner/work task to be `SUCCEEDED`, and no unresolved failure or blocked task.
55
-
56
- ## Task record
57
-
58
- Each task is one GitHub PR comment containing a human-readable summary plus a hidden `agents-relay:task:v1` JSON marker. Minimum durable fields:
59
-
60
- - task id and parent task id
61
- - dependencies
62
- - capabilities
63
- - adapter + execution input
64
- - routing decision for model-backed workers
65
- - state + attempt
66
- - lease owner and expiry
67
- - execution id/thread id
68
- - result summary and artifact references
69
- - timestamps
70
-
71
- Task submission is append-only at creation time, which avoids a single giant mutable manifest and makes agent-to-agent delegation natural.
72
-
73
- ## Execution
74
-
75
- 1. An agent submits a task to the PR.
76
- 2. A task comment is created durably.
77
- 3. The relay wakes and reloads the PR.
78
- 4. Scheduler marks dependency-satisfied tasks ready.
79
- 5. Dispatcher validates routing/capabilities and claims a lease.
80
- 6. Adapter launches the worker.
81
- 7. Worker reports progress over the optional event channel.
82
- 8. Relay persists terminal task state/result back into the task comment.
83
- 9. Relay reconciles dependents and/or resumes the parent continuation.
84
- 10. Periodic watchdog reconciliation only repairs missed wake-ups and expired leases; webhook mode uses targeted reconciliation for the normal path and cached dashboard reads do not trigger broad discovery.
85
-
86
- ## Continuations
87
-
88
- Continuations are adapters, separate from worker adapters. v1 supports Codex-thread continuation plus a generic command/webhook contract. Hosted web chat continuations may rely on their host integration; their durable state remains recoverable from the PR.
89
-
90
- ## v1 acceptance criteria
91
-
92
- 1. A reusable TypeScript package/CLI named `agents-relay`.
93
- 2. GitHub PR/comment durable store with job/task marker parsing and updates.
94
- 3. CLI to initialize a PR job, submit child tasks, inspect status, reconcile, retry/cancel tasks, and run the daemon.
95
- 4. Dependency scheduling and nested parent/child lineage.
96
- 5. Worker lease, expiry detection, recovery, retry, timeout, and cancellation semantics.
97
- 6. At least Codex and shell worker adapters behind one adapter interface.
98
- 7. Routing metadata is mandatory before a model-backed worker can launch.
99
- 8. Optional event integration for wake-ups/progress with durable-state reconciliation.
100
- 9. Parent continuation interface with Codex-thread continuation implemented.
101
- 10. Dashboard web page showing jobs, task tree/state, worker/model, dependencies, results, leases, and live events/status.
102
- 11. SSE or equivalent local live dashboard updates.
103
- 12. Tests for marker parsing, state transitions, scheduling, reconciliation/recovery, and API/dashboard data.
104
- 13. `skills/agents-relay/SKILL.md` plus supporting agent files describing how an agent submits async work, propagates lineage, consumes results, and treats events vs durable state.
105
- 14. Documentation and a runnable local dogfood example.
106
- 15. Browser-level verification of the dashboard before merge.
107
-
108
- ## Non-goals for v1
109
-
110
- - Reimplement GitHub as a database.
111
- - Build a general workflow-language/DAG editor.
112
- - Require Redis, Postgres, Temporal, or Kubernetes.
113
- - Make NATS mandatory.
114
- - Implement a hosted multi-tenant control plane.
115
- - Encode domain-specific agent roles in the core runtime.
116
- - Build a broad public agent marketplace or unauthenticated remote HTTP API.
117
-
118
- ## Dogfood
119
-
120
- This PR is the first top-level Agents Relay job. Development, review, fixes, and verification remain on this PR until the v1 acceptance criteria are met.
@@ -1,121 +0,0 @@
1
- # Autonomous Objective-Driven Jobs
2
-
3
- This document records the design, implementation flow, recovery semantics, and validation of autonomous objective-driven jobs in Agents Relay.
4
-
5
- ## Objective
6
-
7
- Existing jobs remain `fixed` by default. An `autonomous` job may grow its own durable task tree until the objective is satisfied. Planning is represented by real tasks rather than hidden generations or a second state store. GitHub-backed durable state remains authoritative.
8
-
9
- ## Design principles
10
-
11
- - `executionMode` is explicitly `fixed` or `autonomous`; legacy jobs deserialize as `fixed`.
12
- - A planner is a real durable task (`kind: planner`) in the same parent/subtask hierarchy as execution work.
13
- - Reconciliation owns liveness: it decides when another planner is required, including after daemon restart or a missed wake-up.
14
- - The planner owns reasoning: it returns `objective_status`, an assessment, and typed `next_tasks`.
15
- - The default planner is the installable `skills/agents-relay/agents/planner.agent.md` agent contract shipped with the npm package; runtime command configuration is only an override.
16
- - `in_progress` creates durable child work; `satisfied` stops growth but does not bypass normal completion gates.
17
- - Failed or blocked work prevents autonomous completion and prevents blindly creating another planning round.
18
- - Planner and generated task creation are idempotent so repeated reconciliation does not duplicate work.
19
-
20
- ## Autonomous lifecycle
21
-
22
- ```mermaid
23
- flowchart TD
24
- A[Job created] --> B{executionMode}
25
- B -->|fixed| C[Existing fixed-job scheduler]
26
- B -->|autonomous| D[Reconciliation]
27
- D --> E{Runnable execution work?}
28
- E -->|yes| F[Run durable tasks]
29
- F --> D
30
- E -->|no| G{Failed or blocked work?}
31
- G -->|yes| H[Remain open for recovery]
32
- G -->|no| I{Active planner?}
33
- I -->|yes| J[Wait for planner outcome]
34
- I -->|no| K[Create durable planner task]
35
- K --> L[Run planner]
36
- L --> M{objective_status}
37
- M -->|in_progress| N[Create typed child tasks]
38
- N --> D
39
- M -->|satisfied| O[Existing completion gates]
40
- O -->|pass| P[Eligible for normal completion]
41
- O -->|fail| H
42
- ```
43
-
44
- ## Durable hierarchy
45
-
46
- Planning causality is visible directly in the existing task tree. No wave/generation abstraction is introduced.
47
-
48
- ```mermaid
49
- graph TD
50
- J[Autonomous Job] --> P1[planner-1]
51
- P1 --> T1[implementation task]
52
- P1 --> T2[test task]
53
- P1 --> P2[planner-2]
54
- P2 --> T3[remediation task]
55
- P2 --> P3[planner-3: satisfied]
56
- ```
57
-
58
- Each planner result is persisted with its assessment and generated work. Stable generated task IDs plus durable append semantics make replay safe.
59
-
60
- ## Reconciliation and restart recovery
61
-
62
- The daemon does not depend on a planner remembering to schedule its successor. Reconciliation reconstructs the next action from durable state.
63
-
64
- ```mermaid
65
- sequenceDiagram
66
- participant W as Webhook/Startup/Watchdog
67
- participant R as Reconciler
68
- participant S as Durable Store
69
- participant P as Planner
70
- participant X as Worker
71
- W->>R: wake/reconcile
72
- R->>S: load job + task tree
73
- alt lost or expired execution lease
74
- R->>S: reclaim to READY or terminal failure
75
- end
76
- alt autonomous subtree settled
77
- R->>S: append planner task idempotently
78
- R->>P: execute planner
79
- P-->>R: in_progress + next_tasks
80
- R->>S: persist planner result + child tasks
81
- R->>X: launch READY child work
82
- else objective satisfied
83
- R->>S: persist satisfied planner result
84
- R->>R: apply normal completion gates
85
- end
86
- ```
87
-
88
- A restarted daemon can reclaim a durable planner lease owned by the previous runtime. Repeated reconciliation observes existing planner/task markers instead of creating duplicate work.
89
-
90
- ## Completion semantics
91
-
92
- Planner satisfaction is evidence about the objective, not authority to close the job. The existing job lifecycle remains authoritative. An autonomous job can stop growing only after a planner reports `satisfied`; it can complete only when the normal completion conditions also allow it. Failed or blocked tasks remain visible and prevent accidental success.
93
-
94
- ## Compatibility and surfaces
95
-
96
- Fixed jobs retain their previous scheduling behavior. Execution mode and planner state are serialized in durable markers, exposed through CLI/status data, and surfaced in the dashboard. Legacy markers without an execution mode are interpreted as `fixed`.
97
-
98
- ## Implementation sequence
99
-
100
- ```mermaid
101
- flowchart LR
102
- A[Add durable execution mode] --> B[Add planner result/task model]
103
- B --> C[Integrate planner with reconciler]
104
- C --> D[Add idempotent child creation]
105
- D --> E[Add restart recovery]
106
- E --> F[Expose CLI/status/dashboard]
107
- F --> G[Update documentation]
108
- G --> H[Build + full test suite]
109
- ```
110
-
111
- ## Validation
112
-
113
- The completed branch was validated with `npm test`, which runs the TypeScript build before the Node test suite. Final result on 2026-09-19:
114
-
115
- - TypeScript build: passed.
116
- - Tests: **108 passed, 0 failed, 0 skipped/cancelled**.
117
- - Focused coverage includes marker round-trip and legacy defaults, fixed-job compatibility, autonomous task-tree growth, stable/idempotent generated task IDs, restart planner-lease recovery, objective satisfaction, and failed/blocked completion gates.
118
-
119
- ## Follow-up
120
-
121
- GitHub API quota exhaustion prevented managed PR bootstrap during this implementation. The feature was therefore completed and validated on `feat/autonomous-objective-jobs`; managed PR/job bootstrap can be retried after quota recovery. The next P0 work is intentionally separate: GitHub App authentication and reducing residual GitHub API consumption while retaining webhook-driven targeted reconciliation.
package/docs/example.md DELETED
@@ -1,30 +0,0 @@
1
- # Local dogfood
2
-
3
- ```sh
4
- npx agents-relay init --file /tmp/agents-relay.json --id dogfood --title "Local dogfood"
5
- npx agents-relay submit --file /tmp/agents-relay.json --task-id build --input "echo build" --adapter shell
6
- npx agents-relay reconcile --file /tmp/agents-relay.json
7
- npx agents-relay status --file /tmp/agents-relay.json
8
- ```
9
-
10
- Autonomous local jobs use the same file and task tree. The mode is durable and
11
- defaults to fixed:
12
-
13
- ```sh
14
- npx agents-relay init --file /tmp/agents-relay-auto.json --id auto --title "Objective" --mode autonomous
15
- npx agents-relay reconcile --file /tmp/agents-relay-auto.json --planner-command ./planner.sh
16
- npx agents-relay status --file /tmp/agents-relay-auto.json
17
- ```
18
-
19
- `planner.sh` reads the current objective/task snapshot from stdin and prints a
20
- typed planner result. Its `next_tasks` IDs must remain stable across retries.
21
-
22
- For a nested task, submit `--parent build --deps build`; reconciliation releases it only after `build` succeeds.
23
-
24
- The same flow against the dogfood PR is:
25
-
26
- ```sh
27
- npx agents-relay init --repo lalalic/agents-relay --pr 1 --id agents-relay-dev-20260918-1302 --title "Dogfood"
28
- npx agents-relay submit --repo lalalic/agents-relay --pr 1 --id agents-relay-dev-20260918-1302 --task-id safe-shell --input "printf dogfood" --adapter shell --capabilities shell
29
- npx agents-relay reconcile --repo lalalic/agents-relay --pr 1 --id agents-relay-dev-20260918-1302
30
- ```