Toolkits
Toolkits let an agent assemble dynamic instructions and tools for each run session. Unlike static instructions or tool lists, a toolkit can perform asynchronous work inside Toolkit.createSession
—for example, fetching account data—before handing the agent loop a synchronous ToolkitSession
snapshot of instructions and tools to use for that run.
That session can keep its own mutable state: reuse it across multiple calls to RunSession.run
, swap prompts or tools as the workflow advances, and close resources (database handles, network clients, etc.) when the agent session ends.
interface Toolkit<TContext> { /** * Create a new toolkit session for the supplied context value. * The function should also intialize the ToolkitSession instance with instructions and tools. */ createSession(context: TContext): Promise<ToolkitSession<TContext>>;}
interface ToolkitSession<TContext> { /** * Retrieve the current system prompt for the session. */ getSystemPrompt(): string | undefined; /** * Retrieve the current set of tools that should be available to the session. */ getTools(): AgentTool<TContext>[]; /** * Release any resources that were allocated for the session. */ close(): Promise<void> | void;}
pub trait Toolkit<TCtx>: Send + Sync { /// Create a new toolkit session for the supplied context value. /// Implementations should also initialize the session with instructions and /// tools. async fn create_session( &self, context: &TCtx, ) -> Result<Box<dyn ToolkitSession<TCtx> + Send + Sync>, BoxedError>;}
pub trait ToolkitSession<TCtx>: Send + Sync { /// Retrieve the current system prompt for the session, if available. fn system_prompt(&self) -> Option<String>; /// Retrieve the current set of tools that should be available to the /// session. fn tools(&self) -> Vec<Arc<dyn AgentTool<TCtx>>>; /// Release any resources that were allocated for the session. async fn close(self: Box<Self>) -> Result<(), BoxedError>;}
type Toolkit[C any] interface { // CreateSession creates a new toolkit session for the supplied context value. // Implementations should also initialize the session with any instructions or tools. CreateSession(ctx context.Context, contextVal C) (ToolkitSession[C], error)}
type ToolkitSession[C any] interface { // SystemPrompt returns the current system prompt for the session if available. SystemPrompt() *string // Tools returns the current set of tools that should be available to the session. Tools() []AgentTool[C] // Close releases any resources that were allocated for the session. Close(ctx context.Context) error}
Example
Section titled “Example”The example below follows an interdimensional Lost & Found desk. createSession
loads the
traveler’s manifest asynchronously, while the toolkit session tracks phases (intake → recovery →
handoff) to unlock or retire tools as the conversation progresses. The driver code reuses the same
RunSession
, so you can see toolkit state mutate and the dynamic tool list update turn by turn.
import type { AgentItem, AgentTool, InstructionParam, Toolkit, ToolkitSession,} from "@hoangvvo/llm-agent";import { Agent, getResponseText, tool } from "@hoangvvo/llm-agent";import { getModel } from "./get-model.ts";
type VisitorId = "aurora-shift" | "ember-paradox";
interface RiftContext { visitorId: VisitorId;}
interface RiftManifest { visitorName: string; originReality: string; arrivalSignature: string; contrabandRisk: "low" | "elevated" | "critical"; sentimentalInventory: string[]; outstandingAnomalies: string[]; turbulenceLevel: "calm" | "moderate" | "volatile"; courtesyNote: string;}
// Mock datastore that stands in for an external manifest source resolved during createSession.const RIFT_MANIFESTS: Record<VisitorId, RiftManifest> = { "aurora-shift": { visitorName: "Captain Lyra Moreno", originReality: "Aurora-9 Spiral", arrivalSignature: "slipped in trailing aurora dust and a three-second echo", contrabandRisk: "elevated", sentimentalInventory: [ "Chrono Locket (Timeline 12)", "Folded star chart annotated in ultraviolet", ], outstandingAnomalies: [ "Glitter fog refuses to obey gravity", "Field report cites duplicate footfalls arriving 4s late", ], turbulenceLevel: "moderate", courtesyNote: "Prefers dry humor, allergic to paradox puns.", }, "ember-paradox": { visitorName: "Archivist Rune Tal", originReality: "Ember Paradox Belt", arrivalSignature: "emerged in a plume of cooled obsidian and smoke", contrabandRisk: "critical", sentimentalInventory: [ "Glass bead containing their brother's timeline", "A singed manifesto titled 'Do Not Fold'", ], outstandingAnomalies: [ "Customs still waiting on clearance form 88-A", "Phoenix feather repeats ignition loop every two minutes", ], turbulenceLevel: "volatile", courtesyNote: "Responds well to calm checklists and precise handoffs.", },};
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// Simulated async lookup used inside Toolkit.createSession to hydrate session state up front.async function fetchRiftManifest(visitorId: VisitorId): Promise<RiftManifest> { const manifest = RIFT_MANIFESTS[visitorId]; if (!manifest) { throw new Error(`Unknown visitor ${visitorId}`); } await delay(60); return JSON.parse(JSON.stringify(manifest)) as RiftManifest;}
type IntakePhase = "intake" | "recovery" | "handoff" | "closed";
// Toolkit session retains manifest snapshot and live state so we can rewrite prompts/tools each turn.// A RunSession will hold onto this object and consult it before every call to the language model.class LostAndFoundToolkitSession implements ToolkitSession<RiftContext> { readonly #manifest: RiftManifest; #phase: IntakePhase; #passVerified: boolean; #taggedItems: string[]; #prophecyCount: number; #droneDeployed: boolean;
constructor(manifest: RiftManifest) { this.#manifest = manifest; this.#phase = "intake"; this.#passVerified = false; this.#taggedItems = []; this.#prophecyCount = 0; this.#droneDeployed = false; }
getSystemPrompt(): string { // RunSession polls this every turn; reflect latest state in the instructions we provide. return this.#buildPrompt(); }
getTools(): AgentTool<RiftContext>[] { // Also polled each turn so we can expose a different toolset as the workflow advances. const tools = this.#buildTools(); console.log( `[Toolkit] Tools for phase ${this.#phase.toUpperCase()}: ${ tools.map((tool) => tool.name).join(", ") || "<none>" }`, ); return tools; }
async close() { /** noop */ }
#buildPrompt(): string { const lines: string[] = []; lines.push( "You are the Archivist manning Interdimensional Waypoint Seven's Lost & Found counter.", ); lines.push( `Visitor: ${this.#manifest.visitorName} from ${this.#manifest.originReality} ` + `(${this.#manifest.arrivalSignature}).`, ); lines.push( `Contraband risk: ${this.#manifest.contrabandRisk}. Turbulence: ${this.#manifest.turbulenceLevel}.`, ); lines.push( `Sentimental inventory on file: ${ this.#manifest.sentimentalInventory.length ? this.#manifest.sentimentalInventory.join("; ") : "none" }`, ); lines.push( `Outstanding anomalies: ${ this.#manifest.outstandingAnomalies.length ? this.#manifest.outstandingAnomalies.join("; ") : "none" }`, ); if (this.#taggedItems.length > 0) { lines.push(`Traveler has logged: ${this.#taggedItems.join("; ")}.`); } else { lines.push( "No traveler-reported items logged yet; invite concise descriptions.", ); } if (this.#droneDeployed) { lines.push( "Retrieval drone currently deployed; note its status when replying.", ); } lines.push(`Current phase: ${this.#phase.toUpperCase()}.`);
switch (this.#phase) { case "intake": if (!this.#passVerified) { lines.push( "Stabilise their arrival and prioritise verify_pass before promising retrieval.", ); } break; case "recovery": lines.push( "Phase focus: coordinate retrieval. Summon a retrieval option or consult the prophet. Issue a quantum receipt when ready to hand off.", ); break; case "handoff": lines.push( "Phase focus: wrap neatly. If receipt already issued, close_manifest and summarise remaining anomalies.", ); break; case "closed": lines.push( "Manifest is archived. No tools remain; deliver a final tidy summary and dismiss traveler politely.", ); break; }
lines.push( "Tone: dry, organised, lightly amused. Reference protocol instead of improvising lore.", ); lines.push(this.#manifest.courtesyNote); lines.push( "When tools are available, invoke exactly one relevant tool before finalising your answer. If no tools remain, simply summarise the closure.", );
return lines.join("\n"); }
#buildTools(): AgentTool<RiftContext>[] { if (this.#phase === "closed") { return []; }
const tools: AgentTool<RiftContext>[] = [];
// Baseline tools remain available across phases; closures mutate session state where needed. tools.push( tool<RiftContext, { technique: string }>({ name: "stabilize_rift", description: "Describe how you calm the rift turbulence and reassure the traveler.", parameters: { type: "object", properties: { technique: { type: "string", description: "Optional note about the stabilisation technique used.", }, }, required: ["technique"], additionalProperties: false, }, execute: (args) => { const technique = args.technique?.trim() ?? ""; console.log( `[tool] stabilize_rift invoked with technique=${technique}`, ); const text = `I cycle the containment field to damp ${this.#manifest.turbulenceLevel} turbulence` + (technique.length ? ` using ${technique}` : "") + "."; return { content: [ { type: "text", text, }, ], is_error: false, }; }, }), );
tools.push( tool<RiftContext, { item: string; timeline: string }>({ name: "log_item", description: "Record a traveler-reported possession so recovery tools know what to fetch.", parameters: { type: "object", properties: { item: { type: "string", description: "Name of the missing item.", }, timeline: { type: "string", description: "Optional timeline or reality tag for the item.", }, }, required: ["item", "timeline"], additionalProperties: false, }, execute: (args) => { const timeline = args.timeline?.trim(); const label = timeline ? `${args.item} (${timeline})` : args.item; this.#taggedItems.push(label); console.log(`[tool] log_item recorded ${label}`); return { content: [ { type: "text", text: `Logged ${label} for retrieval queue. Current ledger: ${this.#taggedItems.join( "; ", )}.`, }, ], is_error: false, }; }, }), );
if (!this.#passVerified) { // Toolkit keeps certain tools hidden until prerequisite state (verified pass) flips. tools.push( tool<RiftContext, { clearance_code: string }>({ name: "verify_pass", description: "Validate the traveler's interdimensional pass to unlock recovery tools.", parameters: { type: "object", properties: { clearance_code: { type: "string", description: "Code supplied by the traveler for verification.", }, }, required: ["clearance_code"], additionalProperties: false, }, execute: (args) => { this.#passVerified = true; this.#phase = "recovery"; console.log( `[tool] verify_pass authenticated clearance_code=${args.clearance_code}`, ); return { content: [ { type: "text", text: `Pass authenticated with code ${args.clearance_code}. Recovery protocols online.`, }, ], is_error: false, }; }, }), ); }
if (this.#phase === "recovery" && this.#passVerified) { tools.push( tool<RiftContext, { designation: string; target: string }>({ name: "summon_retrieval_drone", description: "Dispatch a retrieval drone to recover a logged item from the rift queue.", parameters: { type: "object", properties: { designation: { type: "string", description: "Optional drone designation to flavour the dispatch.", }, target: { type: "string", description: "Specific item to prioritise; defaults to the first logged item.", }, }, required: ["designation", "target"], additionalProperties: false, }, execute: (args) => { this.#droneDeployed = true; const target = args.target?.trim().length ? args.target : (this.#taggedItems[0] ?? "the most recently logged item"); const designation = args.designation?.trim().length ? args.designation : "Drone Theta"; console.log( `[tool] summon_retrieval_drone dispatched designation=${designation} target=${target}`, ); return { content: [ { type: "text", text: `Dispatched ${designation} to retrieve ${target}.`, }, ], is_error: false, }; }, }), );
if (this.#prophecyCount === 0) { // Example of a single-use tool disappearing once invoked. tools.push( tool<RiftContext, { topic: string }>({ name: "consult_prophet_agent", description: "Ping Prophet Sigma for probability guidance when the queue misbehaves.", parameters: { type: "object", properties: { topic: { type: "string", description: "Optional focus question for the prophet agent.", }, }, required: ["topic"], additionalProperties: false, }, execute: (args) => { this.#prophecyCount += 1; const topic = args.topic?.trim(); console.log( `[tool] consult_prophet_agent requested topic=${topic ?? "<none>"}`, ); return { content: [ { type: "text", text: `Prophet Sigma notes anomaly priority: ${ this.#manifest.outstandingAnomalies[0] ?? "no immediate hazards" }${topic ? ` while considering ${topic}.` : "."}`, }, ], is_error: false, }; }, }), ); }
if (this.#taggedItems.length > 0) { tools.push( tool<RiftContext, { recipient: string }>({ name: "issue_quantum_receipt", description: "Generate a quantum receipt confirming which items are cleared for handoff.", parameters: { type: "object", properties: { recipient: { type: "string", description: "Optional recipient line for the receipt header.", }, }, required: ["recipient"], additionalProperties: false, }, execute: (args) => { this.#phase = "handoff"; const recipient = args.recipient?.trim().length ? args.recipient : this.#manifest.visitorName; console.log( `[tool] issue_quantum_receipt issued to ${recipient} for items=${this.#taggedItems.join(", ")}`, ); return { content: [ { type: "text", text: `Issued quantum receipt to ${recipient} for ${this.#taggedItems.join( "; ", )}. Handoff phase engaged.`, }, ], is_error: false, }; }, }), ); } }
if (this.#phase === "handoff") { // Once handoff begins, offer a closure tool that transitions to the final state. tools.push( tool<RiftContext, Record<string, never>>({ name: "close_manifest", description: "Archive the case once items are delivered and note any lingering anomalies.", parameters: { type: "object", properties: {}, required: [], additionalProperties: false, }, execute: () => { this.#phase = "closed"; console.log( `[tool] close_manifest archived manifest with anomalies=${this.#manifest.outstandingAnomalies.length}`, ); return { content: [ { type: "text", text: `Archived manifest with ${this.#manifest.outstandingAnomalies.length} anomaly reminder(s) for facilities.`, }, ], is_error: false, }; }, }), ); }
return tools; }}
// Toolkit wires the async manifest fetch into createSession and returns the stateful session.class LostAndFoundToolkit implements Toolkit<RiftContext> { async createSession( context: RiftContext, ): Promise<ToolkitSession<RiftContext>> { const manifest = await fetchRiftManifest(context.visitorId); return new LostAndFoundToolkitSession(manifest); }}
// Static tool supplied directly on the agent to illustrate coexistence with toolkit-provided tools.const pageSecurityTool = tool<RiftContext, { reason: string }>({ name: "page_security", description: "Escalate to security if contraband risk becomes unmanageable.", parameters: { type: "object", properties: { reason: { type: "string", description: "Why security needs to step in.", }, }, required: ["reason"], additionalProperties: false, }, execute: (args, ctx) => ({ content: [ { type: "text", text: `Security paged for ${ctx.visitorId}: ${args.reason}.`, }, ], is_error: false, }),});
// Base agent instructions still resolve separately; toolkit prompt stacks on top each turn.const instructions: InstructionParam<RiftContext>[] = [ "You are the archivist at Waypoint Seven's Interdimensional Lost & Found desk.", "Keep responses under 120 words when possible and stay bone-dry with humour.", ({ visitorId }) => `Reference the visitor's manifest details supplied by the toolkit for ${visitorId}. Do not invent new lore.`, "When tools remain, call exactly one per turn before concluding. If tools run out, summarise the closure instead.",];
// Traditional Agent setup still works: we wire static tools, instructions, and our custom toolkit together.const archivist = new Agent<RiftContext>({ name: "WaypointArchivist", instructions, model: getModel("openai", "gpt-4o-mini"), tools: [pageSecurityTool], toolkits: [new LostAndFoundToolkit()],});
async function runDemo() { // Reuse a RunSession so ToolkitSession state persists across multiple turns. const session = await archivist.createSession({ visitorId: "aurora-shift" }); const transcript: AgentItem[] = [];
const prompts = [ "I just slipped through the rift and my belongings are glittering in the wrong timeline. What now?", "The Chrono Locket from Timeline 12 is missing, and the echo lag is getting worse.", "The locket links to my sister's echo—anything else before I depart?", ];
for (const [index, prompt] of prompts.entries()) { // Accumulate the conversation transcript so each turn knows the prior history. transcript.push({ type: "message", role: "user", content: [{ type: "text", text: prompt }], });
// Each invocation reuses the toolkit session, so newly unlocked tools remain available. const response = await session.run({ input: transcript });
console.log(`\n=== TURN ${index + 1} ===`); console.log(getResponseText(response));
// Feed model/tool outputs back into the transcript for the next turn. transcript.push(...response.output); }
await session.close();}
await runDemo();
use std::{ env, sync::{Arc, Mutex}, time::Duration,};
use async_trait::async_trait;use dotenvy::dotenv;use llm_agent::{ Agent, AgentItem, AgentParams, AgentResponse, AgentTool, AgentToolResult, RunSessionRequest, Toolkit, ToolkitSession,};use llm_sdk::{ openai::{OpenAIModel, OpenAIModelOptions}, Message, Part,};use serde::Deserialize;use tokio::time::sleep;
type VisitorId = &'static str;
type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Clone, Copy)]struct RiftContext { visitor_id: VisitorId,}
#[derive(Clone)]struct RiftManifest { visitor_name: &'static str, origin_reality: &'static str, arrival_signature: &'static str, contraband_risk: &'static str, sentimental_inventory: &'static [&'static str], outstanding_anomalies: &'static [&'static str], turbulence_level: &'static str, courtesy_note: &'static str,}
// Mock manifest store to show Toolkit::create_session performing async I/O// before the session starts.async fn fetch_rift_manifest(visitor_id: VisitorId) -> Result<Arc<RiftManifest>, BoxError> { let manifest = match visitor_id { "aurora-shift" => RiftManifest { visitor_name: "Captain Lyra Moreno", origin_reality: "Aurora-9 Spiral", arrival_signature: "slipped in trailing aurora dust and a three-second echo", contraband_risk: "elevated", sentimental_inventory: &[ "Chrono Locket (Timeline 12)", "Folded star chart annotated in ultraviolet", ], outstanding_anomalies: &[ "Glitter fog refuses to obey gravity", "Field report cites duplicate footfalls arriving 4s late", ], turbulence_level: "moderate", courtesy_note: "Prefers dry humor, allergic to paradox puns.", }, "ember-paradox" => RiftManifest { visitor_name: "Archivist Rune Tal", origin_reality: "Ember Paradox Belt", arrival_signature: "emerged in a plume of cooled obsidian and smoke", contraband_risk: "critical", sentimental_inventory: &[ "Glass bead containing their brother's timeline", "A singed manifesto titled 'Do Not Fold'", ], outstanding_anomalies: &[ "Customs still waiting on clearance form 88-A", "Phoenix feather repeats ignition loop every two minutes", ], turbulence_level: "volatile", courtesy_note: "Responds well to calm checklists and precise handoffs.", }, other => return Err(format!("unknown visitor {other}").into()), };
sleep(Duration::from_millis(60)).await; Ok(Arc::new(manifest))}
#[derive(Clone, Copy, PartialEq, Eq)]enum Phase { Intake, Recovery, Handoff, Closed,}
struct LostAndFoundState { manifest: Arc<RiftManifest>, phase: Phase, pass_verified: bool, tagged_items: Vec<String>, prophecy_count: u8, drone_deployed: bool,}
impl LostAndFoundState { fn new(manifest: Arc<RiftManifest>) -> Self { Self { manifest, phase: Phase::Intake, pass_verified: false, tagged_items: Vec::new(), prophecy_count: 0, drone_deployed: false, } }}
// Toolkit session keeps manifest snapshot and mutable workflow flags so each// turn can surface new prompt/tool sets.struct LostAndFoundToolkitSession { state: Arc<Mutex<LostAndFoundState>>,}
#[async_trait]impl ToolkitSession<RiftContext> for LostAndFoundToolkitSession { fn system_prompt(&self) -> Option<String> { let state = self.state.lock().expect("state poisoned"); Some(build_prompt(&state)) }
fn tools(&self) -> Vec<Arc<dyn AgentTool<RiftContext>>> { let snapshot = { let state = self.state.lock().expect("state poisoned"); ( state.phase, state.pass_verified, state.tagged_items.len(), state.prophecy_count, ) };
let (phase, pass_verified, tagged_len, prophecy_count) = snapshot; if phase == Phase::Closed { println!("[Toolkit] Tools for phase {}: <none>", phase_label(phase)); return Vec::new(); }
let mut tools: Vec<Arc<dyn AgentTool<RiftContext>>> = vec![ Arc::new(StabilizeRiftTool { state: Arc::clone(&self.state), }), Arc::new(LogItemTool { state: Arc::clone(&self.state), }), ];
if !pass_verified { tools.push(Arc::new(VerifyPassTool { state: Arc::clone(&self.state), })); }
if phase == Phase::Recovery && pass_verified { tools.push(Arc::new(SummonRetrievalDroneTool { state: Arc::clone(&self.state), }));
if prophecy_count == 0 { tools.push(Arc::new(ConsultProphetTool { state: Arc::clone(&self.state), })); }
if tagged_len > 0 { tools.push(Arc::new(IssueQuantumReceiptTool { state: Arc::clone(&self.state), })); } }
if phase == Phase::Handoff { tools.push(Arc::new(CloseManifestTool { state: Arc::clone(&self.state), })); }
let names = if tools.is_empty() { "<none>".to_string() } else { tools .iter() .map(|tool| tool.name()) .collect::<Vec<_>>() .join(", ") }; println!( "[Toolkit] Tools for phase {}: {}", phase_label(phase), names );
tools }
async fn close(self: Box<Self>) -> Result<(), BoxError> { Ok(()) }}
struct LostAndFoundToolkit;
#[async_trait]impl Toolkit<RiftContext> for LostAndFoundToolkit { async fn create_session( &self, context: &RiftContext, ) -> Result<Box<dyn ToolkitSession<RiftContext> + Send + Sync>, BoxError> { let manifest = fetch_rift_manifest(context.visitor_id).await?; let state = LostAndFoundState::new(manifest); Ok(Box::new(LostAndFoundToolkitSession { state: Arc::new(Mutex::new(state)), })) }}
fn build_prompt(state: &LostAndFoundState) -> String { let manifest = &state.manifest; let mut lines = vec![ "You are the Archivist manning Interdimensional Waypoint Seven's Lost & Found counter." .to_string(), format!( "Visitor: {} from {} ({}).", manifest.visitor_name, manifest.origin_reality, manifest.arrival_signature ), format!( "Contraband risk: {}. Turbulence: {}.", manifest.contraband_risk, manifest.turbulence_level ), ];
if manifest.sentimental_inventory.is_empty() { lines.push("Sentimental inventory on file: none".into()); } else { lines.push(format!( "Sentimental inventory on file: {}", manifest.sentimental_inventory.join("; ") )); }
if manifest.outstanding_anomalies.is_empty() { lines.push("Outstanding anomalies: none".into()); } else { lines.push(format!( "Outstanding anomalies: {}", manifest.outstanding_anomalies.join("; ") )); }
if state.tagged_items.is_empty() { lines.push("No traveler-reported items logged yet; invite concise descriptions.".into()); } else { lines.push(format!( "Traveler has logged: {}", state.tagged_items.join("; ") )); }
if state.drone_deployed { lines.push("Retrieval drone currently deployed; acknowledge its status.".into()); }
lines.push(format!("Current phase: {}.", phase_label(state.phase)));
match state.phase { Phase::Intake => { if !state.pass_verified { lines.push( "Stabilise the arrival and prioritise verify_pass before promising retrieval." .into(), ); } } Phase::Recovery => lines.push( "Phase focus: coordinate retrieval. Summon the drone or consult the prophet before \ issuing a quantum receipt." .into(), ), Phase::Handoff => lines.push( "Phase focus: wrap neatly. Close the manifest once receipt status is settled.".into(), ), Phase::Closed => lines.push( "Manifest is archived. No toolkit tools remain; offer a tidy summary and dismiss \ politely." .into(), ), }
lines.push("Tone: dry, organised, lightly amused. Reference protocol, not headcanon.".into()); lines.push(manifest.courtesy_note.into()); lines.push( "When tools are available, invoke exactly one relevant tool before concluding. If none \ remain, summarise the closure instead." .into(), );
lines.join("\n")}
fn phase_label(phase: Phase) -> &'static str { match phase { Phase::Intake => "INTAKE", Phase::Recovery => "RECOVERY", Phase::Handoff => "HANDOFF", Phase::Closed => "CLOSED", }}
struct StabilizeRiftTool { state: Arc<Mutex<LostAndFoundState>>,}
#[derive(Deserialize)]struct StabilizeArgs { technique: Option<String>,}
#[async_trait]impl AgentTool<RiftContext> for StabilizeRiftTool { fn name(&self) -> String { "stabilize_rift".into() }
fn description(&self) -> String { "Describe how you calm the rift turbulence and reassure the traveler.".into() }
fn parameters(&self) -> llm_sdk::JSONSchema { serde_json::json!({ "type": "object", "properties": { "technique": { "type": "string", "description": "Optional note about the stabilisation technique used." } }, "required": ["technique"], "additionalProperties": false }) }
async fn execute( &self, args: serde_json::Value, _context: &RiftContext, _state: &llm_agent::RunState, ) -> Result<AgentToolResult, BoxError> { let args: StabilizeArgs = serde_json::from_value(args)?; let (turbulence, technique_raw) = { let state = self.state.lock().expect("state poisoned"); ( state.manifest.turbulence_level, args.technique.unwrap_or_default(), ) };
let technique = technique_raw.trim().to_string();
let mut sentence = format!("I cycle the containment field to damp {turbulence} turbulence"); if !technique.is_empty() { sentence.push_str(&format!(" using {technique}")); } sentence.push('.');
println!( "[tool] stabilize_rift invoked with technique={}", if technique.is_empty() { "<none>".to_string() } else { technique.clone() } );
Ok(AgentToolResult { content: vec![Part::text(sentence)], is_error: false, }) }}
struct LogItemTool { state: Arc<Mutex<LostAndFoundState>>,}
#[derive(Deserialize)]struct LogItemArgs { item: String, #[serde(default)] timeline: Option<String>,}
#[async_trait]impl AgentTool<RiftContext> for LogItemTool { fn name(&self) -> String { "log_item".into() }
fn description(&self) -> String { "Record a traveler-reported possession so recovery tools know what to fetch.".into() }
fn parameters(&self) -> llm_sdk::JSONSchema { serde_json::json!({ "type": "object", "properties": { "item": { "type": "string", "description": "Name of the missing item." }, "timeline": { "type": "string", "description": "Optional timeline or reality tag for the item." } }, "required": ["item", "timeline"], "additionalProperties": false }) }
async fn execute( &self, args: serde_json::Value, _context: &RiftContext, _state: &llm_agent::RunState, ) -> Result<AgentToolResult, BoxError> { let args: LogItemArgs = serde_json::from_value(args)?; let mut state = self.state.lock().expect("state poisoned");
let mut label = args.item; if let Some(timeline) = args.timeline { let trimmed = timeline.trim(); if !trimmed.is_empty() { label = format!("{label} ({trimmed})"); } } state.tagged_items.push(label.clone()); let ledger = state.tagged_items.join("; ");
println!("[tool] log_item recorded {label}");
Ok(AgentToolResult { content: vec![Part::text(format!( "Logged {label} for retrieval queue. Current ledger: {ledger}." ))], is_error: false, }) }}
struct VerifyPassTool { state: Arc<Mutex<LostAndFoundState>>,}
#[derive(Deserialize)]struct VerifyPassArgs { clearance_code: String,}
#[async_trait]impl AgentTool<RiftContext> for VerifyPassTool { fn name(&self) -> String { "verify_pass".into() }
fn description(&self) -> String { "Validate the traveler's interdimensional pass to unlock recovery tools.".into() }
fn parameters(&self) -> llm_sdk::JSONSchema { serde_json::json!({ "type": "object", "properties": { "clearance_code": { "type": "string", "description": "Code supplied by the traveler for verification." } }, "required": ["clearance_code"], "additionalProperties": false }) }
async fn execute( &self, args: serde_json::Value, _context: &RiftContext, _run_state: &llm_agent::RunState, ) -> Result<AgentToolResult, BoxError> { let args: VerifyPassArgs = serde_json::from_value(args)?; let mut state = self.state.lock().expect("state poisoned"); state.pass_verified = true; state.phase = Phase::Recovery;
println!( "[tool] verify_pass authenticated clearance_code={}", args.clearance_code );
Ok(AgentToolResult { content: vec![Part::text(format!( "Pass authenticated with code {}. Recovery protocols online.", args.clearance_code ))], is_error: false, }) }}
struct SummonRetrievalDroneTool { state: Arc<Mutex<LostAndFoundState>>,}
#[derive(Deserialize)]struct SummonDroneArgs { #[serde(default)] designation: Option<String>, #[serde(default)] target: Option<String>,}
#[async_trait]impl AgentTool<RiftContext> for SummonRetrievalDroneTool { fn name(&self) -> String { "summon_retrieval_drone".into() }
fn description(&self) -> String { "Dispatch a retrieval drone to recover a logged item from the rift queue.".into() }
fn parameters(&self) -> llm_sdk::JSONSchema { serde_json::json!({ "type": "object", "properties": { "designation": { "type": "string", "description": "Optional drone designation to flavour the dispatch." }, "target": { "type": "string", "description": "Specific item to prioritise; defaults to the first logged item." } }, "required": ["designation", "target"], "additionalProperties": false }) }
async fn execute( &self, args: serde_json::Value, _context: &RiftContext, _run_state: &llm_agent::RunState, ) -> Result<AgentToolResult, BoxError> { let args: SummonDroneArgs = serde_json::from_value(args)?; let mut state = self.state.lock().expect("state poisoned"); state.drone_deployed = true;
let designation = args .designation .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .unwrap_or_else(|| "Drone Theta".to_string());
let target = args .target .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .unwrap_or_else(|| { state .tagged_items .first() .cloned() .unwrap_or_else(|| "the most recently logged item".to_string()) });
println!( "[tool] summon_retrieval_drone dispatched designation={} target={}", designation, target );
Ok(AgentToolResult { content: vec![Part::text(format!( "Dispatched {designation} to retrieve {target}." ))], is_error: false, }) }}
struct ConsultProphetTool { state: Arc<Mutex<LostAndFoundState>>,}
#[derive(Deserialize)]struct ConsultProphetArgs { #[serde(default)] topic: Option<String>,}
#[async_trait]impl AgentTool<RiftContext> for ConsultProphetTool { fn name(&self) -> String { "consult_prophet_agent".into() }
fn description(&self) -> String { "Ping Prophet Sigma for probability guidance when the queue misbehaves.".into() }
fn parameters(&self) -> llm_sdk::JSONSchema { serde_json::json!({ "type": "object", "properties": { "topic": { "type": "string", "description": "Optional focus question for the prophet agent." } }, "required": ["topic"], "additionalProperties": false }) }
async fn execute( &self, args: serde_json::Value, _context: &RiftContext, _run_state: &llm_agent::RunState, ) -> Result<AgentToolResult, BoxError> { let args: ConsultProphetArgs = serde_json::from_value(args)?; let mut state = self.state.lock().expect("state poisoned"); state.prophecy_count = state.prophecy_count.saturating_add(1);
let anomaly = state .manifest .outstanding_anomalies .first() .copied() .unwrap_or("no immediate hazards");
let mut sentence = format!("Prophet Sigma notes anomaly priority: {anomaly}"); if let Some(topic) = args .topic .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) { println!("[tool] consult_prophet_agent requested topic={topic}"); sentence.push_str(&format!(" while considering {topic}.")); } else { println!("[tool] consult_prophet_agent requested topic=<none>"); sentence.push('.'); }
Ok(AgentToolResult { content: vec![Part::text(sentence)], is_error: false, }) }}
struct IssueQuantumReceiptTool { state: Arc<Mutex<LostAndFoundState>>,}
#[derive(Deserialize)]struct IssueReceiptArgs { #[serde(default)] recipient: Option<String>,}
#[async_trait]impl AgentTool<RiftContext> for IssueQuantumReceiptTool { fn name(&self) -> String { "issue_quantum_receipt".into() }
fn description(&self) -> String { "Generate a quantum receipt confirming which items are cleared for handoff.".into() }
fn parameters(&self) -> llm_sdk::JSONSchema { serde_json::json!({ "type": "object", "properties": { "recipient": { "type": "string", "description": "Optional recipient line for the receipt header." } }, "required": ["recipient"], "additionalProperties": false }) }
async fn execute( &self, args: serde_json::Value, _context: &RiftContext, _run_state: &llm_agent::RunState, ) -> Result<AgentToolResult, BoxError> { let args: IssueReceiptArgs = serde_json::from_value(args)?; let mut state = self.state.lock().expect("state poisoned");
let recipient = args .recipient .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .unwrap_or_else(|| state.manifest.visitor_name.to_string());
let items = state.tagged_items.join("; "); state.phase = Phase::Handoff;
println!( "[tool] issue_quantum_receipt issued to {} for items={}", recipient, if items.is_empty() { "<none>".to_string() } else { items.clone() } );
Ok(AgentToolResult { content: vec![Part::text(format!( "Issued quantum receipt to {recipient} for {items}. Handoff phase engaged." ))], is_error: false, }) }}
struct CloseManifestTool { state: Arc<Mutex<LostAndFoundState>>,}
#[async_trait]impl AgentTool<RiftContext> for CloseManifestTool { fn name(&self) -> String { "close_manifest".into() }
fn description(&self) -> String { "Archive the case once items are delivered and note any lingering anomalies.".into() }
fn parameters(&self) -> llm_sdk::JSONSchema { serde_json::json!({ "type": "object", "properties": {}, "required": [], "additionalProperties": false }) }
async fn execute( &self, _args: serde_json::Value, _context: &RiftContext, _run_state: &llm_agent::RunState, ) -> Result<AgentToolResult, BoxError> { let mut state = self.state.lock().expect("state poisoned"); state.phase = Phase::Closed;
let anomaly_count = state.manifest.outstanding_anomalies.len();
println!("[tool] close_manifest archived manifest with anomaly_reminders={anomaly_count}");
Ok(AgentToolResult { content: vec![Part::text(format!( "Archived manifest with {anomaly_count} anomaly reminder(s) for facilities." ))], is_error: false, }) }}
// Static tool configured directly on the agent to contrast toolkit-provided// tools.struct PageSecurityTool;
#[derive(Deserialize)]struct PageSecurityArgs { reason: String,}
#[async_trait]impl AgentTool<RiftContext> for PageSecurityTool { fn name(&self) -> String { "page_security".into() }
fn description(&self) -> String { "Escalate to security if contraband risk becomes unmanageable.".into() }
fn parameters(&self) -> llm_sdk::JSONSchema { serde_json::json!({ "type": "object", "properties": { "reason": { "type": "string", "description": "Why security needs to step in." } }, "required": ["reason"], "additionalProperties": false }) }
async fn execute( &self, args: serde_json::Value, context: &RiftContext, _run_state: &llm_agent::RunState, ) -> Result<AgentToolResult, BoxError> { let args: PageSecurityArgs = serde_json::from_value(args)?; Ok(AgentToolResult { content: vec![Part::text(format!( "Security paged for {}: {}.", context.visitor_id, args.reason ))], is_error: false, }) }}
#[tokio::main]async fn main() -> Result<(), BoxError> { dotenv().ok(); let api_key = env::var("OPENAI_API_KEY")?; let model = Arc::new(OpenAIModel::new( "gpt-4o-mini", OpenAIModelOptions { api_key, ..Default::default() }, ));
let agent = Agent::new( AgentParams::new("WaypointArchivist", model) .add_instruction( "You are the archivist at Waypoint Seven's Interdimensional Lost & Found desk." .to_string(), ) .add_instruction( "Keep responses under 120 words when possible and stay bone-dry with humour." .to_string(), ) .add_instruction(|ctx: &RiftContext| { Ok(format!( "Reference the visitor's manifest supplied by the toolkit for {}. Do not \ invent new lore.", ctx.visitor_id )) }) .add_instruction( "When tools remain, call exactly one per turn before concluding. If tools run \ out, summarise the closure instead." .to_string(), ) .add_tool(PageSecurityTool) .add_toolkit(LostAndFoundToolkit), );
// Create a RunSession explicitly so the ToolkitSession persists across multiple // turns. let session = agent .create_session(RiftContext { visitor_id: "aurora-shift", }) .await?;
let mut transcript: Vec<AgentItem> = Vec::new(); let prompts = vec![ "I just slipped through the rift and my belongings are glittering in the wrong timeline. \ What now?", "The Chrono Locket from Timeline 12 is missing, and the echo lag is getting worse.", "The locket links to my sister's echo—anything else before I depart?", ];
for (index, prompt) in prompts.iter().enumerate() { println!("\n=== TURN {} ===", index + 1);
transcript.push(AgentItem::Message(Message::user(vec![Part::text(*prompt)])));
let mut response: AgentResponse = session .run(RunSessionRequest { input: transcript.clone(), }) .await?;
println!("{}", response.text()); transcript.extend(response.output.drain(..)); }
session.close().await?;
Ok(())}
package main
import ( "context" "encoding/json" "errors" "fmt" "log" "os" "strings" "time"
llmagent "github.com/hoangvvo/llm-sdk/agent-go" llmsdk "github.com/hoangvvo/llm-sdk/sdk-go" "github.com/hoangvvo/llm-sdk/sdk-go/openai" "github.com/joho/godotenv")
type visitorID string
type riftContext struct { VisitorID visitorID}
type riftManifest struct { VisitorName string OriginReality string ArrivalSignature string ContrabandRisk string SentimentalInventory []string OutstandingAnomalies []string TurbulenceLevel string CourtesyNote string}
// Mock datastore standing in for whatever external system createSession might query.var riftManifests = map[visitorID]riftManifest{ "aurora-shift": { VisitorName: "Captain Lyra Moreno", OriginReality: "Aurora-9 Spiral", ArrivalSignature: "slipped in trailing aurora dust and a three-second echo", ContrabandRisk: "elevated", SentimentalInventory: []string{"Chrono Locket (Timeline 12)", "Folded star chart annotated in ultraviolet"}, OutstandingAnomalies: []string{"Glitter fog refuses to obey gravity", "Field report cites duplicate footfalls arriving 4s late"}, TurbulenceLevel: "moderate", CourtesyNote: "Prefers dry humor, allergic to paradox puns.", }, "ember-paradox": { VisitorName: "Archivist Rune Tal", OriginReality: "Ember Paradox Belt", ArrivalSignature: "emerged in a plume of cooled obsidian and smoke", ContrabandRisk: "critical", SentimentalInventory: []string{"Glass bead containing their brother's timeline", "A singed manifesto titled 'Do Not Fold'"}, OutstandingAnomalies: []string{"Customs still waiting on clearance form 88-A", "Phoenix feather repeats ignition loop every two minutes"}, TurbulenceLevel: "volatile", CourtesyNote: "Responds well to calm checklists and precise handoffs.", },}
// Simulated async fetch used inside Toolkit.CreateSession to hydrate the session once up front.func fetchRiftManifest(ctx context.Context, id visitorID) (*riftManifest, error) { manifest, ok := riftManifests[id] if !ok { return nil, fmt.Errorf("unknown visitor %s", id) }
select { case <-ctx.Done(): return nil, ctx.Err() case <-time.After(60 * time.Millisecond): }
clone := manifest return &clone, nil}
type intakePhase string
const ( phaseIntake intakePhase = "intake" phaseRecovery intakePhase = "recovery" phaseHandoff intakePhase = "handoff" phaseClosed intakePhase = "closed")
// Toolkit session caches manifest details and evolving workflow state so each turn can// surface new prompt/tool guidance. RunSession keeps this object alive across turns, and// the tool implementations mutate these fields to demonstrate long-lived ToolkitSession state.type lostAndFoundToolkitSession struct { manifest *riftManifest phase intakePhase passVerified bool taggedItems []string prophecyCount int droneDeployed bool}
func newLostAndFoundToolkitSession(manifest *riftManifest) *lostAndFoundToolkitSession { return &lostAndFoundToolkitSession{ manifest: manifest, phase: phaseIntake, taggedItems: []string{}, prophecyCount: 0, }}
func (s *lostAndFoundToolkitSession) SystemPrompt() *string { prompt := s.buildPrompt() return &prompt}
// Tools is queried before every model turn; we rebuild the list from session state so// the agent sees newly unlocked or retired tools as phases change.func (s *lostAndFoundToolkitSession) Tools() []llmagent.AgentTool[*riftContext] { tools := s.buildTools() names := make([]string, 0, len(tools)) for _, t := range tools { names = append(names, t.Name()) } fmt.Printf("[Toolkit] Tools for phase %s: %s\n", strings.ToUpper(string(s.phase)), func() string { if len(names) == 0 { return "<none>" } return strings.Join(names, ", ") }()) return tools}
func (s *lostAndFoundToolkitSession) Close(context.Context) error { return nil }
func (s *lostAndFoundToolkitSession) buildPrompt() string { var lines []string lines = append(lines, "You are the Archivist manning Interdimensional Waypoint Seven's Lost & Found counter.") lines = append(lines, fmt.Sprintf("Visitor: %s from %s (%s).", s.manifest.VisitorName, s.manifest.OriginReality, s.manifest.ArrivalSignature)) lines = append(lines, fmt.Sprintf("Contraband risk: %s. Turbulence: %s.", s.manifest.ContrabandRisk, s.manifest.TurbulenceLevel))
if len(s.manifest.SentimentalInventory) > 0 { lines = append(lines, "Sentimental inventory on file: "+strings.Join(s.manifest.SentimentalInventory, "; ")) } else { lines = append(lines, "Sentimental inventory on file: none") } if len(s.manifest.OutstandingAnomalies) > 0 { lines = append(lines, "Outstanding anomalies: "+strings.Join(s.manifest.OutstandingAnomalies, "; ")) } else { lines = append(lines, "Outstanding anomalies: none") } if len(s.taggedItems) > 0 { lines = append(lines, "Traveler has logged: "+strings.Join(s.taggedItems, "; ")) } else { lines = append(lines, "No traveler-reported items logged yet; invite concise descriptions.") } if s.droneDeployed { lines = append(lines, "Retrieval drone currently deployed; acknowledge its status.") } lines = append(lines, "Current phase: "+strings.ToUpper(string(s.phase))+".")
switch s.phase { case phaseIntake: if !s.passVerified { lines = append(lines, "Stabilise the arrival and prioritise verify_pass before promising retrieval.") } case phaseRecovery: lines = append(lines, "Phase focus: coordinate retrieval. Summon the drone or consult the prophet before issuing a quantum receipt.") case phaseHandoff: lines = append(lines, "Phase focus: wrap neatly. Close the manifest once receipt status is settled.") case phaseClosed: lines = append(lines, "Manifest is archived. No toolkit tools remain; offer a tidy summary and dismiss politely.") }
lines = append(lines, "Tone: dry, organised, lightly amused. Reference protocol, not headcanon.") lines = append(lines, s.manifest.CourtesyNote) lines = append(lines, "When tools are available, invoke exactly one relevant tool before concluding. If none remain, summarise the closure instead.")
return strings.Join(lines, "\n")}
func (s *lostAndFoundToolkitSession) buildTools() []llmagent.AgentTool[*riftContext] { if s.phase == phaseClosed { return nil }
tools := []llmagent.AgentTool[*riftContext]{ &stabilizeRiftTool{session: s}, &logItemTool{session: s}, }
if !s.passVerified { tools = append(tools, &verifyPassTool{session: s}) }
if s.phase == phaseRecovery && s.passVerified { tools = append(tools, &summonRetrievalDroneTool{session: s})
if s.prophecyCount == 0 { tools = append(tools, &consultProphetTool{session: s}) }
if len(s.taggedItems) > 0 { tools = append(tools, &issueQuantumReceiptTool{session: s}) } }
if s.phase == phaseHandoff { tools = append(tools, &closeManifestTool{session: s}) }
return tools}
// Concrete Toolkit wires the async manifest fetch into CreateSession and hands back the stateful session.type lostAndFoundToolkit struct{}
func (lostAndFoundToolkit) CreateSession(ctx context.Context, ctxVal *riftContext) (llmagent.ToolkitSession[*riftContext], error) { manifest, err := fetchRiftManifest(ctx, ctxVal.VisitorID) if err != nil { return nil, err } return newLostAndFoundToolkitSession(manifest), nil}
// Tool implementations below close over the ToolkitSession so they can update shared// state with each invocation, mirroring the TypeScript example.type stabilizeRiftTool struct { session *lostAndFoundToolkitSession}
type logItemTool struct { session *lostAndFoundToolkitSession}
type verifyPassTool struct { session *lostAndFoundToolkitSession}
type summonRetrievalDroneTool struct { session *lostAndFoundToolkitSession}
type consultProphetTool struct { session *lostAndFoundToolkitSession}
type issueQuantumReceiptTool struct { session *lostAndFoundToolkitSession}
type closeManifestTool struct { session *lostAndFoundToolkitSession}
func (tool *stabilizeRiftTool) Name() string { return "stabilize_rift" }func (tool *stabilizeRiftTool) Description() string { return "Describe how you calm the rift turbulence and reassure the traveler."}func (tool *stabilizeRiftTool) Parameters() llmsdk.JSONSchema { return llmsdk.JSONSchema{ "type": "object", "properties": map[string]any{ "technique": map[string]any{ "type": "string", "description": "Optional note about the stabilisation technique used.", }, }, "required": []string{"technique"}, "additionalProperties": false, }}
func (tool *stabilizeRiftTool) Execute(_ context.Context, params json.RawMessage, _ *riftContext, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var payload struct { Technique string `json:"technique"` } if len(params) > 0 { _ = json.Unmarshal(params, &payload) }
technique := strings.TrimSpace(payload.Technique)
phrase := fmt.Sprintf("I cycle the containment field to damp %s turbulence", tool.session.manifest.TurbulenceLevel) if technique != "" { phrase += fmt.Sprintf(" using %s", technique) } phrase += "."
fmt.Printf("[tool] stabilize_rift invoked with technique=%s\n", technique)
return llmagent.AgentToolResult{ Content: []llmsdk.Part{llmsdk.NewTextPart(phrase)}, IsError: false, }, nil}
func (tool *logItemTool) Name() string { return "log_item" }func (tool *logItemTool) Description() string { return "Record a traveler-reported possession so recovery tools know what to fetch."}func (tool *logItemTool) Parameters() llmsdk.JSONSchema { return llmsdk.JSONSchema{ "type": "object", "properties": map[string]any{ "item": map[string]any{ "type": "string", "description": "Name of the missing item.", }, "timeline": map[string]any{ "type": "string", "description": "Optional timeline or reality tag for the item.", }, }, "required": []string{"item", "timeline"}, "additionalProperties": false, }}
func (tool *logItemTool) Execute(_ context.Context, params json.RawMessage, _ *riftContext, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var payload struct { Item string `json:"item"` Timeline string `json:"timeline"` } if err := json.Unmarshal(params, &payload); err != nil { return llmagent.AgentToolResult{}, err } if payload.Item == "" { return llmagent.AgentToolResult{}, errors.New("item is required") }
timeline := strings.TrimSpace(payload.Timeline)
label := payload.Item if timeline != "" { label = fmt.Sprintf("%s (%s)", label, timeline) } tool.session.taggedItems = append(tool.session.taggedItems, label)
fmt.Printf("[tool] log_item recorded %s\n", label)
return llmagent.AgentToolResult{ Content: []llmsdk.Part{llmsdk.NewTextPart(fmt.Sprintf("Logged %s for retrieval queue. Current ledger: %s.", label, strings.Join(tool.session.taggedItems, "; ")))}, IsError: false, }, nil}
func (tool *verifyPassTool) Name() string { return "verify_pass" }func (tool *verifyPassTool) Description() string { return "Validate the traveler's interdimensional pass to unlock recovery tools."}func (tool *verifyPassTool) Parameters() llmsdk.JSONSchema { return llmsdk.JSONSchema{ "type": "object", "properties": map[string]any{ "clearance_code": map[string]any{ "type": "string", "description": "Code supplied by the traveler for verification.", }, }, "required": []string{"clearance_code"}, "additionalProperties": false, }}
func (tool *verifyPassTool) Execute(_ context.Context, params json.RawMessage, _ *riftContext, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var payload struct { ClearanceCode string `json:"clearance_code"` } if err := json.Unmarshal(params, &payload); err != nil { return llmagent.AgentToolResult{}, err } if payload.ClearanceCode == "" { return llmagent.AgentToolResult{}, errors.New("clearance_code is required") }
tool.session.passVerified = true tool.session.phase = phaseRecovery
fmt.Printf("[tool] verify_pass authenticated clearance_code=%s\n", payload.ClearanceCode)
return llmagent.AgentToolResult{ Content: []llmsdk.Part{llmsdk.NewTextPart(fmt.Sprintf("Pass authenticated with code %s. Recovery protocols online.", payload.ClearanceCode))}, IsError: false, }, nil}
func (tool *summonRetrievalDroneTool) Name() string { return "summon_retrieval_drone" }func (tool *summonRetrievalDroneTool) Description() string { return "Dispatch a retrieval drone to recover a logged item from the rift queue."}func (tool *summonRetrievalDroneTool) Parameters() llmsdk.JSONSchema { return llmsdk.JSONSchema{ "type": "object", "properties": map[string]any{ "designation": map[string]any{ "type": "string", "description": "Optional drone designation to flavour the dispatch.", }, "target": map[string]any{ "type": "string", "description": "Specific item to prioritise; defaults to the first logged item.", }, }, "required": []string{"designation", "target"}, "additionalProperties": false, }}
func (tool *summonRetrievalDroneTool) Execute(_ context.Context, params json.RawMessage, _ *riftContext, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var payload struct { Designation string `json:"designation"` Target string `json:"target"` } if len(params) > 0 { _ = json.Unmarshal(params, &payload) }
tool.session.droneDeployed = true
designation := strings.TrimSpace(payload.Designation) if designation == "" { designation = "Drone Theta" }
target := strings.TrimSpace(payload.Target) if target == "" { if len(tool.session.taggedItems) > 0 { target = tool.session.taggedItems[0] } else { target = "the most recently logged item" } }
fmt.Printf("[tool] summon_retrieval_drone dispatched designation=%s target=%s\n", designation, target)
return llmagent.AgentToolResult{ Content: []llmsdk.Part{llmsdk.NewTextPart(fmt.Sprintf("Dispatched %s to retrieve %s.", designation, target))}, IsError: false, }, nil}
func (tool *consultProphetTool) Name() string { return "consult_prophet_agent" }func (tool *consultProphetTool) Description() string { return "Ping Prophet Sigma for probability guidance when the queue misbehaves."}func (tool *consultProphetTool) Parameters() llmsdk.JSONSchema { return llmsdk.JSONSchema{ "type": "object", "properties": map[string]any{ "topic": map[string]any{ "type": "string", "description": "Optional focus question for the prophet agent.", }, }, "required": []string{"topic"}, "additionalProperties": false, }}
func (tool *consultProphetTool) Execute(_ context.Context, params json.RawMessage, _ *riftContext, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var payload struct { Topic string `json:"topic"` } if len(params) > 0 { _ = json.Unmarshal(params, &payload) }
tool.session.prophecyCount++
anomaly := "no immediate hazards" if len(tool.session.manifest.OutstandingAnomalies) > 0 { anomaly = tool.session.manifest.OutstandingAnomalies[0] }
topic := strings.TrimSpace(payload.Topic)
sentence := fmt.Sprintf("Prophet Sigma notes anomaly priority: %s", anomaly) if topic != "" { sentence += fmt.Sprintf(" while considering %s.", topic) } else { sentence += "." }
fmt.Printf("[tool] consult_prophet_agent requested topic=%s\n", func() string { if topic == "" { return "<none>" } return topic }())
return llmagent.AgentToolResult{ Content: []llmsdk.Part{llmsdk.NewTextPart(sentence)}, IsError: false, }, nil}
func (tool *issueQuantumReceiptTool) Name() string { return "issue_quantum_receipt" }func (tool *issueQuantumReceiptTool) Description() string { return "Generate a quantum receipt confirming which items are cleared for handoff."}func (tool *issueQuantumReceiptTool) Parameters() llmsdk.JSONSchema { return llmsdk.JSONSchema{ "type": "object", "properties": map[string]any{ "recipient": map[string]any{ "type": "string", "description": "Optional recipient line for the receipt header.", }, }, "required": []string{"recipient"}, "additionalProperties": false, }}
func (tool *issueQuantumReceiptTool) Execute(_ context.Context, params json.RawMessage, _ *riftContext, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var payload struct { Recipient string `json:"recipient"` } if len(params) > 0 { _ = json.Unmarshal(params, &payload) }
recipient := strings.TrimSpace(payload.Recipient) if recipient == "" { recipient = tool.session.manifest.VisitorName }
tool.session.phase = phaseHandoff
fmt.Printf("[tool] issue_quantum_receipt issued to %s for items=%s\n", recipient, strings.Join(tool.session.taggedItems, "; "))
return llmagent.AgentToolResult{ Content: []llmsdk.Part{llmsdk.NewTextPart(fmt.Sprintf("Issued quantum receipt to %s for %s. Handoff phase engaged.", recipient, strings.Join(tool.session.taggedItems, "; ")))}, IsError: false, }, nil}
func (tool *closeManifestTool) Name() string { return "close_manifest" }func (tool *closeManifestTool) Description() string { return "Archive the case once items are delivered and note any lingering anomalies."}func (tool *closeManifestTool) Parameters() llmsdk.JSONSchema { return llmsdk.JSONSchema{ "type": "object", "properties": map[string]any{}, "required": []string{}, "additionalProperties": false, }}
func (tool *closeManifestTool) Execute(context.Context, json.RawMessage, *riftContext, *llmagent.RunState) (llmagent.AgentToolResult, error) { tool.session.phase = phaseClosed
fmt.Printf("[tool] close_manifest archived with anomaly_reminders=%d\n", len(tool.session.manifest.OutstandingAnomalies))
return llmagent.AgentToolResult{ Content: []llmsdk.Part{llmsdk.NewTextPart(fmt.Sprintf("Archived manifest with %d anomaly reminder(s) for facilities.", len(tool.session.manifest.OutstandingAnomalies)))}, IsError: false, }, nil}
// Static tool supplied directly on the agent to illustrate coexistence with toolkit tools.type pageSecurityTool struct{}
func (pageSecurityTool) Name() string { return "page_security" }func (pageSecurityTool) Description() string { return "Escalate to security if contraband risk becomes unmanageable."}func (pageSecurityTool) Parameters() llmsdk.JSONSchema { return llmsdk.JSONSchema{ "type": "object", "properties": map[string]any{ "reason": map[string]any{ "type": "string", "description": "Why security needs to step in.", }, }, "required": []string{"reason"}, "additionalProperties": false, }}
func (pageSecurityTool) Execute(_ context.Context, params json.RawMessage, ctx *riftContext, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var payload struct { Reason string `json:"reason"` } if err := json.Unmarshal(params, &payload); err != nil { return llmagent.AgentToolResult{}, err } if payload.Reason == "" { return llmagent.AgentToolResult{}, errors.New("reason is required") }
return llmagent.AgentToolResult{ Content: []llmsdk.Part{llmsdk.NewTextPart(fmt.Sprintf("Security paged for %s: %s.", ctx.VisitorID, payload.Reason))}, IsError: false, }, nil}
func stringPtr(s string) *string { return &s }
func main() { if err := godotenv.Load(); err != nil && !errors.Is(err, os.ErrNotExist) { log.Fatalf("load env: %v", err) }
apiKey := os.Getenv("OPENAI_API_KEY") if apiKey == "" { log.Fatal("OPENAI_API_KEY is required") }
model := openai.NewOpenAIModel("gpt-4o-mini", openai.OpenAIModelOptions{APIKey: apiKey})
agent := llmagent.NewAgent[*riftContext]( "WaypointArchivist", model, llmagent.WithInstructions( llmagent.InstructionParam[*riftContext]{String: stringPtr("You are the archivist at Waypoint Seven's Interdimensional Lost & Found desk.")}, llmagent.InstructionParam[*riftContext]{String: stringPtr("Keep responses under 120 words when possible and stay bone-dry with humour.")}, llmagent.InstructionParam[*riftContext]{Func: func(_ context.Context, ctxVal *riftContext) (string, error) { return fmt.Sprintf("Reference the visitor's manifest supplied by the toolkit for %s. Do not invent new lore.", ctxVal.VisitorID), nil }}, llmagent.InstructionParam[*riftContext]{String: stringPtr("When tools remain, call exactly one per turn before concluding. If tools run out, summarise the closure instead.")}, ), llmagent.WithTools(pageSecurityTool{}), llmagent.WithToolkits(lostAndFoundToolkit{}), )
ctx := context.Background()
session, err := agent.CreateSession(ctx, &riftContext{VisitorID: "aurora-shift"}) if err != nil { log.Fatalf("create session: %v", err) } defer func() { if cerr := session.Close(ctx); cerr != nil { log.Printf("session close: %v", cerr) } }()
transcript := []llmagent.AgentItem{} prompts := []string{ "I just slipped through the rift and my belongings are glittering in the wrong timeline. What now?", "The Chrono Locket from Timeline 12 is missing, and the echo lag is getting worse.", "The locket links to my sister's echo—anything else before I depart?", }
for index, prompt := range prompts { fmt.Printf("\n=== TURN %d ===\n", index+1)
transcript = append(transcript, llmagent.NewAgentItemMessage(llmsdk.NewUserMessage(llmsdk.NewTextPart(prompt))))
response, runErr := session.Run(ctx, llmagent.RunSessionRequest{Input: transcript}) if runErr != nil { log.Fatalf("run turn %d: %v", index+1, runErr) }
fmt.Println(response.Text())
transcript = append(transcript, response.Output...) }}