Engine-agnostic multi-agent workflow orchestration with journaled replay/resume
Dependencies
///|
async fn verdict_runner(call : @workflow.AgentCall) -> @workflow.AgentOutcome {
@async.pause()
let verdict : Json = if call.input is { "query": String(q), .. } &&
q.contains("lens=repro") {
{ "confirmed": false }
} else {
{ "confirmed": true }
}
Finished(value=verdict, attempt={
attempt_id: "sr-\{call.label}",
steps_used: 3,
prompt_tokens: 70,
completion_tokens: 30,
cost_usd: None,
})
}
///|
async test "fan out three lenses, gate on a 2-of-3 quorum" {
let wf = @workflow.Workflow(
runner=@workflow.Runner(verdict_runner),
max_concurrent=4,
max_calls=16,
)
wf.phase("Verify")
let results = @workflow.fan_out(["correctness", "security", "repro"], lens => {
@workflow.attempt(() => {
wf.agent(
kind="judge",
prompt="Judge the finding through lens=\{lens}: real?",
label="verify:\{lens}",
)
})
})
let confirmed = @workflow.quorum(results, need=2)
.filter(v => v is { "confirmed": True, .. })
.length()
assert_eq(confirmed, 2)
assert_eq(wf.calls_made(), 3)
assert_eq(wf.tokens_spent(), 300)
}///|
async test "retry until the engine actually answers" {
let flaky : Ref[Int] = { val: 0, }
let wf = @workflow.Workflow(
runner=@workflow.Runner(call => {
@async.pause()
flaky.val 1
if flaky.val < 3 {
DidNotFinish(failure=NoReport, attempt=None)
} else {
Finished(value={ "attempt": flaky.val }, attempt={
attempt_id: "sr-\{call.label}",
steps_used: 1,
prompt_tokens: 40,
completion_tokens: 10,
cost_usd: None,
})
}
}),
)
let report = @workflow.retry(
() => wf.agent(prompt="Name the worst bug in spawn/", kind="judge"),
max_retry=2,
)
assert_eq(report, { "attempt": 3 })
assert_eq(wf.calls_made(), 3)
}///|
async fn slice_runner(call : @workflow.AgentCall) -> @workflow.AgentOutcome {
@async.pause()
guard call.input is { "task": String(task), .. } else {
return DidNotFinish(failure=Failed("not a worker input"), attempt=None)
}
Finished(value={ "status": "done", "task": task }, attempt={
attempt_id: "sr-\{call.label}",
steps_used: 1,
prompt_tokens: 10,
completion_tokens: 5,
cost_usd: None,
})
}
///|
async test "fan a worker kind out through its own input shape" {
let wf = @workflow.Workflow(runner=@workflow.Runner(slice_runner))
let slices = ["rename the seam", "widen the ceiling"]
let done = @workflow.fan_out(slices, task => {
@workflow.attempt(() => {
wf.agent_call(
kind="worker",
input={ "task": task, "worker_root": "/w" },
label="worker:\{task}",
)
})
})
assert_eq(@workflow.all_ok(done).length(), 2)
}///|
struct Confirmation {
confirmed : Bool
} derive(FromJson)
///|
async test "decode the report at the boundary" {
let wf = @workflow.Workflow(runner=@workflow.Runner(verdict_runner))
let verdict : Confirmation = wf.agent_as(
prompt="Judge the finding through lens=security: real?",
kind="judge",
schema={
"type": "object",
"properties": { "confirmed": { "type": "boolean" } },
"required": ["confirmed"],
},
)
assert_true(verdict.confirmed)
}///|
async test "find then verify, with no barrier between the stages" {
let wf = @workflow.Workflow(runner=@workflow.Runner(verdict_runner))
let verified = @workflow.fan_out(["pkg/a", "pkg/b"], target => {
let finding = @workflow.attempt(() => {
wf.agent(prompt="Find the worst bug in \{target}", kind="judge")
})
match finding {
// Each finding proceeds to verification the moment ITS finder
// returns — b's finder may still be running while a verifies.
Ok(_) =>
@workflow.attempt(() => {
wf.agent(
prompt="Adversarially verify the finding in \{target}",
kind="judge",
)
})
Err(error) => Err(error)
}
})
assert_eq(@workflow.all_ok(verified).length(), 2)
}///|
async test "the second generation replays instead of re-paying" {
let journal = @workflow.Journal::in_memory()
let wf1 = @workflow.Workflow(
runner=@workflow.Runner(verdict_runner),
journal~,
)
let first = wf1.agent(
prompt="Judge the finding through lens=security: real?",
kind="judge",
)
assert_eq(wf1.tokens_spent(), 100)
// Same program, next generation: served from the journal — no launch,
// no slot, no fresh spend.
let wf2 = @workflow.Workflow(
runner=@workflow.Runner(verdict_runner),
journal=@workflow.Journal::in_memory(prior=journal.recorded()),
)
assert_eq(
wf2.agent(
prompt="Judge the finding through lens=security: real?",
kind="judge",
),
first,
)
assert_eq(wf2.calls_made(), 0)
assert_eq(wf2.calls_replayed(), 1)
assert_eq(wf2.tokens_spent(), 0)
}///|
let runner = @spawn.contract_runner(launch=_ => {
command: "my-engine",
args: [],
cwd: None,
extra_env: None,
deadline_ms: None,
})///|
let runner = @spawn.contract_runner(launch=call => {
command: "moonx",
// read-only by default; `worker` calls (or --writable) may edit
args: [
"moonbitlang/workflow/shim/claude", "--model", "claude-sonnet-5", "--", "--max-budget-usd",
"2",
],
cwd: None,
extra_env: None,
deadline_ms: None,
})just shims # _build/native/debug/build/shim/{claude,codex}/*.exe///|
let runner = @workflow.Runner(call => {
// spawn something, await it, and account honestly:
Finished(value=report_json, attempt={
attempt_id,
steps_used,
prompt_tokens,
completion_tokens,
cost_usd: None, // `Double?` has no default: name it, even when unknown
})
})pub suberror JournalCorrupted {
JournalCorrupted(path~ : String, line~ : Int)
}impl Show for JournalCorruptedpub suberror WorkflowError {
AgentFailed(label~ : String, failure~ : AgentFailure)
CallBudgetExhausted(label~ : String)
QuorumNotReached(need~ : Int, ok~ : Int, of~ : Int)
}impl Show for WorkflowErrorimpl Show for AgentFailurepub(all) enum AgentOutcome {
Finished(value~ : Json, attempt~ : AgentAttempt)
DidNotFinish(failure~ : AgentFailure, attempt~ : AgentAttempt?)
} derive(Eq, ToJson, FromJson)type Journalpub struct JournalEntry {
call : AgentCall
outcome : AgentOutcome
phase : String?
started_at : Int64?
finished_at : Int64?
} derive(Eq)impl ToJson for JournalEntryimpl FromJson for JournalEntryfn JournalEntry::JournalEntry(call~ : AgentCall, outcome~ : AgentOutcome, phase? : String, started_at? : Int64, finished_at? : Int64) -> JournalEntrytype Runnerfn Runner::Runner(run : async (AgentCall) -> AgentOutcome, validate_replay? : async (AgentCall, AgentOutcome) -> ReplayDecision) -> Runnertype Workflowfn Workflow::Workflow(runner~ : Runner, max_concurrent? : Int, max_calls? : Int, journal? : Journal, replay_scope? : String, on_event? : (WorkflowEvent) -> Unit) -> Workflow raisepub enum WorkflowEvent {
PhaseStarted(String)
Log(String)
AgentStarted(label~ : String, kind~ : String, phase~ : String?)
AgentFinished(label~ : String, disposition~ : AgentDisposition)
AgentReplayed(label~ : String, disposition~ : AgentDisposition)
}fn[T] collect_ok(results : Array[Result[T, WorkflowError]], min_ok? : Int) -> Array[T] raise WorkflowErrorasync fn[A, B] fan_out(items : Array[A], run : async (A) -> Result[B, WorkflowError]) -> Array[Result[B, WorkflowError]]async fn[T] parallel(thunks : Array[async () -> Result[T, WorkflowError]]) -> Array[Result[T, WorkflowError]]fn[T] quorum(results : Array[Result[T, WorkflowError]], need~ : Int) -> Array[T] raise WorkflowErrorasync fn[T] retry(attempt : async () -> T, max_retry? : Int, backoff? : RetryMethod, retriable? : (WorkflowError) -> Bool) -> TInstall
Download zipEngine-agnostic multi-agent workflow orchestration with journaled replay/resume
Dependencies