agent-conveyor 0.1.17 → 0.1.19

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
@@ -327,6 +327,11 @@ tmux attach -t codex-live-test
327
327
  blockers, approval counts, and the next recommended manager action. This is
328
328
  also the supported way to inspect asset receipts and per-slot receipt counts;
329
329
  there is no separate `campaign assets` subcommand.
330
+ - `campaign closeout --name C [--failure-mode TEXT] [--json]` —
331
+ Produce a read-only closeout report from the campaign dashboard, including
332
+ verdict, worker thread ids, blockers, receipt counts by assignment, proof
333
+ checks, and the strongest realistic failure mode evidence to carry into a
334
+ manager/operator handoff.
330
335
 
331
336
  Creative Ops Campaign manager loop:
332
337
 
@@ -25,6 +25,15 @@ interface SpawnedCodexSessionDiscoveryOptions {
25
25
  }
26
26
  type TypescriptRuntimeOptions = {
27
27
  args: readonly string[];
28
+ campaignReadbackBeforeVerify?: (context: {
29
+ databasePath: string;
30
+ readback: {
31
+ assignment?: string;
32
+ campaign: string;
33
+ channel?: string;
34
+ slot?: string;
35
+ };
36
+ }) => void;
28
37
  codexCommandResolver?: (name: string) => string | null;
29
38
  cwd?: string;
30
39
  discoverSpawnedCodexSession?: (options: SpawnedCodexSessionDiscoveryOptions) => SpawnedCodexSessionDiscovery;
@@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  import { taskAuditSync } from "../runtime/audit.js";
8
8
  import { appAutopilotPlanSync, appLoopStatusSync, appWakeupDispatchPlanSync, appWakeupPlanSync, directInboxPollCommand, visibleSessionProtocolLines, } from "../runtime/app-autonomy.js";
9
9
  import { classifyBusyWait, classifyStartupOutput } from "../runtime/classify.js";
10
- import { addCampaignWorkerSlotSync, campaignDashboardSync, campaignStatusSync, createCampaignAssignmentSync, createCampaignSync, recordCampaignAssetReceiptSync, updateCampaignWorkerSlotLifecycleSync, upsertCampaignChannelBriefSync, } from "../runtime/campaigns.js";
10
+ import { addCampaignWorkerSlotSync, campaignDashboardSync, campaignSetupReadbackProofSync, campaignStatusSync, createCampaignAssignmentSync, createCampaignSync, recordCampaignAssetReceiptSync, updateCampaignWorkerSlotLifecycleSync, upsertCampaignChannelBriefSync, } from "../runtime/campaigns.js";
11
11
  import { exportTaskSync } from "../runtime/export.js";
12
12
  import { ingestSessionSync } from "../runtime/ingest.js";
13
13
  import { acceptanceCriteriaForTaskSync, loopEvidenceCriterion, recordAdversarialLoopEvidenceSync, recordLoopEvidenceSync, recordVisualDiffLoopEvidenceSync, } from "../runtime/loop-evidence.js";
@@ -402,6 +402,7 @@ function commandHelpText(program, command) {
402
402
  ` ${program} campaign asset --name launch --slot campaign-slot-id --assignment campaign-assignment-id --asset-type copy --title "Hooks v2" --allow-additional-receipt --json`,
403
403
  ` ${program} campaign status --name launch --json`,
404
404
  ` ${program} campaign dashboard --name launch --json`,
405
+ ` ${program} campaign closeout --name launch --failure-mode "hidden duplicate receipt" --json`,
405
406
  ],
406
407
  "manager-ack": [
407
408
  `usage: ${program} manager-ack <task> --from-stdin ${path}`,
@@ -476,7 +477,7 @@ function commandHelpText(program, command) {
476
477
  };
477
478
  return linesByCommand[command] ?? [`usage: ${program} ${command} [-h] [options]`];
478
479
  }
479
- const CAMPAIGN_ACTION_NAMES = ["create", "add-slot", "attach-slot", "rotate-slot", "archive-slot", "brief", "assign", "asset", "status", "dashboard"];
480
+ const CAMPAIGN_ACTION_NAMES = ["create", "add-slot", "attach-slot", "rotate-slot", "archive-slot", "brief", "assign", "asset", "status", "dashboard", "closeout"];
480
481
  const CAMPAIGN_ACTIONS = new Set(CAMPAIGN_ACTION_NAMES);
481
482
  const CAMPAIGN_STRING_FLAGS = {
482
483
  "--artifact-path": "artifactPath",
@@ -485,6 +486,7 @@ const CAMPAIGN_STRING_FLAGS = {
485
486
  "--brief-json": "briefJson",
486
487
  "--channel": "channel",
487
488
  "--expected-thread-id": "expectedThreadId",
489
+ "--failure-mode": "failureMode",
488
490
  "--instructions": "instructions",
489
491
  "--objective": "objective",
490
492
  "--prompt-summary": "promptSummary",
@@ -6094,12 +6096,16 @@ function runCampaignCommand(parsed, options) {
6094
6096
  name: campaign,
6095
6097
  objective,
6096
6098
  });
6099
+ const readback = campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, {
6100
+ campaign: campaignId,
6101
+ });
6097
6102
  return campaignResult(parsed, {
6098
6103
  action,
6099
6104
  campaign,
6100
6105
  campaign_id: campaignId,
6101
6106
  created: true,
6102
- }, [`campaign ${campaign} created ${campaignId}`]);
6107
+ ledger_readback: readback,
6108
+ }, [`campaign ${campaign} created ${campaignId}`, campaignLedgerReadbackText(readback)]);
6103
6109
  }
