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

Introduction

Agents forget why work happened.

They may leave behind chat transcripts, tool logs, test output, and Git diffs, but those artifacts rarely answer the question a maintainer asks later:

Why does this state exist?

yoagent-state is a small Rust continuity layer for long-running agents. It records durable state and lineage for agent work without replacing the agent loop, Git, the filesystem, CI, or a project database.

The full continuity chain starts from a goal:

goal -> task -> run -> observation -> failure -> hypothesis -> patch -> artifact -> eval -> decision -> promotion
flowchart LR
  goal["goal"]
  task["task"]
  run["run"]
  observation["observation"]
  failure["failure"]
  hypothesis["hypothesis"]
  patch["patch"]
  artifact["artifact"]
  eval["eval"]
  decision["decision"]
  promoted["promoted status"]

  task -- serves --> goal
  run -- produces --> observation
  observation -- observes --> failure
  hypothesis -- explains --> failure
  patch -- addresses --> failure
  patch -- advances --> goal
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision
  decision -- allows --> promoted

That chain is the product. It tells you what the agent was trying to achieve, what work it started, what happened during the run, what failed, what the agent believed, what it proposed, what project artifact it referenced, what tested it, and what decision approved or rejected it.

It is a causal spine, not a required single linked list. Some runs start at a failure, some start at a goal, and some only record tool or model calls. The important part is that the graph can connect intent, execution, evidence, change, and decision.

A diff is usually one of those artifacts. The graph can also attach test logs, model transcripts, screenshots, benchmark output, review notes, or any other evidence an agent needs to explain the work later.

Promotion is represented as a patch status transition. The promotion should be backed by eval and decision lineage, not hidden inside a commit message.

What yoagent-state does

yoagent-state gives agents and humans a durable explanation layer:

  • append-only events record what happened
  • a graph projection turns events into queryable semantic state
  • patches connect failures, evidence, artifacts, evals, and decisions
  • lineage reports explain why a node exists
  • JSONL persistence lets state survive process restart

The implementation is intentionally boring: Rust structs, JSON payloads, append-only storage, and an in-memory graph projection.

The boundary

Keep the boundary sharp:

Git stores what changed.
yoagent-state stores why it changed, what tested it, and what it means.

yoagent-state is not a replacement for Git, a workflow engine, or a graph database. It is the continuity layer that sits beside an agent loop and records the meaning of the work.

Who it is for

Use it if you are building:

  • long-running agents
  • agentic coding loops
  • eval-driven project improvement systems
  • tools that need patch/eval/decision lineage
  • yoagent or yoyo evolve integrations

Start with the Quick Start to see the main lineage flow in under a minute.

Quick Start

This page gets you from a fresh clone to a working lineage report.

Prerequisites

You need a Rust toolchain with Cargo.

Check:

cargo --version

Clone and run the main demo

git clone https://github.com/yologdev/yoagent-state.git
cd yoagent-state
cargo run --example goal_lineage

You should see a lineage report like this:

# Make retry behavior reliable

- id: goal_retry_reliability
- kind: goal
- status: InProgress

## Incoming
- serves <- task_retry_timeout
- blocks <- failure_retry_timeout
- advances <- patch_retry_state

Read it as: goal_retry_reliability is being served by a task, blocked by a failure, and advanced by a patch.

flowchart LR
  task["task_retry_timeout<br/>kind: task"]
  failure["failure_retry_timeout<br/>kind: failure"]
  patch["patch_retry_state<br/>kind: patch"]
  goal["goal_retry_reliability<br/>kind: goal<br/>status: InProgress"]

  task -- serves --> goal
  failure -- blocks --> goal
  patch -- advances --> goal

That is the core promise: state is not just a log. It is a graph that connects intent, work, evidence, change, and decision.

To inspect the patch/eval/decision lane directly:

cargo run --example patch_eval_decision

Run the test suite

cargo test

The tests cover event append and scan, state ops, replay, goal/task/failure lineage, typed packs, policy approvals, behavior subscriptions, fork/diff helpers, patch status transitions, lineage, JSONL persistence, and changed-file observer helpers.

Try local persistence

Initialize a local JSONL event log:

cargo run --bin yoagent-state -- init

Inspect the current graph:

cargo run --bin yoagent-state -- graph

Use a custom event log path:

YOAGENT_STATE_EVENTS=.yoyo/state/events.jsonl cargo run --bin yoagent-state -- events

The default local event log path is .yoagent-state/events.jsonl.

Why Agents Need State

Logs tell you what happened. They do not reliably tell you why it mattered.

Long-running agents need a continuity layer because their work often spans many observations, tool calls, hypotheses, patches, evals, and decisions. Without durable state, the reasoning chain gets scattered across chat history, terminal output, temporary files, and Git commits.

The failure mode

An agent can make a good change and still leave behind a weak explanation:

test failed
agent tried something
files changed
test passed
commit created

That is not enough when someone later asks:

  • What failure caused this patch?
  • What evidence supported the hypothesis?
  • Which eval validated it?
  • What concrete diff did the patch refer to?
  • Was the patch approved, rejected, promoted, or later made stale?

yoagent-state records that chain directly.

In the current runtime, that chain usually starts with durable intent:

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

The exact run may only use part of the graph, but the state model has a place for each piece.

Logs are not enough

Logs are chronological. Lineage is causal.

Chronology says:

tool ran, model responded, file changed, test passed

Lineage says:

