harn-stdlib 0.10.130

Embedded Harn standard library source catalog
Documentation
import "std/agent/loop_call_budget"
import "std/agent/loop_call_resolution"
import "std/agent/loop_denial_cutoff"
import "std/agent/loop_finalize"
import "std/agent/loop_foundation"
import "std/agent/loop_purpose_labels"
import "std/agent/loop_resource_dispatch"
import "std/agent/loop_result_status"
import "std/agent/loop_support"
import "std/agent/loop_terminal"
import "std/agent/loop_tool_calls"
import "std/agent/loop_turn_options"
import "std/agent/loop_turn_scope"

// The turn-setup phase of the agent loop: everything between the top of an
// iteration and the request that is about to be sent (harn#7753).
//
// WHY THIS IS ITS OWN MODULE. `loop_run.harn` was one function carrying the
// whole iteration, and it sat against the hard source-length cap with a handful
// of lines to spare, so any change to the agent loop had to fight the file
// before it could fight the problem. The loop's later phases already live in
// owned siblings — call resolution, denial cutoff, tool calls, the terminal
// boundary — and this is the same treatment for the phase that runs first.
//
// WHAT IT OWNS. Four guards that can end the run before a request is made
// (iteration budget, a suspend checkpoint, the pre-call budget block, and the
// input guardrail / pre-turn scope classifier pair), and the assembly of the
// options, prompt and prefill that the request is built from.
//
// CONTROL FLOW CONTRACT. Same shape as `std/agent/loop_terminal`: the caller
// gets an explicit `action` rather than a `break` smuggled across a module
// boundary. `action: "break"` means a guard ended the run and the returned
// record carries exactly the state that guard mutated; `action: "proceed"`
// means the turn is assembled and the returned record carries the values the
// request phase consumes. This is a pure relocation of existing behavior: no
// guard changed, no order changed, and no state that was written before is
// left unwritten.
/**
 * Run the turn-setup phase for one agent-loop iteration.
 *
 * @effects: [host, agent]
 * @errors: [runtime]
 */
