Ardian About Directory
Claude Code
Email me

Chronica

2026 — A Go library that keeps one session for a human and many AI agents

01 One human, many agents

An agent remembers a conversation only through the history it is handed before it answers. When one person works with several agents in the same conversation, like a concierge that plans, a search agent that finds places and a booking agent that holds the table, all of them need the same record of what was said and done, and each of them needs only part of it.

Chronica keeps that record. A chronicum is the session and an actum is one thing that happened in it, and chronica and acta are simply their Latin plurals.

02 Recording

Every actum has a Kind, which is a message, a thought, a tool request or a tool response, and an ActorKind, which is human, agent or system. Actor holds a stable ID for whoever did it, and display names are left to a higher layer.

RecordActum checks the actum before the store is touched. Messages and thoughts need content, while tool requests and responses may be empty, because a call with no arguments and a call that returns nothing are both real.

Chronica always assigns the ID itself and replaces any the caller sent. If the chronicum does not exist yet, it is created on the spot and bound to the owner in that call.

c := chronica.NewChronicarius(inmemory.NewStore())

stored, err := c.RecordActum(ctx, "ardian", chronica.Actum{
ChronicumID: "dinner-friday",
Kind: chronica.ActumMessage,
ActorKind: chronica.ActorHuman,
Actor: "ardian",
Content: "Find us a quiet table for four in Kemang on Friday at 7pm.",
})

03 Context windows

GetActa is how an agent gets its context. WithActumKinds and WithActorKinds choose which acta count, so a reply can leave out thoughts and system events, and WithLastN keeps the most recent of the ones that passed. The filters run before the limit, never after it.

A limit of zero or less means no limit, and leaving a filter out means every kind passes.

The order is the order RecordActum was called in, oldest first. The store stamps At when it writes an actum, but that time is only advisory and never decides the order, so two acta written in the same instant still come back the way they went in.

acta, err := c.GetActa(ctx, "ardian", "dinner-friday",
chronica.WithLastN(20),
chronica.WithActumKinds(chronica.ActumMessage, chronica.ActumToolResponse),
chronica.WithActorKinds(chronica.ActorHuman, chronica.ActorAgent),
)

04 Ownership

Every read and write names an owner, and Chronicarius checks it against the chronicum before any acta are read or written. The store methods take no owner and check nothing, which is why they are meant to be called only through Chronicarius.

A chronicum that belongs to another owner answers ErrChronicumNotFound, the same as one that does not exist, on GetActa, GetChronicum and RecordActum alike. An empty owner fails with ErrEmptyOwnerID.

The owner is a plain string from whatever identity provider you use, so proving who the caller is stays with your application.

05 Retries

When a write times out, the agent cannot tell whether its reply landed. With WithIdempotencyKey, a retry that uses the same key in the same chronicum returns the actum stored the first time and discards the new payload, so a reply the model regenerated in other words is still recorded once. Concurrent calls with one key store exactly one actum.

Deduplication is something a store opts into by also implementing IdempotentStore, and the in-memory store does. Given a key and a store that does not, RecordActum fails with ErrIdempotencyUnsupported before it creates the chronicum, because quietly ignoring the key is how duplicates get in.

stored, err := c.RecordActum(ctx, "ardian", reply,
chronica.WithIdempotencyKey("turn-3-reply"),
)

06 Stores

A Store is four methods, Create, Record, Acta and Get, and it holds no policy. Ownership, validation, IDs and creating a chronicum on first write all live in Chronicarius, so a plain and correct CRUD backend is already a correct store, and no backend author has to re-implement the rules. A real database goes behind the same four methods.

storeconformance turns that contract into tests. RunTest checks the base contract, from insertion order across a thousand acta to forty appends at once, and RunIdempotentTest checks deduplication, including forty concurrent writes with one key.

The bundled in-memory store passes both suites. It is meant for tests and development, since it keeps everything in one process and its idempotency keys grow without bound.

func TestConformance(t *testing.T) {
storeconformance.RunTest(t, inmemory.NewStore)
storeconformance.RunIdempotentTest(t, inmemory.NewIdempotentStore)
}

07 What it leaves out

There is no delete. A chronicum is an append-only record of what the agents and their human did, and nothing in the agent layer needs to erase an actum.

There are no pagination cursors either. Cursors drive scrolling lists in an interface, while agents assemble context windows, so WithLastN and the filters take their place.

Reaching for either one means you are building an interface or a CRUD layer on top of Chronica, and that layer is where they belong.

Chronica

Hands an agent its context window in one call, oldest first, and keeps it from anyone but the session's owner