harn-stdlib 0.10.127

Embedded Harn standard library source catalog
Documentation
// std/agent/completion_evidence — typed, bounded terminal evidence contracts.
import { CompletionRequirementEvidenceRecord } from "std/agent/completion_requirements"
import { CompletionVerificationState } from "std/agent/judge_internals"
import { CompletionObligations } from "std/agent/obligations"
import {
  AgentTranscriptToolLifecycle,
  AgentTranscriptToolLifecycleStatus,
} from "std/agent/transcript"

const COMPLETION_EVIDENCE_FIELD_CHAR_LIMIT = 4500

const COMPLETION_EVIDENCE_LABEL_CHAR_LIMIT = 160

const COMPLETION_FINAL_OUTPUT_EVIDENCE_INDEX = -1

pub const COMPLETION_VERIFICATION_EVIDENCE_INDEX = -2

pub type CompletionEvidenceRole = "observation" | "mutation" | "verification" | "other"

pub type CompletionToolSemantics = {
  name: string,
  kind: string,
  side_effect_level: string,
  read_only: bool,
  evidence_role: CompletionEvidenceRole,
}

pub type CompletionEvidenceAction = {
  ordinal: int,
  evidence_index: int,
  tool_call_id: string,
  name: string,
  semantics: CompletionToolSemantics,
  lifecycle_status: AgentTranscriptToolLifecycleStatus,
  arguments: string,
  observation: string,
  result_facts: string,
  has_error_result: bool,
}

/**
 * One read-only call, reduced to what a completion authority needs to weigh it.
 *
 * A goal that resolves to "there is nothing to build here" can only be
 * supported by read-only evidence, so the packet must be able to represent it.
 * These records are deliberately narrow: a name, what was looked at, and a
 * one-line digest of what came back. The full result body stays out of the
 * packet and remains on the raw transcript for deterministic gates and replay.
 */
pub type CompletionReadOnlyEvidence = {
  ordinal: int,
  evidence_index: int,
  name: string,
  target: string,
  result_digest: string,
  has_error_result: bool,
}

/**
 * The bounded read-only record set, with its own budget accounting.
 *
 * `read_only_call_count` is every read-only call in the turn. `included_count`
 * is how many carry a record here, `omitted_count` the remainder. The budget
 * degrades to counts, never to silence: a summary that could not carry a single
 * record still reports how many reads happened, so a judge is never shown a
 * window narrower than it believes.
 */
pub type CompletionResearchSummary = {
  schema: "harn.completion_judge_research_summary.v1",
  read_only_call_count: int,
  included_count: int,
  omitted_count: int,
  reads: list<CompletionReadOnlyEvidence>,
}

pub type CompletionEvidenceIssue = {
  code: string,
  record_index: int,
  message: string,
  details: string,
}

pub type CompletionEvidencePacket = {
  schema: "harn.completion_judge_evidence.v2",
  raw_message_count: int,
  raw_transcript_digest: string,
  relevant_action_count: int,
  // Read-only calls that carry no record in `research_summary`. Reads that do
  // carry one are represented, not omitted, and are not counted here.
  omitted_read_only_call_count: int,
  omitted_older_action_count: int,
  lifecycle_issue_count: int,
  lifecycle_issues: list<CompletionEvidenceIssue>,
  actions: list<CompletionEvidenceAction>,
  research_summary: CompletionResearchSummary,
  fallback_observations: list<CompletionEvidenceAction>,
  requirement_evidence: list<CompletionRequirementEvidenceRecord>,
}

pub type CompletionEvidenceSelection = {
  name: string,
  evidence_role: CompletionEvidenceRole,
  evidence_index: int,
}

pub type CompletionEvidenceProjectionStats = {
  schema: "harn.completion_judge_evidence_projection.v1",
  raw_message_count: int,
  relevant_call_count: int,
  included_action_count: int,
  included_fallback_observation_count: int,
  included_read_only_call_count: int,
  omitted_read_only_call_count: int,
  omitted_older_action_count: int,
  lifecycle_issue_count: int,
  serialized_chars: int,
  raw_transcript_digest: string,
  selected_actions: list<CompletionEvidenceSelection>,
}

