import "std/agent/loop_resource_dispatch"
import "std/agent/loop_result_status"
import "std/agent/loop_support"
import "std/agent/loop_tool_calls"
import "std/agent/loop_turn_options"
import "std/agent/loop_turn_scope"
/*
* The typed cause of a suspension, for the host that has to tell a person why
* the run stopped and what would let it continue.
*
* `agent_await_resumption` validates the reason and the resume conditions at
* the call that produces them, and the suspend record keeps both. This hands
* that record to the finalize seam unchanged rather than re-deriving a summary
* the loop already has. A run that did not park contributes nothing, and so
* does a park with no declared reason, so an absent record always means there
* was no cause to carry and never that one was dropped.
*/
fn __agent_loop_suspension_record(suspend_result: any) -> dict? {
if suspend_result == nil {
return nil
}
const reason = trim(to_string(suspend_result?.reason ?? ""))
if reason == "" {
return nil
}
return {
reason: reason,
initiator: to_string(suspend_result?.initiator ?? ""),
conditions: suspend_result?.conditions,
}
}
/*
* Terminal bookkeeping lives outside the turn engine so the engine can stay
* focused on one iteration's control flow. The state record is deliberately
* assembled by loop_run: it keeps this boundary explicit without making the
* Harn module depend on a second, parallel result schema.
*/
pub fn __agent_loop_finalize(harness: Harness, message: any, session: dict, state: dict) -> dict {
let final_status = state.final_status
let stop_reason = state.stop_reason
let budget_exhausted_emitted = state.budget_exhausted_emitted
let budget_decisions = state.budget_decisions
if final_status == "budget_exhausted" && !budget_exhausted_emitted {
const projection = __agent_loop_budget_error_projection(state.terminal_error)
const terminal_exhaustion = __agent_loop_budget_aggregates(
harness.agent,
harness.clock,
session.session_id,
nil,
state.loop_start_ms,
)
+ {
exhausted: true,
// Naming a default ceiling here would invent a cause: a break that left
// `stop_reason` unset reached this seam without one, and guessing
// "max_iterations" reads as a spent iteration budget even at iteration 1
// of 8. Say the reason is missing and let the reader go look.
kind: stop_reason ?? "unattributed",
iteration: state.iteration,
max_iterations: state.current_max,
}
__agent_loop_emit_budget_exhausted(
harness.agent,
session.session_id,
terminal_exhaustion,
projection,
)
budget_exhausted_emitted = true
budget_decisions = __agent_loop_record_budget_stop(
budget_decisions,
state.iteration,
state.current_max,
terminal_exhaustion.kind,
)
}
const opts = state.opts
const suspend_result = state.suspend_result
const terminal_error = state.terminal_error
const wrapup_enabled = opts?.final_wrapup ?? true
// A no-tool terminal normally skips wrap-up (nothing to summarize), but a
// host that supplied its own directive is explicitly asking for sad-path
// coverage — including the zero-write terminals where the user is most owed
// a truthful closing message — so honor it even at last_tool_count == 0.
const wrapup_tools_ok = state.last_tool_count > 0
|| __agent_loop_has_host_wrapup_directive(opts)
if wrapup_enabled
&& suspend_result == nil
&& terminal_error == nil
&& wrapup_tools_ok
&& __agent_loop_wrapup_eligible_status(final_status) {
let _ = try {
__agent_loop_final_wrapup(
harness,
message,
session,
opts,
final_status,
stop_reason,
state.iteration,
)
}
}
const output_contract = if terminal_error == nil
&& suspend_result == nil
&& (final_status == "" || final_status == "done")
&& __agent_loop_has_terminal_output_contract(opts) {
__agent_loop_prepare_output_contract(harness, opts, session, state.iteration)
} else {
nil
}
if opts?.daemon && final_status != "" {
agent_daemon_snapshot(harness.agent, session, opts, final_status, state.iteration)
}
try {
__host_drain_file_edits(session.session_id)
} catch (e) {
nil
}
agent_stage(harness.agent, session.session_id, "loop_exit", {iteration: state.iteration})
__drain_audit_flushes(state.audit_background_tasks)
const result = agent_session_finalize(
harness.agent,
session.session_id,
{
final_status: final_status,
stop_reason: __agent_loop_sealed_stop_reason(stop_reason, final_status),
iterations: state.iteration,
error: terminal_error,
suspension: __agent_loop_suspension_record(suspend_result),
},
)
return {result: result, budget_decisions: budget_decisions, output_contract: output_contract}
}
pub fn __agent_loop_project_terminal_result(
harness: Harness,
result: any,
message: any,
session: dict,
state: dict,
) -> dict {
const opts = state.opts
const final_status = state.final_status
const stop_reason = state.stop_reason
const suspend_result = state.suspend_result
const terminal_error = state.terminal_error
const terminated_ok = terminal_error == nil
&& suspend_result == nil
&& (final_status == "" || final_status == "done")
const final_stall_state = if terminated_ok {
agent_stall_clear_current_failure(state.stall_state)
} else {
state.stall_state
}
const result_with_stalls = agent_stall_apply_result(
result,
state.stall_enabled_seen,
final_stall_state,
)
const enforced = if suspend_result != nil {
result_with_stalls
} else {
agent_required_tools_enforce(harness.agent, harness.obs, result_with_stalls, opts)
}
let final_result = enforced
if opts?.verify_completion_judge != nil {
final_result = final_result
+ {
completion_judge: {
invocations: state.verify_completion_judge_invocations,
vetoes: state.verify_completion_judge_vetoes,
max_invocations: agent_verify_completion_judge_cap(
opts?.verify_completion_judge,
agent_completion_review(harness.llm, opts),
),
cap_reached: final_status == "completion_unverified"
&& stop_reason
== "completion_judge_cap_reached",
},
}
}
if agent_has_turn_end_condition(opts?.turn_end_condition) {
final_result = final_result
+ {
turn_end_condition: {
invocations: state.turn_end_judge_invocations,
vetoes: state.turn_end_judge_vetoes,
max_invocations: agent_turn_end_judge_cap(opts?.turn_end_condition),
cap_reached: final_status == "completion_unverified"
&& stop_reason == "turn_end_judge_cap_reached",
},
}
}
const budget = state.budget
if budget.expose_decisions {
final_result = final_result
+ {
adaptive_budget: {
mode: budget.mode,
initial: budget.initial,
max: budget.max,
final_limit: state.current_max,
extensions_used: state.extensions_used,
decisions: state.budget_decisions,
},
}
}
// harn#7915: the abandonment evidence rides BESIDE the terminal, on the same
// result the host already reads, so the typed kind says the run failed and
// this says what the loop saw. Absent whenever the run was not convicted, so
// a present record always means the rule fired and never that a default was
// stamped.
const abandonment = state?.abandonment
if abandonment != nil {
final_result = final_result + {abandonment: abandonment}
}
// harn#7915 cause 3: the declared-artifact audit is reported on every run
// that declared a contract, PASSING RUNS INCLUDED. A verdict published only
// on conviction would be indistinguishable from a contract that was never
// checked, and a caller could not tell a satisfied declaration from a
// skipped one.
const declared_artifacts = state?.declared_artifacts
if declared_artifacts != nil {
final_result = final_result + {declared_artifacts: declared_artifacts}
}
if suspend_result != nil {
final_result = final_result + suspend_result
}
if terminated_ok && state.output_contract != nil {
final_result = final_result + state.output_contract
}
return final_result
}