agents-relay 1.0.45 → 1.0.47
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 +13 -14
- package/dist/cli.js +12 -39
- package/dist/reconciler.js +2 -0
- package/package.json +1 -1
- package/skills/agents-relay/contracts/task-job-contract.md +26 -1
package/README.md
CHANGED
|
@@ -10,10 +10,10 @@ For GitHub-backed work, create or adopt the PR through Agents Relay itself. This
|
|
|
10
10
|
npx agents-relay job create --repo OWNER/REPO --head feat/example --base main \
|
|
11
11
|
--id job-1 --title "Objective" --body "PR description" --mode fixed
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
npx agents-relay
|
|
15
|
-
--
|
|
16
|
-
--
|
|
13
|
+
# The orchestrator decides the initial batch separately
|
|
14
|
+
npx agents-relay submit --repo OWNER/REPO --pr 12 --id job-1 \
|
|
15
|
+
--task-id initial --adapter codex --provider openai --model MODEL --output task-pr \
|
|
16
|
+
--input "Implement the objective and publish a terminal event."
|
|
17
17
|
|
|
18
18
|
npx agents-relay submit --repo OWNER/REPO --pr 12 --id job-1 \
|
|
19
19
|
--task-id child --adapter codex --provider openai --model MODEL --output task-pr \
|
|
@@ -31,14 +31,14 @@ npx agents-relay task update --repo OWNER/REPO --pr 12 --id job-1 --task-id chil
|
|
|
31
31
|
--capabilities model,chatgpt,browser-harness
|
|
32
32
|
~~~
|
|
33
33
|
|
|
34
|
-
Jobs default to `fixed
|
|
35
|
-
executable
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
reconciliation
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
Jobs default to `fixed`. `job create` persists only the durable job and never
|
|
35
|
+
creates executable tasks. The top-level orchestrator decides whether the user's
|
|
36
|
+
request should start immediately and, if so, submits the first task batch
|
|
37
|
+
separately. That submit path records the orchestrator planning decision used as
|
|
38
|
+
the provenance root for those tasks. A zero-task autonomous job remains idle;
|
|
39
|
+
reconciliation does not invent its first planner. After an autonomous job has
|
|
40
|
+
been explicitly started, later planning rounds may extend the work. Fixed jobs
|
|
41
|
+
never grow another planning round.
|
|
42
42
|
|
|
43
43
|
The durable status JSON and dashboard show the mode and planning provenance. An
|
|
44
44
|
autonomous runtime uses the bundled `skills/agents-relay/agents/planner.agent.md` by default; `--planner-command` is an optional override
|
|
@@ -55,8 +55,7 @@ A repository heartbeat invokes `tick` at 10-minute resolution. See
|
|
|
55
55
|
[Scheduled autonomous jobs](docs/scheduled-autonomous-jobs.md) for the exact
|
|
56
56
|
operational contract.
|
|
57
57
|
|
|
58
|
-
job create first resolves an existing open PR with the same head/base, then creates it only when needed. Re-running it is idempotent: it reuses the PR and maintains exactly one trusted agents-relay:job:v1 marker.
|
|
59
|
-
Use `--start` to create/reuse the job and submit its initial task in one invocation. Start mode requires explicit `--adapter`, `--model`, `--output`, and non-empty `--input`; the task ID defaults to `initial`. Re-running with the same task definition reuses that task and never duplicates it. Omitting `--start` preserves create-only behavior, leaving an `OPEN` job with zero tasks until a caller submits work.
|
|
58
|
+
job create first resolves an existing open PR with the same head/base, then creates it only when needed. Re-running it is idempotent: it reuses the PR and maintains exactly one trusted agents-relay:job:v1 marker. Job creation is always create-only. Task execution options belong to `submit`, and the orchestrator may submit zero, one, or multiple initial tasks according to user intent.
|
|
60
59
|
|
|
61
60
|
To adopt an existing unmanaged PR, including a PR with no comments:
|
|
62
61
|
|
package/dist/cli.js
CHANGED
|
@@ -49,7 +49,7 @@ Usage:
|
|
|
49
49
|
npx agents-relay job <create|adopt|repair> [options]
|
|
50
50
|
|
|
51
51
|
Actions:
|
|
52
|
-
create Find or create an open PR, then persist its job marker
|
|
52
|
+
create Find or create an open PR, then persist its job marker
|
|
53
53
|
adopt Attach a job marker to an existing PR
|
|
54
54
|
repair Repair duplicate markers for the same job
|
|
55
55
|
|
|
@@ -65,7 +65,6 @@ Common options:
|
|
|
65
65
|
--schedule-next-run ISO Optional first run (must match the cron expression)
|
|
66
66
|
--missed-run-policy run|skip Run each missed slot or skip to the next future slot
|
|
67
67
|
--schedule-concurrency skip|queue Skip or retain the slot while work is active
|
|
68
|
-
--start Create/reuse the job and submit its initial task
|
|
69
68
|
|
|
70
69
|
Example:
|
|
71
70
|
npx agents-relay job create --repo OWNER/REPO --head feat/example --base main --id job-1`,
|
|
@@ -73,8 +72,7 @@ Example:
|
|
|
73
72
|
|
|
74
73
|
Required: --repo OWNER/REPO, --id JOB_ID, and --head BRANCH.
|
|
75
74
|
Common: --base BRANCH (default: main), --title TEXT, --body TEXT, --mode fixed|autonomous, --no-auto-merge, --no-review.
|
|
76
|
-
|
|
77
|
-
and --input TEXT. The initial task ID defaults to 'initial'; use --task-id to override it.
|
|
75
|
+
Job creation never creates executable tasks. The orchestrator decides the initial task batch separately with submit.
|
|
78
76
|
|
|
79
77
|
Example:
|
|
80
78
|
npx agents-relay job create --repo OWNER/REPO --head feat/example --base main --id job-1 --title "Objective"`,
|
|
@@ -297,7 +295,7 @@ export async function ensureManagedGitHubJob(client, repositoryName, trustedAuth
|
|
|
297
295
|
if (!(error instanceof Error) || !error.message.includes('has no durable'))
|
|
298
296
|
throw error;
|
|
299
297
|
const now = new Date().toISOString();
|
|
300
|
-
job = { id: options.id, title: options.title || pr.title || 'Agents Relay job', objective: options.objective || pr.body || undefined, description: pr.body || undefined, priority: options.priority ?? 'P2', executionMode: options.executionMode ?? 'fixed', autoMerge: options.autoMerge ?? true, reviewEnabled: options.reviewEnabled ?? true, prNumber: pr.number, repository: repositoryName, state: 'OPEN', continuation: options.continuation ?? null,
|
|
298
|
+
job = { id: options.id, title: options.title || pr.title || 'Agents Relay job', objective: options.objective || pr.body || undefined, description: pr.body || undefined, priority: options.priority ?? 'P2', executionMode: options.executionMode ?? 'fixed', autoMerge: options.autoMerge ?? true, reviewEnabled: options.reviewEnabled ?? true, prNumber: pr.number, repository: repositoryName, state: 'OPEN', continuation: options.continuation ?? null, createdAt: now, updatedAt: now, tasks: [] };
|
|
301
299
|
}
|
|
302
300
|
if (!job.description && pr.body)
|
|
303
301
|
job.description = pr.body;
|
|
@@ -317,25 +315,7 @@ export async function ensureManagedGitHubJob(client, repositoryName, trustedAuth
|
|
|
317
315
|
job.priority = 'P2';
|
|
318
316
|
if (!job.title && pr.title)
|
|
319
317
|
job.title = pr.title;
|
|
320
|
-
|
|
321
|
-
const decisionId = job.initialPlanning?.decisionId ?? 'planner-0';
|
|
322
|
-
job.initialPlanning = { status: 'pending', decisionId };
|
|
323
|
-
job.updatedAt = new Date().toISOString();
|
|
324
|
-
await store.saveJob(job);
|
|
325
|
-
const decision = orchestratorPlanningDecision(job, options.initialTasks, decisionId, null, 0);
|
|
326
|
-
if (!(await store.load(options.id)).tasks.some(task => task.id === decision.id))
|
|
327
|
-
await store.appendTask(decision);
|
|
328
|
-
for (const original of options.initialTasks) {
|
|
329
|
-
const task = { ...original, parentTaskId: decision.id, planningDecisionId: decision.id, planningSource: 'orchestrator', planningRound: 0 };
|
|
330
|
-
if (!(await store.load(options.id)).tasks.some(item => item.id === task.id))
|
|
331
|
-
await store.appendTask(task);
|
|
332
|
-
}
|
|
333
|
-
job.initialPlanning = { status: 'complete', decisionId };
|
|
334
|
-
job.updatedAt = new Date().toISOString();
|
|
335
|
-
await store.saveJob(job);
|
|
336
|
-
}
|
|
337
|
-
else
|
|
338
|
-
await store.saveJob(job);
|
|
318
|
+
await store.saveJob(job);
|
|
339
319
|
if (job.schedule)
|
|
340
320
|
await syncScheduleLabels(client, repositoryName, job);
|
|
341
321
|
return { job: await store.load(options.id), pr };
|
|
@@ -353,7 +333,7 @@ async function appendPlannedTask(store, job, task) {
|
|
|
353
333
|
job.tasks.push(planned, decision);
|
|
354
334
|
return planned;
|
|
355
335
|
}
|
|
356
|
-
function workerTaskFromArgs(job, args, now
|
|
336
|
+
function workerTaskFromArgs(job, args, now) {
|
|
357
337
|
const model = arg(args, '--model');
|
|
358
338
|
const adapterValue = arg(args, '--adapter');
|
|
359
339
|
const adapter = (adapterValue || 'codex');
|
|
@@ -364,8 +344,6 @@ function workerTaskFromArgs(job, args, now, requireInput = false) {
|
|
|
364
344
|
throw new Error('submit adapter must be codex, chatgpt, or orchestrator');
|
|
365
345
|
if (adapter !== 'orchestrator' && !model)
|
|
366
346
|
throw new Error('submit requires --model for codex/chatgpt tasks');
|
|
367
|
-
if (requireInput && !input)
|
|
368
|
-
throw new Error('job create --start requires --input');
|
|
369
347
|
if (outputKind !== 'task-pr' && outputKind !== 'file')
|
|
370
348
|
throw new Error('submit requires --output task-pr|file');
|
|
371
349
|
if (outputKind === 'file' && !outputPath)
|
|
@@ -502,13 +480,11 @@ async function runJobCommand(action, args) {
|
|
|
502
480
|
const id = arg(args, '--id');
|
|
503
481
|
if (!id)
|
|
504
482
|
throw new Error('--id is required for managed GitHub jobs');
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (!hasArg(args, '--adapter'))
|
|
511
|
-
throw new Error('job create --start requires --adapter codex|chatgpt');
|
|
483
|
+
if (action === 'create') {
|
|
484
|
+
const taskOnlyFlags = ['--start', '--task-id', '--input', '--adapter', '--provider', '--model', '--profile', '--reasoning', '--story-size', '--project', '--agent', '--parent', '--deps', '--capabilities', '--attachments', '--max-attempts', '--timeout', '--cwd', '--chatgpt-project', '--chatgpt-tab-close-policy', '--output', '--output-path'];
|
|
485
|
+
const invalid = taskOnlyFlags.find(flag => hasArg(args, flag));
|
|
486
|
+
if (invalid)
|
|
487
|
+
throw new Error(`job create does not accept task execution option ${invalid}; create the job first, then let the orchestrator submit the initial task batch`);
|
|
512
488
|
}
|
|
513
489
|
const auth = await githubAuthContext(args);
|
|
514
490
|
const client = auth.client;
|
|
@@ -532,12 +508,9 @@ async function runJobCommand(action, args) {
|
|
|
532
508
|
prNumber,
|
|
533
509
|
head: arg(args, '--head') || undefined,
|
|
534
510
|
base: arg(args, '--base', 'main'),
|
|
535
|
-
body: arg(args, '--body') || undefined
|
|
536
|
-
initialTasks: initialTask ? [initialTask] : undefined
|
|
511
|
+
body: arg(args, '--body') || undefined
|
|
537
512
|
});
|
|
538
|
-
|
|
539
|
-
await publishWake(eventBus(args), result.job, initialTask?.id ?? 'initial', `Initial planning decision and task ${initialTask?.id ?? 'initial'} recorded`);
|
|
540
|
-
console.log(JSON.stringify({ repository: repo, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state, task: start ? arg(args, '--task-id', 'initial') : undefined }, null, 2));
|
|
513
|
+
console.log(JSON.stringify({ repository: repo, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state }, null, 2));
|
|
541
514
|
}
|
|
542
515
|
export function runtimePlanner(args) { const plannerCommand = arg([...args], '--planner-command'); const modelRuntime = arg([...args], '--codex', 'codex'); return plannerCommand ? new CommandObjectivePlanner(plannerCommand) : new AgentObjectivePlanner(modelRuntime); }
|
|
543
516
|
export function runtimeReviewer() { return { requirements: { capabilities: ['model'] } }; }
|
package/dist/reconciler.js
CHANGED
|
@@ -386,6 +386,8 @@ export class Reconciler {
|
|
|
386
386
|
if (job.initialPlanning?.status === 'pending')
|
|
387
387
|
return job;
|
|
388
388
|
const allPlanners = job.tasks.filter(task => task.kind === 'planner').sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
389
|
+
if (allPlanners.length === 0)
|
|
390
|
+
return job;
|
|
389
391
|
const slot = job.schedule?.currentRunSlot ? Date.parse(job.schedule.currentRunSlot) : Number.NaN;
|
|
390
392
|
const scopedTasks = Number.isFinite(slot) ? job.tasks.filter(task => Date.parse(task.createdAt) >= slot) : job.tasks;
|
|
391
393
|
const planners = scopedTasks.filter(task => task.kind === 'planner').sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
package/package.json
CHANGED
|
@@ -40,8 +40,33 @@ A normal task never merges a PR. After all non-superseded work gates pass, Relay
|
|
|
40
40
|
|
|
41
41
|
Final-review routing follows the same worker-selection boundary as executable work. Capability, availability, explicit reviewer constraints, and resource health are hard gates. Among eligible workers, Relay prefers a reviewer with a different worker identity from the implementation tasks; when that is unavailable it prefers a different provider/model, then falls back to the first eligible worker. The selected worker, `selectedBy` reason, and `reviewerRouting` event data are durable evidence. No provider is a universal reviewer default.
|
|
42
42
|
|
|
43
|
+
## Hotfix workflow
|
|
44
|
+
|
|
45
|
+
A hotfix is an orchestrator-owned fast path for a live blocking problem:
|
|
46
|
+
|
|
47
|
+
1. The current active orchestrator thread patches the live/runtime target directly.
|
|
48
|
+
2. It immediately reproduces the failure and verifies the fix against the live runtime.
|
|
49
|
+
3. Once verified, the same logical fix is applied to canonical `main`, tested, committed, and pushed.
|
|
50
|
+
4. The orchestrator completes the normal post-merge/publish reconciliation and cleanup.
|
|
51
|
+
|
|
52
|
+
Do not create a blocking job/PR before fixing the live runtime. Keep the hotfix minimal and do not leave the live patch as the only durable implementation.
|
|
53
|
+
|
|
54
|
+
## P0 / self job workflow
|
|
55
|
+
|
|
56
|
+
A **P0 job** (alias: **self job**) is executed directly by the current active orchestrator thread:
|
|
57
|
+
|
|
58
|
+
1. The orchestrator creates the durable job.
|
|
59
|
+
2. The orchestrator immediately creates the required orchestrator-owned task(s); do not leave the P0 job idle.
|
|
60
|
+
3. The current orchestrator thread starts executing those tasks immediately rather than delegating them to another worker.
|
|
61
|
+
4. After implementation, the orchestrator performs/reconciles review against the current head.
|
|
62
|
+
5. If review passes and merge requirements are satisfied, the orchestrator merges the job itself and completes normal post-merge reconciliation.
|
|
63
|
+
|
|
64
|
+
`job create` still only creates durable job state; the P0 workflow requires the orchestrator to follow it immediately with task creation and execution.
|
|
65
|
+
|
|
43
66
|
## Fixed and autonomous jobs
|
|
44
67
|
|
|
45
|
-
`
|
|
68
|
+
`job create` persists only the durable job. It never creates executable work. The top-level orchestrator owns the initial execution decision and, when the user intends the job to start, submits the first task batch separately. A zero-task autonomous job stays idle and reconciliation must not bootstrap its first planner.
|
|
69
|
+
|
|
70
|
+
`fixed` jobs execute the orchestrator-provided task set and then proceed to final review. Once an autonomous job has been explicitly started by an orchestrator-provided planning batch, it may ask the planner for additional task batches. Planner-produced tasks must obey this same contract, including explicit `storySize`.
|
|
46
71
|
|
|
47
72
|
A scheduled autonomous job adds cron/timezone/next-run and missed-run/concurrency policy to the trusted job marker, plus the `agents-relay:autonomous` label and one bounded time-of-day `agents-relay:next-run:*` index label. The GitHub heartbeat discovers only indexed due candidates, validates the marker's exact `nextRunAt`, claims the slot idempotently, advances the durable next run, and pulses `agents-relay:schedule-wake`. GitHub webhook delivery then wakes local Relay for planner/worker execution. While the schedule is active, each satisfied run returns the job to `WAITING` instead of entering final-review/auto-merge; older-cycle failures are historical evidence and do not block a later slot.
|