harn-stdlib 0.8.39

Embedded Harn standard library source catalog
Documentation
import { agent_emit_event, agent_session_inject_feedback } from "std/agent/state"

type AgentStallConfig = {
  enabled: bool,
  threshold: int,
  inject_feedback: bool,
  max_feedback: int,
  exempt_tools: list,
  include_arguments: bool,
}

type AgentStallWarning = {
  iteration: int,
  tool_name: string,
  repeat_count: int,
  threshold: int,
  arguments_digest: string,
  signature_digest: string,
  arguments?: dict,
}

type AgentStallState = {
  last_signature: string,
  streak: int,
  warnings: list<AgentStallWarning>,
  repeated_tool_calls: int,
  feedback_count: int,
}

type AgentStallObservation = {
  state: AgentStallState,
  enabled: bool,
  warning: AgentStallWarning?,
  feedback_deferred: bool,
  config: AgentStallConfig,
}

fn __agent_stall_bool(value, fallback: bool, field: string) -> bool {
  if value == nil {
    return fallback
  }
  if type_of(value) == "bool" {
    return value
  }
  throw "agent_loop: stall_diagnostics." + field + " must be a bool; got " + type_of(value)
}

fn __agent_stall_list(value, field: string) -> list {
  if value == nil {
    return []
  }
  if type_of(value) == "list" {
    return value
  }
  throw "agent_loop: stall_diagnostics." + field + " must be a list; got " + type_of(value)
}

fn __agent_stall_config(value) -> AgentStallConfig {
  if value == nil {
    return {
      enabled: false,
      threshold: 3,
      inject_feedback: true,
      max_feedback: 1,
      exempt_tools: [],
      include_arguments: false,
    }
  }
  if type_of(value) == "bool" {
    return {
      enabled: value,
      threshold: 3,
      inject_feedback: true,
      max_feedback: 1,
      exempt_tools: [],
      include_arguments: false,
    }
  }
  if type_of(value) != "dict" {
    throw "agent_loop: `stall_diagnostics` must be a dict, bool, or nil; got " + type_of(value)
  }
  let threshold = value?.threshold ?? 3
  let max_feedback = value?.max_feedback ?? 1
  let exempt_tools = if value?.exempt_tools != nil {
    __agent_stall_list(value.exempt_tools, "exempt_tools")
  } else {
    __agent_stall_list(value?.allow_repeated_tools, "allow_repeated_tools")
  }
  return {
    enabled: __agent_stall_bool(value?.enabled, true, "enabled"),
    threshold: if type_of(threshold) == "int" && threshold >= 2 {
      threshold
    } else {
      3
    },
    inject_feedback: __agent_stall_bool(value?.inject_feedback, true, "inject_feedback"),
    max_feedback: if type_of(max_feedback) == "int" && max_feedback >= 0 {
      max_feedback
    } else {
      1
    },
    exempt_tools: exempt_tools,
    include_arguments: __agent_stall_bool(value?.include_arguments, false, "include_arguments"),
  }
}

/**
 * agent_stall_initial_state creates repeated-tool-call diagnostic state.
 *
 * @effects: []
 * @allocation: heap
 * @errors: []
 * @api_stability: experimental
 * @example: agent_stall_initial_state()
 */
pub fn agent_stall_initial_state() -> AgentStallState {
  return {last_signature: "", streak: 0, warnings: [], repeated_tool_calls: 0, feedback_count: 0}
}

fn __agent_stall_reset_state(state: AgentStallState) -> AgentStallState {
  return state + {last_signature: "", streak: 0}
}

fn __agent_tool_call_name(call) -> string {
  return to_string(call?.name ?? call?.tool_name ?? "")
}

fn __agent_tool_call_args(call) -> dict {
  let raw = call?.arguments ?? call?.tool_args
  if type_of(raw) == "dict" {
    return raw
  }
  return {}
}

fn __agent_tool_call_signature(call) -> string {
  let args_text = json_stringify(__agent_tool_call_args(call))
  return __agent_tool_call_name(call) + "\n" + args_text
}

fn __agent_stall_feedback_text(warning: AgentStallWarning) -> string {
  return "Stall diagnostic: the last "
    + to_string(warning.repeat_count)
    + " tool calls repeated `"
    + warning.tool_name
    + "` with identical arguments. Use different evidence, finish, or explain why another identical call is necessary before repeating it."
}

fn __agent_stall_warning_record(
  iteration: int,
  tool_name: string,
  args: dict,
  signature: string,
  repeat_count: int,
  config: AgentStallConfig,
) -> AgentStallWarning {
  let args_text = json_stringify(args)
  var record = {
    iteration: iteration,
    tool_name: tool_name,
    repeat_count: repeat_count,
    threshold: config.threshold,
    arguments_digest: "sha256:" + sha256(args_text),
    signature_digest: "sha256:" + sha256(signature),
  }
  if config.include_arguments {
    record = record + {arguments: args}
  }
  return record
}

/**
 * agent_stall_inject_feedback injects bounded stall feedback.
 *
 * @effects: [agent]
 * @allocation: heap
 * @errors: []
 * @api_stability: experimental
 * @example: agent_stall_inject_feedback(session_id, warning, config, state)
 */
