Skip to content

Agentic development with event sourcing: a guide

Agentic development with event sourcing means letting AI coding agents design, change and operate a system whose state is an append-only log of named events, whose behavior is expressed as commands, and whose rules are written as executable Given/When/Then specifications. The agent reads and writes the same explicit contracts a human reviewer reads. The log records what happened, and the specifications state what must stay true. Neither depends on what the agent remembers or guesses.

This page explains why that combination helps, which Cratis tools implement each part, how they fit together, and what is still experimental. It is written for tech leads who are adopting coding agents and worry about correctness and auditability. Cratis is built by a team that works this way, and the tooling described here is the tooling we use. Where something is not finished, the page says so.

A coding agent works from what it can see: the files in the repository, the prompt, and whatever it can query. Three properties of a typical create-read-update-delete (CRUD) system work against it.

  • Hidden state. The current row is the only truth. The agent cannot tell why a value is what it is, which code path last wrote it, or which of three similar columns the business actually relies on.
  • No history. An update overwrites the previous value. When an agent’s change corrupts data, nothing in the database says what the data looked like before, so a fix is a guess and an audit is a reconstruction from logs.
  • Implicit contracts. Behavior lives inside service methods, triggers and ORM hooks. An agent that changes one path cannot see which other behavior depended on it, and neither can the reviewer reading the diff.

None of this is a defect in the agent. A human who joins a CRUD system cold has the same problems and solves them by asking colleagues. An agent cannot ask, so it fills the gap with the most plausible answer, and a plausible wrong answer is the dangerous one.

Event sourcing removes each of these. State is the history of events, so nothing is hidden and nothing is overwritten. Every change is a named command with a typed shape, so the contract is explicit. The rest of this page builds on that.

If you are new to the idea, what is event sourcing covers the basics, and coming from CRUD maps the concepts you already use.

Events, commands and specifications as a contract agents can read

Section titled “Events, commands and specifications as a contract agents can read”

Three artifacts carry the contract in a Cratis application. Each is plain code in the repository, so an agent reads it the same way it reads any other source file.

ArtifactWhat it statesWhy it helps an agent
CommandOne intention a caller can express, with its inputs, validation and the events it may recordThe agent finds every way the system can change, named and typed, in one place
EventA fact that happened, named in the past tense (BookBorrowed), immutableThe agent sees what the system records and cannot silently rewrite it
SpecificationA Given/When/Then example of the behavior: facts that already happened, the action, the expected factsThe agent has an executable definition of “correct” that does not depend on its own reading of the code

Given/When/Then maps one to one onto event sourcing. The given is the events that already happened, the when is the command or event under test, and the then is the events, read models or side effects that result. That is why a specification reads in the vocabulary of the event model, and why a domain expert can check it without reading the implementation. Testing with Cratis shows the mapping in C#, Kotlin, Java and TypeScript, and Specifications documents the Establish(), Because() and Destroy() lifecycle.

Here is a real specification from the Decisions that hold walkthrough. It checks a rule that is easy to get wrong: two callers borrow the last copy of a book at the same time.

public class when_the_last_copy_is_borrowed_concurrently : Specification
{
static readonly ISBN _isbn = new("978-0316389704");
readonly CommandScenario<BorrowBook> _scenario = new();
CommandResult _result;
void Establish()
{
_scenario.Given.ForEventSource(_isbn).Events(new BookAddedToInventory("The Last Wish", AuthorId.New(), 1));
_scenario.AppendConcurrently(_isbn, new BookBorrowed(_isbn, "The Last Wish", MemberId.New(), DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(21)));
}
async Task Because() => _result = await _scenario.Execute(new BorrowBook(_isbn, MemberId.New()));
void Destroy() => _scenario.Dispose();
[Fact] void should_not_succeed() => _result.ShouldNotBeSuccessful();
[Fact] void should_report_a_conflict() => _result.ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation);
[Fact] void should_append_nothing() => _scenario.AppendedEvents.Count.ShouldEqual(0);
}

The scenario seeds one copy, so the validator sees one available copy and passes. A competing BookBorrowed is appended after the command reads and before it commits. The command is refused with a concurrency violation and appends nothing. The spec runs in process, without a Chronicle server or a database, so an agent can run it after every change.

An agent that changes BorrowBook has a precise target: keep this spec green. An agent that weakens the rule fails the spec, not a reviewer’s patience.

Event models as the shared source for humans and AI

Section titled “Event models as the shared source for humans and AI”

