harn-stdlib 0.10.44

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

/**
 * One feedback candidate before turn-level policy is applied.
 *
 * `source` names the producer and `reason` names the observed condition.
 * `action` is optional; when both stop and continue candidates exist, stop
 * wins explicitly. `next_step` participates in knowledge-state deduplication.
 */
pub type AgentFeedbackCandidate = {
  content: string,
  source: string,
  reason: string,
  action?: "inform" | "continue" | "stop",
  kind?: string,
  priority?: int,
  next_step?: string,
}

/** One surviving, reason-coded feedback message. */
pub type AgentFeedbackMessage = {
  content: string,
  sources: list<string>,
  reasons: list<string>,
  action: "inform" | "continue" | "stop",
  kind: string,
  priority: int,
  next_step?: string,
}

fn __feedback_action(value) -> string {
  if value == "stop" || value == "continue" {
    return value
  }
  return "inform"
}

fn __feedback_action_priority(action: string) -> int {
  if action == "stop" {
    return 300
  }
  if action == "continue" {
    return 200
  }
  return 100
}

fn __feedback_unique(values: list<string>) -> list<string> {
  let out = []
  for value in values {
    if value != "" && !contains(out, value) {
      out = out.appending(value)
    }
  }
  return out
}

fn __feedback_candidate(candidate) -> dict {
  const action = __feedback_action(candidate?.action)
  const source = to_string(candidate?.source ?? "unknown")
  const reason = to_string(candidate?.reason ?? "unspecified")
  return {
    content: to_string(candidate?.content ?? ""),
    sources: [source],
    reasons: [reason],
    action: action,
    kind: to_string(candidate?.kind ?? source),
    priority: candidate?.priority ?? __feedback_action_priority(action),
    next_step: trim(to_string(candidate?.next_step ?? "")),
  }
}

fn __feedback_merge(left, right) -> dict {
  const right_wins = right.priority > left.priority
  const winner = if right_wins {
    right
  } else {
    left
  }
  return winner
    + {
    sources: __feedback_unique(left.sources + right.sources),
    reasons: __feedback_unique(left.reasons + right.reasons),
    next_step: if winner.next_step != "" {
      winner.next_step
    } else if left.next_step != "" {
      left.next_step
    } else {
      right.next_step
    },
  }
}

fn __feedback_has_stop(messages) -> bool {
  for message in messages {
    if message.action == "stop" {
      return true
    }
  }
  return false
}

/**
 * Compose feedback for one turn.
 *
 * Policy is deterministic:
 * - byte-identical content is merged once, preserving every source/reason code;
 * - stop candidates suppress contradictory continue candidates;
 * - an unchanged `next_step` is suppressed when `context.history` carries the
 *   same knowledge-state hash;
 * - survivors retain input order, so equal-priority sources never reorder.
 *
 * Returns `{messages, suppressed, history}`. `suppressed` is reason-coded so a
 * durable caller can explain every dropped candidate.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 */
pub fn agent_feedback_compose(candidates, context = nil) -> dict {
  const ctx = context ?? {}
  const history = ctx?.history ?? {}
  const knowledge_state = to_string(ctx?.knowledge_state ?? "")
  let messages = []
  let suppressed = []
  for raw in candidates ?? [] {
    const candidate = __feedback_candidate(raw)
    if candidate.content == "" {
      suppressed = suppressed.appending(
        {
          source: candidate.sources[0],
          reason: candidate.reasons[0],
          suppression_reason: "empty_content",
        },
      )
      continue
    }
    if candidate.next_step != ""
      && candidate.next_step
      == to_string(history?.next_step ?? "")
      && knowledge_state != ""
      && knowledge_state
      == to_string(history?.knowledge_state ?? "") {
      suppressed = suppressed.appending(
        {
          source: candidate.sources[0],
          reason: candidate.reasons[0],
          suppression_reason: "unchanged_next_step",
        },
      )
      continue
    }
    let duplicate_index = -1
    for entry in messages.enumerate() {
      if duplicate_index < 0 && entry.value.content == candidate.content {
        duplicate_index = entry.index
      }
    }
    if duplicate_index >= 0 {
      messages[duplicate_index] = __feedback_merge(messages[duplicate_index], candidate)
      suppressed = suppressed.appending(
        {
          source: candidate.sources[0],
          reason: candidate.reasons[0],
          suppression_reason: "byte_identical_duplicate",
        },
      )
    } else {
      messages = messages.appending(candidate)
    }
  }
  if __feedback_has_stop(messages) {
    let kept = []
    for message in messages {
      if message.action == "continue" {
        suppressed = suppressed.appending(
          {
            source: message.sources[0],
            reason: message.reasons[0],
            suppression_reason: "contradicted_by_stop",
          },
        )
      } else {
        kept = kept.appending(message)
      }
    }
    messages = kept
  }
  let next_history = history
  for message in messages {
    if message.next_step != "" {
      next_history = {next_step: message.next_step, knowledge_state: knowledge_state}
    }
  }
  return {messages: messages, suppressed: suppressed, history: next_history}
}

/**
 * Compose, durably record, and inject one turn's feedback.
 *
 * The typed checkpoint is the durable explanation channel; the ordinary
 * feedback messages remain backward-compatible model-visible transcript rows.
 *
 * @effects: [agent]
 * @errors: []
 * @api_stability: experimental
 */
pub fn agent_feedback_emit(session_id: string, candidates, context = nil) -> dict {
  const composed = agent_feedback_compose(candidates, context)
  agent_emit_event(
    session_id,
    "typed_checkpoint",
    {
      schema: "harn.agent_feedback_composition.v1",
      messages: composed.messages,
      suppressed: composed.suppressed,
      knowledge_state: to_string(context?.knowledge_state ?? ""),
      history: composed.history,
    },
  )
  for message in composed.messages {
    agent_session_inject_feedback(session_id, message.kind, message.content)
  }
  return composed
}

/**
 * Render a feedback exemplar through the session-locked tool format owner.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 */
pub fn agent_feedback_tool_example(tool_name: string, args = [], options = nil) -> string {
  return agent_render_tool_call_exemplar(tool_name, args, options)
}