import { agent_emit_event, agent_session_inject_feedback } from "std/agent/state"
type AgentRequiredToolRequirement = string | list<string>
type AgentRequiredToolsEnvelope = {
schema_version: int,
kind: "missing_required_tools",
reason: string,
missing: list<string>,
successful_tool_names: list<string>,
iterations: int,
}
fn __agent_required_tool_satisfied(requirement: AgentRequiredToolRequirement, successful: list) -> bool {
if type_of(requirement) == "list" {
for candidate in requirement {
if contains(successful, candidate) {
return true
}
}
return false
}
return contains(successful, requirement)
}
fn __agent_required_tool_missing(requirement: AgentRequiredToolRequirement, successful: list) -> bool {
return !__agent_required_tool_satisfied(requirement, successful)
}
fn __agent_required_tool_label(requirement) -> string {
if type_of(requirement) == "list" {
return join(requirement, "|")
}
return to_string(requirement)
}
fn __agent_required_tools_from_options(opts) -> list {
let required = opts?.require_successful_tools
if required == nil {
return []
}
return required
}
/**
* agent_required_tools_missing_from_result returns missing required tools.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_required_tools_missing_from_result(result, opts)
*/
pub fn agent_required_tools_missing_from_result(result: dict, opts) -> list<string> {
let required = __agent_required_tools_from_options(opts)
if len(required) == 0 {
return []
}
let successful = result?.tools?.successful ?? []
var missing = []
for requirement in required {
if !__agent_required_tool_satisfied(requirement, successful) {
missing = missing.push(__agent_required_tool_label(requirement))
}
}
return missing
}
/**
* agent_required_tools_missing_from_session returns missing accumulated tools.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_required_tools_missing_from_session(opts, successful_tools_seen)
*/
pub fn agent_required_tools_missing_from_session(opts, successful_tools_seen: list) -> list<string> {
let required = __agent_required_tools_from_options(opts)
if len(required) == 0 {
return []
}
var missing = []
for requirement in required {
if __agent_required_tool_missing(requirement, successful_tools_seen) {
missing = missing.push(__agent_required_tool_label(requirement))
}
}
return missing
}
fn __agent_required_tools_friction_event(result: dict, opts, missing: list<string>) -> dict {
let session_id = to_string(result?.session_id ?? "")
var recurrence = ["missing_required_tools=" + to_string(len(missing))]
if opts?.persona != nil {
recurrence = recurrence.push("persona=" + to_string(opts.persona))
}
return {
kind: "tool_gap",
source: "agent_loop.require_successful_tools",
actor: opts?.persona,
run_id: if session_id == "" {
nil
} else {
session_id
},
redacted_summary: "agent_loop completed without invoking required tool(s): "
+ join(missing, ", "),
recurrence_hints: recurrence,
metadata: {
missing_required_tools: missing,
successful_tool_names: result?.tools?.successful ?? [],
iterations: result?.llm?.iterations ?? 0,
preset_kind: opts?._preset_kind,
},
}
}
fn __agent_required_tools_envelope(result: dict, missing: list<string>, iterations: int) -> AgentRequiredToolsEnvelope {
return {
schema_version: 1,
kind: "missing_required_tools",
reason: "Required tool(s) did not succeed",
missing: missing,
successful_tool_names: result?.tools?.successful ?? [],
iterations: iterations,
}
}
fn __agent_required_tools_emit_friction(result: dict, opts, missing: list<string>) {
let event = __agent_required_tools_friction_event(result, opts, missing)
let _ = try {
friction_record(event)
}
let session_id = to_string(result?.session_id ?? "")
if session_id != "" {
let _ = try {
agent_emit_event(session_id, "require_successful_tools_violation", event)
}
}
}
/**
* agent_required_tools_enforce fails results that missed required tools.
*
* @effects: [agent]
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_required_tools_enforce(result, opts)
*/
pub fn agent_required_tools_enforce(result: dict, opts) -> dict {
let missing = agent_required_tools_missing_from_result(result, opts)
if len(missing) == 0 {
return result
}
let iterations = result?.llm?.iterations ?? 0
let envelope = __agent_required_tools_envelope(result, missing, iterations)
__agent_required_tools_emit_friction(result, opts, missing)
return result
+ {
status: "failed",
final_status: "failed",
stop_reason: "missing_required_tools",
missing_required_tools: missing,
error: "Required tools did not succeed: " + join(missing, ", "),
error_envelope: envelope,
}
}
/**
* agent_required_tools_feedback builds feedback for missing required tools.
*
* @effects: []
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_required_tools_feedback(["run_tests"])
*/
pub fn agent_required_tools_feedback(missing: list<string>) -> string {
return "Required tool(s) have not succeeded yet: "
+ join(missing, ", ")
+ ". Continue working and call the missing required tool(s) before finalizing."
}
/**
* agent_required_tools_inject_feedback injects missing-tool feedback.
*
* @effects: [agent]
* @allocation: heap
* @errors: []
* @api_stability: experimental
* @example: agent_required_tools_inject_feedback(session_id, missing)
*/
pub fn agent_required_tools_inject_feedback(session_id: string, missing: list<string>) {
agent_session_inject_feedback(session_id, "required_tools", agent_required_tools_feedback(missing))
}