/**
* @harn-entrypoint-category workflow.stdlib
*
* std/workflow/patterns — small, deterministic workflow recipes. These are
* builders and control-flow helpers, not another workflow runtime and not a
* provider client.
*/
fn __patterns_dict(value) {
if type_of(value) == "dict" {
return value
}
return {}
}
fn __patterns_list(value) {
if type_of(value) == "list" {
return value
}
return []
}
fn __patterns_is_callable(value) -> bool {
let kind = type_of(value)
return kind == "function" || kind == "closure" || kind == "fn"
}
fn __patterns_non_empty_string(value) {
if type_of(value) != "string" {
return nil
}
let trimmed = trim(value)
if trimmed == "" {
return nil
}
return trimmed
}
fn __patterns_model_policy(config, key) {
let cfg = __patterns_dict(config)
let specific = __patterns_dict(cfg?[key])
let shared = __patterns_dict(cfg?.model_policy)
return shared + specific
}
fn __patterns_context_policy(config, key) {
let cfg = __patterns_dict(config)
let specific = __patterns_dict(cfg?[key])
let shared = __patterns_dict(cfg?.context_policy)
return shared + specific
}
fn __patterns_output_contract(default_kind, override_cfg) {
let cfg = __patterns_dict(override_cfg)
if cfg?.output_contract != nil {
return cfg.output_contract
}
return {output_kinds: [default_kind]}
}
fn __patterns_stage_node(config, key, defaults) {
let cfg = __patterns_dict(config)
let explicit_node = cfg?[key + "_node"]
let override_cfg = if explicit_node != nil {
__patterns_dict(explicit_node)
} else if key == "verify" {
{}
} else {
__patterns_dict(cfg?[key])
}
var node = defaults + override_cfg
if node?.model_policy == nil {
node = node + {model_policy: __patterns_model_policy(cfg, key + "_model_policy")}
}
if node?.context_policy == nil {
node = node + {context_policy: __patterns_context_policy(cfg, key + "_context_policy")}
}
return node
}
/**
* Build a one-stage graph that runs an acting stage and then a verify stage.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn workflow_self_verifying_graph(config = nil) {
let cfg = __patterns_dict(config)
let act_id = __patterns_non_empty_string(cfg?.act_id) ?? "act"
let verify_id = __patterns_non_empty_string(cfg?.verify_id) ?? "verify"
let act = __patterns_stage_node(
cfg,
"act",
{
kind: "stage",
mode: cfg?.mode ?? "agent",
model_policy: cfg?.model_policy ?? {},
output_contract: __patterns_output_contract(cfg?.act_output_kind ?? "draft", cfg?.act),
},
)
let verify = __patterns_stage_node(
cfg,
"verify",
{
kind: "verify",
mode: cfg?.verify_mode ?? "command",
verify: cfg?.verify ?? {},
output_contract: __patterns_output_contract(cfg?.verify_output_kind ?? "verification_result", cfg?.verify_node),
},
)
return workflow_graph(
{
name: cfg?.name ?? "self_verifying",
entry: act_id,
nodes: {[act_id]: act, [verify_id]: verify},
edges: [{from: act_id, to: verify_id}],
},
)
}
/**
* Build the common implement -> verify -> repair -> verify loop.
*
* The graph is explicit: verification failure follows branch `"failed"` into
* the repair stage, then repair returns to verify. Callers bound total work
* with workflow_execute({max_steps}) and per-agent `iteration_budget`.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn workflow_command_verify_graph(config = nil) {
let cfg = __patterns_dict(config)
let implement_id = __patterns_non_empty_string(cfg?.implement_id) ?? "implement"
let verify_id = __patterns_non_empty_string(cfg?.verify_id) ?? "verify"
let repair_id = __patterns_non_empty_string(cfg?.repair_id) ?? "repair"
let implement = __patterns_stage_node(
cfg,
"implement",
{
kind: "stage",
mode: cfg?.mode ?? "agent",
model_policy: cfg?.model_policy ?? {},
output_contract: __patterns_output_contract(cfg?.implement_output_kind ?? "draft", cfg?.implement),
},
)
let verify = __patterns_stage_node(
cfg,
"verify",
{
kind: "verify",
mode: cfg?.verify_mode ?? "command",
verify: cfg?.verify ?? {},
output_contract: __patterns_output_contract(cfg?.verify_output_kind ?? "verification_result", cfg?.verify_node),
},
)
let repair_policy = __patterns_dict(cfg?.repair_model_policy)
let repair = __patterns_stage_node(
cfg,
"repair",
{
kind: "stage",
mode: cfg?.repair_mode ?? "agent",
model_policy: __patterns_dict(cfg?.model_policy) + repair_policy,
metadata: {repair: true, verifies: verify_id, repairs: implement_id},
output_contract: __patterns_output_contract(cfg?.repair_output_kind ?? "patch", cfg?.repair),
},
)
return workflow_graph(
{
name: cfg?.name ?? "command_verify",
entry: implement_id,
nodes: {[implement_id]: implement, [verify_id]: verify, [repair_id]: repair},
edges: [
{from: implement_id, to: verify_id},
{from: verify_id, to: repair_id, branch: "failed"},
{from: repair_id, to: verify_id},
],
},
)
}
/**
* Build a graph with only a verification stage. Useful for replaying a
* deterministic verifier against supplied artifacts.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn workflow_verification_only_graph(config = nil) {
let cfg = __patterns_dict(config)
let verify_id = __patterns_non_empty_string(cfg?.verify_id) ?? "verify"
let verify = __patterns_stage_node(
cfg,
"verify",
{
kind: "verify",
mode: cfg?.verify_mode ?? "command",
verify: cfg?.verify ?? {},
output_contract: __patterns_output_contract(cfg?.verify_output_kind ?? "verification_result", cfg?.verify_node),
},
)
return workflow_graph(
{name: cfg?.name ?? "verification_only", entry: verify_id, nodes: {[verify_id]: verify}, edges: []},
)
}
fn __failover_disposition(value, fallback_action) {
if value == nil {
return {action: fallback_action}
}
if type_of(value) == "string" {
return {action: value}
}
if type_of(value) == "dict" {
return value + {action: value?.action ?? value?.decision ?? value?.status ?? fallback_action}
}
return {action: fallback_action, raw: value}
}
fn __failover_route_id(route, index) -> string {
let explicit = __patterns_non_empty_string(route?.id ?? route?.name ?? route?.model ?? route?.provider)
if explicit != nil {
return explicit
}
return "route-" + to_string(index)
}
fn __failover_evaluate(config, route, ctx) {
let evaluator = config?.evaluate
if __patterns_is_callable(evaluator) {
return __failover_disposition(evaluator(route, ctx), "call")
}
return {action: "call"}
}
fn __failover_classify(config, route, result, ctx) {
let classifier = config?.classify
if __patterns_is_callable(classifier) {
return __failover_disposition(classifier(route, result, ctx), "continue")
}
if result?.ok ?? false {
return {action: "success"}
}
return {action: "continue"}
}
fn __failover_terminal_status(decision) -> string {
let action = to_string(decision?.action ?? "")
if action == "success" {
return "success"
}
if action == "break" || action == "terminal" {
return to_string(decision?.status ?? decision?.reason ?? "terminal")
}
return action
}
/**
* Run a deterministic failover loop over opaque route handles.
*
* Required config: `{routes, call}`. Optional callbacks:
* - `evaluate(route, ctx) -> {action: "call"|"skip", reason?}`
* - `classify(route, result, ctx) -> {action: "success"|"continue"|"break"|"terminal", reason?}`
*
* Harn owns attempt ordering and receipts. The caller owns provider HTTP,
* credentials, endpoint policy, billing, and any route-handle semantics.
*
* @effects: []
* @errors: ["workflow_failover: call must be callable"]
* @api_stability: experimental
*/
pub fn workflow_failover(config = nil) {
let cfg = __patterns_dict(config)
let routes = __patterns_list(cfg?.routes)
let caller = cfg?.call
if !__patterns_is_callable(caller) {
throw "workflow_failover: call must be callable"
}
let max_attempts = to_int(cfg?.max_attempts ?? len(routes))
let base_ctx = __patterns_dict(cfg?.context)
var ctx = base_ctx + {attempts: []}
var attempts = []
var call_attempts = 0
var index = 0
for route in routes {
let route_id = __failover_route_id(route, index)
let evaluated = __failover_evaluate(cfg, route, ctx)
if evaluated.action == "skip" {
let attempt = {
index: index,
route_id: route_id,
route: route,
status: "skipped",
decision: "skip",
reason: evaluated?.reason,
}
attempts = attempts + [attempt]
ctx = ctx + {attempts: attempts, last_attempt: attempt}
index = index + 1
continue
}
if call_attempts >= max_attempts {
break
}
call_attempts = call_attempts + 1
let result = caller(route, ctx)
let decision = __failover_classify(cfg, route, result, ctx + {result: result})
let action = to_string(decision?.action ?? "continue")
let attempt = {
index: index,
route_id: route_id,
route: route,
status: if action == "success" {
"succeeded"
} else {
"failed"
},
decision: action,
reason: decision?.reason,
result: result,
}
attempts = attempts + [attempt]
ctx = ctx + {attempts: attempts, last_attempt: attempt}
if action == "success" {
return {
ok: true,
status: "success",
selected: route,
selected_route_id: route_id,
value: result,
attempts: attempts,
call_attempts: call_attempts,
}
}
if action == "break" || action == "terminal" {
return {
ok: false,
status: __failover_terminal_status(decision),
selected: route,
selected_route_id: route_id,
value: result,
attempts: attempts,
call_attempts: call_attempts,
}
}
index = index + 1
}
return {
ok: false,
status: "exhausted",
selected: nil,
selected_route_id: nil,
value: nil,
attempts: attempts,
call_attempts: call_attempts,
}
}