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:

When to use pugmark

Reach for pugmark when you want to:

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

SurfaceStabilityNotes
Storage layout on diskStable from v0.1.0Sessions written today readable forever
IPC wire protocol (stdin/stdout JSON + HTTP object fetch)Stable from v0.1.0Handlers in any language survive SDK changes
Go SDKPre-1.0Ergonomics may change; breaking changes called out per release
Python SDKPre-1.0Same
CLI commandsPre-1.0Flags and output formats may change