Contracts are cheaper to agree on before the code exists. Event modeling lays a system out as a timeline of screens, commands, events and read models. Because each step is named and typed, a model is a description an agent can read, and a plan a human can review before any code is written.

Cratis puts the model in a file. Screenplay is a declarative .play language for one bounded context: its events, commands, queries, projections, screens and rules. Studio is the collaborative environment for editing Screenplay models. Stage compiles Screenplay, verifies the specifications declared in the model, and renders a reviewable Arc and Chronicle application on disk.

The practical effect is a shared source. The model is what the team reviews. The specifications declared in the model are what Stage verifies. The generated application is what the agent starts from and extends. When the model changes, the discussion happens on the model, in terms the business understands, rather than in a diff of generated code.

The event model is also the test: one column of a model becomes the Given/When/Then rows of a specification, as the testing guide shows step by step.

If you already use EventModelers.ai, its Build Kits give an agent a model-to-code path for C#, Kotlin and Java. The page EventModelers.ai explains what a Build Kit does and how to pick one.

Letting agents inspect the runtime: Chronicle MCP and the CLI

Section titled “Letting agents inspect the runtime: Chronicle MCP and the CLI”

A specification covers behavior you thought of. Production shows the rest. An agent that is asked “why is this order cancelled?” or “why is this projection behind?” should read the system, not infer from the code.

Cratis gives the agent two documented routes into a running Chronicle store.

The Chronicle MCP server speaks the Model Context Protocol. It runs over stdio as the cratis/chronicle-mcp container image and connects to a running Chronicle server. Releases are also listed in the official MCP Registry as io.github.Cratis/chronicle-mcp. Because it talks to Chronicle directly, it works with every Chronicle client language: .NET, TypeScript, Kotlin/Java and Elixir.

It exposes two kinds of tools:

  • Operate-side tools inspect a live store: list_event_types, get_events, get_tail_sequence_number, list_observers, list_failed_partitions, list_jobs and others.
  • Design-time tools turn a request into a reviewable proposal: describe an event type, build an event catalog, find event types nothing consumes, run an ad-hoc projection, and scaffold a read model.

The design-time tools follow one rule: introspect the store before generating anything. Field names, types and event names come from the schema registered with Chronicle, not from the model’s memory. When a requested event type is not registered, the tool says so and does not invent a shape.

The tool that most directly answers the audit question is explain_causal_trace. It reads the ordered events of one event source together with each event’s correlation id and causation chain, so the agent can say what happened and why. The documentation calls it the equivalent of git blame for state.

A minimal configuration for an MCP host that runs the server through Docker looks like this (taken from the Chronicle MCP getting started guide):

{
"servers": {
"Chronicle": {
"type": "stdio",
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-eCratis__Chronicle__Mcp__ConnectionString=chronicle://host.docker.internal:35000",
"cratis/chronicle-mcp"
]
}
}
}

The server starts in the ReadOnly profile. In that profile the tools that change job state (stop_job, resume_job and delete_job) are not in the tool list and cannot be called. The Mutation profile exposes them, and it refuses to start unless the deployment names a principal, an expiry in the future and explicit grants for each store, namespace, operation and target. Operation wildcards are invalid, so a new mutating tool is never granted by accident. Chronicle stays the authority and applies its own authentication and authorization to every call. See deployment profiles.

The Cratis CLI covers the same ground from a terminal and is the better fit for agents that run shell commands. Running cratis init in your project writes a CHRONICLE.md that lists every command the CLI can run, adds instruction files for the AI tools it detects, and adds a chronicle-diagnose slash command. The CLI can also replay an observer, which rebuilds a read model; read replay rebuilds read models before an agent runs it.

Neither route changes the event log. Both read it and operate observers and jobs; job control and replay change read-side state, so they need care and, over MCP, an explicit policy. To change state, the system goes through commands and events, so the history stays honest.

Rules and skills that teach agents the conventions

Section titled “Rules and skills that teach agents the conventions”

A general-purpose agent dropped into a Cratis codebase invents handler classes, misses the vertical-slice layout and hand-writes the API client that proxy generation already produces. Cratis AI is a shared corpus of rules, skills, prompts and subagents that carries the conventions to the agent.

The main path installs the corpus into a repository with the CLI:

Terminal window
cratis ai install

Run without options it asks what the repository needs. To script it, select the harnesses, profiles and languages explicitly:

Terminal window
cratis ai install \
--harnesses claude,codex,copilot,cursor,opencode,pi \
--profiles cratis/application \
--languages csharp,typescript

The CLI installs the managed content under .cratis/ai, creates native adapters for each selected coding tool, and records hashes in .cratis/ai.manifest.json. It refuses to overwrite files you own, and cratis ai status shows what is installed and whether a newer revision is available. Two other channels exist: the @cratis/pi package for Pi, and marketplace plugins for Claude Code, Codex, GitHub Copilot and Cursor, which deliver skills only. Trust and distribution explains how each channel is versioned, and the harness guide has the command for each tool.

Some of what the corpus contains, by name:

  • The new-vertical-slice prompt scaffolds a feature: command, events, projection, query, React and specs.
  • Skills such as cratis-arc-command, cratis-chronicle-read-model and cratis-chronicle-projection build one artifact by convention.
  • The write-specs prompt and the cratis-code-review skill cover a feature with BDD specs and review it against the project’s standards.

Rules and skills do not prove a change is correct. They make the first attempt look like the rest of the codebase, so review and specifications spend their time on behavior instead of style. The code-analysis rules enforce the same framework contracts during the build, whoever wrote the code.

Cratis AI is in Preview. One practical limit applies to every agent: a large profile such as cratis/full can exceed the budget some agents give to skill descriptions, so install the profiles a repository needs rather than everything.

Guardrails: specifications, review and replay

Section titled “Guardrails: specifications, review and replay”

An agent that can write code quickly needs checks that are as fast and that do not depend on the agent’s judgment. A guardrail model for agent-written changes has four layers. Each one is a mechanism you can point at.

LayerMechanismWhat it catches
BuildCode-analysis rules and typed commands, events and conceptsContract mistakes before anything runs
SpecificationsIn-process Given/When/Then scenarios for commands, read models and reactorsA behavior change the spec did not expect
ConstraintsRules Chronicle checks when an event is appended, such as uniqueness, and append conditions for decisions that depend on stateA change that is accepted by the code but violates a rule under concurrency
ReviewA human reads the model, the specs and the diffWrong intent that a green build cannot see

Specifications give an agent a fast loop. It changes the code, runs the specs and reads the failure. Because specs are written in the language of events, a failing spec names the fact that was expected and the fact that happened. Testing with Cratis lists the in-process scenarios for each side: command scenarios for the write side, read-model scenarios for projections and reducers, and reactor scenarios for side effects.

The specs prove behavior, so the first review question for an agent-written change is whether the specs describe the behavior you want. An agent can write a spec that passes and asserts the wrong thing. Review the specification diff as carefully as the implementation diff, and prefer specifications written or approved from the event model.

Some rules cannot be checked by a unit spec alone, because they depend on what other callers do at the same moment. Chronicle checks constraints when an event is appended and rejects the append if the rule fails. For decisions that read state, it can check the state the decision was based on: if a matching event was written since, the append is refused. The keeping decisions correct page explains the model. This matters for agents because the rule is enforced where the data is written, not only where the agent happened to remember it.

The log also makes read models disposable. When you change how a projection or reducer builds a read model, you do not migrate the read model: you replay the observer and it rebuilds from history. The CLI does this with the observers replay command, and replay-partition does it for a single event source. The replay a projection scenario walks through it.

Replay is an operation, not a check. The event log is untouched, but the observer discards its accumulated state and rebuilds from sequence zero, so the read model is unavailable or incomplete while it catches up. Run it deliberately, after the changed projection is deployed, and prefer replay-partition when only one event source is affected. Specifications are the pre-deployment check: they run in process against seeded events and touch no store. Reactors need extra care, because Chronicle’s .NET client replays them unless the class is marked [OnceOnly]; read the reactor replay rules before replaying one.

Nothing here removes the reviewer. It changes what the reviewer reads: an event model and a set of specifications that state intent, plus a diff that has already passed the build and the specs. The record that remains after a change is the log itself. Each event carries correlation, causation and identity, so a later audit can follow a state back to the command that caused it.

Brownfield: start from signals with Prologue

Section titled “Brownfield: start from signals with Prologue”

Most teams are not starting from nothing. The expensive step in moving an existing system to this way of working is the event model: someone has to read years of accreted code and write down what the system does.

Prologue adds observed evidence to that step. It stands beside the running system, without application code changes, and watches selected signals: state-changing HTTP requests, which tables and columns changed after each one, and the telemetry the system already emits. It correlates these into captures, and the Interpreter proposes modules, features and slices with candidate commands, events, read models and projections. It produces a structured result and a draft Screenplay file.

