Start a new agent from zero
The fixture in the spec repo is a complete, conformant agent-repo — use it as the template. Empty the log, make the genesis commit, and you have a brand-new agent that passes all seven checks:
Then make it yours: edit identity/IDENTITY.md to define who the agent is — an identity change updates the identity_hash in AGENT.md in the same human-gated commit (the hash recipe is documented inside AGENT.md itself). Replace skills/retry/ with your agent's first skill, bind a model in .agent/config.toml, and wire in an executor — the next guide.
Restore an agent from a repo URL
Restore is clone + replay. Any conformant runtime performs the same six steps against any conformant repo.
git clone <url>— optionally a partial clone to skip cold transcripts.- Read
AGENT.md+.agent/config.toml; verify spec version and identity hash. - Load
identity/andskills/directly — runtime, never compiled in. - Fold the committed log into state, optionally seeded from the latest verified snapshot. This reconstructs open goals, patch lifecycle, lineage.
- Bind the chosen model/provider as the executor.
- Resume: take the lease, continue the loop.
"Same agent, everywhere" = the same clone + fold on any machine. Snapshots are an optimization, never an authority — restore verifies each snapshot's integrity record against the cloned log and falls back to a full fold on mismatch.
Wire an existing agent into GASP
GASP governs state, not your executor. Keep your model, loop, prompts, and tools; route their state through GASP. Three additive steps — closer to adding OpenTelemetry than adopting a framework.
On start, clone-or-init the state repo from a URL. One line of config; this is the agent's identity + memory + log.
Emit an event at the moments your loop already has: your "task" → task, your test gate → eval, your accepted change → patch + decision.
Fold the log at startup, run your loop, persist at run close. Everything else stays yours.
Don't hand-author the log format. The reference runtime yoagent-state ships paired helpers (record_goal, propose_patch, record_eval, …) that emit each domain event and its state.ops_applied correctly linked, plus GitEventStore — flush-per-event durability, in-append lease, boundary commits with trailers. Repos emitted through it pass all 7 checks.
Implement a conformant runtime
Any language works — a Python or TS agent implements the layout itself and needs no library. Get these four things right:
One JSON object per line of state/events.jsonl: id, schema_version, ts_ms, actor, kind, payload, causation_id, correlation_id. No extra top-level fields, none missing. Physical line order is the authoritative total order — ts_ms is advisory.
{"id":"event_01","schema_version":1,"ts_ms":1739000000000,"actor":{"kind":"agent","id":"evolve"},
"kind":"goal.created","payload":{"id":"goal_retry","title":"Make retry reliable", …},
"causation_id":null,"correlation_id":"goal_retry"}
Every entity-creating domain event (goal.created, patch.proposed, eval.finished, …) is followed by exactly one state.ops_applied whose causation_id is the domain event and whose ops create the entity consistently. Only ops events fold into the graph; domain events are the audit layer. This is what keeps the two from silently diverging.
The JSONL append is the durability guarantee — flush each event immediately so a crash loses nothing. The commit is the shipping guarantee — one closing commit per run with a Run-Id / Goal / Outcome trailer. Committing per event destroys history; buffering until commit loses the tail on crash.
A single-writer lease (.agent/lease, local-only, gitignored) checked inside the store's append makes concurrent-writer corruption structurally impossible. Multiplayer and A/B use branches, not concurrent writes to one ref.
Identity changes are human-gated: one commit that edits identity/, updates the identity hash in AGENT.md, and appends a decision event — all three together, so the manifest never disagrees with identity/ at any point in history.
Wrap a closed agent
For agents you don't control, the adapter sits outside the loop and translates their output — you never touch their internals. Whatever the agent exposes picks the rung:
state/events.jsonl. Transcript formats are undocumented internals: pin the agent version, expect breakage on upgrade.Tool invocation → tool_call; its result → observation; each assistant turn → model_call; session start/stop → run.started / run.finished. Maps nearly 1:1, cheap and faithful.
A passing test command → eval; an accepted edit or commit → patch; the initiating task → goal. Heuristic by nature — you cannot extract decisions the agent never externalized.
What this buys: not resumability — a uniform, ownable record across vendors. Run one agent today and another tomorrow, point both adapters at the same repo, and identity, memory, and lineage survive switching harnesses. An adapter is conformant if its emitted log uses only the GASP vocabulary, is appended durably and committed per the rules, and round-trips through restore.
Migrate an agent with existing history
The manifest binds locations — the standard layout is the default, not a straitjacket. AGENT.md declares where identity and memory actually live, so an adopter with history keeps its layout. Most agents already have GASP-shaped parts; map them:
The one thing migration adds is the semantic log itself — turning git history into a queryable lineage graph, and making the self-improvement ratchet (patch → eval → decision → promotion) a primitive instead of an ad-hoc commit-or-revert script. The reference agent yoyo-evolve is this exact migration, documented in SPEC Part IV.
Pass the conformance kit
A spec is not a standard until "conformant" is verifiable. The checker runs seven checks against any emitted repo and exits non-zero on any failure:
Every line parses to the event envelope and re-serializes structurally equal.
Folding succeeds; every snapshot equals the fold of the log prefix its integrity record names.
Every node kind and relation is baseline vocabulary or declared in a pack at .agent/packs/.
The full history walk: every commit touching an append-only path adds lines at EOF only. This, not a pre-commit hook, is the enforcement.
Ids unique; every causation references an earlier event; roots are creation/start events.
Manifest + identity present, log folds; against the fixture, the fold must reproduce the four asserted graph facts.
The pairing rule, mechanically: every entity-creating domain event is materialized by exactly one paired state.ops_applied whose CreateNode matches the payload's id, kind, and status.
A runtime is conformant iff it can restore the fixture and its own emitted repos pass checks 1–5 and 7. An adapter is conformant iff its projected repo passes the same checks and round-trips through restore — the same bar, whether events came from a native loop or a wrapped agent's transcript.
Questions people actually asked
The conceptual questions that come up when meeting GASP for the first time.
gasp restore related to git restore?No — a name collision only. git restore restores files in a working tree (it discards local edits). gasp restore rebuilds a whole agent from its repo URL: clone, load identity and skills, fold the log, resume. Same English word, unrelated operations.
The current state is the replay's result — GASP just refuses to store it as an authority. A deterministic fold of the same lines yields the same agent on any machine; a stored "current state" would make one copy the truth and the log decoration. And the closed history in the folded graph is load-bearing, not archival: a patch rejected last week stops the agent from blindly proposing it again, every memory fact's derived_from stays checkable against the runs that taught it, and "why does the agent behave this way" is a walk over closed lineage — open goals alone can't answer it. Keep only the open items and you have a to-do list, not an agent with a past.
No — a snapshot is a cache for reaching now faster. Restore verifies its integrity record (a hash of the exact log prefix it folds through) and then folds only the tail; on any mismatch it falls back to a full fold. Deleting every snapshot loses nothing, ever. Viewing a past state is the separate feature: --at <event-id> deliberately stops the fold partway — the agent exactly as it was at that moment, for debugging, audit, or forking from before a bad decision.
identity/ and memory/ — why fold state at all?Because each layer is thin on purpose, and none of the others holds the agent's ongoing life. identity/ is who the agent is — static, human-gated. memory/facts.jsonl is distilled lessons only — the admission criterion forbids it from mirroring history. The folded graph is the episodic layer: open goals, in-flight runs, the patch → eval → decision record. In human terms: identity is personality, skills are abilities, memory is semantic knowledge (“stoves are hot”), and the fold is episodic memory — what you're working on, what you tried yesterday, why you decided what you decided. A person with the first three but not the fourth wakes up every morning as a competent stranger to their own projects. Memory can stay small precisely because history has its own home.
Two different "too big"s. For the machine, a year of events is trivial — on the reference agent's cadence, roughly 36k lines / ~20 MB, folded in well under a second, and snapshots make restore O(tail) anyway. For the model, the folded graph never enters context wholesale — the control loop reads view(state), a projection: the distilled synthesis, open goals, the current run's frame, plus targeted lineage queries on demand. Closed history doesn't qualify for the view, so a 365-day-old agent and a 3-day-old agent present the same context footprint. What grows with age is the depth of what can be queried — the asset, not the liability. Your episodic memory holds decades; your working memory holds seven things. Recall, not loading.
GASP standardizes what's stored and recallable; the recall policy — view() — is deliberately the executor's. Best practice is four layers, each with a different trigger:
- Standing — identity + the distilled synthesis, always loaded, defended by an explicit token budget (the admission criterion and the synthesis step exist to protect this layer).
- Situation-triggered — at run start, query the graph by status and adjacency: open goals, last run's outcome, failures on the target at hand. For operational state, graph structure beats semantic search.
- On-demand — give the model query tools (
lineage(goal),why(patch)) and let it pull when uncertain; each lookup is itself a logged event, so recall is auditable. - Negative-space — before proposing an action, force a structural check for prior rejections on the same target. This one can't be left to the model's initiative: it can't choose to recall a rejection it has no hint exists.
And because state is standardized and executors are swappable, recall policy stops being folklore: two executors with different view() strategies, same repo, A/B'd on branches, judged by their eval outcomes.