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

FieldDescription
actionShort name identifying what happened (e.g., searched_web, wrote_file)
resultHuman-readable description of the outcome
tagsList of string labels for filtering
metadataOptional 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.

Observe at these points for maximum replay value:

  1. Before each major tool call: what you intend to do
  2. After each major tool call: what happened (result summary)
  3. On errors: what failed and why
  4. At plan revision points: why the plan changed
  5. 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?;
}