Migrating to 0.10
Harn 0.10 removes long-deprecated resilience and composition surfaces.
There are no compatibility shims. harn check and the runtime reject removed
LLM option names and report the canonical replacement.
LLM call options
The llm_call family, streams, and agent_loop dispatch now share one option
registry. Update option dicts before upgrading the runtime; stale computed dicts
fail at the call boundary instead of losing fields during projection.
Update a call
- Replace each removed top-level key using the table below.
- Move provider-specific knobs below
provider_options.<provider>. - Run
harn check <path>to find removed names in literals. - Exercise any computed option builders. Runtime validation covers shapes the checker cannot know statically.
// Before
const old = llm_call(prompt, nil, {
output_schema: schema,
output_validation: "error",
reasoning_effort: "high",
system_prefix: "Be precise.",
timeout: 120,
ollama: {num_ctx: 32768},
})
// After
import {system_before} from "std/llm/prompts"
const current = llm_call(prompt, nil, {
output: {schema: schema, validation: "error"},
effort: "high",
system: [system_before("Be precise.")],
timeout_ms: 120000,
provider_options: {ollama: {num_ctx: 32768}},
})
Replacement table
| Removed | Replacement |
|---|---|
schema, json_schema, output_schema | output: schema or output: {schema, ...} |
response_format, output_format | output: "json" or output: {schema, ...} |
output_validation | output: {schema, validation: ...} |
schema_stream_abort | output: {schema, stream_abort: ...} |
llm_repair | repair |
system_preamble, system_prefix, system_context | system: [{content, position: "before"}] |
system_appendix, system_suffix | system: [{content, position: "after"}] |
system_prompt_parts | system |
caps, project_context_profile | capabilities, context_profile |
api, role | api_mode, model_role |
prefer, fallback_strategy, strategy | route_policy |
model_ladder | models for inline steps; ladder for a catalog name |
budget_usd | budget: {max_cost_usd: ...} |
reasoning_effort | effort |
thinking_policy, problem_scale, task_kind, task | reasoning_policy, reasoning_scale, reasoning_task |
hosted_tools | provider_tools |
response_store, responses_store | store |
timeout, idle_timeout | timeout_ms, idle_timeout_ms (milliseconds) |
fast | speed: "fast" |
top-level provider names such as openai or ollama | provider_options: {openai: {...}} |
transcript | open/resume a session and pass session_id |
The complete live surface is in the llm_call reference.
llm_retries / llm_backoff_ms (removed)
The per-call transient-retry options on llm_call-family surfaces and
agent_loop are gone. llm_call and agent_loop are now fail-fast on
transient provider errors (429 / 5xx / connection); retry policy is
composed on the caller seam with with_retry from std/llm/handlers.
Mind the off-by-one: llm_retries: K counted retries after the
first attempt, while with_retry's max_attempts counts total
attempts, so llm_retries: K → with_retry(..., {max_attempts: K + 1}).
Before (0.9)
const r = llm_call("prompt", nil, {llm_retries: 2, llm_backoff_ms: 500})
After (0.10)
import {default_llm_caller} from "std/llm/caller"
import {with_retry} from "std/llm/handlers"
const caller = with_retry(default_llm_caller(), {max_attempts: 3, base_ms: 500})
const envelope = caller({prompt: "prompt", system: nil, opts: {}})
For agent_loop, wire the composed caller into the llm_caller: seam:
const result = agent_loop(task, system, {
loop_until_done: true,
llm_caller: with_retry(default_llm_caller(), {max_attempts: 3}),
})
Behavior changes that ride along:
agent_loopprofiles no longer default to two transient retries. The pre-0.10 profiles injectedllm_retries: 2; loops that relied on that implicit resilience must opt in viallm_caller:+with_retry— or build their options withagent_preset(kind, ...), which bakes the equivalent bounded transport retry (with_retry,max_attempts: 3) onto the effectivellm_caller:by default (retry: falseopts out).- The zero-token empty-completion retry is now a fixed built-in: one
silent retry per call for provider-shaped routes (none for the
mockprovider, whose scripted turns must stay deterministic). It is no longer widened byllm_retries. - The proactive per-route rate limiter, shared 429/overload cooldowns, the network circuit breaker, and the one-shot tool-channel / stream-transport degrades are unchanged — they never depended on the removed options.
std/agent/stack (removed)
The std/agent/stack module and its composition bundle are gone:
agent_stack, agent_llm_caller, agent_tool_stack,
agent_stack_audit_line, and agent_stack_model_policy no longer
exist. Compose the pieces directly:
- Model/route resolution:
agent_model_optionsandagent_sanitize_model_optionssurvive and now live instd/agent/options(same signatures; theaudit.kindlabel changed fromagent_stack.model_optionstoagent_model_options). They are ordinary module exports now, not ambient builtins — addimport {agent_model_options} from "std/agent/options". - LLM middleware:
compose/with_retry/with_logging/with_cache/with_budgetfromstd/llm/handlerson thellm_caller:seam. - Tool middleware:
with_required_reason,compose_tool_callers, andtools_use_middlewarefromstd/llm/tool_middlewareon thetool_caller:seam. agent_stack_model_policy's projection is apick_keys(options, [...], {drop_nil: true})call with the stable model-policy keys.
Before (0.9)
import {agent_stack} from "std/agent/stack"
const stack = agent_stack({role: "planner", retry: {max_attempts: 3}, required_reason: true})
const result = agent_loop(task, system, stack.options + {loop_until_done: true})
After (0.10)
import {agent_model_options} from "std/agent/options"
import {default_llm_caller} from "std/llm/caller"
import {with_retry} from "std/llm/handlers"
import {compose_tool_callers, with_required_reason} from "std/llm/tool_middleware"
const route = agent_model_options({role: "planner"})
const mw = with_required_reason({})
const result = agent_loop(task, system, route.options + {
loop_until_done: true,
llm_caller: with_retry(default_llm_caller(), {max_attempts: 3}),
tool_caller: compose_tool_callers([mw.caller]),
})
*_agent preset wrappers and agent_budget (removed)
The eight one-line wrappers in std/agent/presets — audit_agent,
repair_agent, summary_agent, verify_agent, merge_captain_agent,
review_captain_agent, oncall_captain_agent,
release_captain_agent — are gone. agent_preset(kind, options?) is
the single preset surface; spread its result into agent_loop:
Before (0.9)
import {audit_agent} from "std/agent/presets"
const result = audit_agent("Audit the release", {tools: tools})
After (0.10)
import {agent_preset} from "std/agent/presets"
const opts = agent_preset("audit", {tools: tools})
const result = agent_loop("Audit the release", opts?.system, opts)
agent_budget(kind_or_options, overrides?) is also gone. Preset kinds
already install their default iteration_budget; to override, pass an
iteration_budget dict (or the "adaptive" string sugar) directly:
const opts = agent_preset("repair", {iteration_budget: {mode: "adaptive", initial: 4, max: 24, extend_by: 2}})
transcript_policy (verified gone)
The pre-0.7 transcript_policy dict on workflow nodes and the
workflow_set_transcript_policy builtin were already removed in 0.7 —
sessions are the sole surface (see
Migrating from 0.6.x to 0.7.0). 0.10 re-verified that no
acceptance remains. Note that the worker-resume transcript policy
(continue_transcript on worker resume/replay) and the task-plan IR's
forward-declared transcript_policy metadata field are different,
still-supported mechanisms.