Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Completion control

Agent loops combine mode-aware completion instructions, optional judges, input guardrails, and a deterministic completion gate. These controls share the loop contract but can be configured independently.

Completion instructions

The default nudge depends on the tool mode:

  • Tagged text-tool stages ask for concrete tool progress and reserve <done>##DONE##</done> for real completion.
  • Native-tool stages ask for concrete tool progress and treat final text with no tool calls as completion.
  • No-tool sentinel stages ask for concrete progress and reserve bare ##DONE## for completion.

With loop_until_done: true, the system prompt states the same mode-aware completion rule and requires the agent to keep working until the task is done.

Completion judges

done_judge adds a second gate after completion is detected. The loop projects the lossless transcript into bounded effect and verification evidence, then expects one strict object with action: "accept" | "continue", reason, repair, specific_gaps, and accepted_evidence. It accepts no aliases or additional fields. On a continuation, the loop preserves the repair, gaps, and reason together as recovery feedback.

A veto injects runtime feedback and the loop continues until the judge accepts, done_judge.max_invocations is reached, or max_verify_attempts is exhausted. Every judge call emits JudgeDecision with session_id, iteration, verdict, reasoning, next_step, and judge_duration_ms, plus optional trigger.

Set top-level done_judge.max_invocations to a positive integer to cap repeated done-judge vetoes. Once reached, the loop stops with status: "completion_unverified" and stop_reason: "done_judge_cap_reached". The result carries {done_judge: {invocations, vetoes, max_invocations, cap_reached}}. Set it to 0 to disable the terminal cap.

Use done_judge.cadence to gate the judge. Omit it to judge every completion candidate. every: N judges turns N, 2N, and so on; min_iterations_before_first skips the first K turns; and when accepts "always", "stalled", or a closure receiving the same loop-state shape as loop_control.

With when: "stalled", a stall warning can fire the judge directly. An accept action stops the loop with stalled_done_judge before the repeated tool call dispatches. A continue action also skips that pending call and starts the next turn with the judge recovery. Generic stall feedback is used only when the judge returned no recovery text. The corresponding JudgeDecision event carries trigger: "stalled".

import { AgentSpec } from "std/agent/options"

const judged_opts: AgentSpec = {
  loop_until_done: true,
  done_judge: {
    cadence: {every: 5, when: "always", max_invocations: 3},
  },
}
agent_loop(harness, task, system, judged_opts)

when: "stalled" does not fire on ordinary completion candidates. It lets stall diagnostics request a completion check from an observed signal instead of a fixed prompt.

Implementation details and trace schemas live in Agent plane ownership.

Input guardrail (agent_input_guardrail)

agent_input_guardrail(classifier?, options?) from std/agent/guardrails builds the input-side bookend for agent_completion_gate. It runs before the first main agent_loop model turn and spreads into loop options as input_guardrail. A tripwire records an input_guardrail_verdict event, writes a zero-token assistant explanation, and stops the loop with status: "input_guardrail" and stop_reason: "input_guardrail_tripwire".

import { agent_input_guardrail } from "std/agent/guardrails"

const guardrail_opts = agent_input_guardrail(
  { payload -> return cheap_policy_classifier(payload.user_message) },
  {confidence_threshold: 0.8},
)
agent_loop(harness, task, system, base_opts + guardrail_opts)

For an explicit preflight verdict instead of loop composition, use agent_input_guardrail_check(task, classifier?, options?). It returns the same {tripwire, reason, label, confidence} shape.

Completion gate (agent_completion_gate)

agent_completion_gate(runtime, options) returns an options fragment for agent_loop. It checks host-supplied write and verification facts through verify_completion and can add a bounded LLM judge.

import { agent_completion_gate } from "std/agent/completion_gate"

agent_loop(harness, task, system, base_opts + agent_completion_gate(harness.runtime, {
  facts: fn(ctx) { return host_completion_facts(ctx.session_id) },
  verify_command: fn() { return host_run_verify() },
  judge: true,          // optional bounded LLM judge, capped at 5 by default
}))

See Completion gate (std/agent/completion_gate) for the option table, fact types, veto ladder, and bounded judge.