patch_42 addresses failure_17
patch_42 references diff artifact patch_42.diff
eval_55 validated patch_42
decision_9 approved patch_42
flowchart LR
  failure["failure_17"]
  patch["patch_42"]
  artifact["patch_42.diff"]
  eval["eval_55"]
  decision["decision_9"]

  patch -- addresses --> failure
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision

The second form is what agents and maintainers need to explain project evolution.

Durable state vs project diff

Git owns the concrete project state. yoagent-state owns the agent-facing meaning.

Git stores what changed.
yoagent-state stores why it changed, what tested it, and what it means.

This keeps the library small. It does not parse every symbol, mirror every file, or replace source control.

When to use it

Use yoagent-state when:

  • an agent runs across multiple steps or sessions
  • a patch needs evidence before promotion
  • an eval result should be tied to a change
  • a future agent should understand prior decisions
  • project evolution needs an explainable history

Skip it when:

  • the agent is stateless
  • the task is a one-off script
  • Git commit messages already capture enough context
  • you need a full workflow engine, not a lineage layer

Core Mental Model

yoagent-state starts with three moving parts:

append events -> replay graph -> query lineage
flowchart LR
  events["append-only events"]
  replay["deterministic replay"]
  graphNode["semantic graph projection"]
  lineage["lineage queries"]

  events --> replay --> graphNode --> lineage

The full runtime adds typed packs, behaviors, policies, replay, forks, frames, and views on top of that event-sourced base.

The graph is not the source of truth. It is a projection derived from append-only events.

Core graph shape

The current runtime is goal-centered. The common causal spine is:

goal -> task -> run -> observation -> failure -> hypothesis -> patch -> artifact -> eval -> decision -> promotion
flowchart LR
  goal["goal"]
  task["task"]
  run["run"]
  observation["observation"]
  failure["failure"]
  hypothesis["hypothesis"]
  patch["patch"]
  artifact["artifact"]
  eval["eval"]
  decision["decision"]
  promoted["promoted status"]

  task -- serves --> goal
  run -- produced_by --> task
  run -- produces --> observation
  observation -- observes --> failure
  failure -- blocks --> goal
  hypothesis -- explains --> failure
  patch -- addresses --> failure
  patch -- advances --> goal
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision
  decision -- allows --> promoted

Read that as a graph shape, not a required pipeline:

  • goal captures the durable intent.
  • task is concrete work that serves a goal.
  • run, model_call, and tool_call record execution.
  • observation, failure, and hypothesis preserve what the agent noticed and believed.
  • patch proposes a state or project change.
  • artifact references concrete evidence such as diffs, logs, files, screenshots, or eval output.
  • eval records validation.
  • decision records approval, rejection, or review state.
  • promotion is a PatchStatus::Promoted transition, not a separate graph node.

Side primitives such as policies, behaviors, packs, frames, forks, and views make this graph operational without changing the source-of-truth rule.

Event log

An event is an immutable fact about something that happened.

Examples:

  • run.started
  • tool.finished
  • goal.created
  • task.created
  • failure.observed
  • hypothesis.created
  • patch.proposed
  • patch.status_changed
  • artifact.attached
  • state.ops_applied

Events are append-only. Do not mutate historical events.

State ops

State ops are the small mutation language for the graph projection.

They can:

  • create or update nodes
  • tombstone nodes
  • create or delete relations
  • mark nodes stale
  • attach artifacts

Only state.ops_applied events mutate the graph directly.

Graph projection

The graph is a semantic view of agent state.

Common node kinds:

  • goal
  • task
  • run
  • observation
  • failure
  • hypothesis
  • patch
  • eval
  • decision
  • artifact
  • file
  • model_call
  • tool_call
  • frame

Common relation kinds:

  • serves
  • blocks
  • advances
  • observes
  • addresses
  • explains
  • validated_by
  • approved_by
  • rejected_by
  • modifies
  • references
  • produced_by
  • contained_in_frame
  • forked_from

The graph should stay lossy. It should preserve what matters for continuity and explanation, not every line of a log.

Patches

A state patch is a proposed semantic change with evidence.

It can include:

  • base state version
  • project reference
  • preconditions
  • expected effects
  • evidence nodes
  • artifact refs
  • state ops
  • lifecycle status

Patch lifecycle:

proposed -> applied_in_fork -> evaluated -> approved/rejected -> promoted
stateDiagram-v2
  [*] --> Proposed
  Proposed --> AppliedInFork
  AppliedInFork --> Evaluated
  Evaluated --> Approved
  Evaluated --> Rejected
  Approved --> Promoted
  Proposed --> Stale
  AppliedInFork --> Conflicted
  Evaluated --> Stale

This lifecycle is one lane inside the larger goal-centered graph. A patch usually advances a goal, addresses a failure, references artifacts, is validated by evals, and is approved or rejected by decisions.

Artifacts

Artifacts point to external evidence such as:

  • Git diffs
  • commits
  • files
  • test output
  • build logs
  • eval result JSON
  • model or tool output

Store paths, URIs, summaries, and hashes where practical.

Replay

On startup, the store scans events and replays them into the graph projection.

flowchart LR
  store["EventStore"]
  scan["scan events"]
  projector["Projector"]
  graphNode["Graph"]

  store --> scan --> projector --> graphNode

This makes state durable without requiring a graph database.

Behaviors, policies, and packs

Typed packs validate object and relation shapes.

Behaviors react to event patterns and return state ops.

Policies gate sensitive actions by allowing, denying, or requiring approval.

