harn-stdlib 0.8.22

Embedded Harn standard library source catalog
Documentation
import { pick_keys } from "std/collections"

/** 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. */
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,
    llm_retries: 2,
    llm_backoff_ms: 2000,
    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_agent_options. */
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,
  }
}