pub type CompletionEvidenceProjection = {
  packet: CompletionEvidencePacket,
  serialized: string,
  stats: CompletionEvidenceProjectionStats,
}

pub type CompletionEvidenceSnapshot = {
  schema: "harn.completion_evidence_snapshot.v1",
  evidence_id: string,
  session_id: string,
  task: string,
  stop_reason: string,
  // These three fields are display projections of the candidate turn. The
  // unprojected emission below is the authority for completion declarations.
  text: string,
  visible_text: string,
  last_text: string,
  raw_text: string,
  transcript: string,
  judge_evidence: string,
  judge_evidence_packet: CompletionEvidencePacket,
  judge_evidence_projection: CompletionEvidenceProjectionStats,
  all_tools_used: string,
  successful_tools_used: string,
  iteration: int,
  knowledge_state: string,
  // Deferred effects survive turns. Completion policy alone decides whether
  // later evidence supersedes them, so expose their count instead of hiding a veto.
  pending_tool_batch_effect_count: int,
  // Absent until a deterministic gate actually ran and reported.
  verification?: CompletionVerificationState?,
  // The completion target as amended by accepted mid-run user steering,
  // derived once at the judge payload seam. Every exit authority shown this
  // snapshot — the LLM judge, a `verify_completion` closure, the completion
  // gate — reads this instead of re-deriving the goal from raw history.
  obligations?: CompletionObligations?,
}

fn completion_evidence_clip_to(value: any, limit: int) -> string {
  const text = if type_of(value) == "string" {
    value
  } else {
    json_stringify(value ?? "")
  }
  if len(text) <= limit {
    return text
  }
  const tail_chars = limit / 3
  const head_chars = limit - tail_chars
  const omitted = len(text) - limit
  return substring(text, 0, head_chars)
    + "\n…["
    + to_string(omitted)
    + " chars elided — middle of evidence]…\n"
    + substring(text, len(text) - tail_chars, len(text))
}

fn completion_evidence_clip(value: string) -> string {
  return completion_evidence_clip_to(value, COMPLETION_EVIDENCE_FIELD_CHAR_LIMIT)
}

// The most recent read-only calls carried into the packet. Six records at the
// label and digest limits below stay under ~3k characters, which is the same
// order as one retained action.
const COMPLETION_RESEARCH_SUMMARY_LIMIT = 6

const COMPLETION_READ_DIGEST_CHAR_LIMIT = 200

const COMPLETION_READ_TARGET_KEYS = [
  "path",
  "file_path",
  "file",
  "paths",
  "pattern",
  "query",
  "glob",
  "directory",
  "dir",
  "url",
  "command",
  "cmd",
  "target",
]

/**
 * What a read-only call looked at.
 *
 * Prefer a canonical locator key when the arguments carry one, because that is
 * what a judge weighing "was this stop justified?" needs to see. Fall back to
 * the bounded arguments themselves so an unrecognized shape degrades to less
 * detail rather than to nothing.
 */
fn completion_read_target(args: dict) -> string {
  for key in COMPLETION_READ_TARGET_KEYS {
    const value = args[key] ?? nil
    if value != nil {
      const rendered = trim(
        completion_evidence_clip_to(value, COMPLETION_EVIDENCE_LABEL_CHAR_LIMIT),
      )
      if rendered != "" && rendered != "\"\"" {
        return rendered
      }
    }
  }
  return completion_evidence_clip_to(args, COMPLETION_EVIDENCE_LABEL_CHAR_LIMIT)
}

/** The first non-empty line of what a read returned, bounded to one line. */
fn completion_read_digest(action: CompletionEvidenceAction) -> string {
  for line in action.observation.split("\n") ?? [] {
    const candidate = trim(line)
    if candidate != "" {
      return completion_evidence_clip_to(candidate, COMPLETION_READ_DIGEST_CHAR_LIMIT)
    }
  }
  const facts = trim(action.result_facts)
  if facts != "" && facts != "{}" {
    return completion_evidence_clip_to(facts, COMPLETION_READ_DIGEST_CHAR_LIMIT)
  }
  return ""
}

