Observations
The observation log is the agent's authored record of its reasoning trace. Write observations liberally; they are cheap and provide invaluable debugging and replay capability.
Writing Observations
#![allow(unused)] fn main() { // Basic observation agent.observe( "searched_web", // action name "Found 5 results for 'renewable energy'", // result description vec!["web-search".to_string()], // tags ).await?; }
#![allow(unused)] fn main() { // After a tool call let result = agent.invoke_tool("lm.complete", input).await?; agent.observe( "generated_summary", &format!("Summary: {} (tokens: {})", result["text"], result["output_tokens"]), vec!["llm".to_string(), "success".to_string()], ).await?; }
#![allow(unused)] fn main() { // On error match agent.invoke_tool("web.fetch", input).await { Ok(output) => { agent.observe("fetched_url", "HTTP 200", vec!["success".to_string()]).await?; } Err(e) => { agent.observe("fetch_failed", &e.to_string(), vec!["error".to_string()]).await?; } } }
Observation Fields
| Field | Description |
|---|---|
action | Short name identifying what happened (e.g., searched_web, wrote_file) |
result | Human-readable description of the outcome |
tags | List of string labels for filtering |
metadata | Optional JSON blob for structured data (via raw IPC) |
Querying Observations
Via ash:
# All observations
ash obs query <agent-id>
# Filter by keyword
ash obs query <agent-id> --keyword "error"
# Filter by time range
ash obs query <agent-id> --since 2026-02-22T12:00:00Z --limit 20
Capability Required
spec:
capabilities:
- obs.append # Write observations
- obs.query # Read observations
Hash Chain
Each observation includes prev_hash and hash fields linking it to the previous entry. This makes the observation log tamper-evident; any modification of a historical entry breaks the chain.
Recommended Observation Points
Observe at these points for maximum replay value:
- Before each major tool call: what you intend to do
- After each major tool call: what happened (result summary)
- On errors: what failed and why
- At plan revision points: why the plan changed
- At start and end of each iteration: overall progress
#![allow(unused)] fn main() { // Pattern: intent → action → observation agent.observe("starting_search", "About to search for news", vec!["intent".into()]).await?; let result = agent.invoke_tool("web.search", json!({"query": task})).await?; agent.observe("search_complete", &format!("{} results", result["results"].as_array().map(|a| a.len()).unwrap_or(0)), vec!["done".into()]).await?; }