agent-conveyor 0.1.11 → 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 +33 -5
- package/dist/cli/typescript-runtime.js +182 -5
- package/dist/cli/typescript-runtime.js.map +1 -1
- package/dist/runtime/app-autonomy.d.ts +64 -0
- package/dist/runtime/app-autonomy.js +196 -0
- package/dist/runtime/app-autonomy.js.map +1 -1
- package/docs/manager-recipes.md +11 -0
- package/package.json +1 -1
- package/skills/manage-codex-workers/SKILL.md +24 -0
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
|
|
@@ -404,6 +411,21 @@ tmux attach -t codex-live-test
|
|
|
404
411
|
`sent` is accepted only when the referenced `app-wakeup-dispatch` receipt has
|
|
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`.
|
|
414
|
+
- `app-autopilot start|stop|status TASK [--dispatcher-id ID]
|
|
415
|
+
[--interval SECONDS] [--watch-iterations N] [--stale-after N]
|
|
416
|
+
[--quiet-after N] [--json]` —
|
|
417
|
+
Manage the pair-level app-native heartbeat policy for the active
|
|
418
|
+
manager/worker binding. `start` and `stop` write telemetry receipts and emit
|
|
419
|
+
the exact manager/worker Codex app heartbeat automation specs plus the
|
|
420
|
+
bounded Dispatch watch command. A plain shell CLI cannot call Codex app
|
|
421
|
+
thread tools, so create/pause those heartbeat automations from a Codex app
|
|
422
|
+
operator session using the emitted specs; Conveyor remains the durable source
|
|
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.
|
|
407
429
|
- `discover [QUERY] [--all] [--limit N]` / `search [QUERY]` — Search tasks,
|
|
408
430
|
registered sessions, active bindings, and recent telemetry in one JSON result.
|
|
409
431
|
Use this for conversational setup when a manager or Codex session needs to
|
|
@@ -631,7 +653,10 @@ tmux attach -t codex-live-test
|
|
|
631
653
|
recovery; `--once` performs one pass.
|
|
632
654
|
- `enqueue-notify-manager <task> --message "..." [--correlation-id C]
|
|
633
655
|
[--required-permission P] [--idempotency-key K] [--json]` — Queue a `notify_manager` command row for
|
|
634
|
-
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.
|
|
635
660
|
- `enqueue-nudge-worker <task> --message "..." [--correlation-id C]
|
|
636
661
|
[--required-permission P] [--idempotency-key K] [--json]` — Queue a `nudge_worker` command row for
|
|
637
662
|
Dispatch to claim and deliver to the bound worker. Use this dispatcher-backed
|
|
@@ -784,7 +809,10 @@ same-project `create_thread` worker plus `set_thread_title` before creating the
|
|
|
784
809
|
binding, then pass the worker thread id/title into Conveyor. Use `fork_thread`
|
|
785
810
|
only when the user explicitly asks to fork or resume this conversation. If app
|
|
786
811
|
thread tools are unavailable, create the binding anyway and paste the returned
|
|
787
|
-
`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.
|
|
788
816
|
- `enqueue-continue-iteration TASK --loop-run RUN --requested-iteration N` —
|
|
789
817
|
Queue a manager-requested next loop pass for Dispatch. The command refuses
|
|
790
818
|
same/current iteration requests before they become pending queue rows, while
|
|
@@ -5,7 +5,7 @@ import { homedir, tmpdir } from "node:os";
|
|
|
5
5
|
import { dirname, join, relative, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { taskAuditSync } from "../runtime/audit.js";
|
|
8
|
-
import { appLoopStatusSync, appWakeupDispatchPlanSync, appWakeupPlanSync, directInboxPollCommand, } from "../runtime/app-autonomy.js";
|
|
8
|
+
import { appAutopilotPlanSync, appLoopStatusSync, appWakeupDispatchPlanSync, appWakeupPlanSync, directInboxPollCommand, } from "../runtime/app-autonomy.js";
|
|
9
9
|
import { classifyBusyWait, classifyStartupOutput } from "../runtime/classify.js";
|
|
10
10
|
import { exportTaskSync } from "../runtime/export.js";
|
|
11
11
|
import { ingestSessionSync } from "../runtime/ingest.js";
|
|
@@ -134,6 +134,9 @@ export function runTypescriptRuntimeCommand(options) {
|
|
|
134
134
|
if (parsed.command === "app-wakeup-record-delivery") {
|
|
135
135
|
return runAppWakeupRecordDeliveryCommand(parsed, options);
|
|
136
136
|
}
|
|
137
|
+
if (parsed.command === "app-autopilot") {
|
|
138
|
+
return runAppAutopilotCommand(parsed, options);
|
|
139
|
+
}
|
|
137
140
|
if (parsed.command === "qa-plan") {
|
|
138
141
|
return runQaPlanCommand(parsed);
|
|
139
142
|
}
|
|
@@ -385,6 +388,17 @@ function commandHelpText(program, command) {
|
|
|
385
388
|
` ${program} enqueue-nudge-worker my-task --message "Status and evidence?" --path /tmp/work/workerctl.db`,
|
|
386
389
|
` ${program} dispatch --once --type nudge_worker --path /tmp/work/workerctl.db`,
|
|
387
390
|
],
|
|
391
|
+
"app-autopilot": [
|
|
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
|
+
"",
|
|
394
|
+
"Manage the app-native heartbeat policy for a bound manager/worker pair.",
|
|
395
|
+
"The CLI records policy receipts and emits Codex app heartbeat automation specs; app-thread automation creation still happens through Codex app tools.",
|
|
396
|
+
"",
|
|
397
|
+
"Examples:",
|
|
398
|
+
` ${program} app-autopilot start dogfood --dispatcher-id dispatch-local --path /tmp/work/workerctl.db --json`,
|
|
399
|
+
` ${program} app-autopilot status dogfood --path /tmp/work/workerctl.db`,
|
|
400
|
+
` ${program} app-autopilot stop dogfood --path /tmp/work/workerctl.db --json`,
|
|
401
|
+
],
|
|
388
402
|
pair: [
|
|
389
403
|
`usage: ${program} pair --task <task> --worker-name <worker> --manager-name <manager> [options] ${path}`,
|
|
390
404
|
"",
|
|
@@ -514,6 +528,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
514
528
|
source: null,
|
|
515
529
|
proof: null,
|
|
516
530
|
purpose: null,
|
|
531
|
+
quietAfterCycles: 3,
|
|
517
532
|
questions: false,
|
|
518
533
|
rationale: null,
|
|
519
534
|
receiptOutput: null,
|
|
@@ -1496,7 +1511,8 @@ function parseRuntimeArgs(args, env) {
|
|
|
1496
1511
|
&& command !== "app-heartbeat"
|
|
1497
1512
|
&& command !== "app-loop-status"
|
|
1498
1513
|
&& command !== "app-wakeup-plan"
|
|
1499
|
-
&& command !== "app-wakeup-dispatch"
|
|
1514
|
+
&& command !== "app-wakeup-dispatch"
|
|
1515
|
+
&& command !== "app-autopilot") {
|
|
1500
1516
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --dispatcher-id", explicit, flags, task };
|
|
1501
1517
|
}
|
|
1502
1518
|
const value = valueAfter(queue, index, arg);
|
|
@@ -2463,7 +2479,11 @@ function parseRuntimeArgs(args, env) {
|
|
|
2463
2479
|
index += 1;
|
|
2464
2480
|
}
|
|
2465
2481
|
else if (arg === "--stale-after") {
|
|
2466
|
-
if (command !== "app-heartbeat"
|
|
2482
|
+
if (command !== "app-heartbeat"
|
|
2483
|
+
&& command !== "app-loop-status"
|
|
2484
|
+
&& command !== "app-wakeup-plan"
|
|
2485
|
+
&& command !== "app-wakeup-dispatch"
|
|
2486
|
+
&& command !== "app-autopilot") {
|
|
2467
2487
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --stale-after", explicit, flags, task };
|
|
2468
2488
|
}
|
|
2469
2489
|
const parsedValue = valueAfter(queue, index, arg);
|
|
@@ -2477,6 +2497,21 @@ function parseRuntimeArgs(args, env) {
|
|
|
2477
2497
|
flags.appStaleAfterSeconds = value;
|
|
2478
2498
|
index += 1;
|
|
2479
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
|
+
}
|
|
2480
2515
|
else if (arg === "--terminal-stale-seconds") {
|
|
2481
2516
|
if (command !== "idle-check") {
|
|
2482
2517
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --terminal-stale-seconds", explicit, flags, task };
|
|
@@ -2676,7 +2711,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
2676
2711
|
index += 1;
|
|
2677
2712
|
}
|
|
2678
2713
|
else if (arg === "--interval") {
|
|
2679
|
-
if (command !== "dispatch" && command !== "session-inbox" && command !== "manager-inbox" && command !== "worker-inbox") {
|
|
2714
|
+
if (command !== "dispatch" && command !== "session-inbox" && command !== "manager-inbox" && command !== "worker-inbox" && command !== "app-autopilot") {
|
|
2680
2715
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --interval", explicit, flags, task };
|
|
2681
2716
|
}
|
|
2682
2717
|
const parsedValue = valueAfter(queue, index, arg);
|
|
@@ -2691,7 +2726,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
2691
2726
|
index += 1;
|
|
2692
2727
|
}
|
|
2693
2728
|
else if (arg === "--watch-iterations") {
|
|
2694
|
-
if (command !== "dispatch") {
|
|
2729
|
+
if (command !== "dispatch" && command !== "app-autopilot") {
|
|
2695
2730
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --watch-iterations", explicit, flags, task };
|
|
2696
2731
|
}
|
|
2697
2732
|
const parsedValue = valueAfter(queue, index, arg);
|
|
@@ -2824,6 +2859,12 @@ function parseRuntimeArgs(args, env) {
|
|
|
2824
2859
|
}
|
|
2825
2860
|
flags.subtype = arg;
|
|
2826
2861
|
}
|
|
2862
|
+
else if (command === "app-autopilot" && flags.action === null) {
|
|
2863
|
+
if (!["start", "stop", "status"].includes(arg)) {
|
|
2864
|
+
return { command, enabled, error: `Unsupported app-autopilot action: ${arg}`, explicit, flags, task };
|
|
2865
|
+
}
|
|
2866
|
+
flags.action = arg;
|
|
2867
|
+
}
|
|
2827
2868
|
else if ((command === "qa-plan" || command === "qa-run") && flags.subtype === null) {
|
|
2828
2869
|
flags.subtype = arg;
|
|
2829
2870
|
}
|
|
@@ -3693,6 +3734,90 @@ function runAppWakeupRecordDeliveryCommand(parsed, options) {
|
|
|
3693
3734
|
database.close();
|
|
3694
3735
|
}
|
|
3695
3736
|
}
|
|
3737
|
+
function runAppAutopilotCommand(parsed, options) {
|
|
3738
|
+
const action = parsed.flags.action;
|
|
3739
|
+
if (action !== "start" && action !== "stop" && action !== "status") {
|
|
3740
|
+
return errorResult("app-autopilot requires an action: start, stop, or status");
|
|
3741
|
+
}
|
|
3742
|
+
const taskName = requireTask(parsed);
|
|
3743
|
+
const database = openRuntimeDatabase(parsed, options);
|
|
3744
|
+
try {
|
|
3745
|
+
const timestamp = nowIsoSeconds(options);
|
|
3746
|
+
const dbPath = runtimeDbPath(parsed, options);
|
|
3747
|
+
const dispatcherId = parsed.flags.dispatcherId ?? "dispatch-local";
|
|
3748
|
+
const desiredState = action === "start" ? "active" : action === "stop" ? "stopped" : null;
|
|
3749
|
+
let plan = appAutopilotPlanSync(database, {
|
|
3750
|
+
dbPath,
|
|
3751
|
+
dispatchIntervalSeconds: parsed.flags.intervalSeconds,
|
|
3752
|
+
dispatcherId,
|
|
3753
|
+
desiredState,
|
|
3754
|
+
heartbeatIntervalMinutes: 2,
|
|
3755
|
+
heartbeatStaleSeconds: parsed.flags.appStaleAfterSeconds,
|
|
3756
|
+
now: timestamp,
|
|
3757
|
+
quietAfterCycles: parsed.flags.quietAfterCycles,
|
|
3758
|
+
taskName,
|
|
3759
|
+
watchIterations: parsed.flags.watchIterations ?? 1000000,
|
|
3760
|
+
});
|
|
3761
|
+
let receipt = null;
|
|
3762
|
+
if (action === "start" || action === "stop") {
|
|
3763
|
+
const eventType = action === "start" ? "app_autopilot_started" : "app_autopilot_stopped";
|
|
3764
|
+
const eventId = emitTelemetrySync(database, {
|
|
3765
|
+
actor: "operator",
|
|
3766
|
+
attributes: {
|
|
3767
|
+
automation_specs: plan.automation_specs.map((spec) => ({
|
|
3768
|
+
can_create: spec.can_create,
|
|
3769
|
+
interval_minutes: spec.interval_minutes,
|
|
3770
|
+
name: spec.name,
|
|
3771
|
+
role: spec.role,
|
|
3772
|
+
target_thread_id: spec.target_thread_id,
|
|
3773
|
+
target_thread_title: spec.target_thread_title,
|
|
3774
|
+
})),
|
|
3775
|
+
desired_state: desiredState,
|
|
3776
|
+
dispatcher_command: plan.control.dispatcher_command,
|
|
3777
|
+
dispatcher_id: dispatcherId,
|
|
3778
|
+
interval_minutes: plan.interval_minutes,
|
|
3779
|
+
quiescence: plan.quiescence,
|
|
3780
|
+
summary: plan.summary,
|
|
3781
|
+
},
|
|
3782
|
+
correlation: {
|
|
3783
|
+
action,
|
|
3784
|
+
command: "app-autopilot",
|
|
3785
|
+
dispatcher_id: dispatcherId,
|
|
3786
|
+
},
|
|
3787
|
+
eventType,
|
|
3788
|
+
severity: plan.summary.blocked_automations > 0 ? "warning" : "info",
|
|
3789
|
+
summary: `App autopilot ${action} for ${plan.task.name}.`,
|
|
3790
|
+
taskId: plan.task.id,
|
|
3791
|
+
timestamp,
|
|
3792
|
+
});
|
|
3793
|
+
receipt = {
|
|
3794
|
+
event_id: eventId,
|
|
3795
|
+
event_type: eventType,
|
|
3796
|
+
recorded_at: timestamp,
|
|
3797
|
+
};
|
|
3798
|
+
plan = {
|
|
3799
|
+
...plan,
|
|
3800
|
+
last_policy_event: receipt,
|
|
3801
|
+
};
|
|
3802
|
+
}
|
|
3803
|
+
const output = {
|
|
3804
|
+
action,
|
|
3805
|
+
plan,
|
|
3806
|
+
receipt,
|
|
3807
|
+
};
|
|
3808
|
+
if (parsed.flags.json) {
|
|
3809
|
+
return jsonResult(output);
|
|
3810
|
+
}
|
|
3811
|
+
return {
|
|
3812
|
+
exitCode: 0,
|
|
3813
|
+
handled: true,
|
|
3814
|
+
stdout: renderAppAutopilotText(output),
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
finally {
|
|
3818
|
+
database.close();
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3696
3821
|
function parseAppWakeupDeliveryStatus(value) {
|
|
3697
3822
|
if (value === "sent" || value === "skipped" || value === "blocked") {
|
|
3698
3823
|
return value;
|
|
@@ -3803,6 +3928,39 @@ function renderAppWakeupPlanText(plan) {
|
|
|
3803
3928
|
}
|
|
3804
3929
|
return `${lines.join("\n")}\n`;
|
|
3805
3930
|
}
|
|
3931
|
+
function renderAppAutopilotText(result) {
|
|
3932
|
+
const lines = [
|
|
3933
|
+
`App autopilot ${result.action} for ${result.plan.task.name}: ${result.plan.desired_state}`,
|
|
3934
|
+
`Loop status: ${result.plan.status.ok ? "ok" : "attention required"}`,
|
|
3935
|
+
`Dispatch: ${result.plan.dispatcher.state}${result.plan.dispatcher.required ? " required" : ""}`,
|
|
3936
|
+
`Dispatch command: ${result.plan.control.dispatcher_command}`,
|
|
3937
|
+
`Wake dispatch: ${result.plan.control.wakeup_dispatch_command}`,
|
|
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
|
+
}
|
|
3946
|
+
if (result.receipt) {
|
|
3947
|
+
lines.push(`Receipt: ${result.receipt.event_type} ${result.receipt.event_id}`);
|
|
3948
|
+
}
|
|
3949
|
+
else if (result.plan.last_policy_event) {
|
|
3950
|
+
lines.push(`Last policy: ${result.plan.last_policy_event.event_type} ${result.plan.last_policy_event.event_id}`);
|
|
3951
|
+
}
|
|
3952
|
+
else {
|
|
3953
|
+
lines.push("Last policy: unconfigured");
|
|
3954
|
+
}
|
|
3955
|
+
for (const spec of result.plan.automation_specs) {
|
|
3956
|
+
lines.push(`${spec.role} automation: ${spec.can_create ? "ready" : "blocked"} ${spec.name}`, ` thread: ${spec.target_thread_title ?? "(untitled)"} ${spec.target_thread_id ?? "(missing)"}`, ` schedule: ${spec.rrule}`);
|
|
3957
|
+
if (spec.blocker) {
|
|
3958
|
+
lines.push(` blocker: ${spec.blocker}`);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
lines.push(result.plan.control.note);
|
|
3962
|
+
return `${lines.join("\n")}\n`;
|
|
3963
|
+
}
|
|
3806
3964
|
function boundAppSessionForRoleSync(database, options) {
|
|
3807
3965
|
const sessionJoin = options.role === "manager" ? "manager_session_id" : "worker_session_id";
|
|
3808
3966
|
const row = database.prepare(`
|
|
@@ -14099,6 +14257,7 @@ function isDefaultRuntimeCommand(command) {
|
|
|
14099
14257
|
|| command === "app-wakeup-plan"
|
|
14100
14258
|
|| command === "app-wakeup-dispatch"
|
|
14101
14259
|
|| command === "app-wakeup-record-delivery"
|
|
14260
|
+
|| command === "app-autopilot"
|
|
14102
14261
|
|| command === "loop-templates"
|
|
14103
14262
|
|| command === "loop-triggers"
|
|
14104
14263
|
|| command === "ralph-loop-presets"
|
|
@@ -17084,11 +17243,17 @@ function disposableWorkerHandoff(taskName, runName, dbPath) {
|
|
|
17084
17243
|
const loopClause = runName
|
|
17085
17244
|
? ` for Ralph loop run ${runName}`
|
|
17086
17245
|
: " for this disposable no-tmux binding";
|
|
17246
|
+
const notifyCommand = durableWorkerNotifyManagerCommand(taskName, dbPath);
|
|
17247
|
+
const dispatchCommand = durableWorkerNotifyDispatchCommand(dbPath);
|
|
17087
17248
|
return [
|
|
17088
17249
|
"Use the manage-codex-workers skill.",
|
|
17089
17250
|
"",
|
|
17090
17251
|
`You are the worker for task ${taskName}${loopClause}.`,
|
|
17091
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.",
|
|
17092
17257
|
"",
|
|
17093
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.",
|
|
17094
17259
|
"Do not delete, pause, or disable heartbeat automation just because an inbox poll is idle; the manager or operator owns terminal loop teardown.",
|
|
@@ -17129,6 +17294,8 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17129
17294
|
const workerHeartbeatCommand = disposableAppHeartbeatCommand("worker", taskName, dbPath);
|
|
17130
17295
|
const managerInboxCommand = sessionPollCommand("manager", taskName, dbPath);
|
|
17131
17296
|
const workerInboxCommand = sessionPollCommand("worker", taskName, dbPath);
|
|
17297
|
+
const workerNotifyCommand = durableWorkerNotifyManagerCommand(taskName, dbPath);
|
|
17298
|
+
const workerNotifyDispatchCommand = durableWorkerNotifyDispatchCommand(dbPath);
|
|
17132
17299
|
const wakeupDispatchCommand = `${conveyorPollInvocation()} app-wakeup-dispatch ${shellQuote(taskName)} --path ${shellQuote(dbPath)} --json`;
|
|
17133
17300
|
const deliveryReceiptCommands = disposableDeliveryReceiptCommands(taskName, dbPath);
|
|
17134
17301
|
return {
|
|
@@ -17182,6 +17349,10 @@ function disposableHeartbeatRecommendations(taskName, dbPath) {
|
|
|
17182
17349
|
`Run: ${workerHeartbeatCommand}`,
|
|
17183
17350
|
`If the heartbeat output asks for direct inbox polling, run: ${workerInboxCommand}`,
|
|
17184
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.",
|
|
17185
17356
|
"If no item is consumed, stop after a one-line idle receipt.",
|
|
17186
17357
|
"Do not delete, pause, or disable worker heartbeat automation after an idle poll; the manager or operator owns terminal loop teardown.",
|
|
17187
17358
|
].join("\n"),
|
|
@@ -17201,6 +17372,12 @@ function disposableDeliveryReceiptCommands(taskName, dbPath) {
|
|
|
17201
17372
|
function disposableAppHeartbeatCommand(role, taskName, dbPath) {
|
|
17202
17373
|
return `${conveyorPollInvocation()} app-heartbeat ${shellQuote(taskName)} --role ${role} --path ${shellQuote(dbPath)} --json`;
|
|
17203
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
|
+
}
|
|
17204
17381
|
function sessionPollCommand(role, taskName, dbPath) {
|
|
17205
17382
|
const inbox = role === "worker" ? "worker-inbox" : "manager-inbox";
|
|
17206
17383
|
const task = taskName ? shellQuote(taskName) : "<task>";
|