/**
 * Reduce one read-only lifecycle call to its bounded evidence record.
 *
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn completion_read_only_evidence(
  lifecycle: AgentTranscriptToolLifecycle,
  action: CompletionEvidenceAction,
) -> CompletionReadOnlyEvidence {
  return {
    ordinal: action.ordinal,
    evidence_index: action.evidence_index,
    name: action.name,
    target: completion_read_target(lifecycle.args),
    result_digest: completion_read_digest(action),
    has_error_result: action.has_error_result,
  }
}

/**
 * Reduce read-only calls to a bounded research summary.
 *
 * The last `COMPLETION_RESEARCH_SUMMARY_LIMIT` reads keep a record; older ones
 * survive as `omitted_count`. Nothing about the turn's research becomes
 * unreportable, which is the property that matters: a completion goal can
 * resolve to "there is nothing to build here", and read-only calls are then
 * the only evidence that exists.
 *
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn completion_research_summary(
  reads: list<CompletionReadOnlyEvidence>,
) -> CompletionResearchSummary {
  const total = len(reads)
  const included = if total < COMPLETION_RESEARCH_SUMMARY_LIMIT {
    reads
  } else {
    reads.slice(total - COMPLETION_RESEARCH_SUMMARY_LIMIT, total)
  }
  return {
    schema: "harn.completion_judge_research_summary.v1",
    read_only_call_count: total,
    included_count: len(included),
    omitted_count: max(total - len(included), 0),
    reads: included,
  }
}

fn completion_requirement_evidence_without(
  records: list<CompletionRequirementEvidenceRecord>,
  evidence_index: int,
) -> list<CompletionRequirementEvidenceRecord> {
  return records.filter({ record -> record.evidence_index != evidence_index }).to_list()
}

/**
 * Project final-output and deterministic readings onto one requirement-evidence model.
 *
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn completion_requirement_evidence_packet(
  packet: CompletionEvidencePacket,
  final_text: string,
  verification: CompletionVerificationState? = nil,
) -> CompletionEvidencePacket {
  let records = packet.requirement_evidence
  records = completion_requirement_evidence_without(records, COMPLETION_FINAL_OUTPUT_EVIDENCE_INDEX)
  records = completion_requirement_evidence_without(records, COMPLETION_VERIFICATION_EVIDENCE_INDEX)
  const answer = trim(final_text)
  if answer != "" {
    records = records
      + [
        {
          evidence_index: COMPLETION_FINAL_OUTPUT_EVIDENCE_INDEX,
          role: "assistant_output",
          supports_completion: true,
          summary: completion_evidence_clip(answer),
          representative_artifact_ids: [],
          measurement: "present",
        },
      ]
  }
  if verification != nil {
    records = records
      + [
        {
          evidence_index: COMPLETION_VERIFICATION_EVIDENCE_INDEX,
          role: "deterministic_verification",
          supports_completion: verification.observed == "passed",
          summary: "Deterministic verification observed `" + verification.observed + "`.",
          representative_artifact_ids: [],
          measurement: verification.observed,
        },
      ]
  }
  return packet + {requirement_evidence: records}
}

/**
 * Bind one terminal decision to its complete typed evidence snapshot.
 *
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn completion_evidence_id(
  task: string,
  stop_reason: string,
  text: string,
  packet: CompletionEvidencePacket,
  pending_tool_batch_effect_count: int,
  verification: CompletionVerificationState? = nil,
  obligations_digest: string = "",
) -> string {
  const base = {
    task: task,
    trigger: stop_reason,
    candidate_text: text,
    actions: packet.actions,
    // A turn that only researched changes no action, so without this key two
    // decisions taken on different research share an identity and the second
    // judge call dedupes onto the first.
    research_summary: packet.research_summary,
    fallback_observations: packet.fallback_observations,
    requirement_evidence: packet.requirement_evidence,
    lifecycle_issues: packet.lifecycle_issues,
    pending_tool_batch_effect_count: pending_tool_batch_effect_count,
    verification: verification,
  }
  // Two decisions that differ only in accepted steering were decided under
  // different obligations and must not share an evidence identity. An unsteered
  // run contributes no key at all, so its identity is unchanged by this seam.
  const identity = if obligations_digest == "" {
    base
  } else {
    base + {obligations_digest: obligations_digest}
  }
  return "sha256:" + sha256(json_stringify(identity))
}