-
Notifications
You must be signed in to change notification settings - Fork 42
feat(orchestrator): crash-safe, resumable executor with exactly-once agent payment #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| # Crash-Safe, Resumable Executor & Exactly-Once Agent Payment | ||
|
|
||
| The orchestrator execution engine (`packages/orchestrator/src/executor.ts` & `task-execution-store.ts`) provides crash-safe, durable, and resumable execution of multi-step AI agent workflows with exactly-once on-chain payment settlement. | ||
|
|
||
| ## Problem Context | ||
|
|
||
| When executing a multi-step task moving real USDC per step across agents, a process crash, redeployment, or network timeout could leave execution state ambiguous: | ||
| - Did the agent execute? | ||
| - Was on-chain payment released? | ||
| - What was the intermediate output needed by dependent downstream steps? | ||
|
|
||
| Without durable step tracking and on-chain reconciliation, a restart could result in double-paying agents, re-executing already-completed steps, or losing intermediate pipeline state. | ||
|
|
||
| --- | ||
|
|
||
| ## Step State Machine | ||
|
|
||
| Each step within an `ExecutionPlan` progresses through a strictly defined state machine, persisted durably to `data/task-executions.json` using atomic file writes (`writeJsonSafe`) before and after every external side effect: | ||
|
|
||
| ``` | ||
| ┌───────────────┐ | ||
| │ pending │ | ||
| └───────┬───────┘ | ||
| │ (about to invoke agent / health) | ||
| ▼ | ||
| ┌───────────────┐ | ||
| ┌────►│ executing ├────┐ | ||
| │ └───────┬───────┘ │ | ||
| (retry on │ │ │ (health/agent error) | ||
| restart) │ ▼ │ | ||
| │ ┌───────────────┐ │ | ||
| └─────┤ delivered │ │ | ||
| └───────┬───────┘ │ | ||
| │ │ | ||
| ▼ │ | ||
| ┌───────────────┐ │ | ||
| │ releasing │ │ | ||
| └───────┬───────┘ │ | ||
| │ │ | ||
| ▼ │ | ||
| ┌───────────────┐ │ | ||
| │ released │ ▼ | ||
| └───────────────┘ ┌──────────────┐ | ||
| │ failed │ | ||
| └──────────────┘ | ||
| ``` | ||
|
|
||
| ### State Definitions | ||
|
|
||
| | State | Definition | Durable Write Point | | ||
| |---|---|---| | ||
| | `pending` | Step scheduled in plan; has not started execution. | Written on task initialization (`initTaskExecution`). | | ||
| | `executing` | Agent health check passed; agent endpoint call in-flight. | Written **before** invoking the agent API (`makeX402Payment` / `makeMPPPayment`). | | ||
| | `delivered` | Agent returned valid execution output and tx hash. | Written **immediately upon receiving agent response**, before any vault release. | | ||
| | `releasing` | On-chain vault release in-flight (contract → orchestrator). | Written **before** invoking on-chain `releasePayment`. | | ||
| | `released` | Step output recorded and on-chain payment confirmed/settled. | Written **after** on-chain `releasePayment` confirms. | | ||
| | `failed` | Step failed (unreachable agent, unresolvable vault error). | Written on catch blocks with error reason and latency. | | ||
|
|
||
| --- | ||
|
|
||
| ## Crash-Point Recovery Matrix | ||
|
|
||
| | Crash Scenario | State at Restart | Recovery Action | Payment Invariant | | ||
| |---|---|---|---| | ||
| | **Crash before agent call** | `pending` or `executing` | Agent was not completed. Re-executes step and proceeds through state machine. | Exactly-once payment | | ||
| | **Crash after agent delivery before release** | `delivered` | Re-uses stored output; skips calling agent again. Proceeds directly to vault release. | Exactly-once payment; zero duplicate agent invocations | | ||
| | **Crash during on-chain release (ambiguous)** | `releasing` | Reconciles against on-chain AgentVault. Contract's idempotent step release (`release_payment`) returns `Ok(true)` without double-debiting. Transitions to `released`. | Exactly-once payment | | ||
| | **Crash after local write before next step** | `released` | Reads stored `output` and `payment`. Skips Step 1 completely; feeds output into dependent Step 2. | Zero duplicate payment; seamless pipeline resumption | | ||
| | **User cancelled task on-chain while offline** | `running` | On startup, `getTask(vaultTaskId)` detects `completed: true`. Halts remaining steps immediately and marks task `cancelled`. | Zero unauthorized post-cancellation releases | | ||
| | **Repeated recovery runs** | `completed` / `running` | Idempotent: finished tasks are ignored; in-flight tasks resume safely. | Safe to run repeatedly | | ||
|
|
||
| --- | ||
|
|
||
| ## Startup Resumption Flow | ||
|
|
||
| 1. On server boot (`server.ts`), `recoverUnfinishedTasks()` scans `data/task-executions.json` for tasks in `running` or `pending` status. | ||
| 2. For each task: | ||
| - Resolves the user's orchestrator keypair from `data/orchestrators.json`. | ||
| - Checks on-chain task status via `getTask(vaultTaskId)`. | ||
| - Builds dependency levels (`buildDependencyLevels`). | ||
| - Resumes steps concurrently per level via `Promise.all`: | ||
| - Already `released` steps are skipped. | ||
| - `delivered` steps proceed directly to release. | ||
| - Ambiguous `releasing` steps are reconciled with the vault. | ||
| - Unfinished steps are executed. | ||
| 3. Upon task completion, remaining locked budget is finalized back to the user (`completeTask`) and final results are saved to `data/task-results.json`. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Locate budget finalization and task-result persistence, and check whether recovery reaches them.
rg -n -C4 'completeTask|task-results\.json|saveTaskResult' packages/orchestrator/srcRepository: clevercon-protocol/clevercon Length of output: 6386 🏁 Script executed: #!/bin/bash
printf '%s\n' '--- recovery implementation ---'
sed -n '640,735p' packages/orchestrator/src/executor.ts
printf '%s\n' '--- recovery endpoint caller ---'
sed -n '840,885p' packages/orchestrator/src/server.ts
printf '%s\n' '--- startup recovery caller ---'
sed -n '1495,1528p' packages/orchestrator/src/server.ts
printf '%s\n' '--- normal completion and persistence flow ---'
sed -n '1380,1440p' packages/orchestrator/src/server.ts
printf '%s\n' '--- result persistence implementation ---'
sed -n '1,75p' packages/orchestrator/src/task-results.tsRepository: clevercon-protocol/clevercon Length of output: 10331 Handle recovered task results through the normal completion path
Route recovered results through the normal budget-finalization and persistence flow. 🤖 Prompt for AI Agents |
||
|
|
||
| --- | ||
|
|
||
| ## API Endpoints | ||
|
|
||
| - `POST /api/tasks/recover` — Manually triggers recovery and resumption of all unfinished tasks. | ||
| - `GET /api/tasks/history/:user_address` — View completed task executions. | ||
| - `POST /api/tasks` — Submit new task for execution. | ||
|
|
||
| --- | ||
|
|
||
| ## Data Files | ||
|
|
||
| - `data/task-executions.json` — Durable per-task and per-step execution state. | ||
| - `data/task-results.json` — Persisted completed task history for user dashboard. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
The documented state order does not match the implementation.
Two statements contradict packages/orchestrator/src/executor.ts.
releasingafterdelivered. The code releases first:releasingis written at lines 452-454 andreleasePaymentruns at lines 460-467, before the agent call at lines 512-542 that producesdelivered. Line 66 repeats the wrong order ("Proceeds directly to vault release").executingis written after the health check passes. The code writesexecutingat lines 420-423, beforecheckHealthat line 425.Readers use this table to reason about crash points and payment safety, so correct the order and the write point.
🤖 Prompt for AI Agents