flowchart TB
  event["event"]
  pack["typed pack validation"]
  policy["policy gate"]
  behavior["behavior subscription"]
  ops["state ops"]
  graphNode["graph projection"]

  event --> pack --> policy
  policy -- allow --> ops
  policy -- require approval --> graphNode
  event --> behavior --> ops
  ops --> graphNode

These are runtime features, but they still preserve the same rule: durable state comes from append-only events.

ActiveGraph-Inspired Runtime

yoagent-state is inspired by Yohei Nakajima’s ActiveGraph work, adapted into an idiomatic Rust runtime for yoagent and yoyo evolve.

The full concept is:

append-only event log
  -> deterministic replay
  -> typed graph projection
  -> pattern subscriptions
  -> behaviors
  -> policy-gated patches
  -> replay, fork, and diff
flowchart LR
  log["append-only event log"]
  replay["deterministic replay"]
  graphNode["typed graph projection"]
  patterns["pattern subscriptions"]
  behaviors["behaviors"]
  policies["policy gates"]
  forks["replay / fork / diff"]

  log --> replay --> graphNode --> patterns --> behaviors
  graphNode --> policies
  graphNode --> forks
  behaviors --> log
  policies --> log

What changed in v0.2

The core lineage path now starts from goals:

goal -> task -> run -> observation -> failure -> hypothesis -> patch -> artifact -> eval -> decision -> promotion
flowchart LR
  goal["goal"]
  task["task"]
  run["run"]
  observation["observation"]
  failure["failure"]
  hypothesis["hypothesis"]
  patch["patch"]
  artifact["artifact"]
  eval["eval"]
  decision["decision"]
  promoted["promoted status"]

  task -- serves --> goal
  run -- produces --> observation
  observation -- observes --> failure
  hypothesis -- explains --> failure
  patch -- addresses --> failure
  patch -- advances --> goal
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision
  decision -- allows --> promoted

This is the common graph spine. It is not a claim that every agent run must create every node. artifact includes diffs, logs, screenshots, files, eval output, and other external evidence. promotion is represented by the patch lifecycle status.

yoagent-state now has first-class IDs and helpers for:

  • goals
  • tasks
  • runs
  • observations
  • hypotheses
  • evals
  • decisions
  • project snapshots
  • model calls
  • tool calls
  • frames
  • forks
  • behaviors
  • policies
  • packs
  • views

Runtime layers

YoAgentState remains the simple state API: record events, apply ops, query graph, query lineage.

YoAgentRuntime adds the ActiveGraph-inspired runtime layer:

  • register typed packs
  • validate typed nodes and relations
  • register behavior subscriptions
  • enforce policy gates
  • create approval requests

This keeps simple usage simple while allowing richer agent systems to use the full concept.

Extensible storage

The event log remains the source of truth.

Storage is split into traits:

  • EventStore
  • SnapshotStore
  • ForkStore
  • IndexStore
  • ArtifactStore

JSONL is implemented first because it is inspectable. SQLite, PostgreSQL, and graph-backed projections can be added later behind the same traits.

Behaviors

Behaviors subscribe to event patterns and return state ops.

They do not mutate the graph directly.

event -> matching behavior -> new events/state ops -> replayable state
flowchart LR
  event["failure.observed"]
  behavior["matching behavior"]
  task["create investigation task"]
  ops["state.ops_applied event"]
  graphNode["replayed graph"]

  event --> behavior --> task --> ops --> graphNode

This keeps behavior execution auditable.

Policies

Policies can allow, deny, or require approval for sensitive actions.

The current policy foundation supports approval requests for runtime operations. More policy surfaces can be added without changing the event-sourced model.

flowchart TB
  action["runtime action"]
  policy["policy check"]
  allow["allow"]
  deny["deny"]
  approval["approval request node"]

  action --> policy
  policy --> allow
  policy --> deny
  policy --> approval

Replay, fork, diff

Replay rebuilds graph state from events.

Fork creates an alternate event history from a parent event cutoff.

Diff compares projected graphs so agents can inspect what changed between histories.

flowchart LR
  events["events"]
  cutoff["event cutoff"]
  fork["fork graph"]
  current["current graph"]
  diff["graph diff"]

  events --> cutoff --> fork
  events --> current
  fork --> diff
  current --> diff

First Lineage Example

The smallest useful graph is a failure explained by a hypothesis.

Run:

cargo run --example basic_lineage

Expected shape:

# Attempt count is scoped to the cancelled future

- id: hypothesis_retry_state_lost
- kind: hypothesis
- status: unknown

## Outgoing
- explains -> failure_retry_timeout

What this demonstrates

The example creates two nodes:

  • a failure node
  • a hypothesis node

Then it creates a relation:

hypothesis_retry_state_lost --explains--> failure_retry_timeout
flowchart LR
  hypothesis["hypothesis_retry_state_lost<br/>kind: hypothesis"]
  failure["failure_retry_timeout<br/>kind: failure"]

  hypothesis -- explains --> failure

That edge is the start of lineage. Instead of storing a loose note in a transcript, the state layer records a queryable relationship.

Why it matters

Long-running agents need to preserve small facts like this. A later patch can address the failure, reference the hypothesis as evidence, and attach eval results.

The chain grows naturally:

