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 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
- # Create/reuse the managed job and submit its initial task atomically
14
- npx agents-relay job create --repo OWNER/REPO --head feat/example --base main \
15
- --id job-1 --start --task-id initial --adapter codex --provider openai \
16
- --model MODEL --output task-pr --input "Implement the objective and publish a terminal event."
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`, preserving the original submit-and-run behavior. Every
35
- executable task is now created by a completed durable planning decision. `job
36
- create --start` records `planner-0` (supplied by the orchestrator) and the
37
- initial task atomically from the caller's perspective; retrying the command is
38
- idempotent. Use `--mode autonomous` with `init` or `job create` to make
39
- reconciliation own planner progress. Autonomous creation with only an objective
40
- may create a model planner first, but it cannot materialize work until that
41
- planner succeeds. Fixed jobs never grow another planning round.
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; use --start to submit the initial task
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
- With --start, also require --adapter codex|chatgpt, --model MODEL, --output task-pr|file,
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, initialPlanning: options.initialTasks?.length ? { status: 'pending', decisionId: 'planner-0' } : undefined, createdAt: now, updatedAt: now, tasks: [] };
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
- if (options.initialTasks?.length) {
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, requireInput = false) {
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
- const start = hasArg(args, '--start');
506
- const initialTask = start ? workerTaskFromArgs({ id, title: '', executionMode: 'fixed', prNumber: 0, repository: repo, state: 'OPEN', continuation: null, createdAt: '', updatedAt: '', tasks: [] }, args, new Date().toISOString(), true) : undefined;
507
- if (start && action !== 'create')
508
- throw new Error('--start is only supported by job create');
509
- if (start) {
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
- if (start)
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'] } }; }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- `fixed` jobs execute the orchestrator-provided task set and then proceed to final review. `autonomous` jobs may ask the planner for additional task batches. Planner-produced tasks must obey this same contract, including explicit `storySize`.
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.