import { canon_event_paths } from "std/agent/canon"
import {
COMPLETION_VERIFICATION_EVIDENCE_INDEX,
CompletionEvidenceAction,
CompletionEvidenceIssue,
CompletionEvidenceProjection,
CompletionEvidenceProjectionStats,
CompletionEvidenceRole,
CompletionEvidenceSelection,
CompletionEvidenceSnapshot,
CompletionReadOnlyEvidence,
CompletionToolSemantics,
completion_evidence_action_authorship,
completion_evidence_id,
completion_read_only_evidence,
completion_requirement_evidence_packet,
completion_research_summary,
} from "std/agent/completion_evidence"
import {
CompletionRequirementEvidencePacket,
CompletionRequirementEvidenceRecord,
CompletionRequirementReport,
completion_requirement_assessments_schema,
completion_requirement_contract,
completion_requirement_contract_prompt,
completion_requirement_pending_feedback,
completion_requirement_report,
completion_requirement_report_omitted,
completion_requirement_unassessed_feedback,
} from "std/agent/completion_requirements"
pub import {
COMPLETION_JUDGE_DEFAULT_CAP,
CompletionReview,
agent_completion_review,
agent_completion_review_should_skip_llm,
agent_verify_completion_judge_cap,
} from "std/agent/completion_review"
import { agent_feedback_emit } from "std/agent/feedback"
pub import {
CompletionJudgeInvocations,
CompletionPolicyEvaluation,
CompletionStageVerdict,
__completion_arbitrate_contradiction,
__completion_arbitrate_verification,
} from "std/agent/judge_arbitration"
import { __judge_resolve_contradiction } from "std/agent/judge_contradiction"
import {
COMPLETION_JUDGE_GAP_CLASSES,
CompletionJudgeAdmission,
CompletionJudgeGapClass,
CompletionVerificationState,
__completion_judge_gap_class,
__completion_verification_state_read,
__judge_checkpoint_unavailable,
__judge_emit_decision,
__judge_emit_started,
__judge_run_checkpoint_bounded,
} from "std/agent/judge_internals"
import { __judge_resolve_itemization } from "std/agent/judge_itemization"
pub import {
CompletionJudgeVerdict,
__judge_completion_verdict_contradicts_itself,
__judge_completion_verdict_read,
__judge_completion_verdict_schema,
} from "std/agent/judge_verdict"
import {
agent_completion_obligations,
completion_obligations_digest,
completion_obligations_prompt,
} from "std/agent/obligations"
import { __normalize_judge_config } from "std/agent/options_formats"
import { JudgeConfig } from "std/agent/options_types"
import { __with_prompt_fragment } from "std/agent/preflight"
import { completion_judge_user_prompt } from "std/agent/prompts"
import { agent_emit_event, agent_session_messages } from "std/agent/state"
import { agent_tool_annotations, agent_tool_is_read_only } from "std/agent/tool_annotations"
import {
AgentTranscriptIssue,
AgentTranscriptToolLifecycle,
AgentTranscriptToolLifecycleReport,
agent_transcript_tool_lifecycle_report,
} from "std/agent/transcript"
pub import { agent_turn_end_judge_cap } from "std/agent/turn_end"
// The completion judge is a terminal checkpoint, not a second agent loop. Give
// it the latest effect-bearing actions and their observations without replaying
// an unbounded session or the full callable schemas it cannot use.
const __JUDGE_EVIDENCE_FIELD_CHAR_LIMIT = 4500
const __JUDGE_EVIDENCE_LABEL_CHAR_LIMIT = 160
// Negative indices are reserved for Harn-owned session summaries. Transcript
// lifecycle indices are non-negative, so the two namespaces cannot alias.
const __JUDGE_MUTATION_SUMMARY_EVIDENCE_INDEX = -3
const __JUDGE_MUTATION_ARTIFACT_SAMPLE_LIMIT = 12
/** Action the completion-judge model is allowed to return. */
pub type CompletionJudgeAction = "accept" | "continue"
/** Runtime completion decision after deadline and policy enforcement. */
pub type CompletionDirectiveAction = CompletionJudgeAction | "stop_unverified"
pub type CompletionFeedbackDelivery = "none" | "deferred" | "delivered"
pub type CompletionDirectiveReceipt = {
schema: "harn.completion_directive_receipt.v1",
evidence_id: string,
action: CompletionDirectiveAction,
trigger: string,
source: string,
outcome: string,
reason: string,
invoked: CompletionJudgeInvocations,
feedback_delivery: CompletionFeedbackDelivery,
projection: CompletionEvidenceProjectionStats,
pending_tool_batch_effect_count: int,
// The gap the model judge named, and — when an adjudication was overruled —
// WHAT overruled it. `trigger` names whichever adjudicator ANSWERED, not the
// boundary that asked, so a reader counting conversions must count
// `converted_from` and never filter by `trigger`. The completion gate also
// writes `converted_from`, but only ever a bare ladder reason
// (`failed_verification`), never an arbitration name, so the two never alias.
converted_from?: string?,
gap_class?: string?,
verification?: CompletionVerificationState?,
admission?: CompletionJudgeAdmission?,
checkpoint?: dict?,
requirements?: CompletionRequirementReport?,
}
pub type CompletionDirective = {
action: CompletionDirectiveAction,
evidence_id: string,
feedback?: string?,
repair?: string?,
receipt: CompletionDirectiveReceipt,
feedback_history: dict,
knowledge_state: string,
}
/**
* Normalized result from either a deterministic or model completion check.
* Stable identity for the two model-judge positions in the completion plan.
*/
enum CompletionJudgeSlot {
Verification
Completion
}
/** Ordered checks evaluated at one completion boundary. */
enum CompletionPolicyStage {
Deterministic(check: any)
ModelJudge(
slot: CompletionJudgeSlot,
config: JudgeConfig,
trigger: string?,
prior_invocations: int,
max_invocations: int?,
)
}
fn __unique_names(names: any) -> list<string> {
let unique = []
for name in names {
if name != "" && !contains(unique, name) {
unique = unique.appending(name)
}
}
return unique
}
fn __session_tool_names(lifecycle: AgentTranscriptToolLifecycleReport) -> list<string> {
let names = []
for call in lifecycle.calls {
names = names.appending(call.name)
}
return __unique_names(names)
}
fn __session_successful_tool_names(lifecycle: AgentTranscriptToolLifecycleReport) -> list<string> {
let names: list<string> = []
for call in lifecycle.calls {
if call.status == "completed" && call.result?.outcome == "ok" {
names = names + [call.name]
}
}
return __unique_names(names)
}
fn __judge_tool_entries(opts: dict) {
const registry = opts?.tools
return if type_of(registry) == "dict" {
registry?.tools ?? []
} else if type_of(registry) == "list" {
registry
} else {
[]
}
}
fn __judge_tool_name(entry: dict) -> string {
return to_string(entry?.name ?? entry?.function?.name ?? "")
}
fn __judge_tool_entry(opts: dict, name: any) {
for entry in __judge_tool_entries(opts) {
if __judge_tool_name(entry) == name {
return entry
}
}
return nil
}
fn __judge_declared_evidence_role(value: string?) -> CompletionEvidenceRole? {
const role = lowercase(to_string(value ?? ""))
if role == "observation" || role == "mutation" || role == "verification" {
return role
}
return nil
}
fn __judge_evidence_role(
declared: CompletionEvidenceRole?,
kind: string,
effect: string,
read_only: bool,
) -> CompletionEvidenceRole {
if declared != nil {
return declared
}
if kind == "edit" || effect == "workspace_write" {
return "mutation"
}
if read_only {
return "observation"
}
return "other"
}
fn __judge_tool_descriptor(opts: dict, name: any) -> CompletionToolSemantics {
const entry = __judge_tool_entry(opts, name)
const annotations = agent_tool_annotations(entry, opts?.policy, name)
const read_only = entry != nil && agent_tool_is_read_only(entry, opts?.policy, name)
const kind = lowercase(to_string(annotations?.kind ?? ""))
const effect = lowercase(to_string(annotations?.side_effect_level ?? ""))
const declared_role = __judge_declared_evidence_role(annotations?.completion_evidence_role)
const evidence_role = __judge_evidence_role(declared_role, kind, effect, read_only)
return {
name: __judge_clip_evidence(name, __JUDGE_EVIDENCE_LABEL_CHAR_LIMIT),
kind: __judge_clip_evidence(
annotations?.kind ?? annotations?.tool_kind ?? annotations?.toolKind ?? "",
__JUDGE_EVIDENCE_LABEL_CHAR_LIMIT,
),
side_effect_level: __judge_clip_evidence(
annotations?.side_effect_level ?? annotations?.sideEffectLevel ?? annotations?.side_effect
?? annotations?.sideEffect
?? "",
__JUDGE_EVIDENCE_LABEL_CHAR_LIMIT,
),
read_only: read_only,
evidence_role: evidence_role,
}
}
fn __judge_clip_evidence(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 __judge_bounded_artifact_id(value: unknown) -> string {
const identity = trim(to_string(value ?? ""))
if identity == "" {
return ""
}
if len(identity) <= __JUDGE_EVIDENCE_LABEL_CHAR_LIMIT {
return identity
}
// Keep a readable prefix and a collision-resistant identity for paths longer
// than the evidence envelope. No changed artifact silently disappears.
return substring(identity, 0, 80) + "…#sha256:" + sha256(identity)
}
fn __judge_push_artifact_ids(items: list<string>, bucket: unknown) -> list<string> {
let out = items
if type_of(bucket) != "list" {
return out
}
for value in bucket {
const identity = trim(to_string(value ?? ""))
if identity != "" && !out.contains(identity) {
out = out + [identity]
}
}
return out
}
fn __judge_mutation_artifact_ids(
lifecycle: AgentTranscriptToolLifecycleReport,
opts: dict,
) -> list<string> {
let artifact_ids: list<string> = []
for call in lifecycle.calls {
const descriptor = __judge_tool_descriptor(opts, call.name)
if !__judge_descriptor_is_mutation(descriptor) || call.status != "completed" {
continue
}
for result in call.results {
// Requirement evidence is consumed independently of `packet.actions`, so
// enforce the same authorship boundary here rather than relying on the
// action projection to have removed injected harness feedback already.
if result.origin == "harness_repair" || result.outcome != "ok" {
continue
}
artifact_ids = __judge_push_artifact_ids(artifact_ids, canon_event_paths(result.data))
}
}
return artifact_ids
}
fn __judge_mutation_requirement_evidence(
lifecycle: AgentTranscriptToolLifecycleReport,
opts: dict,
) -> list<CompletionRequirementEvidenceRecord> {
const artifact_ids = __judge_mutation_artifact_ids(lifecycle, opts).sorted()
if len(artifact_ids) == 0 {
return []
}
const sample_count = if len(artifact_ids) < __JUDGE_MUTATION_ARTIFACT_SAMPLE_LIMIT {
len(artifact_ids)
} else {
__JUDGE_MUTATION_ARTIFACT_SAMPLE_LIMIT
}
let representative_ids: list<string> = []
for identity in artifact_ids.slice(0, sample_count) {
representative_ids = representative_ids + [__judge_bounded_artifact_id(identity)]
}
const record: CompletionRequirementEvidenceRecord = {
evidence_index: __JUDGE_MUTATION_SUMMARY_EVIDENCE_INDEX,
role: "mutation_summary",
supports_completion: true,
summary: "Changed " + to_string(len(artifact_ids)) + " artifact(s) across this session.",
representative_artifact_ids: representative_ids,
artifact_count: len(artifact_ids),
artifact_set_digest: "sha256:" + sha256(json_stringify(artifact_ids)),
measurement: "observed",
}
return [record]
}
fn __judge_descriptor_is_verifier(descriptor: dict) -> bool {
return descriptor?.evidence_role == "verification"
}
fn __judge_descriptor_is_mutation(descriptor: dict) -> bool {
return descriptor?.evidence_role == "mutation"
}
fn __judge_descriptor_is_observation(descriptor: dict) -> bool {
return descriptor?.evidence_role == "observation"
}
fn __judge_lifecycle_action(
lifecycle: AgentTranscriptToolLifecycle,
opts: dict,
) -> CompletionEvidenceAction {
const descriptor = __judge_tool_descriptor(opts, lifecycle.name)
// A `harness_repair` result is the harness's own injected feedback, written
// to close an orphaned `tool_use` block under that call's name and id. It is
// not an observation of anything, and reading it as one lets a completion
// veto cite itself as proof of the gap it asserted (harn#7757).
const dispatched = lifecycle.results.filter({ matched -> matched.origin != "harness_repair" })
.to_list()
const result = if len(dispatched) == 0 {
nil
} else {
dispatched[len(dispatched) - 1]
}
let observations = []
for matched in dispatched {
observations = observations + [matched.text]
}
const action: CompletionEvidenceAction = {
ordinal: lifecycle.ordinal,
evidence_index: lifecycle.evidence_index,
tool_call_id: __judge_clip_evidence(lifecycle.tool_call_id, __JUDGE_EVIDENCE_LABEL_CHAR_LIMIT),
name: descriptor.name,
semantics: descriptor,
lifecycle_status: lifecycle.status,
arguments: __judge_clip_evidence(lifecycle.args, __JUDGE_EVIDENCE_FIELD_CHAR_LIMIT),
observation: __judge_clip_evidence(join(observations, "\n"), __JUDGE_EVIDENCE_FIELD_CHAR_LIMIT),
result_facts: __judge_clip_evidence(result?.data ?? {}, __JUDGE_EVIDENCE_FIELD_CHAR_LIMIT),
has_error_result: dispatched.any({ matched -> matched.outcome == "error" }),
}
return completion_evidence_action_authorship(action, len(lifecycle.results), len(dispatched))
}
fn __judge_latest(
current: CompletionEvidenceAction?,
candidate: CompletionEvidenceAction?,
) -> CompletionEvidenceAction? {
if candidate == nil {
return current
}
if current == nil || candidate.evidence_index >= current.evidence_index {
return candidate
}
return current
}
fn __judge_unique_slot_actions(
candidates: list<CompletionEvidenceAction?>,
) -> list<CompletionEvidenceAction> {
let selected: list<CompletionEvidenceAction> = []
let seen = {}
for candidate in candidates {
if candidate == nil {
continue
}
const key = to_string(candidate.ordinal) + ":" + to_string(candidate.evidence_index)
if seen[key] ?? false {
continue
}
seen = seen + {[key]: true}
selected = selected + [candidate]
}
return selected
}
fn __judge_issue_evidence(issue: AgentTranscriptIssue) -> CompletionEvidenceIssue {
return {
code: __judge_clip_evidence(issue.code, __JUDGE_EVIDENCE_LABEL_CHAR_LIMIT),
record_index: issue.record_index,
message: __judge_clip_evidence(issue.message, __JUDGE_EVIDENCE_LABEL_CHAR_LIMIT),
details: __judge_clip_evidence(issue.details, __JUDGE_EVIDENCE_FIELD_CHAR_LIMIT),
}
}
/**
* Build the bounded evidence packet sent to the completion-judge LLM. The raw
* transcript remains on the canonical payload for deterministic gates, replay,
* and audit. Selection is structural: explicit mutation and verification roles
* are retained as actions, and read-only calls are reduced to the bounded
* `research_summary` instead of being dropped. Canonical edit and
* workspace-write annotations imply mutation; execute tools must declare a
* completion evidence role instead of being guessed from provider prose or
* process capability.
*
* @effects: []
* @errors: []
* @api_stability: internal
*/
fn __judge_evidence_projection_from_lifecycle(
messages: string | bytes | list | dict | set | range | nil,
lifecycle: AgentTranscriptToolLifecycleReport,
opts: dict,
raw_transcript_digest: any = nil,
) -> CompletionEvidenceProjection {
let relevant_call_count = 0
let read_only_evidence: list<CompletionReadOnlyEvidence> = []
let latest_mutation = nil
let latest_verification = nil
let latest_problem = nil
let latest_observation = nil
let omitted_harness_authored_result_count = 0
for call in lifecycle.calls {
const action = __judge_lifecycle_action(call, opts)
omitted_harness_authored_result_count = omitted_harness_authored_result_count
+ (action.omitted_harness_authored_result_count ?? 0)
if action.harness_authored_only ?? false {
// Every result on this call was written by the harness, so the call was
// never dispatched. Counted rather than silently dropped: a judge that
// sees fewer actions than the run made must be able to tell why.
continue
}
if action.semantics.read_only {
read_only_evidence = read_only_evidence + [completion_read_only_evidence(call, action)]
latest_observation = __judge_latest(latest_observation, action)
continue
}
relevant_call_count = relevant_call_count + 1
if __judge_descriptor_is_observation(action.semantics) {
latest_observation = __judge_latest(latest_observation, action)
}
if __judge_descriptor_is_mutation(action.semantics) {
latest_mutation = __judge_latest(latest_mutation, action)
}
if __judge_descriptor_is_verifier(action.semantics) {
latest_verification = __judge_latest(latest_verification, action)
}
if action.lifecycle_status != "completed" || action.has_error_result {
latest_problem = __judge_latest(latest_problem, action)
}
}
let issue_tail = lifecycle.issues
if len(issue_tail) > 3 {
issue_tail = issue_tail.slice(len(issue_tail) - 3, len(issue_tail))
}
let latest_issues = []
for issue in issue_tail {
latest_issues = latest_issues + [__judge_issue_evidence(issue)]
}
const selected = __judge_unique_slot_actions(
[latest_mutation, latest_verification, latest_problem],
)
const observation_fallback = if len(selected) == 0 && latest_observation != nil {
[latest_observation]
} else {
[]
}
const raw_digest = raw_transcript_digest
?? ("sha256:" + sha256(json_stringify(messages)))
const research_summary = completion_research_summary(read_only_evidence)
let packet: CompletionEvidencePacket = {
schema: "harn.completion_judge_evidence.v2",
raw_message_count: len(messages),
raw_transcript_digest: raw_digest,
relevant_action_count: relevant_call_count,
omitted_read_only_call_count: research_summary.omitted_count,
omitted_older_action_count: max(relevant_call_count - len(selected), 0),
lifecycle_issue_count: lifecycle.invalid_count,
lifecycle_issues: latest_issues,
actions: selected,
research_summary: research_summary,
fallback_observations: observation_fallback,
requirement_evidence: __judge_mutation_requirement_evidence(lifecycle, opts),
}
if omitted_harness_authored_result_count > 0 {
packet = packet
+ {omitted_harness_authored_result_count: omitted_harness_authored_result_count}
}
let selection: list<CompletionEvidenceSelection> = []
for action in selected {
selection = selection
+ [
{
name: action.name,
evidence_role: action.semantics.evidence_role,
evidence_index: action.evidence_index,
},
]
}
const serialized = json_stringify(packet)
return {
packet: packet,
serialized: serialized,
stats: {
schema: "harn.completion_judge_evidence_projection.v1",
raw_message_count: len(messages),
relevant_call_count: relevant_call_count,
included_action_count: len(selected),
included_fallback_observation_count: len(observation_fallback),
included_read_only_call_count: research_summary.included_count,
omitted_read_only_call_count: research_summary.omitted_count,
omitted_older_action_count: max(relevant_call_count - len(selected), 0),
omitted_harness_authored_result_count: omitted_harness_authored_result_count,
lifecycle_issue_count: lifecycle.invalid_count,
serialized_chars: len(serialized),
raw_transcript_digest: packet.raw_transcript_digest,
selected_actions: selection,
},
}
}
pub fn __judge_evidence_projection(
messages: string | bytes | list | dict | set | range | nil,
opts: dict,
raw_transcript_digest: any = nil,
) -> CompletionEvidenceProjection {
return __judge_evidence_projection_from_lifecycle(
messages,
agent_transcript_tool_lifecycle_report(messages),
opts,
raw_transcript_digest,
)
}
fn __judge_knowledge_state(lifecycle: AgentTranscriptToolLifecycleReport, text: string) -> string {
let tool_evidence = []
for call in lifecycle.calls {
tool_evidence = tool_evidence.appending(
{
name: call.name,
tool_call_id: call.tool_call_id,
status: call.status,
results: call.results.map(
{ result -> {outcome: result.outcome, text: result.text, data: result.data} },
)
.to_list(),
},
)
}
return "sha256:" + sha256(json_stringify({latest_text: text, tool_evidence: tool_evidence}))
}
fn __judge_payload(
agent: HarnessAgent,
session: dict,
opts: dict,
stop_reason: string,
text: string,
iteration: int,
raw_text: string = "",
) -> CompletionEvidenceSnapshot {
const messages = agent_session_messages(agent, session.session_id)
const lifecycle = agent_transcript_tool_lifecycle_report(messages)
const tool_names = __session_tool_names(lifecycle)
const successful_tool_names = __session_successful_tool_names(lifecycle)
const transcript = json_stringify(messages)
const projected = __judge_evidence_projection_from_lifecycle(
messages,
lifecycle,
opts,
"sha256:" + sha256(transcript),
)
const evidence_packet = completion_requirement_evidence_packet(projected.packet, text)
const evidence_serialized = json_stringify(evidence_packet)
const evidence_stats = projected.stats + {serialized_chars: len(evidence_serialized)}
const pending_tool_batch_effect_count = len(
opts?._tool_batch_dependency_state?.deferred_signatures ?? [],
)
// Read from the session's control records, not from `messages`: an accepted
// stop delivers no user message, so the transcript in hand cannot show one.
const obligations = agent_completion_obligations(agent, session.session_id, session?.task ?? "")
const evidence_id = completion_evidence_id(
session?.task ?? "",
stop_reason,
text,
evidence_packet,
pending_tool_batch_effect_count,
nil,
completion_obligations_digest(obligations),
)
return {
schema: "harn.completion_evidence_snapshot.v1",
evidence_id: evidence_id,
session_id: session.session_id,
task: session?.task ?? "",
obligations: obligations,
stop_reason: stop_reason,
text: text,
visible_text: text,
last_text: text,
raw_text: raw_text == "" ? text : raw_text,
transcript: transcript,
judge_evidence: evidence_serialized,
judge_evidence_packet: evidence_packet,
judge_evidence_projection: evidence_stats,
all_tools_used: join(tool_names, ", "),
successful_tools_used: join(successful_tool_names, ", "),
iteration: iteration,
knowledge_state: __judge_knowledge_state(lifecycle, text),
pending_tool_batch_effect_count: pending_tool_batch_effect_count,
verification: nil,
}
}
/** Bind a gate reading onto the snapshot the remaining stages will be shown. */
fn __judge_payload_with_verification(
payload: CompletionEvidenceSnapshot,
state: CompletionVerificationState?,
) -> CompletionEvidenceSnapshot {
if state == nil {
return payload
}
const verification = state
+ {observed_at_evidence_index: COMPLETION_VERIFICATION_EVIDENCE_INDEX}
const evidence_packet = completion_requirement_evidence_packet(
payload.judge_evidence_packet,
payload.last_text,
verification,
)
const evidence_serialized = json_stringify(evidence_packet)
return payload
+ {
evidence_id: completion_evidence_id(
payload.task,
payload.stop_reason,
payload.last_text,
evidence_packet,
payload.pending_tool_batch_effect_count,
verification,
completion_obligations_digest(payload?.obligations),
),
judge_evidence: evidence_serialized,
judge_evidence_packet: evidence_packet,
judge_evidence_projection: payload.judge_evidence_projection
+ {serialized_chars: len(evidence_serialized)},
verification: verification,
}
}
fn __judge_stable_prefix(
judge_cfg: any,
opts: dict,
payload: CompletionEvidenceSnapshot,
system: string,
schema: any,
) -> string {
const rubric = trim(to_string(judge_cfg?.rubric ?? opts?.rubric ?? ""))
const rubric_block = if rubric == "" {
""
} else {
"\n\nCompletion rubric:\n" + rubric
}
const requirement_block = completion_requirement_contract_prompt(
completion_requirement_contract(judge_cfg?.requirement_contract),
)
// The steering block sits AFTER the goal, rubric, and requirement rows so its
// supersession clause reads over all of them. It renders empty for an
// unsteered run, which keeps that run's prefix byte-identical (and cacheable).
return system
+ "\n\nStable completion goal:\n"
+ payload.task
+ rubric_block
+ requirement_block
+ completion_obligations_prompt(payload?.obligations)
+ "\n\nRequired verdict schema:\n"
+ json_stringify(schema)
}
fn __judge_invoke_closure(
verify_completion: unknown,
payload: CompletionEvidenceSnapshot,
) -> CompletionStageVerdict {
const result = verify_completion(payload)
if result == nil || result == "" {
return {vetoed: false, confirm: true, source: "deterministic", trigger: "verify_completion"}
}
if type_of(result) == "bool" {
return {vetoed: !result, confirm: result, source: "deterministic", trigger: "verify_completion"}
}
if type_of(result) == "string" {
return {
vetoed: true,
feedback: result,
confirm: false,
source: "deterministic",
trigger: "verify_completion",
}
}
if type_of(result) == "dict" {
const confirm = result?.confirm == true
const message = to_string(result?.message ?? result?.feedback ?? "")
return {
vetoed: !confirm,
feedback: message,
confirm: confirm,
verification: __completion_verification_state_read(result?.verification),
source: "deterministic",
reason: if result?.reason == nil {
nil
} else {
to_string(result.reason)
},
converted_from: if result?.converted_from == nil {
nil
} else {
to_string(result.converted_from)
},
escalation_recommended: if result?.escalation_recommended == nil {
nil
} else {
result.escalation_recommended
},
escalation_target: if result?.escalation_target == nil {
nil
} else {
to_string(result.escalation_target)
},
trigger: to_string(result?.trigger ?? "verify_completion"),
}
}
return {
vetoed: false,
confirm: false,
source: "deterministic",
trigger: "verify_completion",
reason: "completion_verifier_invalid_result",
terminal_judge_reason: "completion_verifier_invalid_result",
terminal_evidence_preserved: true,
}
}
fn __judge_invoke_structured(
harness: Harness,
judge_cfg: JudgeConfig,
opts: dict,
payload: CompletionEvidenceSnapshot,
) -> CompletionStageVerdict {
const system = judge_cfg.system ?? ""
const contract = completion_requirement_contract(judge_cfg.requirement_contract)
const schema = __judge_completion_verdict_schema(contract)
agent_emit_event(
harness.agent,
payload.session_id,
"typed_checkpoint",
payload.judge_evidence_projection,
)
const stable_prefix = __judge_stable_prefix(judge_cfg, opts, payload, system, schema)
const user = completion_judge_user_prompt(harness.fs, payload)
const ran = __judge_run_checkpoint_bounded(
harness,
{
judge_cfg: judge_cfg,
opts: opts,
payload: payload,
checkpoint_name: "agent.completion_judge",
system: stable_prefix,
user: user,
schema: schema,
},
)
const checkpoint = ran.checkpoint
const duration_ms = ran.duration_ms
if !checkpoint.ok {
const unavailable = __judge_checkpoint_unavailable(checkpoint)
if unavailable {
const judge_outcome = if checkpoint?.status == "admission_refused" {
"admission_refused"
} else if checkpoint?.status == "model_unconfigured" {
// Its own outcome, not a timeout. A misconfiguration reported as a
// deadline sends the reader to look at latency for a setting that was
// never set, which is how this class of defect stays open.
"model_unconfigured"
} else {
"timeout"
}
const terminal_reason = if judge_outcome == "admission_refused" {
"completion_judge_deadline_admission_refused"
} else if judge_outcome == "model_unconfigured" {
"completion_judge_model_unconfigured"
} else {
"completion_judge_deadline_timeout"
}
return {
vetoed: false,
confirm: false,
verdict: "unavailable",
reasoning: to_string(checkpoint.error),
next_step: "",
judge_duration_ms: duration_ms,
source: "llm",
judge_outcome: judge_outcome,
reason: terminal_reason,
terminal_judge_reason: terminal_reason,
terminal_evidence_preserved: true,
admission: ran.admission,
typed_checkpoint: checkpoint,
}
}
// A completion judge is an authority gate, not an advisory reviewer. If it
// cannot decide, the claim remains unverified.
//
// The reply was retried to its bound and still could not be read, so this
// authority is spent. It terminates on its own reason rather than vetoing:
// a `continue` here is a veto no turn can answer, which leaves the run to
// die on an unrelated budget with an unrelated reason. `terminal_judge_reason`
// is the same field the deadline path above already sets, so both exit
// authorities read this stop through the vocabulary they already share.
return {
vetoed: false,
confirm: false,
feedback: to_string(checkpoint.error),
verdict: "unavailable",
reasoning: to_string(checkpoint.error),
next_step: "",
judge_duration_ms: duration_ms,
source: "llm",
judge_outcome: "error",
reason: "completion_judge_error",
terminal_judge_reason: "completion_judge_error",
terminal_evidence_preserved: true,
admission: ran.admission,
typed_checkpoint: checkpoint,
}
}
const reask_request = {
judge_cfg: judge_cfg,
opts: opts,
payload: payload,
checkpoint_name: "agent.completion_judge",
system: stable_prefix,
user: user,
schema: schema,
}
// A judge that answered `done` without itemizing has assessed nothing. Re-ask
// the JUDGE once, rather than sending the actor feedback it cannot act on.
const resolved = __judge_resolve_itemization(
harness,
reask_request,
contract,
checkpoint.data,
__judge_completion_verdict_read(checkpoint.data).verdict == "done",
)
const itemized_decoded = __judge_completion_verdict_read(
resolved.data,
contract,
payload.judge_evidence_packet,
)
// A judge that answered `done` while naming a gap has contradicted itself.
// Same treatment, same reason: re-ask the JUDGE once with the contradiction
// named, rather than sending the actor a refusal built out of the judge's own
// claim that the work is finished. Without this the identical reply comes back
// on every invocation and the run dies at the cap. See harn#8060.
const contradiction = __judge_resolve_contradiction(
harness,
reask_request,
resolved.data,
__judge_completion_verdict_contradicts_itself(itemized_decoded),
)
// `contradiction.data` is the reply of record: the re-ask when one happened,
// and the itemization pass's reply unchanged when none did. Every read below
// goes through it so no clause is left describing a superseded reply.
const decoded = if contradiction.reasked {
__judge_completion_verdict_read(contradiction.data, contract, payload.judge_evidence_packet)
} else {
itemized_decoded
}
const requirement_report = decoded.requirement_report
const unassessed = resolved.omitted && requirement_report != nil
// A judge that REFUSED and itemized nothing has assessed nothing either, but
// unlike the approval path there is nothing to fail closed against: a
// `continue` closes no ledger. Charging it as pending would hand the actor a
// complaint about rows nobody looked at, naming evidence kinds the actor does
// not supply, in place of the reason the judge actually gave.
const declined_unassessed = requirement_report != nil
&& !resolved.omitted
&& completion_requirement_report_omitted(contract, contradiction.data?.requirement_report)
const requirements_pending = requirement_report != nil
&& !requirement_report.complete
&& !declined_unassessed
// A judge that accepts while naming ANY gap — one of the four named classes
// or a bare `other` — has contradicted itself inside one object, and the
// contradiction is readable without a model. Taking the verdict and
// dropping the gap is how a run that was blocked from doing its work
// reaches a host as `done`, `natural`, with no stop reason. `other` is not
// exempted: it is the value a compliant `done` reply leaves the field unset
// for, so a `done` that populates it anyway — even with `other` — is the
// same self-contradiction wearing the one label this check used to let
// through (harn#7910). The gap is the half backed by evidence, so it wins.
const gap_contradicts_done = __judge_completion_verdict_contradicts_itself(decoded)
const verdict = requirements_pending || gap_contradicts_done ? "continue" : decoded.verdict
const detail = if unassessed {
completion_requirement_unassessed_feedback(requirement_report)
} else if requirements_pending {
completion_requirement_pending_feedback(requirement_report)
} else {
decoded.detail
}
const feedback_default = judge_cfg.feedback_fallback ?? ""
const feedback = if detail == "" {
feedback_default
} else {
detail
}
return {
vetoed: verdict == "continue",
verdict: verdict,
gap_class: decoded.gap_class,
// Whether the self-contradiction was already pointed out to the judge and
// came back anyway. The arbitration below reads it so a FIRST contradiction
// is never overruled — only one that survived its resolution.
contradiction_reasked: contradiction.reasked,
reason: if unassessed {
"completion_judge_report_omitted"
} else if requirements_pending {
"completion_requirements_pending"
} else if gap_contradicts_done {
"completion_judge_gap_contradicts_done"
} else {
nil
},
feedback: if verdict == "continue" {
feedback
} else {
nil
},
reasoning: detail,
next_step: if verdict == "continue" {
detail
} else {
""
},
judge_duration_ms: duration_ms,
source: "llm",
admission: ran.admission,
typed_checkpoint: checkpoint,
requirement_report: requirement_report,
}
}
/**
* agent_judge_config.
*
* Normalize a `turn_end_condition` or `verify_completion_judge` option. Returns nil when
* no judge can run — absent, `false`, or a value of a type the judge seam
* cannot invoke — and the configuration dict otherwise. `true` normalizes to an
* empty dict, meaning "a judge with default settings".
*
* @effects: []
* @errors: []
* @api_stability: experimental
* @example: agent_judge_config(opts?.turn_end_condition)
*/
pub fn agent_judge_config(judge_cfg: JudgeConfig?) -> JudgeConfig? {
return __normalize_judge_config(judge_cfg)
}
fn __done_contract_primary_prompt(opts: dict) {
const marker = to_string(opts?.done_sentinel ?? "the configured done sentinel")
return join(
[
"Before yielding as done, state a concise Done claim immediately before " + marker + ".",
"The claim must cover: requested work, evidence observed, files or artifacts changed, and anything deliberately not done.",
"Do not claim completion from a green self-authored check alone; connect the evidence back to the user's visible request.",
],
"\n",
)
}
pub fn __done_contract_judge_system(opts: dict) -> string {
const extra = trim(to_string(opts?.rubric ?? ""))
let lines = [
"You are a strict done-contract judge for an autonomous agent.",
"Decide whether the latest completion claim is justified by the task, transcript, tool results, and changed artifacts.",
"Return `done` only when the claim covers the visible request and names adequate evidence.",
"Return `continue` when evidence is missing, self-authored tests are too weak, or visible requirements are unaddressed.",
"Judge the substance of the claim, never its wire format. Response markers, sentinels, and layout are enforced elsewhere, and their absence is never a reason to continue.",
"Return only `verdict`, one concise `detail`, and the `gap_class` the gap belongs to.",
"On `done`, use `detail` for the strongest evidence that every requirement is met.",
"On `continue`, use `detail` for the single most important gap and the substantive next action that closes it, never a restatement or formatting instruction.",
]
if extra != "" {
lines = lines.appending("Additional rubric:\n" + extra)
}
return join(lines, "\n")
}
/**
* agent_done_contract.
*
* Build an agent_loop option bundle for a bidirectional done contract:
* the primary agent must state a concise completion claim before yielding, and
* the turn-end judge must return one bounded evidence detail on approval or one
* bounded gap-and-action detail on veto. The helper rides the existing `turn_end_condition`
* seam and context-profile prompt-fragment channel; it does not add a new loop
* hook. Compose it with base options:
*
* ```harn,ignore
* agent_loop(harness, task, nil, base_opts + agent_done_contract({max_invocations: 3}))
* ```
*
* @effects: []
* @errors: [runtime]
* @api_stability: experimental
*/
pub fn agent_done_contract(options: dict = {}) -> dict {
const opts = if type_of(options) == "dict" {
options
} else {
{}
}
const judge = if type_of(opts?.judge) == "dict" {
opts.judge
} else {
{}
}
const max_invocations = opts?.max_invocations ?? judge?.max_invocations ?? 3
const bundle = {
turn_end_condition: judge
+ {
feedback_fallback: opts?.feedback_fallback
?? judge?.feedback_fallback
?? "The done claim is not yet justified; address the specific gaps before finishing.",
max_invocations: max_invocations,
model: opts?.model ?? judge?.model,
provider: opts?.provider ?? judge?.provider,
system: opts?.system ?? judge?.system ?? __done_contract_judge_system(opts),
},
loop_until_done: opts?.loop_until_done ?? true,
}
return __with_prompt_fragment(
bundle,
{body: __done_contract_primary_prompt(opts), id: "done_contract", source: "std/agent/judge"},
)
}
fn __judge_directive(
verdict: dict,
payload: dict,
invoked: any,
completion_judge_cap_reached: any,
turn_end_judge_cap_reached: any,
feedback_composition: any,
feedback_history: dict,
defer_feedback: bool,
) -> CompletionDirective {
const terminal_reason = trim(to_string(verdict?.terminal_judge_reason ?? ""))
const reason = if terminal_reason != "" {
terminal_reason
} else if completion_judge_cap_reached {
"completion_judge_cap_reached"
} else if turn_end_judge_cap_reached {
"turn_end_judge_cap_reached"
} else {
to_string(verdict?.reason ?? verdict?.reasoning ?? "")
}
const action = if terminal_reason != ""
|| completion_judge_cap_reached
|| turn_end_judge_cap_reached {
"stop_unverified"
} else if verdict.vetoed {
"continue"
} else {
"accept"
}
const has_feedback = verdict.vetoed
&& trim(to_string(verdict?.feedback ?? "")) != ""
const feedback_delivery = if len(feedback_composition?.messages ?? []) > 0 {
"delivered"
} else if has_feedback && defer_feedback {
"deferred"
} else {
"none"
}
const receipt = {
schema: "harn.completion_directive_receipt.v1",
evidence_id: payload.evidence_id,
action: action,
trigger: verdict?.trigger ?? payload.stop_reason,
source: verdict?.source ?? "policy",
outcome: verdict?.judge_outcome ?? verdict?.verdict ?? action,
reason: reason,
invoked: invoked,
feedback_delivery: feedback_delivery,
projection: payload.judge_evidence_projection,
pending_tool_batch_effect_count: payload.pending_tool_batch_effect_count,
converted_from: verdict?.converted_from,
gap_class: verdict?.gap_class,
verification: payload.verification,
admission: verdict?.admission,
checkpoint: verdict?.typed_checkpoint,
requirements: verdict?.requirement_report,
}
return {
action: action,
evidence_id: payload.evidence_id,
feedback: verdict?.feedback,
repair: verdict?.next_step,
receipt: receipt,
feedback_history: feedback_history,
// A caller that owns delivery needs the same composition context the
// inline emit above would have used, or its feedback is deduplicated
// against a knowledge state it cannot see.
knowledge_state: payload.knowledge_state,
}
}
fn __completion_judge_applies(stop_reason: string, due: bool) -> bool {
if !due {
return false
}
return contains(["sentinel", "natural", "stalled"], stop_reason)
}
/** Build the one ordered plan for a completion boundary. */
fn __completion_policy_plan(
opts: dict,
stop_reason: string,
review: CompletionReview = {scrutiny: "standard"},
) -> list<CompletionPolicyStage> {
let stages: list<CompletionPolicyStage> = []
if opts?.verify_completion != nil {
stages = stages + [CompletionPolicyStage.Deterministic(opts.verify_completion)]
}
const verification_config = agent_judge_config(opts?.verify_completion_judge)
if verification_config != nil {
stages = stages
+ [
CompletionPolicyStage.ModelJudge(
CompletionJudgeSlot.Verification(),
verification_config,
opts?._verify_completion_judge_trigger,
opts?._verify_completion_judge_invocations ?? 0,
agent_verify_completion_judge_cap(verification_config, review),
),
]
}
const completion_config = agent_judge_config(opts?.turn_end_condition)
if completion_config != nil
&& __completion_judge_applies(stop_reason, opts?._turn_end_judge_due ?? true) {
stages = stages
+ [
CompletionPolicyStage.ModelJudge(
CompletionJudgeSlot.Completion(),
completion_config,
opts?._turn_end_judge_trigger,
opts?._turn_end_judge_invocations ?? 0,
agent_turn_end_judge_cap(completion_config),
),
]
}
return stages
}
/**
* Whether this run declares anything that could adjudicate a completion at
* `stop_reason`.
*
* A caller that is about to end the run and wants the turn adjudicated first
* needs to know whether asking is worth a provider call — and, more importantly,
* whether an answer it gets back means anything. An evaluation with no stages is
* not vetoed, so the directive seals `accept`: silence read as approval. Ask
* this before evaluating, and treat a false as "nobody was asked".
*
* The plan is the single owner of what counts as an adjudicator; this only
* reports whether it produced any stages.
*
* @effects: []
* @errors: []
* @api_stability: experimental
* @example: agent_completion_adjudicator_declared(opts, "stalled")
*/
pub fn agent_completion_adjudicator_declared(opts: dict, stop_reason: string) -> bool {
return len(__completion_policy_plan(opts, stop_reason)) > 0
}
fn __completion_policy_initial_evaluation(
payload: CompletionEvidenceSnapshot,
) -> CompletionPolicyEvaluation {
return {
verdict: {vetoed: false},
invoked: {deterministic: false, verify_completion_judge: false, turn_end_condition: false},
verification_judge_cap_reached: false,
completion_judge_cap_reached: false,
payload: payload,
deterministic_reason: "",
}
}
fn __completion_policy_can_advance(evaluation: CompletionPolicyEvaluation) -> bool {
return !evaluation.verdict.vetoed
&& trim(to_string(evaluation.verdict.terminal_judge_reason ?? ""))
== ""
&& !evaluation.verification_judge_cap_reached
&& !evaluation.completion_judge_cap_reached
}
fn __completion_policy_mark_cap(
evaluation: CompletionPolicyEvaluation,
slot: CompletionJudgeSlot,
) -> CompletionPolicyEvaluation {
match slot {
Verification() -> { return evaluation + {verification_judge_cap_reached: true} }
Completion() -> { return evaluation + {completion_judge_cap_reached: true} }
}
}
fn __completion_policy_mark_model_invoked(
evaluation: CompletionPolicyEvaluation,
slot: CompletionJudgeSlot,
invoked: bool,
) -> CompletionPolicyEvaluation {
const prior = evaluation.invoked
match slot {
Verification() -> { return evaluation
+ {invoked: prior + {verify_completion_judge: invoked}} }
Completion() -> { return evaluation + {invoked: prior + {turn_end_condition: invoked}} }
}
}
fn __completion_policy_run_stage(
harness: Harness,
opts: dict,
iteration: int,
evaluation: CompletionPolicyEvaluation,
stage: CompletionPolicyStage,
) -> CompletionPolicyEvaluation {
const payload = evaluation.payload
match stage {
Deterministic(check) -> {
const verdict = __judge_invoke_closure(check, payload)
__judge_emit_decision(harness.agent, payload.session_id, iteration, verdict)
return evaluation
+ {
verdict: verdict,
invoked: evaluation.invoked + {deterministic: true},
payload: __judge_payload_with_verification(payload, verdict.verification),
deterministic_reason: to_string(verdict.reason ?? ""),
}
}
ModelJudge(slot, config, trigger, prior_invocations, max_invocations) -> {
if max_invocations != nil && prior_invocations >= max_invocations {
return __completion_policy_mark_cap(evaluation, slot)
}
// Decide whether the LLM confirmation is needed BEFORE announcing that a
// judge started. A skipped judge never ran, so emitting a started event
// for it would put a call in the timeline that no decision ever answers.
match slot {
Verification() -> {
if agent_completion_review_should_skip_llm(
agent_completion_review(harness.llm, opts),
evaluation,
payload,
) {
const skipped = {
vetoed: false,
verdict: "done",
source: "catalog",
judge_outcome: "skipped_light_scrutiny",
judge_duration_ms: 0,
trigger: trigger,
}
__judge_emit_decision(harness.agent, payload.session_id, iteration, skipped)
return evaluation + {verdict: skipped}
}
}
Completion() -> {
}
}
__judge_emit_started(harness.agent, payload.session_id, iteration, trigger)
let verdict = __judge_invoke_structured(harness, config, opts, payload)
verdict = __completion_arbitrate_contradiction(
__completion_arbitrate_verification(verdict, evaluation),
evaluation,
)
+ {trigger: trigger}
__judge_emit_decision(harness.agent, payload.session_id, iteration, verdict)
const next = evaluation + {verdict: verdict}
return __completion_policy_mark_model_invoked(
next,
slot,
verdict.judge_outcome != "admission_refused",
)
}
}
}
fn __completion_policy_evaluate(
harness: Harness,
opts: dict,
payload: CompletionEvidenceSnapshot,
iteration: int,
stages: list<CompletionPolicyStage>,
) -> CompletionPolicyEvaluation {
let evaluation = __completion_policy_initial_evaluation(payload)
for stage in stages {
if !__completion_policy_can_advance(evaluation) {
break
}
evaluation = __completion_policy_run_stage(harness, opts, iteration, evaluation, stage)
}
return evaluation
}
/**
* Evaluate one verification/completion boundary and return one typed directive.
*
* `text` is the projected candidate turn — what a reader should be shown.
* `raw_text` is that same turn before projection, and is the field a policy
* must read to decide whether the model declared completion: the parser has
* already stripped completion markers out of `text`. Callers with the
* unprojected turn in hand should pass it; omitting it leaves `raw_text`
* equal to `text`, which preserves the pre-existing behavior.
*
* @effects: [agent, host, llm.call]
* @errors: []
*/
pub fn agent_evaluate_completion(
harness: Harness,
session: dict,
opts: dict,
stop_reason: string,
text: string,
iteration: int = 0,
raw_text: string = "",
) -> CompletionDirective {
const payload = __judge_payload(
harness.agent,
session,
opts,
stop_reason,
text,
iteration,
raw_text,
)
const stages = __completion_policy_plan(
opts,
stop_reason,
agent_completion_review(harness.llm, opts),
)
const evaluation = __completion_policy_evaluate(harness, opts, payload, iteration, stages)
const verdict = evaluation.verdict
// The evaluated snapshot, not the one built before any stage ran: it is the
// one carrying whatever the deterministic gate observed.
const evaluated = evaluation.payload
let feedback_composition = nil
if verdict.vetoed
&& verdict.feedback != nil
&& verdict.feedback != ""
&& !(opts?._defer_judge_feedback ?? false) {
feedback_composition = agent_feedback_emit(
harness.agent,
session.session_id,
[
{
kind: "verify_completion",
content: verdict.feedback,
source: verdict.trigger ?? "completion_judge",
reason: "judge_veto",
action: "continue",
next_step: verdict.next_step ?? "",
},
],
{history: opts?._feedback_history ?? {}, knowledge_state: evaluated.knowledge_state},
)
}
const directive = __judge_directive(
verdict,
evaluated,
evaluation.invoked,
evaluation.verification_judge_cap_reached,
evaluation.completion_judge_cap_reached,
feedback_composition,
feedback_composition?.history ?? opts?._feedback_history ?? {},
opts?._defer_judge_feedback ?? false,
)
agent_emit_event(harness.agent, session.session_id, "typed_checkpoint", directive.receipt)
return directive
}