pub fn agent_stall_inject_feedback(
  session_id: string,
  warning: AgentStallWarning,
  config: AgentStallConfig,
  state: AgentStallState,
) -> AgentStallState {
  let wants_feedback = config.inject_feedback ?? true
  let should_feedback = wants_feedback && state.feedback_count < config.max_feedback
  if should_feedback {
    agent_session_inject_feedback(session_id, "stall_diagnostics", __agent_stall_feedback_text(warning))
    return state + {feedback_count: state.feedback_count + 1}
  }
  return state
}

fn __agent_stall_maybe_emit(
  session_id: string,
  warning: AgentStallWarning,
  config: AgentStallConfig,
  state: AgentStallState,
  defer_feedback: bool,
) {
  agent_emit_event(session_id, "agent_loop_stall_warning", warning)
  if defer_feedback {
    return {state: state, warning: warning, feedback_deferred: true}
  }
  return {
    state: agent_stall_inject_feedback(session_id, warning, config, state),
    warning: warning,
    feedback_deferred: false,
  }
}

/**
 * agent_stall_observe_tool_calls detects repeated identical tool calls.
 *
 * @effects: [agent]
 * @allocation: heap
 * @errors: [agent_loop]
 * @api_stability: experimental
 * @example: agent_stall_observe_tool_calls(session_id, calls, iteration, config, state, true)
 */
pub fn agent_stall_observe_tool_calls(
  session_id: string,
  tool_calls: list,
  iteration: int,
  raw_config,
  state: AgentStallState,
  defer_feedback: bool,
) -> AgentStallObservation {
  let config = __agent_stall_config(raw_config)
  if !config.enabled {
    return {state: state, enabled: false, warning: nil, feedback_deferred: false, config: config}
  }
  if len(tool_calls) == 0 {
    return {
      state: __agent_stall_reset_state(state),
      enabled: true,
      warning: nil,
      feedback_deferred: false,
      config: config,
    }
  }
  var next_state = state
  var emitted_warning = nil
  var feedback_deferred = false
  for call in tool_calls {
    let tool_name = __agent_tool_call_name(call)
    if tool_name == "" || contains(config.exempt_tools, tool_name) {
      next_state = __agent_stall_reset_state(next_state)
      continue
    }
    let signature = __agent_tool_call_signature(call)
    let streak = if signature == next_state.last_signature {
      next_state.streak + 1
    } else {
      1
    }
    let repeated = if streak > 1 {
      next_state.repeated_tool_calls + 1
    } else {
      next_state.repeated_tool_calls
    }
    next_state = next_state
      + {last_signature: signature, streak: streak, repeated_tool_calls: repeated}
    if streak == config.threshold {
      let warning = __agent_stall_warning_record(
        iteration,
        tool_name,
        __agent_tool_call_args(call),
        signature,
        streak,
        config,
      )
      let emitted = __agent_stall_maybe_emit(session_id, warning, config, next_state, defer_feedback)
      next_state = emitted.state
      if emitted_warning == nil {
        emitted_warning = warning
      }
      feedback_deferred = feedback_deferred || emitted?.feedback_deferred ?? false
      next_state = next_state + {warnings: next_state.warnings.push(warning)}
    }
  }
  return {
    state: next_state,
    enabled: true,
    warning: emitted_warning,
    feedback_deferred: feedback_deferred,
    config: config,
  }
}

/**
 * agent_stall_apply_result adds stall diagnostics to an agent-loop result.
 *
 * @effects: []
 * @allocation: heap
 * @errors: []
 * @api_stability: experimental
 * @example: agent_stall_apply_result(result, enabled, state)
 */
pub fn agent_stall_apply_result(result: dict, stall_enabled: bool, stall_state: AgentStallState) -> dict {
  if !stall_enabled && len(stall_state.warnings) == 0 {
    return result
  }
  return result
    + {
    repeated_tool_calls: stall_state.repeated_tool_calls,
    stall_warnings: stall_state.warnings,
    suspected_loop: len(stall_state.warnings) > 0,
  }
}

fn __agent_done_judge_stall_cadence(opts) {
  let judge = opts?.done_judge
  if type_of(judge) != "dict" {
    return nil
  }
  let cadence = judge?.cadence
  if type_of(cadence) != "dict" || cadence?.when != "stalled" {
    return nil
  }
  return cadence
}

/**
 * agent_stall_done_judge_due decides whether the stall done judge is due.
 *
 * @effects: []
 * @allocation: stack
 * @errors: []
 * @api_stability: experimental
 * @example: agent_stall_done_judge_due(opts, 0, 3)
 */
pub fn agent_stall_done_judge_due(opts, invocations: int, turn_number: int) -> bool {
  let cadence = __agent_done_judge_stall_cadence(opts)
  if cadence == nil {
    return false
  }
  let max_invocations = cadence?.max_invocations
  if max_invocations != nil && invocations >= max_invocations {
    return false
  }
  let min_iterations = cadence?.min_iterations_before_first
  if min_iterations != nil && turn_number <= min_iterations {
    return false
  }
  let every = cadence?.every
  if every != nil && turn_number % every != 0 {
    return false
  }
  return true
}