import { pick_keys } from "std/collections"
// -------------------------------------------------------------------------------------------------
// Typed option aliases (the documented way to build workflow stage specs).
//
// Co-located with the stage-option normalizers below so the alias and the
// normalization logic stay in one file. Rust twins live in
// `crates/harn-vm/src/orchestration/policy/types.rs` (`RetryPolicy`,
// `ModelPolicy`, `StageContract`) and
// `crates/harn-vm/src/orchestration/workflow.rs` (`WorkflowNode`); key
// parity is pinned by `crates/harn-vm/tests/typed_options_parity.rs`.
// -------------------------------------------------------------------------------------------------
/**
* Per-stage retry policy (`retry_policy:` on a stage spec).
*
* Rust twin: `RetryPolicy` in
* `crates/harn-vm/src/orchestration/policy/types.rs`. The
* `repair_prompt_builder` and `feedback` keys are the retry-with-feedback
* surface: a builder callback (or bounded template) that threads the prior
* attempt's verification findings into the next attempt's prompt. They are
* typed ahead of the stage-loop inversion that executes them.
*/
pub type WorkflowRetryPolicy = {
max_attempts?: int,
verify?: bool,
repair?: bool,
backoff_ms?: int,
backoff_multiplier?: float,
repair_prompt_builder?: any,
feedback?: bool | {max_chars?: int},
}
/**
* Stage input/output artifact contract (`input_contract:` /
* `output_contract:` on a stage spec).
*
* Rust twin: `StageContract` in
* `crates/harn-vm/src/orchestration/policy/types.rs`.
*/
pub type StageContract = {
input_kinds?: list<string>,
output_kinds?: list<string>,
min_inputs?: int,
max_inputs?: int,
require_transcript?: bool,
schema?: dict,
}
/**
* Per-stage model and tool-loop policy (`model_policy:` on a stage spec).
* Mirrors the Rust struct field-for-field; the broader normalizer input
* shape (which also accepts reasoning/thinking hints) is
* `WorkflowModelPolicy` below.
*
* Rust twin: `ModelPolicy` in
* `crates/harn-vm/src/orchestration/policy/types.rs`.
*/
pub type ModelPolicySpec = {
provider?: string,
model?: string,
model_tier?: string,
temperature?: float,
max_tokens?: int,
max_iterations?: int,
iteration_budget?: string | dict,
max_nudges?: int,
nudge?: string,
tool_examples?: string,
post_turn_callback?: any,
stop_after_successful_tools?: list<string>,
require_successful_tools?: list,
turn_policy?: {require_action_or_yield?: bool, allow_done_sentinel?: bool, max_prose_chars?: int},
tool_format?: string,
native_tool_fallback?: string,
}
// Inline copy of `TurnPolicy` (std/agent/options) — cross-module type
// references do not structurally resolve for importing consumers yet.
/**
* One workflow stage node as accepted by `workflow_execute` graphs.
*
* Rust twin: `WorkflowNode` in
* `crates/harn-vm/src/orchestration/workflow.rs`. `context_assembler` has
* no serde twin (it can carry closures and is preserved as a raw value on
* the Rust side).
*/
pub type StageSpec = {
id?: string,
kind?: string,
mode?: string,
prompt?: string,
system?: string,
task_label?: string,
done_sentinel?: string,
tools?: any,
model_policy?: ModelPolicySpec,
auto_compact?: bool | dict,
output_visibility?: string,
context_policy?: dict,
retry_policy?: WorkflowRetryPolicy,
capability_policy?: dict,
approval_policy?: any,
input_contract?: StageContract,
output_contract?: StageContract,
branch_semantics?: dict,
map_policy?: dict,
join_policy?: dict,
reduce_policy?: dict,
escalation_policy?: dict,
verify?: any,
exit_when_verified?: bool,
metadata?: dict,
context_assembler?: dict,
}
/**
* Run-level options accepted by `workflow_execute(task, graph, artifacts?,
* options?)`. See `docs/src/workflow-runtime.md` for semantics.
*
* Rust twin: none — these are consumed by the workflow driver in
* `std/workflow/execute` and the persistence layer key-by-key.
*/
pub type WorkflowExecuteOptions = {
max_steps?: int,
persist_path?: string,
resume_path?: string,
resume_run?: any,
replay_path?: string,
replay_run?: any,
replay_mode?: string,
audit?: bool | dict,
mutation_scope?: string | dict,
approval_policy?: any,
}
/**
* workflow_execute_options is the typed constructor for `workflow_execute`
* run options. Direct dict literals are checked against
* `WorkflowExecuteOptions`.
*
* @effects: []
* @errors: []
*/
pub fn workflow_execute_options(options: WorkflowExecuteOptions = {}) -> WorkflowExecuteOptions {
return options
}
/**
* Concise spec accepted by `workflow_stages` (std/workflow/patterns) — the
* ergonomic builder that expands a linear list of `StageSpec`s into the
* `{entry, nodes, edges}` graph shape `workflow_execute` consumes. `stages`
* are wired head-to-tail (each stage's `success`/default branch flows to the
* next) unless explicit `edges` are supplied; `entry` defaults to the first
* stage's id. Pure sugar: the result is byte-identical to the equivalent
* hand-authored `workflow_graph({...})`.
*/
pub type WorkflowStagesSpec = {
stages: list<StageSpec>,
name?: string,
entry?: string,
edges?: list<dict>,
}
/** Allowed values for `WorkflowAutonomyPolicyConfig.autonomy_tier`. */
type WorkflowAutonomyTier = "shadow" | "suggest" | "act_with_approval" | "act_auto"
/** Caller-supplied config consumed by `workflow_autonomy_policy`. */
type WorkflowAutonomyPolicyConfig = {
agent_id?: string,
agent?: string,
autonomy_tier?: WorkflowAutonomyTier,
tier?: WorkflowAutonomyTier,
action_tiers?: dict,
agent_tiers?: dict,
agent_action_tiers?: dict,
reviewers?: list<string>,
}
/** Normalized autonomy policy returned by `workflow_autonomy_policy`. */
type WorkflowAutonomyPolicy = {
agent_id: string?,
autonomy_tier: WorkflowAutonomyTier,
action_tiers: dict,
agent_tiers: dict,
agent_action_tiers: dict,
reviewers: list<string>,
}
/**
* Per-stage model and tool-loop policy. Open shape — every field is optional
* so callers can supply only the dimensions they care about.
*/
type WorkflowModelPolicy = {
provider?: string,
model?: string,
model_tier?: string,
temperature?: float,
top_p?: float,
top_k?: int,
max_tokens?: int,
thinking?: any,
reasoning_effort?: string,
reasoning_policy?: any,
thinking_policy?: any,
reasoning_scale?: any,
problem_scale?: any,
reasoning_task?: string,
nudge?: any,
tool_examples?: string,
turn_policy?: string,
stop_after_successful_tools?: list<string>,
require_successful_tools?: list<string>,
max_iterations?: int,
iteration_budget?: any,
max_nudges?: int,
tool_format?: string,
native_tool_fallback?: string,
}
/** Host-supplied tool-format defaults consumed when normalizing a stage. */
type WorkflowStageHostConfig = {env_tool_format?: string, default_tool_format?: string}
/** Caller-supplied config consumed by `workflow_stage_agent_options`. */
type WorkflowStageOptionsConfig = {
model_policy?: WorkflowModelPolicy,
host?: WorkflowStageHostConfig,
session_id?: string,
done_sentinel?: any,
exit_when_verified?: bool,
mode?: string,
has_tools?: bool,
}
/** Normalized stage options returned by `workflow_stage_agent_options`. */
type WorkflowStageAgentOptions = {
run_agent_loop: bool,
tool_format: string,
llm_options: dict,
agent_loop_options: dict,
}
fn __workflow_non_empty_string(value) {
if type_of(value) != "string" {
return nil
}
let trimmed = trim(value)
if trimmed == "" {
return nil
}
return trimmed
}
fn __workflow_stage_tool_format(model_policy: WorkflowModelPolicy, host: WorkflowStageHostConfig) -> string {
let explicit = __workflow_non_empty_string(model_policy.tool_format)
if explicit != nil {
return explicit
}
let env = __workflow_non_empty_string(host.env_tool_format)
if env != nil {
return env
}
let default_format = __workflow_non_empty_string(host.default_tool_format)
if default_format != nil {
return default_format
}
return "text"
}
fn __workflow_native_tool_fallback(value) -> string {
let policy = __workflow_non_empty_string(value)
if policy != nil {
return policy
}
return "reject"
}
fn __workflow_autonomy_tier(value) -> WorkflowAutonomyTier {
let tier = value ?? "act_auto"
if tier == "shadow" || tier == "suggest" || tier == "act_with_approval" || tier == "act_auto" {
return tier
}
throw "workflow_autonomy_policy: tier must be one of shadow, suggest, act_with_approval, act_auto"
}
/**
* workflow_autonomy_policy normalizes tier policy for with_autonomy_policy.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn workflow_autonomy_policy(config: WorkflowAutonomyPolicyConfig = {}) -> WorkflowAutonomyPolicy {
return {
agent_id: config.agent_id ?? config.agent,
autonomy_tier: __workflow_autonomy_tier(config.autonomy_tier ?? config.tier),
action_tiers: config.action_tiers ?? {},
agent_tiers: config.agent_tiers ?? {},
agent_action_tiers: config.agent_action_tiers ?? {},
reviewers: config.reviewers ?? [],
}
}
fn __workflow_llm_options(
config: WorkflowStageOptionsConfig,
model_policy: WorkflowModelPolicy,
tool_format: string,
) -> dict {
let model_option_keys = ["provider", "model", "model_tier", "temperature", "max_tokens", "thinking", "reasoning_effort"]
var options = {tool_format: tool_format} + pick_keys(model_policy, model_option_keys, {drop_nil: true})
if __workflow_non_empty_string(config.session_id) != nil {
options = options + {session_id: __workflow_non_empty_string(config.session_id)}
}
return options
}
fn __workflow_agent_loop_options(
config: WorkflowStageOptionsConfig,
model_policy: WorkflowModelPolicy,
tool_format: string,
) -> dict {
let loop_option_keys = [
"nudge",
"tool_examples",
"turn_policy",
"stop_after_successful_tools",
"require_successful_tools",
"iteration_budget",
"reasoning_policy",
"thinking_policy",
"reasoning_scale",
"problem_scale",
"reasoning_task",
]
var options = {
loop_until_done: true,
max_iterations: model_policy.max_iterations ?? 16,
max_nudges: model_policy.max_nudges ?? 3,
tool_retries: 0,
tool_backoff_ms: 1000,
schema_retries: 0,
tool_format: tool_format,
native_tool_fallback: __workflow_native_tool_fallback(model_policy.native_tool_fallback),
daemon: false,
exit_when_verified: config.exit_when_verified ?? false,
loop_detect_warn: 2,
loop_detect_block: 3,
loop_detect_skip: 4,
}
if __workflow_non_empty_string(config.session_id) != nil {
options = options + {session_id: __workflow_non_empty_string(config.session_id)}
}
if config.done_sentinel != nil {
options = options + {done_sentinel: config.done_sentinel}
}
return options + pick_keys(model_policy, loop_option_keys, {drop_nil: true})
}
/**
* workflow_stage_spec is the typed constructor for one workflow stage node.
* Direct dict literals are checked against `StageSpec`, so stage-spec typos
* fail at check time instead of being silently dropped by graph
* normalization.
*
* @effects: []
* @errors: []
*/
pub fn workflow_stage_spec(spec: StageSpec = {}) -> StageSpec {
return spec
}
/**
* workflow_stage_agent_options.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn workflow_stage_agent_options(config: WorkflowStageOptionsConfig = {}) -> WorkflowStageAgentOptions {
let model_policy: WorkflowModelPolicy = config.model_policy ?? {}
let host: WorkflowStageHostConfig = config.host ?? {}
let tool_format: string = __workflow_stage_tool_format(model_policy, host)
let llm_options = __workflow_llm_options(config, model_policy, tool_format)
let loop_options = __workflow_agent_loop_options(config, model_policy, tool_format)
return {
run_agent_loop: config.mode == "agent" || (config.has_tools ?? false),
tool_format: tool_format,
llm_options: llm_options,
agent_loop_options: llm_options + loop_options,
}
}