goal -> task -> run -> observation -> failure -> hypothesis -> patch -> artifact -> eval -> decision -> promotion
flowchart LR
  goal["goal"]
  task["task"]
  run["run"]
  observation["observation"]
  failure["failure"]
  hypothesis["hypothesis"]
  patch["patch"]
  artifact["artifact"]
  eval["eval"]
  decision["decision"]
  promoted["promoted status"]

  task -- serves --> goal
  run -- produces --> observation
  observation -- observes --> failure
  hypothesis -- explains --> failure
  patch -- addresses --> failure
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision
  decision -- allows --> promoted

For this tiny example, only the hypothesis -> failure edge is created. The larger runtime can later connect that edge back to a goal and forward to a patch, artifacts, evals, and decisions.

Use this pattern whenever an agent observes something and forms a belief that should survive beyond the current run.

Patch, Eval, Decision Tutorial

This tutorial walks through the patch promotion lane:

failure -> patch -> diff artifact -> eval -> decision -> promotion
flowchart LR
  failure["failure_17<br/>kind: failure"]
  patch["patch_42<br/>kind: patch"]
  artifact["patch_42.diff<br/>kind: git.diff artifact"]
  eval["eval_55<br/>cargo test passed"]
  decision["decision_9<br/>approved"]
  promoted["PatchStatus::Promoted"]

  patch -- addresses --> failure
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision
  decision -- allows --> promoted

This lane is important because it shows how a proposed change earns promotion: the patch addresses a failure, references a concrete artifact, is validated by an eval, and is approved by a decision.

It is not the whole yoagent-state model. It sits inside the larger goal-centered graph:

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

Run:

cargo run --example patch_eval_decision

Step 1: Record a failure

The example records a concrete failure:

failure_17: tool_retry_survives_timeout fails

The failure node explains what went wrong:

Retry state is lost when timeout cancels the future.

Step 2: Propose a patch

The patch is not just a title. It carries intent, evidence, preconditions, expected effects, and artifacts.

In the example:

  • patch id: patch_42
  • title: Persist retry state across timeout
  • evidence: failure_17
  • precondition: tool_retry_survives_timeout is still failing
  • expected effect: tool_retry_survives_timeout passes

Step 3: Attach a diff artifact

Concrete project changes stay outside yoagent-state.

The patch references a fake Git diff artifact:

file://.yoyo/artifacts/patch_42.diff

This keeps the boundary clear:

Git stores what changed.
yoagent-state stores why it changed.

Step 4: Record an eval

The example records:

eval_55: cargo test tool_retry_survives_timeout passed

Then it creates the relation:

patch_42 --validated_by--> eval_55

Step 5: Record a decision

The example records a human approval decision:

decision_9: Eval passed; approve promotion

Then it creates:

patch_42 --approved_by--> decision_9

Step 6: Promote the patch

Finally, the patch status becomes Promoted.

The resulting lineage report shows:

patch_42
status: Promoted
addresses: failure_17
validated_by: eval_55
approved_by: decision_9

That is the point of this lane: a promoted patch is not just an event log entry. It has a durable explanation.

Examples

The examples move from small lineage to the current goal-centered runtime features.

Goal lineage

cargo run --example goal_lineage

Start here for the current core graph shape. It shows a goal being served by a task, blocked by a failure, and advanced by a patch.

flowchart LR
  task["task_retry_timeout"]
  failure["failure_retry_timeout"]
  patch["patch_retry_state"]
  goal["goal_retry_reliability"]

  task -- serves --> goal
  failure -- blocks --> goal
  patch -- advances --> goal

Basic lineage

cargo run --example basic_lineage

Use this first if you want to understand nodes and relations.

It creates:

hypothesis_retry_state_lost --explains--> failure_retry_timeout
flowchart LR
  hypothesis["hypothesis_retry_state_lost"]
  failure["failure_retry_timeout"]

  hypothesis -- explains --> failure

You should see a markdown lineage report with the hypothesis as the root and the failure as an outgoing relation.

Patch, eval, decision

cargo run --example patch_eval_decision

This is the main patch lifecycle demo.

It records:

  • a failure
  • a patch that addresses the failure
  • a fake Git diff artifact
  • an eval result
  • an approval decision
  • a promoted patch status

Use this pattern when an agent proposes a change and needs evidence before promotion.

yoagent integration

cargo run --example yoagent_integration

This uses YoAgentStateSink and YoAgentStateAdapter to record run, model, and tool lifecycle events.

It demonstrates how yoagent-state stays optional: the agent loop emits events to a sink, but the state layer does not take over execution.

yoyo evolve demo

cargo run --example yoyo_evolve_demo

This records a compact growth loop with:

  • a failure
  • a project reference
  • a diff artifact
  • changed file relations
  • an eval result
  • an approval decision
  • a promoted patch status

Use this example when you want to see how project-level artifacts connect to semantic lineage.

Behavior subscription

cargo run --example behavior_subscription

This registers a behavior that reacts to failure.observed and creates an investigation task.

flowchart LR
  failure["failure.observed"]
  behavior["behavior subscription"]
  task["investigation task"]

  failure --> behavior --> task

Policy approval

cargo run --example policy_approval

This registers a policy requiring approval before node creation. The attempted operation is blocked and an approval request node is created.

flowchart LR
  action["create node"]
  policy["policy"]
  request["approval request"]

  action --> policy --> request

Replay and fork

cargo run --example replay_and_fork

This creates a graph, forks at an earlier event, and diffs the fork against current state.

flowchart LR
  event["event cutoff"]
  fork["fork graph"]
  current["current graph"]
  diff["graph diff"]

  event --> fork
  event --> current
  fork --> diff
  current --> diff

