GASP (Git Agent State Protocol) is a standard for portable agent state native to git. An agent's durable state lives in a git repository — an append-only event log that folds into a typed graph of goals, patches, evals, and decisions. That graph, not a flat transcript, constitutes agent state. The core idea: an agent is stateless; its durable state is an append-only event log that folds into a queryable graph, committed into a git repo, and that repo — not the model or the runtime — is the agent. Any GASP-conformant runtime pointed at the repo URL resumes the same agent anywhere, on any model.
A Rust reference runtime (yoagent-state) and an independent Python re-implementation (minigasp) demonstrate that an agent's state both restores identically under a second runtime and can be driven forward by it. A production case study — yoyo, a self-evolving coding agent that has run autonomously for 127 days — shows the protocol carrying real state, including auditable self-evolution lineage.
Key Points
Defines a git-native, append-only event log as the single source of truth for agent state; the repository is the agent.
Introduces a typed self-evolution schema: a causal spine goal → task → run → observation → failure → hypothesis → patch → eval → decision → promotion with a pairing rule that keeps domain events (audit) and ops events (graph mutations) mechanically consistent.
Five conformance rules: (1) State lives in a git repository with prescribed layout (log, identity, skills, projections). (2) The event log is append-only, committed, and uses the specified event vocabulary. (3) Identity and skills are committed artifacts, loaded at runtime, never compiled in. (4) Restore is clone + replay — fold the committed log into state; load identity and skills. (5) The executor is swappable; swapping model or runtime touches .agent/config.toml, nothing else.
Provides a mechanically checkable conformance kit: five rules, a canonical fixture, and seven fail-closed checks (Check 7 verifies the domain↔ops pairing).
Demonstrates runtime independence: minigasp (~250 lines of Python, no shared code) restores a live 445-event production repository to an identical 152-node, 119-relation graph and drives a conformant run forward that the Rust reference runtime accepts.
Backed by production evidence: yoyo, a self-evolving agent running for 127 days with no human-authored code, emits conformant GASP across all session types (evolve, skill-evolution, social, dream, genesis).
Adaptation contract for closed agents: three rungs of fidelity — native hooks (highest), transcript projector (universal fallback), model/API proxy (medium). A closed agent yields a transcript-faithful log; the rich graph is only as good as what the adapter can lift.
Self-improvement lifecycle: A skill change is a patch that advances a goal, validated by an eval, and promoted or reverted via a decision. Promotion policy is executor-specific.
Related work shows GASP complements existing protocols (MCP, A2A) that standardize communication rather than state, and extends prior event-sourced agent graphs (ActiveGraph, ESAA) by binding to git as an interchange substrate with a conformance kit.
Concepts
Domain events vs ops events: Domain events (e.g., goal.created, patch.proposed, eval.finished, decision.created) provide a human- and machine-readable audit layer. Ops events (state.ops_applied) carry graph mutations (CreateNode, UpdateNode, etc.). Only ops events fold into the graph; domain events narrate.
Pairing rule: Each entity-creating domain event is followed by exactly one state.ops_applied whose causation_id names that domain event and whose ops materialize the same entity. Check 7 verifies this mechanically.
Folding: Replaying the log in line order, applying each ops event's mutations to an in-memory graph. Folding is deterministic: the same lines yield the same graph on any machine. The graph is a disposable projection — delete it and fold again, nothing is lost.
Restoration: git clone the agent repository, read AGENT.md (manifest) and .agent/config.toml (executor binding), verify spec version and identity hash, load identity/ and skills/ directly at runtime (never compiled in), fold state/events.jsonl into the graph (optionally seeded from a verified snapshot), bind a model/provider as executor and resume.
Identity and skills: identity/ and skills/ are first-class committed artifacts. Identity is human-gated; skills are versioned (one change per commit). Skills use YAML frontmatter (required name and description, optional tools/allowed-tools) and markdown body, compatible with the open Agent Skills convention.
Memory and projections: memory/facts.jsonl holds derived facts (append-only, committed) that are useful without originating context and not derivable from a lineage query over the fold. memory/active_memory.md, journal/JOURNAL.md, and snapshots/ are deterministic projections of the log and facts. JOURNAL.md is append-only (new run entries appended, never rewritten).
Four tiers of state: (1) First-class, committed: event log, identity/, skills/. (2) Derived, append-only: memory/facts.jsonl — distilled facts. (3) Projections, regenerable: memory/active_memory.md, journal/, snapshots. (4) Cold, prunable: transcripts/, never source of truth.
Identity human-gated: Changing identity/ requires a single commit that edits files, updates the identity hash in the manifest, and appends a decision event, so the manifest never disagrees with identity/ at any point in history.
Concurrency: Single-writer lease (local-only, gitignored) prevents concurrent-writer corruption; multiplayer and A/B use git branches, not concurrent writes.
Boundary commits: Each run closes with one commit bearing Run-Id, Goal, and Outcome trailers, so git log reads as a list of runs.
Adapter rungs: For agents one does not control, GASP defines three adapter rungs: native hooks, transcript projection, and a model/API proxy.
Conformance is mechanically checkable: A runtime is conformant if it can restore the fixture and its emitted repos pass checks 1–5 and 7. An adapter is conformant if its projected repo passes the same checks and round-trips through restore.
Details
Repository Layout
The repo has four tiers:
First-class, committed: state/events.jsonl (the event log), identity/, skills/.
Derived, append-only: memory/facts.jsonl (extracted from runs by a synthesis step, typically an LLM).
Projections, regenerable: memory/active_memory.md, journal/JOURNAL.md, snapshots/. These are deterministic functions of the log and facts. JOURNAL.md is append-only: new run entries are appended, existing lines never rewritten.
Cold, prunable: transcripts/ (optional, gzipped JSON lines per run).
The .agent/ directory is the control plane: config.toml is committed; HEAD and lease are machine-local and gitignored.
Physical line order in state/events.jsonl is the authoritative total order; timestamps are advisory. Events come in two families: domain events and ops events. Domain events include goal.created, task.created, run.started, observation.recorded, observation.created, failure.recorded, failure.observed, hypothesis.proposed, hypothesis.created, patch.proposed, eval.finished, decision.created, frame.created, patch.status (Promoted/Rejected), run.finished, model.called, tool.called, project.snapshot, and update.*.status. Ops events are state.ops_applied carrying mutations like CreateNode, UpdateNode, DeleteNode, CreateRelation, UpdateRelation, DeleteRelation.
The pairing rule binds the two families: each entity-creating domain event (e.g., patch.proposed with payload {id: patch_9}) is followed by exactly one state.ops_applied whose causation_id names that domain event and whose ops materialize the same entity (e.g., CreateNode for patch_9). The runtime's typed helpers (record_goal, propose_patch, record_eval, record_decision_node, etc.) are the only writer of ops, so the two families cannot drift. Events during an open run auto-chain to the run's start event and carry the run id as correlation_id.
Rendering diagram…
Conformance Kit
The conformance kit consists of a canonical fixture (a minimal agent repository asserting four graph facts: 4 nodes, 3 relations) and seven fail-closed checks (exit non-zero on any failure):
Append-only: For every commit touching state/events.jsonl, */facts.jsonl, or JOURNAL.md, the diff adds lines only at EOF — no modification or deletion. Full git history is walked; any in-place edit or deletion fails. A directory that is not a git repository fails, and any error during the walk fails closed.
Unique event ids: Every id in the log is unique.
Acyclic causation: Every non-null causation_id references an earlier event id in the log. Roots are *.created / *.started events or ops-only state.ops_applied maintenance events.
Pairing rule: Every entity-creating domain event has exactly one paired state.ops_applied whose CreateNode matches the payload's id, kind, and status. All claimants are checked; an ops event chained to another ops event fails.
Manifest and identity present: AGENT.md and identity artifacts must exist and the log folds.
Fixture restoration: Running with --fixture must reproduce the four asserted graph facts.
Runtime restore obligations: Manifest-declared alternate locations, skills loading, and identity-hash verification are the runtime's responsibilities (not yet mechanically checked in the conformance kit).
All seven checks pass against the canonical fixture, yoyo's live production repository, and a fresh repository from the reference runtime's example. The kit's own test suite includes negative fixtures (mutated line, unknown kind, dangling causation) — each correctly rejected; 22 kit tests pass.
Reference Runtime: yoagent-state (Rust)
yoagent-state is the canonical Rust implementation (crates.io, MIT-licensed). It exposes paired typed helpers (record_goal, record_task, record_observation, record_failure, record_hypothesis, propose_patch, record_eval, record_decision_node, record_frame, plus record_run_started, record_run_finished, record_model_call, record_tool_call, record_project_snapshot, and update_*_status). Each emits a domain event and its state.ops_applied as a causation-linked pair. Folding is a pure function of the log; the projector folds only ops events. Persistence is pluggable behind a store trait with EventStore, SnapshotStore, ForkStore, IndexStore, ArtifactStore. Implementations include MemoryEventStore, JsonlEventStore, and GitEventStore (durable, cross-process single-writer lease, pathspec-scoped boundary commits). Durable append (flush each event immediately) and boundary commit (one commit per run boundary) are separate operations. The single-writer lease lives in the append path. For callback-driven agents, YoAgentStateSink trait provides on_run_started, on_run_finished, on_model_called, on_model_finished, on_tool_called, on_tool_finished. Known 0.4.0 gaps: no snapshot emitter yet; the open-run marker is in-memory only; adapters' *_finished callbacks record raw events without updating call nodes' outcome props; run structs' metadata fields are not persisted by paired helpers.
Independent Runtime: minigasp
minigasp is a conformant runtime (~250 lines of Python, standard library only, no code shared with Rust runtime). It implements both restore and emit. It restores the fixture (verifying all four canonical graph facts) and yoyo's 445-event repository, folding to the identical 152-node, 119-relation graph the Rust runtime produces (Table 2). minigasp also answers lineage queries over the graph. It drove a clone of yoyo's repository forward, appending a full conformant run (run.started → patch.proposed → eval.finished → decision.created → promotion, each entity-creating event paired per the rule) and closed with a boundary commit. The extended repository passed all seven checks; the Rust reference runtime folded the Python-authored patch to Promoted — reading exactly what the second runtime wrote. Limitation: minigasp's decisions are scripted; it demonstrates runtime-independence but not that a different model reasoned the run.
Production Case Study: yoyo (E4)
yoyo is a self-evolving coding agent that reads its own source, plans improvements, implements them, runs tests, and commits or reverts, autonomously every few hours. At time of writing it has run for 127 days with no human-authored code — an existence proof of a genuine long-horizon agent. Instrumented with GASP; its state now lives in a public agent repository.
Over the instrumentation window the log holds 445 events across 27 production runs spanning all session types: evolve (10), skill-evolution (3), social (11), dream (1), plus genesis, under six standing goals: self-improvement, product value, skill quality, community, dreaming, continuity. Of 28 proposed patches, 23 were promoted and 5 reverted; evaluations and decisions match (23 passed/approved, 5 failed/rejected) because a revert is a recorded chain: eval.finished:Failed → decision:Rejected → patch:Rejected. Every run is conformant. Alongside the log, the repository carries 14 versioned skills and 244 distilled facts.
Qualitative payoff: auditability. One evolve run proposed a patch, its evaluation failed, the decision reverted it, yoyo filed an issue against itself, picked it up in a later session, fixed it, and promoted it. That entire arc is a graph query over the log (lineage(goal)), not archaeology through commit messages — the auditable self-evolution provenance that prior self-improving agents identify as missing.
Fact Extraction and Memory
memory/facts.jsonl holds what the agent learned, not what it did. A fact earns a line only if (a) useful without originating context — changes future behavior rather than recording past behavior — and (b) not derivable from a lineage query over the fold. Examples that belong: "The flaky test is retry_timeout; ignore single failures", "this API rate-limits at 10 rps", "the maintainer prefers squash merges". "Ran cargo test at 14:32, passed" never belongs — that is a tool.called/eval.finished event. Format: one JSON object per line, append-only: {"id": "fact_<uuid>", "ts_ms": ..., "text": "...", "derived_from": ["event_..." | "run_..."], "supersedes": "fact_..." | null}. derived_from keeps every fact auditable back to events. Superseding a stale fact appends a new line pointing at the old one — append-only compaction; the synthesis step that writes active_memory.md uses only the latest version of each fact chain. Fact extraction is a synthesis step (usually an LLM), not a deterministic fold, so facts.jsonl is committed and append-only like the log; only active_memory.md (the current synthesis of live facts) is freely regenerable. Version the synthesis prompt.
Self-Improvement as a Log Pattern
A skill change is a patch that advances a goal (and addresses a failure if it fixes one). The patch pins the skill commit — as a prop or attached artifact. The verification gate (tests, an oracle) produces an eval: patch --validated_by--> eval. Promote or revert is a decision: the patch is approved_by (or rejected_by) the decision, and the patch's status is set to Promoted (or reverted) via an UpdateNode. Promotion is a status on the patch, not a separate node. A version's track record (pass rate, sample size, trend) is a fold over its patches + evals + decisions — data that travels with the repo. The boundary: the eval and decision facts live in the log (part of GASP); the promotion policy (minimum sample size, win margin to replace an incumbent) is the executor's, never GASP's. The oracle is a plug-in; the policy that consumes its evals stays out of the log format.
Adaptation Contract for Third-Party and Closed Agents
Conformance for agents not built with GASP is always via an adapter that translates native records into the GASP vocabulary. The adapter sits outside the loop for agents you don't control.
Three rungs of fidelity (best to worst):
Native hooks / session-store: The agent fires callbacks at its own boundaries; emit GASP events in-band as it runs. Real-time, highest fidelity, and (where hooks are documented) the only supported surface.
Transcript projector: Tail or post-process the session transcript into state/events.jsonl and commit. Universal fallback, but transcript formats are often undocumented internals; pin the agent version and expect breakage on upgrade.
Model/API proxy: Run the agent behind a shim that sees its model calls and tool I/O; emit from there. Medium fidelity, for when there is neither hook nor transcript.
Closed agents emit raw events (messages, tool calls), not semantic ones (goal, hypothesis, patch, eval, decision). The lineage spine is not in a raw transcript; it must be inferred, supplied out-of-band, or left thin. Export is easy; import (restore) is partial — you cannot inject state back mid-tool-call. Restore for a closed agent means "start a new session that knows who it is and what happened," not "resume exact loop state." What this buys for closed agents is a uniform, ownable record across vendors: identity, memory, and lineage become vendor-independent; one portable spine under a heterogeneous fleet.
An adapter is conformant if its emitted log (a) uses only the GASP vocabulary, (b) is appended durably and committed per the rules, and (c) round-trips — a conformant runtime can restore the resulting repo and continue.
Restore Contract
The restore operation is gasp restore <git-url> [--at <event-id>] [--model <model>]. Steps: git clone (optionally partial); read AGENT.md + .agent/config.toml; verify spec version; fold the committed log into state; load identity and skills. Snapshots on cadence (snapshots/<event-id>/graph.json + integrity record) allow restore from a checkpoint instead of all history. For closed agents, restore means session rehydration (start a new session that knows who it is and what happened), not exact loop state resume.
Evaluation Results
E1 — Conformance breadth: All seven checks pass against canonical fixture, yoyo's repository, and reference runtime example. Kit test suite: 22 tests pass, covering fail-closed paths with negative fixtures.
E2 — Restore cost: Cloning yoyo's 445-event repository takes 2.4 s wall-clock, running all seven checks 2.3 s, folding the log 0.57 s on a laptop. Folding is O(log length) and not the bottleneck; network clone dominates. Snapshots seed the fold at a verified prefix.
E3 — Executor-independence: Both Rust and Python runtimes fold the same repositories to identical graphs (Table 2: fixture 4 nodes, 3 rel.; yoyo-gasp 152 nodes, 119 rel.). yoyo's repository, written entirely by the Rust runtime under a single claude-opus-4-6 executor, is restored intact by the Python runtime.
E4 — Production deployment: yoyo's 127-day autonomous operation with GASP instrumentation across all session types, emitting 445 conformant events.
Limitations and Sharp Edges
Git scaling: Append-only log grows without bound; long-lived agents will need epoch rotation and log sharding — the snapshot integrity record anticipates this but the reference runtime does not yet automate it.
Secrets: Append-only log is the wrong place for credentials; GASP keeps secrets out of the committed tree and in local-only control-plane files.
Crash granularity: The durable unit is the appended event; a process that dies between appends can lose the in-flight event, though not any already recorded.
Concurrency: Single-writer leasing prevents corruption but pushes multiplayer to branch-and-merge, whose semantics are left to future work.
Closed agents: The adapter rungs recover a conformant log at decreasing fidelity; an agent that never externalizes its decisions cannot have that lineage lifted from a transcript.
Case-study window: yoyo is a 127-day agent, but its GASP instrumentation is recent; the log demonstrates conformant emission across all session types in production, not 127 days of telemetry.
Determinism at tool boundaries: Replay reconstructs recorded state faithfully, but re-executing side-effecting tools during a fork requires care that GASP delegates to the executor.
Single-author ecosystem: Both conformant runtimes here are the authors'; the independent one drives runs with scripted decisions rather than a different model reasoning them. Two things remain open: a run driven forward by a genuinely different model, and adoption by independent third parties.