Concepts
Motivation
Building an agent means running it again and again: change a prompt, swap a tool, see what comes out different. But an agent run is ephemeral. When it goes somewhere interesting, or somewhere wrong, you can’t return to the moment before it chose that tool and try the other branch. You start over and hope the run reproduces.
Pugmark treats an agent’s history the way Git treats a codebase. Every step the agent takes becomes a JSON event appended to a session log in object storage. From any point in that log you can:
- Branch. Fork a session at any step and send it somewhere new: a different prompt, an edited tool result, another model. The fork keeps all the history before it and diverges from there.
- Replay. The log is the entire context, so re-running an agent simply replays its events. A run reproduces exactly, and an interrupted one resumes on its own.
- Inspect. Read any session as a plain list of events with
pugmark cat. The full trajectory is on disk, not trapped in a process.
When to use pugmark
Reach for pugmark when you want to:
- Explore alternate agent trajectories. Fork a run at any step to try a different prompt, tool, or model, then compare the branches.
- Debug and audit by replay. A run is a readable list of events you can reproduce exactly and diff against another.
- Run one agent everywhere. The same handler script runs under
pugmark runlocally, in a container, or on a serverless function, backed by S3, GCS, or local files.
If you want a batteries-included framework that makes the LLM calls and owns the control flow, that’s not pugmark. Pugmark is the substrate underneath. Bring your own framework, or none, on top.
How a session is put together
A session is a thread of work, identified by a 128-bit ID that carries its own creation time in the leading bytes. Sessions form a tree: any one of them can be forked from a parent at a chosen point, and the child inherits the whole history that came before the fork.
The events themselves are stored as objects, addressed by the SHA-256 of their bytes. Because the address is the content, two events that happen to carry the same payload share a single copy on disk, and anything smaller than a kilobyte is kept inline so a reader can decode it without a second round trip.
Sitting between a session and its objects are snapshots: immutable, numbered captures of what the session contained at a given version. Most are checkpoints, the ordinary durable kind the runtime watches for and reacts to. A few are provisional, holding writes that arrived out of band until the next checkpoint absorbs them; the distinction rarely matters until it does. A small manifest per session records its parent and which handler should pick it up. The objects, the snapshots, and the manifest: that’s the whole data model.
The handler holds no state
A handler is just a process. It reads the session’s events on standard input, fetches any object bodies it needs over HTTP, works out the one event that should come next, and writes it back on standard output. Nothing has to survive between runs, because the runtime replays the entire log into the handler on every invocation.
Nearly every handler ends up with the same shape. Walk the log, folding each event into whatever state you want to track; look at the most recent event; and append exactly one new one.
for obj in pugmark.read(): # the full log, in order
fold(state, obj.decode_json())
match last_event: # decide based on the tail
case {"kind": "..."}: pugmark.write({"kind": "...", ...})
Letting the log be the only source of truth pays off three ways. The first is durable execution. An agent that crashes, times out, or is killed mid-step doesn’t lose its work and doesn’t give up; the next invocation sees the same log on object storage and continues from the first missing step. The second is exact reproducibility. The unpredictable parts of a run, a model’s reply or a timestamp, get written down the moment they happen, so a replay yields the same trajectory. The third is portability: the same handler runs unchanged on your laptop or on Lambda and Cloud Run, where a notification on each new object stands in for the local run loop.
What’s actually underneath
There is not much to pugmark. An object store addressed by content, the session and snapshot model above it, and a protocol the runtime and handler use to talk. Two specifications pin all of it down, and both have held stable since v0.1.0: the handler protocol describes the exchange between runtime and handler, while the storage layout describes exactly what ends up in the bucket and which guarantees hold across it.
Stability
| Surface | Stability | Notes |
|---|---|---|
| Storage layout on disk | Stable from v0.1.0 | Sessions written today readable forever |
| IPC wire protocol (stdin/stdout JSON + HTTP object fetch) | Stable from v0.1.0 | Handlers in any language survive SDK changes |
| Go SDK | Pre-1.0 | Ergonomics may change; breaking changes called out per release |
| Python SDK | Pre-1.0 | Same |
| CLI commands | Pre-1.0 | Flags and output formats may change |