pub fn __agent_loop_turn_setup(harness: Harness, session: dict, state: dict) -> dict {
  const message = state.message
  let opts = state.opts
  const iteration = state.iteration
  let call_budget = state.call_budget
  let stall_state = state.stall_state
  let command_hold_cap_tokens = state.command_hold_cap_tokens
  let budget_decisions = state.budget_decisions
  let audit_background_tasks = state.audit_background_tasks
  const budget = state.budget
  const current_max = state.current_max
  const loop_start_ms = state.loop_start_ms
  const primary_provider = state.primary_provider
  const primary_model = state.primary_model
  const boundary_exhaustion = __agent_loop_budget_exhaustion(
    harness.agent,
    harness.clock,
    session.session_id,
    budget,
    iteration,
    nil,
    loop_start_ms,
    current_max,
  )
  if boundary_exhaustion.exhausted {
    __agent_loop_emit_budget_exhausted(harness.agent, session.session_id, boundary_exhaustion)
    budget_decisions = __agent_loop_record_budget_stop(
      budget_decisions,
      iteration,
      current_max,
      boundary_exhaustion.kind,
    )
    return state
      + {
        action: "break",
        final_status: "budget_exhausted",
        stop_reason: boundary_exhaustion.kind,
        budget_decisions: budget_decisions,
        budget_exhausted_emitted: true,
      }
  }
  const checkpoint = __agent_loop_suspend_checkpoint(
    harness.agent,
    harness.runtime,
    session,
    iteration,
  )
  if checkpoint != nil {
    __drain_audit_flushes(audit_background_tasks)
    audit_background_tasks = []
    return state
      + {
        action: "break",
        suspend_result: checkpoint,
        audit_background_tasks: [],
        final_status: "suspended",
        stop_reason: "suspended",
      }
  }
  const turn_budget = call_budget + {iteration: iteration, max: current_max}
  call_budget = __agent_loop_project_call_budget(harness, session, turn_budget)
  if agent_budget_pre_call_blocked(harness.agent, session, opts) {
    // Unset, this gate is indistinguishable from an unexplained break:
    // `loop_finalize.harn` stamps `kind: "unattributed"` for this path.
    return state
      + {
        action: "break",
        call_budget: call_budget,
        final_status: "budget_exhausted",
        stop_reason: "pre_call_budget_block",
      }
  }
  const iteration_index = iteration
  const monologue_actuation_prior_read_streak = stall_state.monologue_actuation.read_only_streak
  stall_state = stall_state
    + {monologue_actuation: stall_state.monologue_actuation + {read_only_streak: 0}}
  const pending_prefill = __purpose_label_arm(harness.agent, session, opts)
  opts = pending_prefill.options
  const iteration_opts = __agent_loop_effective_llm_options(harness.llm, opts)
  agent_emit_event(
    harness.agent,
    session.session_id,
    "iteration_start",
    {
      iteration: iteration_index + 1,
      provider: iteration_opts?.provider ?? "",
      model: iteration_opts?.model ?? "",
    },
  )
  try {
    __host_drain_file_edits(session.session_id)
  } catch (e) {
    nil
  }
  agent_stage(
    harness.agent,
    session.session_id,
    "iteration_start",
    {iteration: iteration_index + 1},
  )
  let turn_opts = agent_skills_match(harness.agent, session, iteration_opts, iteration_index)
  if turn_opts?._skill_activated_this_turn ?? false {
    opts = agent_reset_tool_surface_narrowing(opts)
    turn_opts = agent_reset_tool_surface_narrowing(turn_opts)
  }
  if opts?.mid_conversation_mcp_mount ?? false {
    const skill_mcp_specs = __agent_loop_active_skill_mcp_specs(turn_opts?.active_skills)
    if len(skill_mcp_specs) > 0 {
      opts = agent_mcp_mount_additional(harness.tools, session, opts, skill_mcp_specs)
      const mcp_delta = opts?._mcp_delta_bootstrap
      if mcp_delta != nil {
        turn_opts = agent_mcp_admit_bootstrap(turn_opts, mcp_delta)
      }
    }
  }
  turn_opts = agent_tool_search_inject_if_needed(harness.llm, turn_opts)
  turn_opts = agent_apply_tool_surface_narrowing(turn_opts)
  turn_opts = agent_stance_apply(turn_opts)
  if command_hold_cap_tokens != nil {
    turn_opts = turn_opts + {max_tokens: command_hold_cap_tokens}
    command_hold_cap_tokens = nil
  }
  const turn_llm_opts = __turn_llm_opts(harness, session, turn_opts, iteration_index)
  turn_opts = turn_opts + {purpose_labels: opts?.purpose_labels}
  agent_stage(harness.agent, session.session_id, "pre_compact", {iteration: iteration_index + 1})
  agent_autocompact_if_needed(harness.agent, session, turn_llm_opts)
  agent_stage(harness.agent, session.session_id, "post_compact", {iteration: iteration_index + 1})
  const input_guardrail_verdict = __run_input_guardrail(
    harness.agent,
    turn_llm_opts?._input_guardrail ?? opts?._input_guardrail,
    session,
    message,
    turn_llm_opts,
    iteration_index,
  )
  if input_guardrail_verdict != nil && (input_guardrail_verdict.tripwire ?? false) {
    __input_guardrail_record_skip_turn(
      harness.agent,
      harness.llm,
      session,
      input_guardrail_verdict,
      iteration_index,
    )
    return state
      + {
        action: "break",
        opts: opts,
        call_budget: call_budget,
        stall_state: stall_state,
        command_hold_cap_tokens: command_hold_cap_tokens,
        iteration: iteration + 1,
        final_status: "input_guardrail",
        stop_reason: "input_guardrail_tripwire",
      }
  }
  const scope_verdict = __run_pre_turn_scope_classifier(
    harness.agent,
    turn_llm_opts?._pre_turn_scope_classifier ?? opts?._pre_turn_scope_classifier,
    session,
    message,
    turn_llm_opts,
    iteration_index,
  )
  if __scope_classifier_skip_main(scope_verdict) {
    __scope_classifier_record_skip_turn(
      harness.agent,
      harness.llm,
      session,
      scope_verdict,
      iteration_index,
    )
    return state
      + {
        action: "break",
        opts: opts,
        call_budget: call_budget,
        stall_state: stall_state,
        command_hold_cap_tokens: command_hold_cap_tokens,
        iteration: iteration + 1,
        final_status: "scope_alert",
        stop_reason: "out_of_scope",
      }
  }
  const turn_prompt = __agent_loop_build_turn_prompt(
    harness,
    session,
    turn_llm_opts,
    iteration_index,
  )
  const direct_llm_opts = agent_direct_llm_options(
    turn_llm_opts,
    turn_prompt,
    session.session_id,
    iteration_index + 1,
  )
  const monologue_actuation = agent_monologue_actuation_take_for_turn(
    harness.agent,
    harness.llm,
    session.session_id,
    iteration_index + 1,
    stall_state.monologue_actuation,
    turn_opts,
    turn_llm_opts,
    pending_prefill.prefill == "",
    agent_tool_call_paradigm(harness.llm, turn_llm_opts),
  )
  stall_state = stall_state + {monologue_actuation: monologue_actuation.state}
  const one_shot_prefill = if monologue_actuation.applied {
    monologue_actuation.prefill
  } else {
    pending_prefill.prefill
  }
  const prefill_eligibility = agent_prefill_eligibility(
    harness.llm,
    one_shot_prefill,
    direct_llm_opts,
  )
  // A prefill is a request-level continuation and therefore pins this
  // one request to the route whose capability was checked. Ordinary
  // requests retain equivalent-route failover.
  const base_llm_opts = if prefill_eligibility.allowed {
    direct_llm_opts
  } else {
    __agent_loop_enable_equivalent_failover(direct_llm_opts, primary_provider, primary_model, opts)
  }
  // Caller-supplied and recovery-generated prefills converge here. The
  // typed eligibility decision above is shared with monologue actuation,
  // so no path can bypass caller, routing, or catalog capability checks.
  const llm_opts = if prefill_eligibility.allowed {
    llm_arm_one_shot_prefill(one_shot_prefill, base_llm_opts)
  } else {
    base_llm_opts
  }
  return state
    + {
      action: "proceed",
      opts: opts,
      iteration: iteration,
      call_budget: call_budget,
      stall_state: stall_state,
      command_hold_cap_tokens: command_hold_cap_tokens,
      iteration_index: iteration_index,
      monologue_actuation_prior_read_streak: monologue_actuation_prior_read_streak,
      turn_opts: turn_opts,
      turn_llm_opts: turn_llm_opts,
      turn_prompt: turn_prompt,
      llm_opts: llm_opts,
    }
}