A library · Free & source-available · BSL 1.1 · TypeScript · Java · .NET · Python
You write zero logging statements.
NarrativeTrace writes every traced call, its parameters and its result as log lines, through the logger you already use. Helps choose better method and parameter names. Secrets redacted. The same traces, formatted for AI, let your agent verify and debug.
Code is the log.
TypeScript on npm · Java on Maven Central · .NET on NuGet · Python on PyPI
Live · running in this tab
You are O. Every method the computer calls to pick its move lands on the right as it happens — a real trace from the real library, no recording, no server. Try the trace level menu, click a square that is taken, then Copy trace. How it works →
Its opening is the widest search in the game — nine candidates, half a million positions. Your reply is usually the narrowest: one tactic fires and the search never runs.
Code is the log
Delete or enrich the logging statements. Keep the story — and the stack.
Before — three careful log lines
placeOrder(req: OrderRequest): Order {
logger.info({ orderId: req.id }, 'placing order');
try {
const customer = this.customers.find(req.id);
const price = this.catalog.price(req.sku);
this.inventory.reserve(req.sku, req.qty);
const payment = this.payments.charge(price);
const order = this.orders.save(customer, payment);
logger.info({ orderId: order.id }, 'order succeeded');
return order;
} catch (err) {
logger.error({ orderId: req.id, err }, 'placing order failed');
throw err;
}
}
After — code is the log
placeOrder(req: OrderRequest): Order {
const customer = this.customers.find(req.id);
const price = this.catalog.price(req.sku);
this.inventory.reserve(req.sku, req.qty);
const payment = this.payments.charge(price);
return this.orders.save(customer, payment);
}
// Zero log lines.
// NarrativeTrace captures the narrative
// automatically.
Before — three careful log lines
public Order placeOrder(OrderRequest req) {
logger.info("Placing order {}", req.id());
try {
var customer = customers.find(req.id());
var price = catalog.price(req.sku());
inventory.reserve(req.sku(), req.qty());
var payment = payments.charge(price);
var order = orders.save(customer, payment);
logger.info("Order succeeded {}", order.id());
return order;
} catch (Exception e) {
logger.error("Placing order failed {}", req.id(), e);
throw e;
}
}
After — code is the log
public Order placeOrder(OrderRequest req) {
var customer = customers.find(req.id());
var price = catalog.price(req.sku());
inventory.reserve(req.sku(), req.qty());
var payment = payments.charge(price);
return orders.save(customer, payment);
}
// Zero log lines.
// NarrativeTrace captures the narrative
// automatically.
Before — three careful log lines
public Order PlaceOrder(OrderRequest req)
{
_logger.LogInformation("Placing order {OrderId}", req.Id);
try
{
var customer = _customers.Find(req.Id);
var price = _catalog.Price(req.Sku);
_inventory.Reserve(req.Sku, req.Qty);
var payment = _payments.Charge(price);
var order = _orders.Save(customer, payment);
_logger.LogInformation("Order succeeded {OrderId}", order.Id);
return order;
}
catch (Exception ex)
{
_logger.LogError(ex, "Placing order failed {OrderId}", req.Id);
throw;
}
}
After — code is the log
public Order PlaceOrder(OrderRequest req)
{
var customer = _customers.Find(req.Id);
var price = _catalog.Price(req.Sku);
_inventory.Reserve(req.Sku, req.Qty);
var payment = _payments.Charge(price);
return _orders.Save(customer, payment);
}
// Zero log lines.
// NarrativeTrace captures the narrative
// automatically.
Before — three careful log lines
def place_order(self, req: OrderRequest) -> Order:
logger.info("Placing order %s", req.id)
try:
customer = self._customers.find(req.id)
price = self._catalog.price(req.sku)
self._inventory.reserve(req.sku, req.qty)
payment = self._payments.charge(price)
order = self._orders.save(customer, payment)
logger.info("Order succeeded %s", order.id)
return order
except Exception:
logger.exception("Placing order failed %s", req.id)
raise
After — code is the log
def place_order(self, req: OrderRequest) -> Order:
customer = self._customers.find(req.id)
price = self._catalog.price(req.sku)
self._inventory.reserve(req.sku, req.qty)
payment = self._payments.charge(price)
return self._orders.save(customer, payment)
# Zero log lines.
# NarrativeTrace captures the narrative
# automatically.
3am, the payment declines
INFO Placing order {customerId: "C-1234", productId: "SKU-KB", quantity: 2}
ERROR Order failed {customerId: "C-1234", productId: "SKU-KB", err: PaymentDeclinedError}
OrderService.placeOrder(customerId: "C-1234", productId: "SKU-KB", quantity: 2)
CustomerService.findCustomer(customerId: "C-1234") → Customer{tier: "gold"} — 4ms
ProductCatalogService.lookupPrice(productId: "SKU-KB") → 24.99 — 2ms
InventoryService.reserve(productId: "SKU-KB", quantity: 2) → Reservation{productId: "SKU-KB", quantity: 2} — 6ms
PaymentService.charge(customerId: "C-1234", amount: 49.98, cardToken: [REDACTED]) !! PaymentDeclinedError: card declined — 640ms
!! PaymentDeclinedError: card declined — 652ms total
Both records come from the same failed call. The catch block wraps the whole method,
so the error log can say a customer and product id and an exception type — not
which of the four calls actually threw. The trace does: PaymentService.charge,
at 640ms. It also has what the log never captured — the price, the redacted card
token, and InventoryService.reserve two lines above it with no matching
release anywhere in either record. Nothing on the right was written by
hand, so it can't go stale the next time someone adds a step.
One run, two readers
The order service places an order for customer id "C-42", quantity 5:
Customers verify good standing for "C-42", returning true.
The pricing service calculates for quantity 5, returning 49.95.
The payment service charges card [REDACTED], returning PaymentConfirmation(txId="T-9").
The order repository saves the order, returning Order{id=ORD-1001, total=49.95}.
- OrderService.placeOrder(customerId, qty) → value
- Customers.verifyGoodStanding(customerId) → value
- PricingService.calculate(qty) → value
- PaymentService.charge(card) → value
- OrderRepository.save(order) → value
// no data, no PII, nothing to inject through
Prose reads exactly as well as your names, which is why clarity scoring grades every name from the runtime narrative — the code learns to tell its own story. Clarity diagnostics →
The panel above is disciplined logging, not a strawman: one line at entry, one on success, one in a catch, the way a careful team ships this method. NarrativeTrace replaces the careful kind too, because a fixed set of lines can only report the calls someone thought to write down ahead of time. The events go where your logs already go, through SLF4J, ILogger, pino or winston, and the statements you have not deleted yet keep working. Logging bridges →
A 2026 study of 4,550 pull requests by AI coding agents against 3,276 by humans, across 81 open-source repositories, found that agents touch logging in only 20.7% of their PRs, fail 67% of the time when a reviewer asks for it, and that humans write 72.5% of the later fixes themselves, in a quiet commit rather than in review (arXiv 2604.09409). When the code is the log there is nothing to write or comply with, and approval traces make observability a deterministic gate, the guardrail the paper's own recommendations call for.
Start here with your agent
Paste this into your coding agent. It sets NarrativeTrace up in your project and shows you its first trace.
Paste this into Claude Code, Cursor or Codex inside your project.
Set up NarrativeTrace in this project and show me its first trace.
1. Read https://narrativetrace.ai/typescript/llms.txt first. It carries the install block and the known traps. Do not guess versions or artifact names.
2. If this directory has no project yet, create the smallest console app that llms.txt's "Install and first trace" block describes. Otherwise work inside the existing project and trace one real service boundary.
3. Add `@narrativetrace/cli` as a dev dependency the way llms.txt shows, then run `npx @narrativetrace/cli init --dry-run` and show me the diff. It installs the NarrativeTrace agent skills into this project and adds a marked section to AGENTS.md. Run it for real only after I have seen the diff.
4. If the `add-narrative-tracing` skill is now available, follow it. Otherwise follow the "Install and first trace (copy this)" block in llms.txt.
5. Add one test that traces a call with a deny-listed parameter and asserts the trace shows `[REDACTED]` for it.
6. Run the program, then run the doctor (`npx @narrativetrace/cli doctor`). Paste the trace and the doctor report, explain the trace in two sentences, and list exactly what changed in the project.
Rules: never disable redaction; do not commit `.received.nt` files; pass parameter names to `traceObject` explicitly so arguments never render as `arg0`; run everything in the foreground and read the output before you report; if you cannot fetch URLs, say so and I will paste llms.txt.
Set up NarrativeTrace in this project and show me its first trace.
1. Read https://narrativetrace.ai/java/llms.txt first. It carries the install block and the known traps. Do not guess versions or artifact names.
2. If this directory has no project yet, create the smallest console app that llms.txt's "Install and first trace" block describes. Otherwise work inside the existing project and trace one real service boundary.
3. Add the plugin the way llms.txt shows, then run `./gradlew narrativetraceInit --diff` and show me the diff. It installs the NarrativeTrace agent skills into this project and adds a marked section to AGENTS.md. Run it for real only after I have seen the diff.
4. If the `add-narrative-tracing` skill is now available, follow it. Otherwise follow the "Install and first trace (copy this)" block in llms.txt.
5. Add one test that traces a call with a deny-listed parameter and asserts the trace shows `[REDACTED]` for it.
6. Run the program, then run the doctor (`./gradlew narrativetraceDoctor`). Paste the trace and the doctor report, explain the trace in two sentences, and list exactly what changed in the project.
Rules: never disable redaction; do not commit `.received.nt` files; keep the `-parameters` compiler flag; run everything in the foreground and read the output before you report; if you cannot fetch URLs, say so and I will paste llms.txt.
Set up NarrativeTrace in this project and show me its first trace.
1. Read https://narrativetrace.ai/dotnet/llms.txt first. It carries the install block and the known traps. Do not guess versions or artifact names.
2. If this directory has no project yet, create the smallest console app that llms.txt's "Install and first trace" block describes. Otherwise work inside the existing project and trace one real service boundary.
3. Install the CLI the way llms.txt shows, then run `dotnet tool run dotnet-narrativetrace init --dry-run` and show me the diff. It installs the NarrativeTrace agent skills into this project and adds a marked section to AGENTS.md. Run it for real only after I have seen the diff.
4. If the `add-narrative-tracing` skill is now available, follow it. Otherwise follow the "Install and first trace (copy this)" block in llms.txt.
5. Add one test that traces a call with a deny-listed parameter and asserts the trace shows `[REDACTED]` for it.
6. Run the program, then run the doctor (`dotnet tool run dotnet-narrativetrace doctor`). Paste the trace and the doctor report, explain the trace in two sentences, and list exactly what changed in the project.
Rules: never disable redaction; do not commit `.received.nt` files; run everything in the foreground and read the output before you report; if you cannot fetch URLs, say so and I will paste llms.txt.
Set up NarrativeTrace in this project and show me its first trace.
1. Read https://narrativetrace.ai/python/llms.txt first. It carries the install block and the known traps. Do not guess versions or artifact names.
2. If this directory has no project yet, create the smallest console app that llms.txt's "Install and first trace" block describes. Otherwise work inside the existing project and trace one real service boundary.
3. Add the `narrativetrace` package the way llms.txt shows, then run `uv run narrativetrace init --dry-run` and show me the diff. It installs the NarrativeTrace agent skills into this project and adds a marked section to AGENTS.md. Run it for real only after I have seen the diff.
4. If the `add-narrative-tracing` skill is now available, follow it. Otherwise follow the "Install and first trace (copy this)" block in llms.txt.
5. Add one test that traces a call with a deny-listed parameter and asserts the trace shows `[REDACTED]` for it.
6. Run the program, then run the doctor (`uv run narrativetrace doctor`). Paste the trace and the doctor report, explain the trace in two sentences, and list exactly what changed in the project.
Rules: never disable redaction; do not commit `.received.nt` files; apply `@not_traced` to the real parameter, because importing it protects nothing; run everything in the foreground and read the output before you report; if you cannot fetch URLs, say so and I will paste llms.txt.
AI-first demo: build a two-service app from nothing
Read https://narrativetrace.ai/typescript/llms.txt
Create a TypeScript console app in a new folder. Keep all application code in main.ts: two service classes, OrderService calling InventoryService, and the entry point. Use NarrativeTrace to capture both services without handwritten log statements.
Set module and moduleResolution to NodeNext. Install @types/node and set types to ["node"]. Typecheck before running.
Create demo.sh (macOS/Linux) and demo.ps1 (Windows) launchers that work from any directory, install project dependencies if needed, and print Markdown traces to the terminal.
Run the launcher supported on this machine for one successful order and one out-of-stock failure. Show the actual generated traces, briefly explain what happened, and give me both launch commands.
Read https://narrativetrace.ai/java/llms.txt
Create a deliberately small Java console app in a new folder, with no package directory. Keep the example in one Main.java file: a public Main entry point and tiny package-private service types (or nested types) with strings and primitives only. Do not add DTO, result, or item classes. Use NarrativeTrace to capture OrderService calling InventoryService, without handwritten log statements.
Use the repository's documented build setup. If there is no Gradle wrapper, initialize one before running. Keep the example compact and explain any unavoidable Java boilerplate; the first run may download the build tool and dependencies.
Create demo.sh (macOS/Linux) and demo.ps1 (Windows) launchers that work from any directory, install project dependencies if needed, and print Markdown traces to the terminal.
Run the launcher supported on this machine for one successful order and one out-of-stock failure. Show the actual Markdown traces, briefly explain what happened, and give me both launch commands. Test the other launcher if the platform supports it; otherwise review it and say that it was not executed.
Read https://narrativetrace.ai/dotnet/llms.txt
Create a C#/.NET app in a new folder using dotnet new console; keep its generated project settings. In Program.cs, put top-level calls before OrderService, InventoryService, and their interfaces. OrderService calls InventoryService; use strings and primitives for inputs and results. Capture both services with NarrativeTrace, without handwritten log statements.
Create demo.sh (macOS/Linux) and demo.ps1 (Windows) launchers that work from any directory, restore dependencies if needed, and print Markdown traces to the terminal. Show restore output and stop on setup or execution errors.
Run the launcher supported on this machine for one successful order and one out-of-stock failure. Show the actual generated traces, briefly explain what happened, and give me both launch commands. State which launchers were executed and which were only reviewed.
Read https://narrativetrace.ai/python/llms.txt
Create a Python console app in a new folder. Keep all application code in main.py: two service classes, OrderService calling InventoryService, and the entry point. Use NarrativeTrace to capture both services without handwritten log statements.
Create demo.sh (macOS/Linux) and demo.ps1 (Windows) launchers that work from any directory, install project dependencies if needed, and print Markdown traces to the terminal.
Run the launcher supported on this machine for one successful order and one out-of-stock failure. Show the actual generated traces, briefly explain what happened, and give me both launch commands.
How it works
Two steps to your first narrative
Wrap a service — or decorate
const context = new AsyncNarrativeContext(
new NarrativeTraceConfig());
const service = traceObject(orderService, context, {
placeOrder: ["customerId", "productId", "quantity"],
});
// or on the class itself:
@traced("customerId", "productId", "quantity")
placeOrder(cId: string, pId: string, qty: number) {}
Read the story
service.placeOrder("C-1234", "SKU-KB", 2);
console.log(
renderIndentedText(context.captureTrace()));
// Vitest: every test writes
// its own trace file.
All @narrativetrace/* packages are live on
npm at the current release.
Node 20+, TypeScript 5.0+ for the decorator form.
Wrap a service — or auto-wrap
var context = new ThreadLocalNarrativeContext();
var service = NarrativeTraceProxy.trace(
orderService, OrderService.class, context);
// or for Spring:
@EnableNarrativeTrace(
basePackages = "com.example.app")
Read the story
service.placeOrder("C-1234", "SKU-KB", 2);
System.out.println(
new IndentedTextRenderer()
.render(context.captureTrace()));
// JUnit 5/4: every test writes
// its own trace file.
All 17 artifacts are on
Maven Central
under ai.narrativetrace at the current release; the plugin id is
ai.narrativetrace. Java 17+, Gradle 8+.
Wrap a service — or auto-wrap
var context = new SyncNarrativeContext(
new NarrativeTraceConfig(TracingLevel.Detail));
var service = NarrativeTraceProxy
.Create<IOrderService>(new OrderService(), context);
// or for the DI container:
services.AddNarrativeTracing(options => options
.Namespaces("MyApp.Services", "MyApp.Domain"));
Read the story
service.PlaceOrder("C-1234", "SKU-KB", 2);
Console.WriteLine(
IndentedTextRenderer.Render(
context.CaptureTrace()));
// xUnit / NUnit: every test writes
// its own trace file.
Every package is live on
nuget.org at the
current release. Targets net10.0 and netstandard2.0, with
NarrativeTrace.Legacy reaching back to .NET Framework 4.8 — and no
compiler flag, because .NET keeps parameter names in metadata already.
Wrap a service — or narrate it
context = ContextVarNarrativeContext()
service = trace_object(OrderService(), context)
# or narrate a method by hand:
@narrated("Placing order of {quantity} {product_id} for customer {customer_id}")
def place_order(self, customer_id, product_id, quantity): ...
Read the story
service.place_order("C-1234", "SKU-KB", 2)
print(MarkdownRenderer().render(context.capture_trace()))
# pytest: every test writes
# its own trace file.
All 8 packages are on PyPI at the current release —
uv add narrativetrace. Python 3.12+;
@on_error stacks the same way for failure narration.
The safety net for AI refactors
Tests check the values.
Approval traces check the calls that produced them.
After every run, NarrativeTrace compares each scenario's call structure with the last green run and says what changed — in one line. Turn on approval mode and an unapproved change (a new call, a dropped call, a different outcome) fails the build with a readable diff until a human approves it. The tests were green; the behavior still changed. Now you know. An additional check beside your tests, not a replacement.
NarrativeTrace — Suite complete
5 scenarios recorded
Clarity: 100% high | 0% moderate | 0% low
Reports: build/narrativetrace
Since last green: 4 scenarios unchanged · 1 changed:
"Customer places order" (−1 call Customers.verifyGoodStanding)
Narrative changed against the approved baseline
src/test/narratives/OrderServiceTest/customer_places_order.approved.nt
- OrderService.placeOrder(customerId, qty) → value
- - Customers.verifyGoodStanding(customerId) → value
- PricingService.calculate(qty) → value
- PaymentService.charge(card) → value
- OrderRepository.save(order) → value
review the .received.nt, then: ./gradlew approveNarratives
One line after every run
No setup: the .nt file each test already writes is the last-green
baseline, so a failing test prints only what changed.
Approve behavior, not just code
A refactor — yours or your agent's — that changes call structure fails
the build until someone reviews the diff and runs approveNarratives.
Value-free by design
Baselines hold names and structure only, so they're safe to commit and safe to hand to an AI agent as the spec of record.
How approval mode works
Where it stands per runtime, because a build gate is a promise: TypeScript, Java, .NET
and Python all ship approval traces; the value-free .nt structural
artifact ships in TypeScript, Java, .NET and Python today.
The questions before you ship
What a production evaluation actually asks
These are the three questions an integrations engineer asked while evaluating NarrativeTrace, before naming what would stop them shipping to production. We would rather answer them here, with numbers and named limits, than let an evaluator find the gaps alone.
How much does this cost in performance and memory under high concurrency?
We will not claim “zero overhead” in any runtime — tracing does work, and work costs something. What NarrativeTrace itself adds is capture: intercepting the call, reading arguments, building the trace tree. Everything after that — the write to your existing sink, the collector, the disk or network — is the same cost your logging stack already pays; NarrativeTrace does not add a second sink. For a team replacing hand-written log statements, the sink side is close to a wash: several log writes per method become one trace write, and those statements stop being written and maintained.
level:'off' · measured 2026-09-07Under concurrency, the durable path (a synchronous listener into your existing logger) writes inline — as crash-safe as the log call it replaces. The best-effort analysis path is a bounded ring buffer that sheds under sustained load instead of blocking the caller, and every runtime counts what it shed and reports the count, rather than losing it silently.
The honest gap: no NarrativeTrace runtime ships sampling today. A percentage- or rate-based sampler is planned, not built. If you need to cap capture volume now, narrow the traced scope or drop to a lower tracing level for the hot path.
How do I know a parameter with PII or credentials won't leak into a trace?
Four independent layers, not one blanket promise. 1. Explicit
redaction — @NotTraced / [NotTraced] /
@not_traced on a parameter, field or property; it always wins, even
under configuration that disables the other layers. 2. An
always-on, multilingual name deny-list — matched on identifier names
(password, token, ssn, plus Spanish,
Portuguese, French and Chinese equivalents), on by default in every runtime, never
opt-in. 3. Value-shape matching, independent of the field name
— a JWT-shaped string, a Luhn-valid card number, or a national-ID checksum is
caught even under an innocuous name like data. 4. The
value-free .nt structural mode — the categorical guarantee. No
runtime value reaches it at all, so there is nothing for a filter to miss.
Be precise about the boundary: name and shape matching (layers 1–3) are heuristic and extensible — they widen as gaps are found, and can miss a name or shape nobody has added yet. The structural mode (layer 4) is the only categorical answer. If your threat model requires that no value can possibly leave the process, that is what to reach for, in every runtime.
Can this cross with a standard correlation ID when a flow spans several services, or is it local only?
Yes, in every runtime, through the same mechanism OpenTelemetry uses: W3C
traceparent. An inbound header is adopted and NarrativeTrace's own
trace id becomes that header's trace id directly — not a separate identifier
merely shaped to match — and an outbound call stamps the current trace id on
its way out. Every runtime also exports NarrativeTrace spans to OpenTelemetry, so an
existing collector, Jaeger, or correlation-id middleware understands the id with
nothing to reconcile.
What stays local everywhere: the narrative tree itself — the nested calls, arguments and narration — is captured per process and is never shipped to another service; only the trace id crosses the boundary. A downstream service produces its own tree correlated to that id, not one merged cross-service tree.
Before your architect asks
Fits the stack you have. Says so in writing.
ILogger, pino and winston
OpenTelemetrynarratives as spans, in every runtime
~1.9 µsper traced call on the JVM, measured; ~40 ns inactive
Redaction@NotTraced plus name-based deny-lists, on by default
Value separationthe architecture behind the .nt trace
Integrations
Meets your stack where it is
ILogger ShippedEvery runtime
One architecture, every runtime
Narrative capture, dual-consumer output and clarity diagnostics: one
architecture, written natively in each language against one shared output specification.
A trace reads the same whichever runtime produced it — which is why the
value-free .nt artifact travels between them.
narrativetrace-typescript
·
narrativetrace-java
·
narrativetrace-dotnet
·
narrativetrace-python.
All @narrativetrace/* packages are live on npm at the current release.
All narrativetrace packages are live on PyPI at the current release. Swift is in
development.
Three recent studies
AI agents log less than humans, and mostly don’t comply when asked to.
Agents don’t log.
In 58.4% of 81 repositories, agents touched logging in a smaller share of their pull requests than humans did; asked outright by a reviewer to add logging, they failed 67% of the time. — Do AI Coding Agents Log Like Humans?, 2026
Raw traces don’t explain.
In a small user study, engineers found the root cause from raw agent traces 42% of the time, versus 89% with a structured explanation, and took nearly three times longer. — XAI for Coding Agent Failures, 2026
Faithful explanations come from structure, not from the model.
A trace-grounded pipeline produced zero failed explanations across 548 traces; free-form LLM narration failed 18% of the time. Every claim has to point at something that happened. — Explaining AI Agents Through Execution Traces, Sapienza University of Rome, 2026
NarrativeTrace applies the third finding to the code the agent ships: the .nt structural trace the agent reads is derived from structure and never contains a runtime value; the log lines a human reads do. The rest of the
numbers, and the answer, are below.
Built for AI-assisted development
Your agent can read the code.
Now it can watch it run — safely.
A coding agent that debugs from source alone guesses at runtime behavior, and guesses wrong. NarrativeTrace hands it ground truth — the real call tree — in the shape a model reads best, at a fraction of the tokens, with the data it must never see already gone.
Fewer tokens, more signal
Log statements are noise tokens. Deleting them makes a service class 15–30% cheaper for an agent to read — and every line of a trace carries signal, because it was generated from names, not prose. Repeated values dedupe to a reference; repeated loops fold to one line.
Markdown, made for the context window
Every test writes its narrative as a .md file — a nested list of calls,
arguments and results that any agent already knows how to read. Paste it, attach it, or
point the agent at the directory your CI already fills. Prose, JSON and sequence diagrams
come from the same trace.
No runtime values, so nothing to inject
Every test also writes a .nt structural trace: the call tree with every
runtime value stripped. No user input reaches it, so no user input can steer the model
that reads it. Zero PII, a fraction of the tokens, safe to commit. The protection is specific: the structural trace carries no runtime value, so no runtime value can carry an instruction.
What comes next Planned
Pseudonymized output: real values replaced by synthetic tokens, so the agent still sees that the same customer flows through three calls without ever seeing who. MCP server: Claude Code, Cursor and Copilot query traces and dependency graphs directly, read-only, structure-only by default.
Preview the MCP designThis site is agent-friendly too: point your assistant at llms.txt, which routes it to your runtime's own llms file, and it can adopt NarrativeTrace for you.
What an agent gets
Four things an agent actually reaches for
llms.txt to read
A structured index built for agents, not search engines — this site's own, and every runtime ships one in its own repository.
Java's llms.txt.nt traces to diff
The call tree with every runtime value stripped — small, structural, and safe for an agent to read and diff.
Structural trace formatApproval traces as a gate
A behavior change becomes a build failure with a readable diff, not a silent surprise, until a human approves it.
How approval mode worksA skill that wires it in
add-narrative-tracing installs the library and gets you to a first
trace; narrativetrace-doctor diagnoses a project that already has it
— both run as skills in Claude Code and Codex.
Self-reported, not benchmarked
What AI agents are saying
The trace of a failed payment made missing compensation immediately visible, and approval testing caught an extra call that ordinary value assertions accepted.
…the release call is present, generated from a real run, not asserted from inside the process being tested.
The always-on name-based deny-list is a genuinely nice piece of design: naming a parameter paymentToken gets you redaction for free, and I could prove it in a test.
Wrapping a service interface with NarrativeTraceProxy.trace() automatically captures all calls, parameters, return values, and durations without any code changes to the service itself. This is elegant.
…the clarity report told meClaimProcessor.processscored 0.10 for a “generic verb” the very first time I ran the suite, and it was right — I renamed it toadjudicate… and the suite average from ~0.78 to ~0.81.
Yes, for agent-driven development of Java applications with meaningful service boundaries. Getting useful traces required little configuration, and the combination of values, exception paths, and structural approvals gave me evidence I could act on.
The rendered Markdown trace was legitimately useful while building this, not just as a deliverable … which is how I caught that my first draft of scenario 6’s numbers didn’t actually trigger InsufficientCoverageError … well before any test told me.
That’s not a library gap so much as a library doing exactly its job: it rendered what was actually being passed around, and what was actually being passed around was more than any of those services needed.
We asked AI agents to build a Java service with NarrativeTrace and write up the experience for another engineer — honestly, naming what was awkward. These are their words, unedited, with the model named. It is not a benchmark: one run each, and we wrote the library. The task and the prompt are under the button, so the next report can be your agent's.
Try it yourself
This is the exercise those reports came from. One difference: our runs handed the agent a copy of the documentation; yours points it at the public repository. Two files, three steps.
- Save the specification as
TASK.mdin an empty directory. - Start your agent in that directory and give it the prompt.
- Read its
FINDINGS.md— and if you post it in the Discussions of the runtime you used (Java · TypeScript · .NET · Python), we will read it too.
The prompt
You are building a Java application from a written specification.
The specification is in `TASK.md` in the current directory. Read it, then build the
application it describes. Build it properly: it should compile, its tests should
pass, and the demo should run.
A library called **NarrativeTrace** is available to you for this project. Its
documentation is in its public repository:
- https://github.com/narrativetrace/narrativetrace-java — start with the README
- https://github.com/narrativetrace/narrativetrace-java/blob/main/documentation/llms.txt
— a short orientation written for AI agents
- https://narrativetrace.ai/java/llms-full.txt — the full documentation written for AI agents
Read that documentation first, then use the library in the application you build.
Its artifacts are published on Maven Central under the group `ai.narrativetrace`;
the Gradle plugin id is `ai.narrativetrace`. Take the version from the install
block in llms.txt; the documentation explains which artifacts you need and how to
wire them up.
Rules for this exercise:
- Work **only** inside the current directory. Do not read, search, or modify
anything outside it, and do not clone the library's source code — the
documentation is what you have.
- You may use the network to read that documentation and to resolve build
dependencies.
- You choose the design, the libraries, the project layout, and the testing
approach. There is no house style to match and no reviewer to please.
- Work in the foreground. Do not background the build or the tests and then end
your turn — run them, wait for them, and read the output.
- When the build and tests are green and the demo runs, write `FINDINGS.md` as
the specification describes. In addition to what the specification asks for,
cover what using NarrativeTrace was actually like: what the documentation got
right or left you guessing about, what integration cost you, and whether the
output it produced was of any use to you while you worked.
Finish by reporting, in your final message: whether the build and tests are
green, what you built, and where things stand.
The specification — TASK.md
# Build task — Claims intake service (Java 17)
Build a small, self-contained Java 17 application called **ClaimFlow** that processes
insurance claims through a chain of collaborating services. It must build and test with
Gradle, and run as an executable demo.
This is a business-logic exercise, not an infrastructure one: everything is in-process
and in-memory. No database, no HTTP server, no external service, no network access.
## The domain
A claim is submitted and passes through five collaborating services. Each is its own
interface with its own implementation — do not collapse them into one class.
1. **PolicyService** — looks up a policy by `policyId`. A policy has a holder name, a
coverage limit in whole cents, a deductible in whole cents, and an active flag.
2. **ClaimValidator** — rejects a claim that names an unknown policy, an inactive
policy, a non-positive amount, or an incident date in the future.
3. **FraudScreen** — scores a claim from 0–100. Any claim scoring above 70 is referred
rather than paid. Scoring may be simple and deterministic, but it must consider at
least the claim amount relative to the coverage limit, and whether the same policy
has had a prior claim in this run.
4. **ReserveLedger** — reserves funds against a policy's remaining coverage before
payment, and can release a reservation. Reserving more than the remaining coverage
fails.
5. **PayoutService** — pays an approved claim, given a payment token. Payment can fail;
your demo must exercise at least one failing payment.
A **ClaimProcessor** orchestrates them and returns a `ClaimOutcome` describing what
happened: paid (with amount and transaction id), referred, or rejected (with a reason).
## Required behaviour
- Payout amount is `min(claimAmount, coverageLimit) - deductible`, never below zero.
- A claim must be validated, then screened, then reserved, then paid — in that order.
- **If payment fails after a reservation was made, the reservation must be released.**
- A referred claim is never paid and never reserves funds.
- Two claims against the same policy must respect the remaining coverage.
- The payment token is a credential and must never appear in any output your program
produces.
- The claim holder's name is personal data; treat it accordingly in whatever your
program writes out.
## Deliverables
1. A Gradle project that compiles under Java 17 and runs `./gradlew test` green.
2. Unit tests covering the behaviour above, including the failure paths.
3. A runnable demo (`./gradlew run` or a `main`) that exercises at least five scenarios:
a straightforward paid claim, a claim above the coverage limit, a referred (high
fraud score) claim, a rejected claim, and a claim whose payment fails after a
reservation.
4. A short `FINDINGS.md` in the project root — see "What to report" below.
## What to report in FINDINGS.md
Write it for another engineer, not for us. Cover:
- What you built and how you verified it works.
- Anything that surprised you, went wrong, or took longer than expected.
- How you satisfied yourself that the compensation rule (release the reservation when
payment fails) actually holds — what evidence do you have?
- How you satisfied yourself that the payment token never leaks into output.
- What you would want before running this in production.
- Anything about the tools or libraries you used that helped or got in your way.
Be honest and specific. A report that says everything went perfectly is less useful
than one that names what was awkward.
For humans
- Prose that reads like a bug report, in your team's language
- Markdown and a sequence diagram written by every test
- One line after every run: what changed since the last green build
For AI agents
- 15–30% fewer tokens than the code with its log statements in
- Markdown a context window was made for — runtime truth, not a guess
- Value-free
.nt: no runtime value reaches it, so nothing to inject through
For legacy modernization
Migrate the system nobody understands — with receipts.
AI agents make rewrites cheap; verifying them is the expensive part. Wrap a legacy system in traces and you get its real runtime behavior before you change it, and a structural comparison of the calls after.
X-ray the architecture you actually have
On the JVM, attach the agent to an unmodified app with one -javaagent
flag, or run the JUnit 4 suite you already have. On .NET,
NarrativeTrace.Legacy reaches back to .NET Framework 4.8. No source
changes, no documentation archaeology.
Show the call structure didn't change
Pin every scenario in a value-free baseline before you touch anything. Then compare whole trace sets before and after a port or AI rewrite, each divergence classified by risk. A structural comparison — the same calls, in the same order, with the same outcomes — not a proof that every value is equal.
Migration diffsFind the code that reads worst
If the trace doesn't read well, the code is lying about itself. Clarity scoring grades naming from the runtime narrative and explains every score. Point your refactoring, or your agent's, at the worst corners first.
Clarity diagnosticsAlso included: a domain glossary harvested from real runs, and the same trace rendered as prose in Chinese, Spanish or Portuguese. Glossary & translated traces →
Your logs have new readers.
They don't sleep, and they don't skim.
Support, on-call, audit, security: each is becoming an agent with a human supervising. They all need the same thing, a complete, readable, PII-free record of what the code actually did, and nobody has to write it by hand.
Same trace, five readers
Support
The one that answers the ticket
Reads the request's story and tells the customer what happened. Why: the whole path is there in plain language, and no engineer was paged.
Output formatsOn-call
The one that decides whether to wake you
Sees where it failed and how long each step took, and judges severity. Why: the failing step and everything above it are always captured.
Capture levelsAudit
The one that assembles the evidence pack
Pulls the annotated actions, each tagged with its action code, into a tamper-evident chain — evidence both auditors and agents can read. Why: the evidence was recorded as the code ran, not reconstructed afterwards.
Audit & SecOps eventsSecurity
The one that checks the control ran
Confirms the control fired and that sensitive values never left redaction. Why: the record holds the structure of what happened, with the values stripped before anything is written.
How separation worksThe coding agent
The one that rewrote your service last night
Diffs the story before and after, and fails the build if it changed. Why: approval traces turn the story into a gate.
How approval mode worksBring your own agent: Claude, Codex, Bits, Seer, or a person at 3 a.m. NarrativeTrace ships OpenTelemetry, so the same story lands in Datadog, Sentry or Honeycomb with the trace attached.
Editions
What is in each edition
The runtime is free and source-available under BSL 1.1 and becomes Apache 2.0 four years after each release; the API and output format are Apache 2.0. Pro and Enterprise are commercial.
Free Available now
Free to use in production, source-available. BSL 1.1; each release converts to Apache 2.0 after four years. API and output format Apache 2.0. Licensing →
- Zero runtime dependencies in the core
- Automatic tracing: proxy wrapping, narration decorators/attributes and HTTP middleware in every runtime; DI-container auto-wrap (Java, .NET, NestJS) and a zero-code bytecode agent (Java)
- All five capture levels, runtime-switchable
- Narration, error-context and no-trace markers on methods and fields — each runtime's own naming convention — plus automatic redaction
- Per-test trace files from JUnit 5/4, xUnit, NUnit, Vitest and pytest: Markdown, JSON, prose, sequence diagrams
- Clarity scoring, the clarity CLI, a CI clarity gate & a bridge into your logger
- Value-free
.nttraces (TypeScript, Java, .NET, Python); approval mode (TypeScript, Java, .NET, Python) - Domain glossary, translated traces, OpenTelemetry
Pro Early access
Cross-trace analytics, AI integration, and compliance for teams.
- Flow summaries & path frequency analysis
- Migration diffs with risk classification
- Runtime dependency graphs
- Audit & SecOps events with policy engine (Java)
- MCP server & tiered AI output for coding agents — coming soon
- Approval governance: semantic diffs, PR review bots, signed baselines — planned
- Clarity trending, AI-assisted renaming, dead-code detection — planned
Enterprise Coming soon
Hosted backend for narratives from every service, environment, and run.
- Managed OTLP ingest for traces
- Multi-tenant storage & search
- Team dashboards & retention policies
- Org-wide clarity & audit reporting
The free runtime ships on
Maven Central under
ai.narrativetrace, on
nuget.org as
NarrativeTrace.*, and as source on
GitHub. The licence terms are on the
licensing page.
Give your code a voice — and your agent its ears.
Sixty seconds to your first trace. No log statements were written in the making of this library.