harn-stdlib 0.10.129

Embedded Harn standard library source catalog
Documentation
import { agent_budget_post_call_stop_reason } from "std/agent/budget"
import {
  __agent_loop_budget_exhaustion,
  __agent_loop_emit_budget_exhausted,
  __agent_loop_iteration_info,
  __agent_loop_record_budget_stop,
  __next_progress_count,
  __next_text_only_count,
} from "std/agent/loop_turn_options"
import {
  agent_emit_event,
  agent_session_record_undispatched_tool_results,
  agent_session_record_usage,
} from "std/agent/state"

/** Whether the turn loop breaks out or starts its next iteration. */
pub type AgentLoopSkippedDispatchAction = "break" | "continue"

/** Everything the skipped turn reads from the loop that owns it. */
pub type AgentLoopSkippedDispatchContext = {
  budget: any,
  budget_decisions: any,
  consecutive_text_only: int,
  current_max: int,
  iteration: int,
  iteration_index: int,
  last_tool_count: int,
  llm_result: any,
  loop_start_ms: int | float,
  tool_calls: any,
  turn_llm_opts: any,
  turn_opts: any,
  turns_since_progress: int,
  visible_text: string,
}

/** The loop state this turn hands back, plus what the loop should do next. */
pub type AgentLoopSkippedDispatchOutcome = {
  action: AgentLoopSkippedDispatchAction,
  final_status: string,
  stop_reason: any,
  budget_exhausted_emitted: bool,
  budget_decisions: any,
  consecutive_text_only: int,
  turns_since_progress: int,
  last_tool_count: int,
}

/**
 * Close out a turn whose tool calls a user interrupt overtook before dispatch.
 *
 * Moved out of the run loop unchanged. It is expressed as an outcome rather
 * than as inline control flow for one reason only: a function cannot `break`
 * its caller's loop, so the two breaks and the trailing continue become an
 * `action` the caller re-dispatches. Every other line is the code that was
 * inline.
 *
 * The turn never dispatches. It records the undispatched results and the usage
 * the model already spent, then either stops on an exhausted budget or takes
 * another iteration with the no-tool-call counters advanced.
 *
 * @effects: [event, agent]
 * @errors: []
 * @api_stability: internal
 * @example: __agent_loop_skipped_dispatch_turn(harness, session, ctx)
 */
pub fn __agent_loop_skipped_dispatch_turn(
  harness: Harness,
  session: any,
  ctx: AgentLoopSkippedDispatchContext,
) -> AgentLoopSkippedDispatchOutcome {
  const budget = ctx.budget
  const current_max = ctx.current_max
  const iteration = ctx.iteration
  const iteration_index = ctx.iteration_index
  const llm_result = ctx.llm_result
  const loop_start_ms = ctx.loop_start_ms
  const tool_calls = ctx.tool_calls
  const turn_llm_opts = ctx.turn_llm_opts
  const turn_opts = ctx.turn_opts
  const visible_text = ctx.visible_text
  let budget_decisions = ctx.budget_decisions
  agent_session_record_undispatched_tool_results(
    harness.agent,
    session.session_id,
    tool_calls,
    "interrupted",
    "a user interrupt arrived before this tool call was dispatched",
  )
  const tool_count_skipped = len(tool_calls)
  const totals_skipped = agent_session_record_usage(
    harness.agent,
    session.session_id,
    llm_result,
    turn_llm_opts,
    iteration_index + 1,
  )
  agent_emit_event(
    harness.agent,
    session.session_id,
    "iteration_end",
    {
      iteration: iteration_index + 1,
      iteration_info: __agent_loop_iteration_info(
        harness.agent,
        harness.clock,
        session.session_id,
        llm_result,
        tool_count_skipped,
        visible_text,
        totals_skipped,
        loop_start_ms,
      )
        + {dispatch_skipped: true, skip_reason: "interrupt_immediate"},
    },
  )
  const skipped_exhaustion = __agent_loop_budget_exhaustion(
    harness.agent,
    harness.clock,
    session.session_id,
    budget,
    iteration,
    totals_skipped,
    loop_start_ms,
    current_max,
  )
  if skipped_exhaustion.exhausted {
    __agent_loop_emit_budget_exhausted(harness.agent, session.session_id, skipped_exhaustion)
    budget_decisions = __agent_loop_record_budget_stop(
      budget_decisions,
      iteration,
      current_max,
      skipped_exhaustion.kind,
    )
    return {
      action: "break",
      final_status: "budget_exhausted",
      stop_reason: skipped_exhaustion.kind,
      budget_exhausted_emitted: true,
      budget_decisions: budget_decisions,
      consecutive_text_only: ctx.consecutive_text_only,
      turns_since_progress: ctx.turns_since_progress,
      last_tool_count: ctx.last_tool_count,
    }
  }
  const skipped_budget_stop = agent_budget_post_call_stop_reason(totals_skipped, turn_opts)
  if skipped_budget_stop != "" {
    return {
      action: "break",
      final_status: "budget_exhausted",
      stop_reason: skipped_budget_stop,
      budget_exhausted_emitted: false,
      budget_decisions: budget_decisions,
      consecutive_text_only: ctx.consecutive_text_only,
      turns_since_progress: ctx.turns_since_progress,
      last_tool_count: ctx.last_tool_count,
    }
  }
  return {
    action: "continue",
    final_status: "",
    stop_reason: nil,
    budget_exhausted_emitted: false,
    budget_decisions: budget_decisions,
    consecutive_text_only: __next_text_only_count(0, ctx.consecutive_text_only),
    turns_since_progress: __next_progress_count(false, ctx.turns_since_progress),
    last_tool_count: 0,
  }
}