Chapters

Real work nests. An agent spawns subtasks; a batch runs one operation per item; a request crosses services. chapter() keeps each piece a complete story of its own, linked to its parent.

Opening one#

sync.ts
const story = new Storyteller({ origin: { who: "sync-agent" }, narration: "live" });

story.report("Starting sync");

for (const account of accounts) {
  const chapter = story.chapter({ origin: { what: account.id } });
  chapter.report("Fetching invoices");
  chapter.report("Reconciling");
  chapter.finish(`Synced ${account.id}`);
}

story.finish("Sync complete");

Each chapter emits its own record, carrying parentStoryId. It's not folded into the parent's beats — one record per story stays true, and a nested story is still a story.

What a chapter inherits#

parentStoryId is captured when the chapter is created, so a parent that finishes first doesn't orphan its chapters.

Rebuilding the run#

Follow parentStoryId and the whole run comes back as a tree. Nest as deep as the work does.

tree.ts
// Every chapter record carries parentStoryId. That is all you need to rebuild the run.
const byParent = groupBy(records, (r) => r.parentStoryId ?? "root");

// Sync complete       (1 beat)
//   Synced acct-1     (2 beats)
//   Synced acct-2     (2 beats)
//   Synced acct-3     (2 beats)

This is what turns a pile of records into a trace — and it's the precondition for asking "what happened during that run?" across more than one step.