Treat the output as another input to human modeling, not a recovered specification. Prologue records no database row values and no HTTP request or response bodies, but endpoint paths, changed column names and telemetry attributes can still be sensitive, so minimize access and protect the capture files. It cannot see a rule the system enforces without leaving a trace. It is the wrong tool for a greenfield system, for a system with no HTTP or database write path to observe, and for data migration. See why Prologue for the full list.

Prologue is experimental.

Maturity labels below follow the capability map. A label says how far the product has come, not how well it works for you.

PartRole in this practiceMaturity
Chronicle, Arc, SpecificationsEvent store, application framework and spec libraryReleased
Chronicle MCP serverAgent access to a running store, read-only by defaultReleased
CLIInspection, diagnosis and cratis ai installReleased
Cratis AIRules, skills, prompts and agents installed into a repositoryPreview
StudioCollaborative design of Screenplay event modelsPreview, paid product
ScreenplayThe .play modeling languageExperimental
StageCompiles Screenplay, verifies modeled specifications, renders an applicationExperimental
PrologueProposes an event model from an existing systemExperimental

Three limits are worth stating plainly:

  • Stage is partial. Its renderer is the priority path. Its runtime performs only the subset of modeled backend behavior it understands, and its specification verification is useful but does not prove every Screenplay construct has executable semantics.
  • Cratis AI is unpinned. cratis ai install reads the current main of the Cratis AI repository. To fix a revision, point --source at a checkout of a known commit.
  • Agents still make mistakes. The mechanisms on this page make mistakes visible and cheap to catch. They do not make an agent correct. Keep the review step, and keep the specs honest.

The roadmap and what’s new track movement on these.

What does “agentic development” mean here?

Section titled “What does “agentic development” mean here?”

It means an AI agent does a meaningful share of the work: reading the codebase, writing changes, running the checks and diagnosing a running system, with a person setting direction and reviewing. On this page it is paired with event sourcing because the log, the commands and the specifications give the agent explicit things to read and verify.

Why does event sourcing help AI coding agents?

Section titled “Why does event sourcing help AI coding agents?”

It makes state, history and behavior explicit. The log records every change as a named fact, so the agent can inspect why state is what it is. Commands name every way the system can change. Specifications give an executable definition of correct behavior. An agent working against explicit contracts has less to guess.

No. The practice does not depend on one product: a typed command model, an append-only log and executable specifications work with other tools. Cratis supplies one integrated set, from the event store and framework to the rules, specifications and MCP server. For a comparison of event stores, see event sourcing in .NET and on the JVM.

Which coding agents does Cratis AI support?

Section titled “Which coding agents does Cratis AI support?”

The managed install targets Claude Code, Codex, GitHub Copilot, Cursor, OpenCode and Pi. Marketplace plugins cover Claude Code, Codex, GitHub Copilot and Cursor with skills only. The harness guide has the command for each.

Can an agent change my production data through the MCP server?

Section titled “Can an agent change my production data through the MCP server?”

Not by default. The server starts read-only and exposes no tool that changes state. The Mutation profile adds job control (stop, resume, delete) behind an explicit, time-limited policy. Application state changes only through commands and events, never through the MCP server.

Does the MCP server work with my Chronicle client language?

Section titled “Does the MCP server work with my Chronicle client language?”

Yes. It connects to the Chronicle server over gRPC, so it works with the .NET, TypeScript, Kotlin/Java and Elixir clients alike. A running Chronicle server is required.

Install the rules and skills with cratis ai install, connect the MCP server or run cratis init, and build one slice by following build a full-stack feature. Then read the Decisions that hold walkthrough to see a rule enforced from model to specification to frontend. Start using Cratis AI covers the first step in detail.

How do I stop an agent from writing specs that pass but assert the wrong thing?

Section titled “How do I stop an agent from writing specs that pass but assert the wrong thing?”

Treat the specification diff as the main review artifact, and derive specs from the event model. A spec should read as a sentence a domain expert accepts. If the agent changed both a rule and the spec that checked it in one commit, that is the change to read first.

Chronicle, Arc, Specifications, the CLI and the MCP server are released. See production readiness for what to check before you depend on them. The model-first layer (Screenplay, Stage, Prologue) is experimental and is best used to explore and draft, with review, rather than as an unattended pipeline.