Typed pack

cargo run --example typed_pack

This registers a pack that validates goal, task, and serves relation shapes.

Choosing an example

Start here:

new to the current model -> goal_lineage
want the smallest relation -> basic_lineage
want the patch lifecycle -> patch_eval_decision
need behaviors -> behavior_subscription
need policy gates -> policy_approval
need replay/fork/diff -> replay_and_fork
need typed validation -> typed_pack
integrating an agent loop -> yoagent_integration
building yoyo-style project evolution -> yoyo_evolve_demo

Persistence Guide

yoagent-state ships with two v0 stores:

  • MemoryEventStore
  • JsonlEventStore

Both implement the same EventStore trait.

Memory store

Use memory storage for tests and examples:

#![allow(unused)]
fn main() {
let state = YoAgentState::load(MemoryEventStore::new()).await?;
}

Memory state disappears when the process exits.

JSONL store

Use JSONL when state should survive restart:

#![allow(unused)]
fn main() {
let state = YoAgentState::load(
    JsonlEventStore::new(".yoagent-state/events.jsonl")
).await?;
}

The JSONL store writes one event per line. This makes state easy to inspect, copy, diff, and replay.

Load means replay

Loading state scans the event log and replays it into the graph:

events.jsonl -> replay -> graph projection
flowchart LR
  jsonl["events.jsonl<br/>append-only"]
  scan["scan"]
  replay["replay"]
  graphNode["in-memory graph projection"]
  query["lineage / graph queries"]

  jsonl --> scan --> replay --> graphNode --> query

The event log is durable. The graph is derived.

Why JSONL first

JSONL is boring and inspectable. That is useful while the state model is still young.

SQLite is intentionally left for later. It should be added after real usage proves the event shape and query needs.

Practical advice

  • Store important artifacts outside the event log and reference them by URI.
  • Hash important diffs, logs, and eval results when possible.
  • Keep payloads useful but not huge.
  • Treat the event log as append-only.

CLI Guide

The CLI is intentionally small. It exists to inspect local event logs and graph projections.

Commands

yoagent-state init
yoagent-state events
yoagent-state graph
yoagent-state node <id>
yoagent-state lineage <id>
yoagent-state lineage <id> --markdown
yoagent-state goal create <id> <title> [summary]
yoagent-state goal list
yoagent-state goal show <id>
yoagent-state goal status <id> <open|in-progress|satisfied|abandoned|blocked|stale>
yoagent-state patch list
yoagent-state patch show <id>
yoagent-state patch promote <id>
yoagent-state fork create <id> [event-id]
yoagent-state replay

When running from source, prefix commands with Cargo:

cargo run --bin yoagent-state -- graph

Event log path

The default event log is:

.yoagent-state/events.jsonl

Set YOAGENT_STATE_EVENTS to use another path:

YOAGENT_STATE_EVENTS=.yoyo/state/events.jsonl cargo run --bin yoagent-state -- events

Common local flow

Initialize:

cargo run --bin yoagent-state -- init

Inspect raw events:

cargo run --bin yoagent-state -- events

Inspect projected graph:

cargo run --bin yoagent-state -- graph

Print lineage:

cargo run --bin yoagent-state -- lineage patch_42 --markdown

List patches:

cargo run --bin yoagent-state -- patch list

Create a goal:

cargo run --bin yoagent-state -- goal create goal_retry "Make retry reliable"

Update goal status:

cargo run --bin yoagent-state -- goal status goal_retry in-progress

Create a fork at an event:

cargo run --bin yoagent-state -- fork create fork_before_patch event_123

Show one patch:

cargo run --bin yoagent-state -- patch show patch_42

Current limitation

The CLI is still intentionally small. Use the Rust API for full behavior/policy/pack flows and use the CLI for local inspection and simple goal/patch/fork operations.

API Guide

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

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

goal -> task -> run -> observation -> failure -> hypothesis -> patch -> artifact -> eval -> decision -> promotion
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:

#![allow(unused)]
fn main() {
let state = YoAgentState::load(MemoryEventStore::new()).await?;
}

Create persisted state

Use JSONL when state should survive process restart:

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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?;
}
#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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.

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
let state = YoAgentState::load(MemoryEventStore::new()).await?;
let mut runtime = YoAgentRuntime::new(state.clone());
}

Register a typed pack:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
runtime.register_policy(Policy::require_approval(
    PolicyId::new("policy_create_node_review"),
    "Creating graph nodes requires review",
    PolicyAction::CreateNode,
));
}

Fork and diff:

