Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Runtime-Aware Tools

RuntimeAwareTool extends the basic Tool trait with runtime context -- current graph state, a store reference, stream writer, tool call ID, and runnable config. Implement this trait for tools that need to read or modify graph state during execution.

The ToolRuntime Struct

When a runtime-aware tool is invoked, it receives a ToolRuntime with the following fields:

pub struct ToolRuntime {
    pub store: Option<Arc<dyn Store>>,
    pub stream_writer: Option<StreamWriter>,
    pub state: Option<Value>,
    pub tool_call_id: String,
    pub config: Option<RunnableConfig>,
}
FieldDescription
storeShared key-value store for cross-tool persistence
stream_writerWriter for pushing streaming output from within a tool
stateSerialized snapshot of the current graph state
tool_call_idThe ID of the tool call being executed
configRunnable config with tags, metadata, and run ID

Implementing with #[tool] and #[inject]

The recommended way to define a runtime-aware tool is with the #[tool] macro. Use #[inject(store)], #[inject(state)], or #[inject(tool_call_id)] on parameters to receive runtime context. These injected parameters are hidden from the LLM schema. Using any #[inject] attribute automatically switches the generated impl to RuntimeAwareTool:

use std::sync::Arc;
use serde_json::{json, Value};
use synaptic::macros::tool;
use synaptic::core::{Store, SynapticError};

/// Save a note to the store.
#[tool]
async fn save_note(
    /// The note key
    key: String,
    /// The note text
    text: String,
    #[inject(store)] store: Arc<dyn Store>,
) -> Result<Value, SynapticError> {
    store.put(
        &["notes"],
        &key,
        json!({"text": text}),
    ).await?;

    Ok(json!({"saved": key}))
}

// save_note() returns Arc<dyn RuntimeAwareTool>
let tool = save_note();

The #[inject(store)] parameter receives the Arc<dyn Store> from the ToolRuntime at execution time. Only key and text appear in the JSON Schema sent to the model.

Using with ToolNode in a Graph

ToolNode automatically injects runtime context into registered RuntimeAwareTool instances. Register them with with_runtime_tool() and optionally attach a store with with_store():

use synaptic::graph::ToolNode;
use synaptic::tools::{ToolRegistry, SerialToolExecutor};

let registry = ToolRegistry::new();
let executor = SerialToolExecutor::new(registry);

let tool_node = ToolNode::new(executor)
    .with_store(store.clone())
    .with_runtime_tool(save_note());  // save_note() returns Arc<dyn RuntimeAwareTool>

When the graph executes this tool node and encounters a tool call matching "save_note", it builds a ToolRuntime populated with the current graph state, the store, and the tool call ID, then calls call_with_runtime().

RuntimeAwareToolAdapter -- Using Outside a Graph

If you need to use a RuntimeAwareTool in a context that expects the standard Tool trait (for example, with SerialToolExecutor directly), wrap it in a RuntimeAwareToolAdapter:

use std::sync::Arc;
use synaptic::core::{RuntimeAwareTool, RuntimeAwareToolAdapter, ToolRuntime};

let tool = save_note();  // Arc<dyn RuntimeAwareTool>
let adapter = RuntimeAwareToolAdapter::new(tool);

// Optionally inject a runtime before calling
adapter.set_runtime(ToolRuntime {
    store: Some(store.clone()),
    stream_writer: None,
    state: None,
    tool_call_id: "call-1".to_string(),
    config: None,
}).await;

// Now use it as a regular Tool
let result = adapter.call(json!({"key": "k", "text": "hello"})).await?;

If set_runtime() is not called before call(), the adapter uses a default empty ToolRuntime with all optional fields set to None and an empty tool_call_id.

create_react_agent with a Store

When building a ReAct agent via create_react_agent, pass a store through AgentOptions to have it automatically wired into the ToolNode for all registered runtime-aware tools:

use synaptic::graph::{create_react_agent, AgentOptions};

let graph = create_react_agent(
    model,
    tools,
    AgentOptions {
        store: Some(store),
        ..Default::default()
    },
);