6104
6110
  if (action === "add-slot") {
6105
6111
  const slotKey = requiredStringFlag(parsed.flags.slotKey, "--slot-key");
@@ -6117,13 +6123,18 @@ function runCampaignCommand(parsed, options) {
6117
6123
  slotKey,
6118
6124
  ...(state ? { state } : {}),
6119
6125
  });
6126
+ const readback = campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, {
6127
+ campaign,
6128
+ slot: slotId,
6129
+ });
6120
6130
  return campaignResult(parsed, {
6121
6131
  action,
6122
6132
  campaign,
6123
6133
  created: true,
6134
+ ledger_readback: readback,
6124
6135
  slot_id: slotId,
6125
6136
  slot_key: slotKey,
6126
- }, [`campaign ${campaign} slot ${slotKey} created ${slotId}`]);
6137
+ }, [`campaign ${campaign} slot ${slotKey} created ${slotId}`, campaignLedgerReadbackText(readback)]);
6127
6138
  }
6128
6139
  if (action === "attach-slot") {
6129
6140
  const slot = requiredStringFlag(parsed.flags.slot, "--slot");
@@ -6192,13 +6203,18 @@ function runCampaignCommand(parsed, options) {
6192
6203
  campaign,
6193
6204
  channel,
6194
6205
  });
6206
+ const readback = campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, {
6207
+ campaign,
6208
+ channel,
6209
+ });
6195
6210
  return campaignResult(parsed, {
6196
6211
  action,
6197
6212
  brief_id: briefId,
6198
6213
  campaign,
6199
6214
  channel,
6215
+ ledger_readback: readback,
6200
6216
  upserted: true,
6201
- }, [`campaign ${campaign} brief ${channel} upserted ${briefId}`]);
6217
+ }, [`campaign ${campaign} brief ${channel} upserted ${briefId}`, campaignLedgerReadbackText(readback)]);
6202
6218
  }
6203
6219
  if (action === "assign") {
6204
6220
  const slot = requiredStringFlag(parsed.flags.slot, "--slot");
@@ -6214,13 +6230,19 @@ function runCampaignCommand(parsed, options) {
6214
6230
  title,
6215
6231
  ...(status ? { status } : {}),
6216
6232
  });
6233
+ const readback = campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, {
6234
+ assignment: assignmentId,
6235
+ campaign,
6236
+ slot,
6237
+ });
6217
6238
  return campaignResult(parsed, {
6218
6239
  action,
6219
6240
  assignment_id: assignmentId,
6220
6241
  campaign,
6221
6242
  created: true,
6243
+ ledger_readback: readback,
6222
6244
  slot_id: slot,
6223
- }, [`campaign ${campaign} assignment created ${assignmentId}`]);
6245
+ }, [`campaign ${campaign} assignment created ${assignmentId}`, campaignLedgerReadbackText(readback)]);
6224
6246
  }
6225
6247
  if (action === "asset") {
6226
6248
  const slot = requiredStringFlag(parsed.flags.slot, "--slot");
@@ -6258,6 +6280,13 @@ function runCampaignCommand(parsed, options) {
6258
6280
  const dashboard = campaignDashboardSync(database, campaign);
6259
6281
  return campaignResult(parsed, dashboard, renderCampaignDashboardText(dashboard));
6260
6282
  }
6283
+ if (action === "closeout") {
6284
+ const dashboard = campaignDashboardSync(database, campaign);
6285
+ const closeout = campaignCloseoutReport(dashboard, {
6286
+ failureMode: parsed.flags.failureMode,
6287
+ });
6288
+ return campaignResult(parsed, closeout, renderCampaignCloseoutText(closeout));
6289
+ }
6261
6290
  return errorResult(unsupportedCampaignActionMessage(action));
6262
6291
  }
