yoagent-state 0.3.0

Durable state and lineage for long-running agents.
Documentation
# API Guide

This page shows the common tasks you perform with `yoagent-state`.

The current API is organized around this goal-centered graph shape:

```text
goal -> task -> run -> observation -> failure -> hypothesis -> patch -> artifact -> eval -> decision -> promotion
```

```mermaid
flowchart LR
  goal["goal"]
  task["task"]
  failure["failure"]
  hypothesis["hypothesis"]
  patch["patch"]
  artifact["artifact"]
  eval["eval"]
  decision["decision"]

  task -- serves --> goal
  failure -- blocks --> goal
  hypothesis -- explains --> failure
  patch -- addresses --> failure
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision
  patch -- advances --> goal
```

You can use only the pieces you need. The graph does not require every flow to create every node.

## Create state in memory

Use memory state for tests, examples, and short-lived runs:

```rust
let state = YoAgentState::load(MemoryEventStore::new()).await?;
```

## Create persisted state

Use JSONL when state should survive process restart:

```rust
let state = YoAgentState::load(
    JsonlEventStore::new(".yoagent-state/events.jsonl")
).await?;
```

`YoAgentState::load` scans the event store and replays events into the graph projection.

## Record a failure

```rust
state.record_failure(
    ActorRef::agent("yoyo-evolve"),
    NodeId::new("failure_17"),
    "tool_retry_survives_timeout fails",
    "Retry state is lost when timeout cancels the future.",
).await?;
```

Use failures for concrete observed problems, not vague concerns.

## Record a goal

```rust
let goal = Goal::new(
    GoalId::new("goal_retry_reliability"),
    "Make retry behavior reliable",
    "Retry attempts should survive timeout cancellation.",
    ActorRef::agent("yoyo-evolve"),
);

state.record_goal(goal).await?;
```

Goals are the top of the common lineage graph.

## Record a task

```rust
let task = Task {
    id: TaskId::new("task_retry_timeout"),
    title: "Fix timeout retry state".to_string(),
    summary: "Investigate and patch retry state loss.".to_string(),
    status: TaskStatus::InProgress,
    goal: Some(GoalId::new("goal_retry_reliability")),
    created_by: ActorRef::agent("yoyo-evolve"),
    metadata: serde_json::json!({}),
};

state.record_task(task).await?;
```

Tasks link to goals with `serves`.

## Record observations and hypotheses

```rust
let observation = Observation {
    id: ObservationId::new("observation_retry_log"),
    title: "Retry attempt reset observed".to_string(),
    summary: "The second attempt starts from zero after timeout.".to_string(),
    observed_in: None,
    metadata: serde_json::json!({}),
};

state.record_observation(actor.clone(), observation).await?;
```

```rust
let hypothesis = Hypothesis {
    id: HypothesisId::new("hypothesis_cancelled_future"),
    title: "Attempt count is scoped to cancelled future".to_string(),
    summary: "The retry state is dropped when the future is cancelled.".to_string(),
    confidence: Some(0.8),
    metadata: serde_json::json!({}),
};

state
    .record_hypothesis(actor, hypothesis, Some(NodeId::new("failure_17")))
    .await?;
```

## Apply low-level state ops

Use `apply_ops` when you want direct control over nodes and relations:

```rust
state.apply_ops(
    ActorRef::agent("demo"),
    vec![StateOp::CreateNode {
        id: NodeId::new("failure_1"),
        kind: "failure".to_string(),
        props: serde_json::json!({ "title": "retry failed" }),
    }],
).await?;
```

This writes a `state.ops_applied` event and updates the graph projection.

## Propose a patch

```rust
let mut patch = StatePatch::new(
    PatchId::new("patch_42"),
    "Persist retry state across timeout",
    "Keep attempt count outside the cancelled future.",
    ActorRef::agent("yoyo-evolve"),
);

patch.evidence.push(NodeId::new("failure_17"));
patch.expected_effects.push(ExpectedEffect::TestPasses {
    name: "tool_retry_survives_timeout".to_string(),
});

state.propose_patch(patch).await?;
```

Patch evidence becomes lineage.

## Attach an artifact

```rust
let artifact = ArtifactRef::new(
    "git.diff",
    "file://.yoyo/artifacts/patch_42.diff",
).with_summary("Fix retry persistence and add timeout regression test");

state.attach_artifact(NodeId::new("patch_42"), artifact).await?;
```

Artifacts should point to concrete external evidence such as diffs, commits, files, logs, or eval reports.

## Record an eval result

```rust
state.record_eval_result(
    ActorRef::agent("yoyo-evolve"),
    NodeId::new("eval_55"),
    PatchId::new("patch_42"),
    "cargo test tool_retry_survives_timeout",
    true,
).await?;
```

This creates an eval node and a `validated_by` relation from the patch to the eval.

## Record a decision

```rust
state.record_decision(
    ActorRef::user("yuanhao"),
    NodeId::new("decision_9"),
    PatchId::new("patch_42"),
    true,
    "Eval passed; approve promotion",
).await?;
```

Approved decisions create `approved_by` relations. Rejected decisions create `rejected_by` relations.

## Update patch status

```rust
state.update_patch_status(
    PatchId::new("patch_42"),
    PatchStatus::Promoted,
    Some("Promoted as commit def456".to_string()),
).await?;
```

Status is stored on the patch node and also recorded as a historical event.

## Query lineage

```rust
let lineage = state.lineage(NodeId::new("patch_42")).await;
println!("{}", lineage.to_markdown());
```

Use lineage when you want to answer why a node exists and what it is connected to.

## Query related entities

```rust
let patches = state.patches_for_failure(NodeId::new("failure_17")).await;
let evals = state.evals_for_patch(PatchId::new("patch_42")).await;
```

These helpers are intentionally narrow and practical.

## Use the runtime layer

`YoAgentRuntime` adds typed packs, behaviors, and policies:

```rust
let state = YoAgentState::load(MemoryEventStore::new()).await?;
let mut runtime = YoAgentRuntime::new(state.clone());
```

Register a typed pack:

```rust
runtime.register_pack(
    Pack::new(PackId::new("pack_lineage"), "lineage", "0.1.0")
        .add_object_type(ObjectType::new("goal").require("title"))
        .add_object_type(ObjectType::new("task").require("title"))
        .add_relation_type(
            RelationType::new("serves")
                .from_kind("task")
                .to_kind("goal"),
        ),
);
```

Register a policy:

```rust
runtime.register_policy(Policy::require_approval(
    PolicyId::new("policy_create_node_review"),
    "Creating graph nodes requires review",
    PolicyAction::CreateNode,
));
```

Fork and diff:

```rust
let fork = state
    .fork_at_event(ForkId::new("fork_before_task"), Some(event_id))
    .await?;

let diff = diff_graphs(&fork.graph, &state.graph().await);
```