agent-conveyor 0.1.12 → 0.1.13
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 +25 -6
- package/dist/cli/typescript-runtime.js +44 -1
- package/dist/cli/typescript-runtime.js.map +1 -1
- package/dist/runtime/app-autonomy.d.ts +11 -0
- package/dist/runtime/app-autonomy.js +103 -0
- package/dist/runtime/app-autonomy.js.map +1 -1
- package/docs/manager-recipes.md +5 -0
- package/package.json +1 -1
- package/skills/manage-codex-workers/SKILL.md +17 -2
package/README.md
CHANGED
|
@@ -174,7 +174,10 @@ thread identity through `--worker-codex-app-thread-id` and
|
|
|
174
174
|
deliver the generated `worker_handoff` bootstrap prompt. The raw terminal
|
|
175
175
|
`conveyor` CLI does not create Codex app threads by itself; if app thread tools
|
|
176
176
|
are unavailable, open a separate Codex app worker manually and paste the
|
|
177
|
-
`worker_handoff` prompt.
|
|
177
|
+
`worker_handoff` prompt. After a worker consumes a manager instruction, its
|
|
178
|
+
completion or blocker report must go back through the generated
|
|
179
|
+
`enqueue-notify-manager` command and a bounded Dispatch watch tick; a direct
|
|
180
|
+
Codex app final answer is not a durable manager receipt.
|
|
178
181
|
|
|
179
182
|
Dispatch is core infrastructure for supervised worker/manager pairs. The
|
|
180
183
|
`pair` workflow starts a detached Dispatch watch process by default so worker
|
|
@@ -371,8 +374,12 @@ tmux attach -t codex-live-test
|
|
|
371
374
|
Codex app sessions, the JSON output also includes
|
|
372
375
|
`heartbeat_recommendations` with role-specific poll prompts; Dispatch can
|
|
373
376
|
deliver into those inboxes, but a heartbeat or operator wake-up is still
|
|
374
|
-
required to make an idle app thread poll autonomously.
|
|
375
|
-
also include
|
|
377
|
+
required to make an idle app thread poll autonomously. The worker handoff and
|
|
378
|
+
worker heartbeat prompt also include the exact durable
|
|
379
|
+
`enqueue-notify-manager` and one-iteration `dispatch --watch` commands that a
|
|
380
|
+
worker must run after completing or blocking on a consumed item. Those
|
|
381
|
+
recommendations also include `wakeup_dispatch_command` and
|
|
382
|
+
`delivery_receipt_commands` for
|
|
376
383
|
app-thread wake recovery. Use them to record sent, skipped, and blocked wake
|
|
377
384
|
outcomes after `app-wakeup-dispatch`; an app-thread send is not task
|
|
378
385
|
completion. The recommendations include a `teardown_policy`: an idle poll is
|
|
@@ -405,7 +412,8 @@ tmux attach -t codex-live-test
|
|
|
405
412
|
a matching `ready_to_send` action with `send_ready=true` and the same thread
|
|
406
413
|
id; healthy and blocked roles must be recorded as `skipped` or `blocked`.
|
|
407
414
|
- `app-autopilot start|stop|status TASK [--dispatcher-id ID]
|
|
408
|
-
[--interval SECONDS] [--watch-iterations N] [--stale-after N]
|
|
415
|
+
[--interval SECONDS] [--watch-iterations N] [--stale-after N]
|
|
416
|
+
[--quiet-after N] [--json]` —
|
|
409
417
|
Manage the pair-level app-native heartbeat policy for the active
|
|
410
418
|
manager/worker binding. `start` and `stop` write telemetry receipts and emit
|
|
411
419
|
the exact manager/worker Codex app heartbeat automation specs plus the
|
|
@@ -413,6 +421,11 @@ tmux attach -t codex-live-test
|
|
|
413
421
|
thread tools, so create/pause those heartbeat automations from a Codex app
|
|
414
422
|
operator session using the emitted specs; Conveyor remains the durable source
|
|
415
423
|
of truth through Dispatch, inboxes, wake receipts, and app heartbeat status.
|
|
424
|
+
`status` also reports `plan.quiescence`: when the loop is healthy, has no
|
|
425
|
+
`next_actions`, and both roles have produced `--quiet-after` paired
|
|
426
|
+
heartbeats since the last command or inbox-consumption receipt, it recommends
|
|
427
|
+
`stop_autopilot` so operators can quiesce blocked/no-progress loops instead
|
|
428
|
+
of repeating idle pulses.
|
|
416
429
|
- `discover [QUERY] [--all] [--limit N]` / `search [QUERY]` — Search tasks,
|
|
417
430
|
registered sessions, active bindings, and recent telemetry in one JSON result.
|
|
418
431
|
Use this for conversational setup when a manager or Codex session needs to
|
|
@@ -640,7 +653,10 @@ tmux attach -t codex-live-test
|
|
|
640
653
|
recovery; `--once` performs one pass.
|
|
641
654
|
- `enqueue-notify-manager <task> --message "..." [--correlation-id C]
|
|
642
655
|
[--required-permission P] [--idempotency-key K] [--json]` — Queue a `notify_manager` command row for
|
|
643
|
-
Dispatch to claim and deliver to the bound manager.
|
|
656
|
+
Dispatch to claim and deliver to the bound manager. Codex app/no-tmux
|
|
657
|
+
workers must use this route for completion and blocker reports after
|
|
658
|
+
consuming a manager instruction; direct app-thread final answers are local
|
|
659
|
+
text, not manager inbox receipts.
|
|
644
660
|
- `enqueue-nudge-worker <task> --message "..." [--correlation-id C]
|
|
645
661
|
[--required-permission P] [--idempotency-key K] [--json]` — Queue a `nudge_worker` command row for
|
|
646
662
|
Dispatch to claim and deliver to the bound worker. Use this dispatcher-backed
|
|
@@ -793,7 +809,10 @@ same-project `create_thread` worker plus `set_thread_title` before creating the
|
|
|
793
809
|
binding, then pass the worker thread id/title into Conveyor. Use `fork_thread`
|
|
794
810
|
only when the user explicitly asks to fork or resume this conversation. If app
|
|
795
811
|
thread tools are unavailable, create the binding anyway and paste the returned
|
|
796
|
-
`worker_handoff` prompt into a manually opened worker session.
|
|
812
|
+
`worker_handoff` prompt into a manually opened worker session. The handoff
|
|
813
|
+
requires a worker to report completion/blockers through
|
|
814
|
+
`enqueue-notify-manager` plus a bounded Dispatch watch run before treating the
|
|
815
|
+
manager as notified.
|
|
797
816
|
- `enqueue-continue-iteration TASK --loop-run RUN --requested-iteration N` —
|
|
798
817
|
Queue a manager-requested next loop pass for Dispatch. The command refuses
|
|
799
818
|
same/current iteration requests before they become pending queue rows, while
|
|
@@ -389,7 +389,7 @@ function commandHelpText(program, command) {
|
|
|
389
389
|
` ${program} dispatch --once --type nudge_worker --path /tmp/work/workerctl.db`,
|
|
390
390
|
],
|
|
391
391
|
"app-autopilot": [
|
|
392
|
-
`usage: ${program} app-autopilot start|stop|status <task> [--dispatcher-id ID] [--interval SECONDS] [--watch-iterations N] [--stale-after N] ${path} [--json]`,
|
|
392
|
+
`usage: ${program} app-autopilot start|stop|status <task> [--dispatcher-id ID] [--interval SECONDS] [--watch-iterations N] [--stale-after N] [--quiet-after N] ${path} [--json]`,
|
|
393
393
|
"",
|
|
394
394
|
"Manage the app-native heartbeat policy for a bound manager/worker pair.",
|
|
395
395
|
"The CLI records policy receipts and emits Codex app heartbeat automation specs; app-thread automation creation still happens through Codex app tools.",
|
|
@@ -528,6 +528,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
528
528
|
source: null,
|
|
529
529
|
proof: null,
|
|
530
530
|
purpose: null,
|
|
531
|
+
quietAfterCycles: 3,
|
|
531
532
|
questions: false,
|
|
532
533
|
rationale: null,
|
|
533
534
|
receiptOutput: null,
|
|
@@ -2496,6 +2497,21 @@ function parseRuntimeArgs(args, env) {
|
|
|
2496
2497
|
flags.appStaleAfterSeconds = value;
|
|
2497
2498
|
index += 1;
|
|
2498
2499
|
}
|
|
2500
|
+
else if (arg === "--quiet-after") {
|
|
2501
|
+
if (command !== "app-autopilot") {
|
|
2502
|
+
return { command, enabled, error: "Unsupported TypeScript runtime option: --quiet-after", explicit, flags, task };
|
|
2503
|
+
}
|
|
2504
|
+
const parsedValue = valueAfter(queue, index, arg);
|
|
2505
|
+
if (parsedValue.error) {
|
|
2506
|
+
return { command, enabled, error: parsedValue.error, explicit, flags, task };
|
|
2507
|
+
}
|
|
2508
|
+
const value = Number(parsedValue.value);
|
|
2509
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
2510
|
+
return { command, enabled, error: "--quiet-after must be a non-negative integer.", explicit, flags, task };
|
|
2511
|
+
}
|
|
2512
|
+
flags.quietAfterCycles = value;
|
|
2513
|
+
index += 1;
|
|
2514
|
+
}
|
|
2499
2515
|
else if (arg === "--terminal-stale-seconds") {
|
|
2500
2516
|
if (command !== "idle-check") {
|
|
2501
2517
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --terminal-stale-seconds", explicit, flags, task };
|
|
@@ -3738,6 +3754,7 @@ function runAppAutopilotCommand(parsed, options) {
|
|
|
3738
3754
|
heartbeatIntervalMinutes: 2,
|
|
3739
3755
|
heartbeatStaleSeconds: parsed.flags.appStaleAfterSeconds,
|
|
3740
3756
|
now: timestamp,
|
|
3757
|
+
quietAfterCycles: parsed.flags.quietAfterCycles,
|
|
3741
3758
|
taskName,
|
|
3742
3759
|
watchIterations: parsed.flags.watchIterations ?? 1000000,
|
|
3743
3760
|
});
|
|
@@ -3759,6 +3776,7 @@ function runAppAutopilotCommand(parsed, options) {
|
|
|
3759
3776
|
dispatcher_command: plan.control.dispatcher_command,
|
|
3760
3777
|
dispatcher_id: dispatcherId,
|
|
3761
3778
|
interval_minutes: plan.interval_minutes,
|
|
3779
|
+
quiescence: plan.quiescence,
|
|
3762
3780
|
summary: plan.summary,
|
|
3763
3781
|
},
|
|
3764
3782
|
correlation: {
|
|
@@ -3918,6 +3936,13 @@ function renderAppAutopilotText(result) {
|
|
|
3918
3936
|
`Dispatch command: ${result.plan.control.dispatcher_command}`,
|
|
3919
3937
|
`Wake dispatch: ${result.plan.control.wakeup_dispatch_command}`,
|
|
3920
3938
|
];
|
|
3939
|
+
if (result.plan.quiescence.recommended_action === "stop_autopilot") {
|
|
3940
|
+
lines.push(`Quiescence: stop recommended - ${result.plan.quiescence.reason}`);
|
|
3941
|
+
lines.push(`Stop command: ${result.plan.control.stop_command}`);
|
|
3942
|
+
}
|
|
3943
|
+
else {
|
|
3944
|
+
lines.push(`Quiescence: ${result.plan.quiescence.state} (${result.plan.quiescence.quiet_cycles}/${result.plan.quiescence.threshold_cycles} quiet cycles)`);
|
|
3945
|
+
}
|
|
3921
3946
|
if (result.receipt) {
|
|
3922
3947
|
lines.push(`Receipt: ${result.receipt.event_type} ${result.receipt.event_id}`);
|
|
3923
3948
|
}
|
|
@@ -17218,11 +17243,17 @@ function disposableWorkerHandoff(taskName, runName, dbPath) {
|
|
|
17218
17243
|
const loopClause = runName
|
|
17219
17244
|
? ` for Ralph loop run ${runName}`
|
|
17220
17245
|
: " for this disposable no-tmux binding";
|
|
17246
|
+
const notifyCommand = durableWorkerNotifyManagerCommand(taskName, dbPath);
|
|
17247
|
+
const dispatchCommand = durableWorkerNotifyDispatchCommand(dbPath);
|
|
17221
17248
|
return [
|
|
17222
17249
|
"Use the manage-codex-workers skill.",
|
|
17223
17250
|
"",
|
|
17224
17251
|
`You are the worker for task ${taskName}${loopClause}.`,
|
|
17225
17252
|
"Keep polling your Conveyor worker inbox until there are no items left or the loop reaches max_iterations. Consume the next item now, treat each consumed item as the manager's next instruction, complete the requested work, and report changed files, exact commands run, evidence, and any residual risk.",
|
|
17253
|
+
"After completing or blocking on a consumed item, send the manager a durable Conveyor notification before your final answer. A direct app-thread final answer is not a manager receipt and is not task completion.",
|
|
17254
|
+
`Run: ${notifyCommand}`,
|
|
17255
|
+
`Then run: ${dispatchCommand}`,
|
|
17256
|
+
"If either notify/dispatch command fails, include that failure as the blocker and do not claim the manager was notified.",
|
|
17226
17257
|
"",
|
|
17227
17258
|
"Because this is a pull-required Codex app/no-tmux session, autonomous operation requires a heartbeat/wake layer that repeats this worker inbox poll while the thread is idle. If no heartbeat automation is available, report the loop as manual-poll only.",
|
|
17228
17259
|
"Do not delete, pause, or disable heartbeat automation just because an inbox poll is idle; the manager or operator owns terminal loop teardown.",
|
|
@@ -17263,6 +17294,8 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17263
17294
|
const workerHeartbeatCommand = disposableAppHeartbeatCommand("worker", taskName, dbPath);
|
|
17264
17295
|
const managerInboxCommand = sessionPollCommand("manager", taskName, dbPath);
|
|
17265
17296
|
const workerInboxCommand = sessionPollCommand("worker", taskName, dbPath);
|
|
17297
|
+
const workerNotifyCommand = durableWorkerNotifyManagerCommand(taskName, dbPath);
|
|
17298
|
+
const workerNotifyDispatchCommand = durableWorkerNotifyDispatchCommand(dbPath);
|
|
17266
17299
|
const wakeupDispatchCommand = `${conveyorPollInvocation()} app-wakeup-dispatch ${shellQuote(taskName)} --path ${shellQuote(dbPath)} --json`;
|
|
17267
17300
|
const deliveryReceiptCommands = disposableDeliveryReceiptCommands(taskName, dbPath);
|
|
17268
17301
|
return {
|
|
@@ -17316,6 +17349,10 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17316
17349
|
`Run: ${workerHeartbeatCommand}`,
|
|
17317
17350
|
`If the heartbeat output asks for direct inbox polling, run: ${workerInboxCommand}`,
|
|
17318
17351
|
"If an item is consumed, execute only that single worker instruction and return exact commands, compact evidence for any completion claim, blockers/residual risk, and exactly one next recommended worker task.",
|
|
17352
|
+
"Before your final answer after any consumed item, notify the manager durably; a direct app-thread final answer is not a manager receipt and is not task completion.",
|
|
17353
|
+
`Run: ${workerNotifyCommand}`,
|
|
17354
|
+
`Then run: ${workerNotifyDispatchCommand}`,
|
|
17355
|
+
"If either notify/dispatch command fails, include that failure as the blocker and do not claim the manager was notified.",
|
|
17319
17356
|
"If no item is consumed, stop after a one-line idle receipt.",
|
|
17320
17357
|
"Do not delete, pause, or disable worker heartbeat automation after an idle poll; the manager or operator owns terminal loop teardown.",
|
|
17321
17358
|
].join("\n"),
|
|
@@ -17335,6 +17372,12 @@ function disposableDeliveryReceiptCommands(taskName, dbPath) {
|
|
|
17335
17372
|
function disposableAppHeartbeatCommand(role, taskName, dbPath) {
|
|
17336
17373
|
return `${conveyorPollInvocation()} app-heartbeat ${shellQuote(taskName)} --role ${role} --path ${shellQuote(dbPath)} --json`;
|
|
17337
17374
|
}
|
|
17375
|
+
function durableWorkerNotifyManagerCommand(taskName, dbPath) {
|
|
17376
|
+
return `${conveyorPollInvocation()} enqueue-notify-manager ${shellQuote(taskName)} --message ${shellQuote("<compact completion/blocker report with files, commands, evidence, residual risk, and next recommended worker task>")} --correlation-id ${shellQuote("<worker-result-id>")} --path ${shellQuote(dbPath)} --json`;
|
|
17377
|
+
}
|
|
17378
|
+
function durableWorkerNotifyDispatchCommand(dbPath) {
|
|
17379
|
+
return `${conveyorPollInvocation()} dispatch --watch --watch-iterations 1 --interval 2 --dispatcher-id dispatch-local --path ${shellQuote(dbPath)} --json`;
|
|
17380
|
+
}
|
|
17338
17381
|
function sessionPollCommand(role, taskName, dbPath) {
|
|
17339
17382
|
const inbox = role === "worker" ? "worker-inbox" : "manager-inbox";
|
|
17340
17383
|
const task = taskName ? shellQuote(taskName) : "<task>";
|