6263
6292
  finally {
@@ -6267,6 +6296,25 @@ function runCampaignCommand(parsed, options) {
6267
6296
  function campaignActionsUsage() {
6268
6297
  return CAMPAIGN_ACTION_NAMES.join("|");
6269
6298
  }
6299
+ function campaignSetupReadbackProofFromConfiguredDatabase(parsed, options, readbackOptions) {
6300
+ const databasePath = runtimeDbPath(parsed, options);
6301
+ options.campaignReadbackBeforeVerify?.({ databasePath, readback: readbackOptions });
6302
+ const database = openDatabaseSync(databasePath);
6303
+ initializeDatabaseSync(database);
6304
+ try {
6305
+ return campaignSetupReadbackProofSync(database, readbackOptions);
6306
+ }
6307
+ catch (error) {
6308
+ const message = error instanceof Error ? error.message : String(error);
6309
+ throw new Error(`campaign ledger readback failed after setup write: ${message}`, { cause: error });
6310
+ }
6311
+ finally {
6312
+ database.close();
6313
+ }
6314
+ }
6315
+ function campaignLedgerReadbackText(proof) {
6316
+ return `ledger_readback ok campaign=${proof.campaign_id} checks=${proof.checks.map((check) => check.entity).join(",")}`;
6317
+ }
6270
6318
  function unsupportedCampaignActionMessage(action) {
6271
6319
  return `Unsupported campaign action: ${action ?? "<missing>"}; expected one of: ${CAMPAIGN_ACTION_NAMES.join(", ")}. Use \`conveyor campaign dashboard --name <campaign> --json\` to list assets and receipt counts.`;
6272
6320
  }
@@ -6342,6 +6390,107 @@ function renderCampaignDashboardText(dashboard) {
6342
6390
  }
6343
6391
  return lines;
6344
6392
  }
6393
+ function campaignCloseoutReport(dashboard, options = {}) {
6394
+ const receiptCountsByAssignment = campaignReceiptCountsByAssignment(dashboard);
6395
+ const activeSlots = dashboard.slots.filter((slot) => slot.state !== "archived");
6396
+ const slotsMissingReceipts = activeSlots.filter((slot) => slot.assignments.length > 0 && slot.asset_receipts === 0);
6397
+ const duplicateAssignmentCounts = receiptCountsByAssignment.filter((item) => item.receipt_count > 1);
6398
+ const failureMode = options.failureMode
6399
+ ?? "A hidden duplicate or missing worker receipt could make the campaign look closed while dashboard receipt counts are wrong.";
6400
+ const receiptEvidence = dashboard.slots
6401
+ .map((slot) => `${slot.slot_key}:assignments=${slot.assignments.length},receipts=${slot.asset_receipts}`)
6402
+ .join("; ");
6403
+ return {
6404
+ action: "closeout",
6405
+ approvals: dashboard.approvals,
6406
+ blockers: dashboard.blockers,
6407
+ campaign: dashboard.campaign,
6408
+ failure_mode: {
6409
+ evidence: `dashboard asset_total=${dashboard.summary.asset_total}; assignment_total=${dashboard.summary.assignment_total}; ${receiptEvidence}`,
6410
+ strongest_realistic_failure_mode: failureMode,
6411
+ },
6412
+ next_manager_action: dashboard.next_manager_action,
6413
+ proof_checks: [
6414
+ {
6415
+ check: "dashboard_loaded",
6416
+ evidence: `campaign_id=${dashboard.campaign.id}; updated_at=${dashboard.campaign.updated_at}`,
6417
+ status: "passed",
6418
+ },
6419
+ {
6420
+ check: "blockers_absent",
6421
+ evidence: `blockers=${dashboard.blockers.length}`,
6422
+ status: dashboard.blockers.length === 0 ? "passed" : "failed",
6423
+ },
6424
+ {
6425
+ check: "active_worker_slots_have_receipts",
6426
+ evidence: slotsMissingReceipts.length === 0
6427
+ ? "all active slots with assignments have at least one receipt"
6428
+ : `missing_receipt_slots=${slotsMissingReceipts.map((slot) => slot.slot_key).join(",")}`,
6429
+ status: slotsMissingReceipts.length === 0 ? "passed" : "attention",
6430
+ },
6431
+ {
6432
+ check: "assignment_receipt_counts",
6433
+ evidence: duplicateAssignmentCounts.length === 0
6434
+ ? "no assignment has more than one receipt"
6435
+ : `additional_receipt_assignments=${duplicateAssignmentCounts.map((item) => `${item.assignment_id}:${item.receipt_count}`).join(",")}`,
6436
+ status: duplicateAssignmentCounts.length === 0 ? "passed" : "attention",
6437
+ },
6438
+ {
6439
+ check: "human_review_gate",
6440
+ evidence: `needs_review=${dashboard.approvals.needs_review}; approved=${dashboard.approvals.approved}; published=${dashboard.approvals.published}`,
6441
+ status: dashboard.approvals.needs_review > 0 && dashboard.approvals.published === 0 ? "passed" : "attention",
6442
+ },
6443
+ ],
6444
+ receipt_counts_by_assignment: receiptCountsByAssignment,
6445
+ summary: dashboard.summary,
6446
+ verdict: campaignCloseoutVerdict(dashboard),
6447
+ workers: dashboard.slots.map((slot) => ({
6448
+ active_assignments: slot.active_assignments,
6449
+ asset_receipts: slot.asset_receipts,
6450
+ blockers: slot.blockers,
6451
+ channel: slot.channel,
6452
+ codex_app_thread_id: slot.codex_app_thread_id,
6453
+ codex_app_thread_title: slot.codex_app_thread_title,
6454
+ lifecycle_state: slot.lifecycle.state,
6455
+ receipt_ids: slot.assets.map((asset) => asset.id),
6456
+ slot_key: slot.slot_key,
6457
+ state: slot.state,
6458
+ })),
6459
+ };
6460
+ }
6461
+ function campaignReceiptCountsByAssignment(dashboard) {
6462
+ return dashboard.slots.flatMap((slot) => slot.assignments.map((assignment) => ({
6463
+ assignment_id: assignment.id,
6464
+ receipt_count: slot.assets.filter((asset) => asset.assignment_id === assignment.id).length,
6465
+ slot_key: slot.slot_key,
6466
+ })));
6467
+ }
6468
+ function campaignCloseoutVerdict(dashboard) {
6469
+ if (dashboard.blockers.length > 0 || dashboard.summary.blocked_assignments > 0 || dashboard.summary.blocked_slots > 0 || dashboard.summary.stale_slots > 0) {
6470
+ return "blocked";
6471
+ }
6472
+ if (dashboard.next_manager_action.action === "close_campaign") {
6473
+ return "ready_to_close";
6474
+ }
6475
+ if (dashboard.approvals.needs_review > 0 || dashboard.approvals.rejected > 0) {
6476
+ return "needs_review";
6477
+ }
6478
+ return "needs_work";
6479
+ }
6480
+ function renderCampaignCloseoutText(report) {
6481
+ return [
6482
+ `campaign ${report.campaign.name} ${report.campaign.status}`,
6483
+ `closeout verdict ${report.verdict}`,
6484
+ `next ${report.next_manager_action.action}: ${report.next_manager_action.reason}`,
6485
+ `summary slots=${report.summary.active_slots}/${report.summary.archived_slots} assignments=${report.summary.assignment_total} assets=${report.summary.asset_total} blockers=${report.blockers.length}`,
6486
+ `approvals needs_review=${report.approvals.needs_review} approved=${report.approvals.approved} rejected=${report.approvals.rejected} published=${report.approvals.published}`,
6487
+ `failure_mode ${report.failure_mode.strongest_realistic_failure_mode}`,
6488
+ `failure_mode_evidence ${report.failure_mode.evidence}`,
6489
+ ...report.proof_checks.map((check) => `proof ${check.status} ${check.check}: ${check.evidence}`),
6490
+ ...report.receipt_counts_by_assignment.map((item) => `assignment_receipts ${item.slot_key} ${item.assignment_id}=${item.receipt_count}`),
6491
+ ...report.workers.slice(0, 8).map((worker) => `worker ${worker.slot_key} ${worker.state}/${worker.lifecycle_state} assignments=${worker.active_assignments} receipts=${worker.asset_receipts} thread=${worker.codex_app_thread_id ?? "none"}`),
6492
+ ];
6493
+ }
6345
6494
  function statusCountsText(counts) {
6346
6495
  return Object.entries(counts).map(([status, count]) => `${status}=${count}`).join(" ");
6347
6496
  }