import {
agent_emit_event,
agent_session_apply_reminder_post_turn,
agent_session_inject_feedback,
agent_session_record_skill_event,
} from "std/agent/state"
fn __default_done_sentinel(opts) {
return opts?.done_sentinel
}
fn __sentinel_hit(text, sentinel, parsed_done_marker) {
if sentinel == nil {
return false
}
if parsed_done_marker != nil && parsed_done_marker != "" {
return contains(parsed_done_marker, sentinel)
}
return contains(text, sentinel)
}
fn __dispatch_results_list(dispatch) {
if dispatch == nil {
return []
}
if type_of(dispatch) == "list" {
return dispatch
}
return dispatch?.results ?? []
}
fn __tool_result_ok(result) {
if result?.ok != nil {
return result.ok ? true : false
}
if result?.success != nil {
return result.success ? true : false
}
let status = result?.status ?? ""
return status == "ok" || status == "success"
}
fn __tool_result_name(result) {
return result?.tool_name ?? result?.name ?? ""
}
fn __tool_names_by_status(dispatch, want_ok) {
let results = __dispatch_results_list(dispatch)
var names = []
for result in results {
let name = __tool_result_name(result)
if name != "" && __tool_result_ok(result) == want_ok {
names = names.push(name)
}
}
return names
}
fn __post_turn_callback_verdict(opts, payload) {
let cb = opts?.post_turn_callback
if cb == nil {
return {kind: "none"}
}
let verdict = cb(payload)
return __interpret_verdict(verdict)
}
fn __interpret_verdict(verdict) {
if verdict == nil || verdict == "" {
return {kind: "none"}
}
if type_of(verdict) == "bool" {
return if verdict {
{kind: "stop"}
} else {
{kind: "none"}
}
}
if type_of(verdict) == "string" {
return {kind: "inject", message: verdict}
}
if type_of(verdict) == "dict" {
let stop = verdict?.stop ?? false
let base_llm_options = if type_of(verdict?.llm_options) == "dict" {
verdict.llm_options
} else {
{}
}
let llm_options = if verdict?.prefill != nil && verdict?.prefill != "" {
base_llm_options + {prefill: verdict?.prefill}
} else {
verdict?.llm_options
}
return {
kind: "rich",
stop: stop,
stop_reason: verdict?.stop_reason,
message: verdict?.message,
next_options: verdict?.next_options,
llm_options: llm_options,
}
}
return {kind: "none"}
}
fn __successful_tool_names(dispatch) {
return __tool_names_by_status(dispatch, true)
}
fn __rejected_tool_names(dispatch) {
return __tool_names_by_status(dispatch, false)
}
fn __stop_after_successful_tools(opts, dispatch) {
let names = opts?.stop_after_successful_tools
if names == nil || len(names) == 0 {
return false
}
let successful = __successful_tool_names(dispatch)
for required in names {
if contains(successful, required) {
return true
}
}
return false
}
/**
* The classic gate for "prose without tool_calls = natural completion":
* loop_until_done with native tools, no explicit completion signal, and
* non-empty visible text without tool_calls. Shared between the
* completion check and the nudge-injection check so they cannot drift.
*/
fn __native_completion_context(opts, has_tool_calls, text) {
if !(opts?.loop_until_done ?? false) || opts?.daemon {
return false
}
if opts?.tool_format != "native" || opts?.tools == nil {
return false
}
if __default_done_sentinel(opts) != nil {
return false
}
if has_tool_calls {
return false
}
return trim(text) != ""
}
/**
* Narrows `__native_completion_context` to the regression-prone case:
* the model is "done" but has emitted ZERO tool_calls so far this
* session (successful OR rejected — a failed tool call still shows
* the model engaged the tool channel). This is the documented Qwen3
* thinking + native tools failure signature:
* reasoning trace narrates tool intent ("let me run git..."), but
* the structured tool_calls channel emits nothing at all. Once the
* model has used the tool channel even once (even unsuccessfully),
* subsequent prose-only turns are legitimate final-answer signals
* and the classic behavior applies.
*/
fn __zero_tool_completion_context(opts, has_tool_calls, text) {
if !__native_completion_context(opts, has_tool_calls, text) {
return false
}
let successful = len(opts?._session_successful_tools ?? [])
let rejected = len(opts?._session_rejected_tools ?? [])
return successful + rejected == 0
}
/**
* In the classic native-completion context, prose-without-tool_calls
* means "I'm done." The one regression-prone subcase is the
* zero-tools-yet case, where we require ONE confirmation nudge before
* accepting prose as completion: `__consecutive_text_only` must be >= 2
* (i.e. the model emitted prose, got the nudge, then emitted prose
* again). After the nudge the model either recovers (calls a tool,
* resetting the counter) or confirms (raising it to 2, which we
* accept). The outer loop's `max_nudges` budget still caps the loop.
*/
fn __native_tool_text_completion(opts, has_tool_calls, text) {
if !__native_completion_context(opts, has_tool_calls, text) {
return false
}
if !__zero_tool_completion_context(opts, has_tool_calls, text) {
return true
}
return opts?._consecutive_text_only ?? 1 >= 2
}
/**
* Inject the one-shot completion-confirmation nudge when we're in the
* regression-prone subcase (zero tools called, prose-only) and haven't
* already nudged this session. Keyed on `_consecutive_text_only` so it
* is idempotent under repeated post-turn calls.
*/
fn __maybe_inject_completion_confirmation(session, opts, has_tool_calls, text, iteration) {
if !__zero_tool_completion_context(opts, has_tool_calls, text) {
return
}
if opts?._consecutive_text_only ?? 1 >= 2 {
return
}
agent_session_inject_feedback(
session.session_id,
"completion_confirmation",
"You ended your turn without calling a tool. If you intended to call a tool, call it now. If this was your final answer, restate it briefly on the next line — do not describe your process.",
)
agent_emit_event(
session.session_id,
"completion_confirmation_nudge",
{iteration: iteration, visible_text_prefix: visible_text_excerpt(text)},
)
}
fn __done_judge_config(opts) {
let judge = opts?.done_judge
if judge == nil {
return nil
}
if type_of(judge) == "bool" {
return if judge {
{}
} else {
nil
}
}
if type_of(judge) == "dict" {
return judge
}
return nil
}
fn __done_judge_cadence(opts) {
let judge = __done_judge_config(opts)
if judge == nil {
return nil
}
let cadence = judge?.cadence
if type_of(cadence) == "dict" {
return cadence
}
return {}
}
fn __done_judge_loop_state(opts, completion_proposed) {
let state = opts?._done_judge_loop_state ?? {}
let base_completion = state?.completion ?? {}
let completion = base_completion + {proposed: completion_proposed}
return state + {completion: completion}
}
fn __done_judge_when_due(opts, cadence, state) {
let when = cadence?.when ?? "always"
if type_of(when) == "closure" {
return when(state) ? true : false
}
if when == "always" {
return true
}
if when == "stalled" {
let trigger = opts?._done_judge_trigger ?? ""
let stalled = state?.stall?.triggered ?? false
return trigger == "stalled" || stalled
}
return false
}
fn __done_judge_every_due(cadence, turn_number) {
let every = cadence?.every
if every == nil {
return true
}
return turn_number % every == 0
}
fn __done_judge_past_warmup(cadence, turn_number) {
let min_iterations = cadence?.min_iterations_before_first
if min_iterations == nil {
return true
}
return turn_number > min_iterations
}
fn __done_judge_under_cap(opts, cadence) {
let max_invocations = cadence?.max_invocations
if max_invocations == nil {
return true
}
let invocations = opts?._done_judge_invocations ?? 0
return invocations < max_invocations
}
fn __done_judge_due(opts, payload, completion_proposed) {
let cadence = __done_judge_cadence(opts)
if cadence == nil {
return false
}
let raw_iteration = payload?.iteration ?? 0
let turn_number = raw_iteration + 1
let state = __done_judge_loop_state(opts, completion_proposed)
return __done_judge_under_cap(opts, cadence)
&& __done_judge_past_warmup(cadence, turn_number)
&& __done_judge_every_due(cadence, turn_number)
&& __done_judge_when_due(opts, cadence, state)
}
fn __completion_result(opts, payload, stop_reason) {
let verify_due = opts?.verify_completion != nil || opts?.verify_completion_judge != nil
let done_due = __done_judge_due(opts, payload, true)
if verify_due || done_due {
return {kind: "break", stop_reason: stop_reason, needs_verify: true, done_judge_due: done_due}
}
if __done_judge_config(opts) != nil {
return {kind: "continue", done_judge_due: false}
}
return {kind: "break", stop_reason: stop_reason, needs_verify: false, done_judge_due: false}
}
fn __post_turn_verdict_result(session, opts, verdict) {
if verdict.kind == "stop" {
return {kind: "break", stop_reason: "post_turn_stop", needs_verify: opts?.verify_completion != nil}
}
if verdict.kind == "inject" {
agent_session_inject_feedback(session.session_id, "post_turn", verdict.message)
return {kind: "continue"}
}
if verdict.kind != "rich" {
return nil
}
if verdict.message != nil {
agent_session_inject_feedback(session.session_id, "post_turn", verdict.message)
}
if verdict.stop {
let stop_reason = if verdict?.stop_reason != nil && verdict.stop_reason != "" {
to_string(verdict.stop_reason)
} else {
"post_turn_stop"
}
return {
kind: "break",
stop_reason: stop_reason,
needs_verify: opts?.verify_completion != nil,
next_options: verdict?.next_options,
llm_options: verdict?.llm_options,
}
}
if verdict.message != nil || verdict?.next_options != nil || verdict?.llm_options != nil {
return {kind: "continue", next_options: verdict?.next_options, llm_options: verdict?.llm_options}
}
return nil
}
fn __tool_surface_name(entry) {
return entry?.name ?? entry?.function?.name ?? ""
}
fn __tool_surface_current_names(opts) {
let registry = opts?.tools
let entries = if type_of(registry) == "dict" {
registry?.tools ?? []
} else if type_of(registry) == "list" {
registry
} else {
[]
}
var names = []
for entry in entries {
let name = __tool_surface_name(entry)
if name != "" && !contains(names, name) {
names = names.push(name)
}
}
return names
}
fn __tool_surface_call_name(call) {
return call?.name ?? call?.function?.name ?? call?.tool ?? call?.tool_name ?? ""
}
fn __tool_surface_attempted_names(llm_result, dispatch_results) {
var names = []
for call in llm_result?.tool_calls ?? [] {
let name = __tool_surface_call_name(call)
if name != "" && !contains(names, name) {
names = names.push(name)
}
}
for result in dispatch_results {
let name = __tool_result_name(result)
if name != "" && !contains(names, name) {
names = names.push(name)
}
}
return names
}
fn __tool_surface_append_history(history, attempted, window_turns) {
var next = history.push(attempted)
while len(next) > window_turns {
next = next[1:]
}
return next
}
fn __tool_surface_window_used(history) {
var used = []
for turn in history {
for name in turn {
if name != "" && !contains(used, name) {
used = used.push(name)
}
}
}
return used
}
fn __tool_surface_removed(candidates, used, hard_keep) {
var removed = []
for name in candidates {
if !contains(used, name) && !contains(hard_keep, name) {
removed = removed.push(name)
}
}
return removed
}
fn __tool_surface_remaining(candidates, removed) {
var remaining = []
for name in candidates {
if !contains(removed, name) {
remaining = remaining.push(name)
}
}
return remaining
}
fn __tool_surface_narrowing_post_turn(session, llm_result, dispatch_results, opts) {
let config = opts?.tool_surface_narrowing ?? {}
if !(config?.enabled ?? true) {
return {history: [], narrowed_tools: nil, changed: false}
}
let window_turns = config?.window_turns ?? 5
let attempted = __tool_surface_attempted_names(llm_result, dispatch_results)
let history = __tool_surface_append_history(opts?._tool_surface_narrowing_history ?? [], attempted, window_turns)
let candidates = __tool_surface_current_names(opts)
if len(candidates) == 0 || len(history) < window_turns {
return {history: history, narrowed_tools: opts?._tool_surface_narrowed_tools, changed: false}
}
let used = __tool_surface_window_used(history)
let hard_keep = config?.hard_keep ?? []
let removed = __tool_surface_removed(candidates, used, hard_keep)
if len(removed) == 0 {
return {history: history, narrowed_tools: opts?._tool_surface_narrowed_tools, changed: false}
}
let remaining = __tool_surface_remaining(candidates, removed)
let reason = "unused across last " + to_string(window_turns) + " turns"
agent_session_record_skill_event(
session.session_id,
"skill_narrow",
{reason: reason, removed_tools: removed, remaining_tools: remaining, window_turns: window_turns},
)
return {
history: history,
narrowed_tools: remaining,
changed: true,
removed_tools: removed,
remaining_tools: remaining,
reason: reason,
}
}
fn __post_turn_with_narrowing(outcome, narrowing) {
if narrowing == nil {
return outcome
}
return outcome + {narrowing: narrowing}
}
fn __tool_surface_keep_entry(entry, names) {
return contains(names, __tool_surface_name(entry))
}
fn __tool_surface_filter_registry(registry, names) {
if len(names) == 0 {
return registry
}
if type_of(registry) == "dict" {
var kept = []
for entry in registry?.tools ?? [] {
if __tool_surface_keep_entry(entry, names) {
kept = kept.push(entry)
}
}
return registry + {tools: kept}
}
if type_of(registry) == "list" {
var kept = []
for entry in registry {
if __tool_surface_keep_entry(entry, names) {
kept = kept.push(entry)
}
}
return kept
}
return registry
}
fn __tool_surface_policy_allowlist(names) {
if len(names) == 0 {
return ["__harn_tool_surface_no_tools__"]
}
return names
}
fn __tool_surface_policy_tools(policy, names) {
if policy != nil && type_of(policy) != "dict" {
return policy
}
let base = policy ?? {}
let existing = base?.tools ?? []
if type_of(existing) == "list" && len(existing) > 0 {
var intersected = []
for name in existing {
if contains(names, name) {
intersected = intersected.push(name)
}
}
return base + {tools: __tool_surface_policy_allowlist(intersected)}
}
return base + {tools: __tool_surface_policy_allowlist(names)}
}
/**
* agent_apply_tool_surface_narrowing.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_apply_tool_surface_narrowing(opts)
*/
pub fn agent_apply_tool_surface_narrowing(opts) {
let names = opts?._tool_surface_narrowed_tools
if len(names ?? []) == 0 {
return opts
}
return opts
+ {
tools: __tool_surface_filter_registry(opts?.tools, names),
policy: __tool_surface_policy_tools(opts?.policy, names),
}
}
/**
* agent_reset_tool_surface_narrowing.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_reset_tool_surface_narrowing(opts)
*/
pub fn agent_reset_tool_surface_narrowing(opts) {
return opts
+ {_tool_surface_narrowed_tools: nil, _tool_surface_narrowing_history: []}
}
/**
* agent_extract_tool_calls.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_extract_tool_calls(llm_result, opts)
*/
pub fn agent_extract_tool_calls(llm_result, opts) {
if len(llm_result?.tool_calls ?? []) > 0 {
return llm_result.tool_calls
}
if opts?.tools == nil {
return []
}
let parsed = agent_parse_tool_calls(llm_result?.text ?? "", opts.tools)
return parsed?.calls ?? []
}
/**
* agent_compute_post_turn.
*
* @effects: [host]
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_compute_post_turn(session, llm_result, dispatch, opts, iteration)
*/
pub fn agent_compute_post_turn(session, llm_result, dispatch, opts, iteration) {
let sentinel = __default_done_sentinel(opts)
let text = llm_result?.text ?? ""
let sentinel_text = llm_result?.raw_text ?? text
let parsed_done_marker = llm_result?.parsed_done_marker
let dispatch_results = __dispatch_results_list(dispatch)
let successful_tool_names = __successful_tool_names(dispatch)
let rejected_tool_names = __rejected_tool_names(dispatch)
let has_tool_calls = len(llm_result?.tool_calls ?? []) > 0 || len(dispatch_results) > 0
let payload = {
session_id: session.session_id,
session: {id: session.session_id},
iteration: iteration,
has_tool_calls: has_tool_calls,
dispatch: dispatch,
tool_results: dispatch_results,
tool_count: len(dispatch_results),
successful_tool_names: successful_tool_names,
rejected_tool_names: rejected_tool_names,
session_successful_tools: opts?._session_successful_tools ?? successful_tool_names,
session_rejected_tools: opts?._session_rejected_tools ?? rejected_tool_names,
text: text,
visible_text: text,
}
__host_fire_session_hook("post_turn", payload)
agent_session_apply_reminder_post_turn(session.session_id, iteration)
let narrowing = if contains(successful_tool_names, "load_skill") {
{history: [], narrowed_tools: nil, changed: false}
} else {
__tool_surface_narrowing_post_turn(session, llm_result, dispatch_results, opts)
}
if __sentinel_hit(sentinel_text, sentinel, parsed_done_marker) {
return __post_turn_with_narrowing(__completion_result(opts, payload, "sentinel"), narrowing)
}
if __stop_after_successful_tools(opts, dispatch) {
return __post_turn_with_narrowing(
{kind: "break", stop_reason: "natural", needs_verify: false, done_judge_due: false},
narrowing,
)
}
let verdict = __post_turn_callback_verdict(opts, payload)
let verdict_result = __post_turn_verdict_result(session, opts, verdict)
if verdict_result != nil {
return __post_turn_with_narrowing(verdict_result, narrowing)
}
if !has_tool_calls && trim(text) != "" && opts?.done_judge != nil {
return __post_turn_with_narrowing(__completion_result(opts, payload, "natural"), narrowing)
}
if __native_tool_text_completion(opts, has_tool_calls, text) {
return __post_turn_with_narrowing(__completion_result(opts, payload, "natural"), narrowing)
}
let loop_until_done = opts?.loop_until_done ?? false
let daemon = opts?.daemon ?? false
if !has_tool_calls && !loop_until_done && !daemon {
return __post_turn_with_narrowing(__completion_result(opts, payload, "natural"), narrowing)
}
__maybe_inject_completion_confirmation(session, opts, has_tool_calls, text, iteration)
return __post_turn_with_narrowing({kind: "continue", done_judge_due: false}, narrowing)
}
fn visible_text_excerpt(text) {
let trimmed = trim(text ?? "")
if len(trimmed) <= 160 {
return trimmed
}
return trimmed[0:160] + "…"
}