///|
/// One request to run a sub-agent: the workflow-side view of a call before
/// any engine specifics (binary path, model, endpoint) attach to it.
/// `kind` names the child mode the engine dispatches on (`explore`,
/// `echo`, later `worker`); `input` is the EXACT JSON the child receives
/// as its input line — the persisted replay identity IS the child input,
/// so an encoder change is an identity change and can never replay stale
/// work. `label` is display metadata: journal replay matches on (kind,
/// input, max_steps), never on the label.
pub(all) struct AgentCall {
kind : String
input : Json
label : String
max_steps : Int?
/// Replay namespace: everything OUTSIDE the input that makes two
/// otherwise-identical calls non-interchangeable — model, prompt
/// revision, tenant, workspace. Empty means "unscoped".
scope : String
} derive(Eq, ToJson, FromJson)
///|
/// The cost accounting of one attempt to run an agent, captured whether or
/// not the attempt produced a report: a timed-out child spent real tokens,
/// and losing that spend would understate every budget built on top.
pub(all) struct AgentAttempt {
attempt_id : String
steps_used : Int
prompt_tokens : Int
completion_tokens : Int
} derive(Eq, ToJson, FromJson)
///|
/// Why an agent produced no usable report — the workflow-facing vocabulary
/// a script can meaningfully react to. Transient transport errors never
/// appear here: retrying those is the engine's job, below this seam.
/// Mirrors `@agent_subrun.SubrunTerminal` minus `Captured` (that one is the
/// success path), plus `Skipped` (a human declined the call — replay keeps
/// it declined rather than overriding the decision).
pub(all) enum AgentFailure {
TimedOut
NoReport
MaxSteps
ContextYield
Skipped
Failed(String)
} derive(Eq, ToJson, FromJson)
///|
/// The lossless envelope one agent run resolves to: either a report value
/// with its attempt accounting, or a failure that STILL carries the
/// attempt's cost. `attempt=None` means no launch was ever TRIED — a
/// `Skipped` call, or a refusal before spawn; a child that failed to
/// spawn or died early is an attempt that observed zero cost, not an
/// absent one. This is the unit the journal persists: replaying it must
/// lose nothing the live run knew.
pub(all) enum AgentOutcome {
Finished(value~ : Json, attempt~ : AgentAttempt)
DidNotFinish(failure~ : AgentFailure, attempt~ : AgentAttempt?)
} derive(Eq, ToJson, FromJson)
///|
/// The typed error channel of the workflow layer. Every call site must
/// either propagate one of these or catch it into a `Result` — forgetting
/// to choose a failure policy is a compile error, not a silent `null`.
pub suberror WorkflowError {
/// The named agent call produced no usable report, and why.
AgentFailed(label~ : String, failure~ : AgentFailure)
/// The workflow's launch allowance was already spent when this call was
/// about to launch — the runaway backstop. Only LAUNCHED agents consume
/// allowance; a call cancelled while queued never counts.
CallBudgetExhausted(label~ : String)
/// A quorum/collect policy over a fan-out did not reach its threshold:
/// `ok` of `of` succeeded, `need` were required.
QuorumNotReached(need~ : Int, ok~ : Int, of~ : Int)
}
///|
pub extend AgentCall with Eq::{equal, not_equal}
///|
pub extend AgentCall with ToJson::{to_json}
///|
pub extend AgentCall with FromJson::{from_json}
///|
pub extend AgentAttempt with Eq::{equal, not_equal}
///|
pub extend AgentAttempt with ToJson::{to_json}
///|
pub extend AgentAttempt with FromJson::{from_json}
///|
pub extend AgentFailure with Eq::{equal, not_equal}
///|
pub extend AgentFailure with ToJson::{to_json}
///|
pub extend AgentFailure with FromJson::{from_json}
///|
pub extend AgentOutcome with Eq::{equal, not_equal}
///|
pub extend AgentOutcome with ToJson::{to_json}
///|
pub extend AgentOutcome with FromJson::{from_json}
///|
pub extend AgentFailure with Show::{to_string, output}
///|
pub extend WorkflowError with Show::{to_string, output}
///|
pub impl Show for AgentFailure with fn output(self, logger) {
let text = match self {
TimedOut => "timed out"
NoReport => "finished without a report"
MaxSteps => "exhausted its step ceiling"
ContextYield => "yielded at the context ceiling"
Skipped => "was skipped by the user"
Failed(reason) => "failed: \{reason}"
}
logger.write_string(text)
}
///|
pub impl Show for WorkflowError with fn output(self, logger) {
let text = match self {
AgentFailed(label~, failure~) => "agent '\{label}' \{failure}"
CallBudgetExhausted(label~) =>
"launch allowance exhausted before agent '\{label}' could launch"
QuorumNotReached(need~, ok~, of~) =>
"quorum not reached: \{ok} of \{of} succeeded, \{need} required"
}
logger.write_string(text)
}