#![allow(unused)]
fn main() {
let fork = state
    .fork_at_event(ForkId::new("fork_before_task"), Some(event_id))
    .await?;

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

Concepts

Event

An event is an immutable fact about something that happened. Events are append-only JSON records with an ID, timestamp, actor, kind, payload, and optional causation or correlation metadata.

Common event kinds:

  • run.started
  • run.finished
  • model.called
  • tool.finished
  • failure.observed
  • patch.proposed
  • patch.status_changed
  • artifact.attached
  • state.ops_applied

Only state.ops_applied mutates the graph projection directly.

flowchart LR
  event["event"]
  ops["state.ops_applied"]
  graphNode["graph projection"]
  history["historical event only"]

  event --> history
  ops --> graphNode

Graph

The graph is a lossy semantic projection derived from events. It contains nodes, typed relations, artifacts, and a monotonically increasing version.

Keep the graph focused on agent continuity. Do not mirror every AST node, every log line, or every file.

flowchart LR
  nodeA["node"]
  relation["typed relation"]
  nodeB["node"]
  artifact["artifact reference"]

  nodeA -- relation --> nodeB
  nodeA -- references --> artifact

Node

A node is a semantic object such as a project, run, failure, hypothesis, patch, eval, decision, artifact, file, or task.

Relation

A relation is a typed edge between two nodes. Useful relation names include:

  • addresses
  • explains
  • validated_by
  • approved_by
  • rejected_by
  • modifies
  • references
  • derived_from

Artifact

Artifacts reference external evidence: diffs, commits, logs, eval JSON, generated reports, model output, or tool output. Store paths, URIs, and hashes where possible.

Patch Lifecycle

A state patch records semantic intent and evidence. It is not a replacement for a Git diff.

The lifecycle is:

proposed -> applied_in_fork -> evaluated -> approved/rejected -> promoted
stateDiagram-v2
  [*] --> Proposed
  Proposed --> AppliedInFork
  AppliedInFork --> Evaluated
  Evaluated --> Approved
  Evaluated --> Rejected
  Approved --> Promoted
  Proposed --> Stale
  AppliedInFork --> Conflicted
  Evaluated --> Stale
  Rejected --> [*]
  Promoted --> [*]

Additional states:

  • stale
  • conflicted

The patch should answer:

  • what failure it addresses
  • what hypothesis or evidence supports it
  • what concrete artifact contains the project diff
  • what eval validated it
  • who or what approved it
  • which commit or promotion contains it

Promotion should require evidence such as a passing eval, a passing test, or explicit human approval.

flowchart LR
  patch["patch"]
  failure["failure"]
  artifact["diff / log / file artifact"]
  eval["passing eval or test"]
  decision["approval decision"]
  promoted["promoted status"]

  patch -- addresses --> failure
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision
  decision --> promoted

yoagent Integration

yoagent-state should be optional. The agent loop emits events to a sink; the state layer records them and builds lineage.

The boundary is:

yoagent = execution
yoagent-state = state, lineage, patches, evals, decisions
flowchart LR
  yoagent["yoagent<br/>execution loop"]
  sink["YoAgentStateSink"]
  adapter["YoAgentStateAdapter"]
  events["append-only events"]
  graphNode["semantic graph"]

  yoagent --> sink --> adapter --> events --> graphNode

Adapter shape

The crate provides YoAgentStateSink and YoAgentStateAdapter.

The adapter records:

  • run started and finished
  • model called and finished
  • tool called and finished
  • failure observed when a tool finishes unsuccessfully

Minimal setup:

#![allow(unused)]
fn main() {
let state = YoAgentState::load(MemoryEventStore::new()).await?;
let sink = YoAgentStateAdapter::new(state, ActorRef::agent("yoagent"));
}

Run lifecycle

A typical run emits:

run.started
model.called
model.finished
tool.called
tool.finished
run.finished
sequenceDiagram
  participant Run
  participant Model
  participant Tool
  participant State
  Run->>State: run.started
  Model->>State: model.called
  Model->>State: model.finished
  Tool->>State: tool.called
  Tool->>State: tool.finished
  Run->>State: run.finished

Those events stay historical unless converted into state ops. This keeps the graph projection focused on durable semantic state.

Example

Run:

cargo run --example yoagent_integration

The example records a short run with model and tool events, then prints the event log as JSON.

Integration advice

  • Keep state recording optional.
  • Attach selected tool outputs as artifacts instead of dumping everything into graph nodes.
  • Use causation and correlation IDs when connecting model/tool events to a run.
  • Convert only meaningful facts into state ops.

The goal is continuity, not a heavier agent runtime.

yoyo evolve Integration

yoyo evolve is the first serious use case for yoagent-state.

It needs to grow a project while preserving why each change exists.

Growth loop

The intended loop is:

record goal
create task
observe project
record snapshot reference
run agent task or eval
observe failure
create hypothesis
propose patch
attach artifacts
apply patch in branch or worktree
run eval
record eval result
decide approve or reject
promote if approved
record lineage
flowchart LR
  goal["goal"]
  task["task"]
  snapshot["project snapshot"]
  failure["failure"]
  hypothesis["hypothesis"]
  patch["patch"]
  artifact["diff artifact"]
  eval["eval result"]
  decision["decision"]
  promoted["promoted patch"]

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

What yoagent-state tracks

For a project yoyo is improving, the state layer should track:

  • why a module exists
  • why a dependency was added
  • what goal and task the change served
  • what test validates behavior
  • what failure caused a patch
  • what decision approved it
  • what assumptions became stale
  • what version introduced behavior

Concrete project diffs remain external. Reference them with ArtifactRef and ProjectRef.

Demo

Run:

cargo run --example yoyo_evolve_demo

The demo records:

  • a retry failure
  • a base project reference
  • a diff artifact
  • changed file relations
  • an eval result
  • a human approval decision
  • a promoted patch status

The resulting lineage includes:

patch_retry_timeout
addresses -> failure_retry_timeout
modifies -> file:crates/yoagent-runtime/src/tool.rs
modifies -> file:crates/yoagent-runtime/tests/retry.rs
validated_by -> eval_retry_timeout
approved_by -> decision_promote_retry_timeout
flowchart LR
  patch["patch_retry_timeout"]
  failure["failure_retry_timeout"]
  source["file:crates/yoagent-runtime/src/tool.rs"]
  test["file:crates/yoagent-runtime/tests/retry.rs"]
  eval["eval_retry_timeout"]
  decision["decision_promote_retry_timeout"]

  patch -- addresses --> failure
  patch -- modifies --> source
  patch -- modifies --> test
  patch -- validated_by --> eval
  patch -- approved_by --> decision

Implementation rule

Do not hide mutation.

Every meaningful project change should have:

  • a patch
  • an artifact reference
  • evidence or expected effect
  • an eval or approval decision before promotion

That makes yoyo’s project growth inspectable instead of magical.

Safety Model

yoagent-state is designed around explicit mutation and evidence-backed promotion.

Self-modification must be explicit

Agents should not silently mutate prompts, policies, tools, memory, or code.

Use patches.

Promotion requires evidence

A patch should not be promoted without at least one of:

  • passing eval
  • passing test
  • human approval
  • policy approval

The evidence should be represented in lineage, not hidden in a transcript.

flowchart LR
  patch["patch"]
  evidence["eval / test / approval"]
  decision["decision"]
  promoted["promoted status"]

  patch -- validated_by --> evidence
  patch -- approved_by --> decision
  evidence --> promoted
  decision --> promoted

Project base must be checked

If a patch was created against commit abc123, do not blindly apply it to another commit.

Mark the patch stale or conflicted, then reobserve.

Staleness is first-class

Assumptions go stale. So do patches, projections, and observations.

Use stale nodes or statuses when state is no longer current.

Important artifacts need hashes

Use hashes for:

  • diffs
  • logs
  • files
  • eval outputs
  • generated reports

Hashes make lineage more trustworthy.

Keep the layer small

Do not turn yoagent-state into a hidden automation engine. Behaviors and policies are allowed, but they should be explicit, registered, replayable, and represented in the graph. State and lineage come first.

For Agents

This page is for coding agents and LLMs working in this repo.

Read the root AGENTS.md first. This page mirrors the most important guidance in the hosted docs.

Project boundary

yoagent = execution
yoagent-state = state, lineage, patches, evals, decisions
yoyo evolve = growth loop using both

Do not turn this crate into a workflow engine, graph database, Git replacement, compiler, or universal memory system.

Where to look

  • src/event.rs: append-only event shape
  • src/patch.rs: state ops, patches, statuses, preconditions, effects
  • src/graph.rs: graph projection data structures
  • src/projector.rs: event replay into graph
  • src/state.rs: high-level public API
  • src/store.rs: memory and JSONL event stores
  • src/primitives.rs: goal/task/observation/hypothesis/eval/decision/frame types
  • src/runtime.rs: typed packs, policies, approvals, and behaviors
  • src/schema.rs: pack schemas and relation validation
  • src/policy.rs: policy gates and approval requests
  • src/behavior.rs: event-pattern behavior subscriptions
  • src/fork.rs: replay fork and graph diff helpers
  • examples/: runnable usage flows
  • tests/: regression coverage

Commands

cargo test
/Users/yuanhao/.cargo/bin/mdbook build docs
cargo run --example goal_lineage
cargo run --example patch_eval_decision

Design rule

Prefer boring, explicit state over clever machinery.

When adding behavior, preserve the goal-centered graph spine:

goal -> task -> run -> observation -> failure -> hypothesis -> patch -> artifact -> eval -> decision -> promotion
flowchart LR
  task["task"]
  failure["failure"]
  patch["patch"]
  goal["goal"]
  artifact["artifact"]
  eval["eval"]
  decision["decision"]

  task -- serves --> goal
  failure -- blocks --> goal
  patch -- advances --> goal
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision

This is not a mandatory linear workflow. It is the common causal shape that lets a later agent answer what goal was being served, what happened, what changed, what evidence existed, and who or what approved the result.

When in doubt, store meaning and references. Let Git and the filesystem store concrete project state.

Roadmap

yoagent-state starts with an event-sourced graph runtime: append-only events, replayed graph projection, goal-first lineage, patch lifecycle, artifacts, policies, behaviors, forks, examples, and mdBook docs.

This roadmap lists the likely next steps without turning the project into a workflow engine, graph database, Git replacement, or universal agent framework.

Current Implementation

Implemented:

  • Core ID, actor, event, artifact, patch, precondition, and expected-effect types.
  • MemoryEventStore and JsonlEventStore.
  • Append-only event recording and replay into an in-memory graph.
  • State operations for nodes, relations, stale markers, tombstones, and artifacts.
  • Goal, task, run, observation, failure, hypothesis, patch, eval, decision, project snapshot, model call, tool call, frame, fork, behavior, policy, pack, and view IDs.
  • Goal/task helpers, failure observation, hypothesis records, patch proposal, patch status changes, eval records, decision records, and lineage queries.
  • Runtime layer for typed packs, policy gates, behavior subscriptions, replay, fork, and diff.
  • Small yoagent sink adapter for run/model/tool lifecycle events.
  • Coarse project observer helpers for changed files and diff artifacts.
  • CLI for init, events, graph, node, lineage, goal create/list/show/status, patch list/show/promote, replay, and fork create.
  • Examples and regression tests for the main state flows.
  • mdBook user guide.

The current core graph shape is:

goal -> task -> run -> observation -> failure -> hypothesis -> patch -> artifact -> eval -> decision -> promotion
flowchart LR
  goal["goal"]
  task["task"]
  run["run"]
  observation["observation"]
  failure["failure"]
  hypothesis["hypothesis"]
  patch["patch"]
  artifact["artifact"]
  eval["eval"]
  decision["decision"]
  promoted["promoted status"]

  task -- serves --> goal
  run -- produces --> observation
  observation -- observes --> failure
  hypothesis -- explains --> failure
  patch -- addresses --> failure
  patch -- advances --> goal
  patch -- references --> artifact
  patch -- validated_by --> eval
  patch -- approved_by --> decision
  decision --> promoted

This is a causal spine. It does not require every flow to create every node.

Phase 1: Runtime Hardening

Goal: make the current crate more reliable for early users.

  • Add crate-level API examples as Rust doc tests.
  • Add tests for tombstones, stale nodes, artifact attachment, relation deletion, and error cases.
  • Add CLI integration tests using temporary JSONL event logs.
  • Add schema compatibility tests for serialized event and patch JSON.
  • Add stricter validation for patch status transitions.
  • Add markdown lineage report tests so output remains stable.
  • Improve error messages for malformed JSONL and missing nodes.

Success criteria:

  • Public examples compile as doc tests.
  • CLI behavior is covered by regression tests for goal, patch, replay, and fork commands.
  • Event JSON compatibility is protected by fixtures.

Phase 2: Persistence and Replay

Goal: make local state durable and inspectable beyond simple demos.

  • Make JSONL append safer for concurrent writers.
  • Add compaction or snapshot support for large event logs.
  • Add scan_after coverage for missing IDs and resumed replay.
  • Add optional SQLite storage after JSONL behavior is proven.
  • Add import/export commands for portable state bundles.

Success criteria:

  • Restart/replay is reliable with larger logs.
  • Users can choose JSONL for simplicity or SQLite for local scale.

Phase 3: Better Query and Reports

Goal: answer practical “why does this exist?” questions directly.

  • Add focused query helpers:
    • patches for failure
    • evals for patch
    • decisions for patch
    • artifacts for node
    • files modified by patch
    • stale assumptions related to patch
  • Expand markdown lineage reports with grouped sections:
    • status
    • addresses
    • evidence
    • evals
    • modified files
    • decisions
    • promotion references
  • Add JSON and markdown output options consistently to CLI commands.

Success criteria:

  • One command can show why a patch exists, what validated it, what files changed, and whether it was promoted.

Phase 4: Project Observer v0+

Goal: connect semantic patches to concrete project diffs without becoming a compiler.

  • Add git command helpers for changed files, base commit, and diff artifact creation.
  • Hash important artifacts such as diffs, logs, and eval outputs.
  • Detect common project changes:
    • tests added or changed
    • docs changed
    • dependency manifest changed
    • source files changed
  • Add base commit precondition helpers.
  • Mark patches stale or conflicted when the project base changes.

Success criteria:

  • A patch can automatically reference changed files, base commit, diff artifact, and basic project facts.

Phase 5: yoagent Integration

Goal: let yoagent emit durable state without becoming state-heavy.

  • Wire the adapter into real yoagent run hooks.
  • Record model/tool causation and correlation IDs.
  • Attach selected tool outputs as artifacts.
  • Add examples that use the actual yoagent crate once its integration point is stable.
  • Add tests for failed tool calls becoming failure observations.

Success criteria:

  • yoagent can run normally with optional state recording enabled.

Phase 6: yoyo evolve Demo

Goal: prove the growth loop end to end.

  • Create a demo command that:
    • observes a failure
    • proposes a patch
    • records a diff artifact
    • runs an eval command
    • records eval output
    • approves or rejects the patch
    • prints a lineage report
  • Use temporary branches or worktrees for patch evaluation.
  • Keep promotion explicit and evidence-backed.

Success criteria:

  • One demo shows goal -> task -> failure -> patch -> diff artifact -> eval -> decision -> promotion with a readable lineage report.

Phase 7: Policy and Safety Gates

Goal: make risky mutation explicit.

  • Add policy checks for:
    • prompt mutation
    • tool configuration changes
    • memory/state mutation
    • project patch promotion
  • Expand policy decisions as first-class decision nodes across more runtime actions.
  • Require evidence before promotion.
  • Add stale/conflicted status helpers for unsafe bases.

Success criteria:

  • The library makes self-modification visible, reviewable, and auditable.

Later, Only If Needed

Potential extensions:

  • More behavior subscription patterns beyond “failure observed -> create task”.
  • Richer fork merge and promotion workflows.
  • Richer project observers for Rust symbols, Cargo dependencies, and test surfaces.
  • Web or TUI inspection UI.
  • Remote artifact storage.
  • Multi-agent views over the same state log.

These should wait until real yoyo evolve runs show that the added complexity is worth it.

Non-Goals to Preserve

Do not turn yoagent-state into:

  • a replacement for Git
  • a workflow engine
  • a graph database platform
  • a compiler or AST database
  • a universal memory system
  • a hidden self-modification mechanism

The guiding rule remains: simple but effective.

Acknowledgments

The core idea for yoagent-state comes from Yohei Nakajima and his ActiveGraph work.

This project is an independent Rust implementation inspired by that idea. It keeps the architecture small for yoagent and yoyo evolve, while preserving the important ActiveGraph-style primitives: append-only events, replayed graph projection, goals, tasks, observations, hypotheses, patches, artifacts, evals, decisions, policies, behaviors, packs, replay, and forks.

The guiding boundary is:

Git stores what changed.
yoagent-state stores why it changed, what tested it, and what it means.