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 +1 -1
- package/dist/cli.js +2 -1
- package/dist/relayd.js +2 -2
- package/dist/store.js +24 -9
- package/package.json +19 -4
- package/.github/workflows/publish.yml +0 -91
- package/AGENTS.md +0 -16
- package/docs/agent-network.md +0 -34
- package/docs/architecture.md +0 -120
- package/docs/autonomous-objective-jobs.md +0 -121
- package/docs/example.md +0 -30
- package/docs/github-app-rate-limit.md +0 -124
- package/docs/service.md +0 -43
- package/pack.json +0 -326
- package/scripts/npm-version.mjs +0 -11
- package/src/adapters.ts +0 -231
- package/src/cli.ts +0 -324
- package/src/continuation.ts +0 -6
- package/src/dashboard.ts +0 -421
- package/src/events.ts +0 -25
- package/src/github-auth.ts +0 -35
- package/src/github-webhook.ts +0 -37
- package/src/markers.ts +0 -33
- package/src/planner.ts +0 -150
- package/src/pool.ts +0 -87
- package/src/reconciler.ts +0 -235
- package/src/registry.ts +0 -35
- package/src/relayd.ts +0 -137
- package/src/scheduler.ts +0 -27
- package/src/store.ts +0 -526
- package/src/types.ts +0 -45
- package/src/usage.ts +0 -385
- package/src/workspace.ts +0 -62
- package/test/adapters.test.js +0 -303
- package/test/autonomous.test.js +0 -119
- package/test/core.test.js +0 -363
- package/test/dashboard.test.js +0 -178
- package/test/github-auth.test.js +0 -51
- package/test/github-webhook.test.js +0 -21
- package/test/service.test.js +0 -116
- package/test/store.test.js +0 -390
- package/test/usage.test.js +0 -88
- package/test/workspace.test.js +0 -95
- package/tsconfig.json +0 -4
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
|
|
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
|
|
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
|
-
|
|
103
|
-
|
|
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!,$
|
|
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
|
|
110
|
-
const
|
|
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.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Durable async agent jobs coordinated through GitHub pull requests",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": {
|
|
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": {
|
|
13
|
-
|
|
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.
|
package/docs/agent-network.md
DELETED
|
@@ -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.
|
package/docs/architecture.md
DELETED
|
@@ -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
|
-
```
|