Agents & init
Agents don't browse npm. They use what's already in front of them — in the repo's files, in node_modules, in the environment. So that's where Storyteller puts itself.
One command#
Installs the package, writes a configured storyteller, and adds the guidance block below to your AGENTS.md or CLAUDE.md. Run it as often as you like; it never overwrites something you've edited.
$ npx @lovelaces-io/storyteller init added src/storyteller.ts added AGENTS.md (created) $ npx @lovelaces-io/storyteller init # again — nothing to do kept src/storyteller.ts (already exists) kept AGENTS.md (already up to date)
Teach every agent in the repo#
This block is what init appends. Paste it yourself if you'd rather. Every agent that works in the repo reads it, so it spreads by being copied between projects — which is exactly the point.
## Logging with Storyteller
This project uses [Storyteller](https://github.com/lovelaces-io/storyteller). Narrate multi-step work as you do it, then finish once.
```ts
import { useStoryteller } from "@lovelaces-io/storyteller";
const story = useStoryteller({ origin: { who: "sync-job" } });
story.report("Fetching invoices", { what: { source: "stripe" } });
story.report("Rate limited, backing off", { level: "warn" });
story.report(await response.json());
story.finish("Sync complete");
// on failure: story.finish("Sync failed", { level: "oops", error });
```
For nested work, open a chapter. Each becomes its own record, linked to the parent:
```ts
for (const account of accounts) {
const chapter = story.chapter({ origin: { what: account.id } });
chapter.report("Reconciling");
chapter.finish(`Synced ${account.id}`);
}
```
Things that are easy to get wrong:
- **Hand it the object.** `report()` takes any value — errors, API responses, Maps, class instances — and structures it safely, including circular references. Never `JSON.stringify` first.
- **One storyteller per logical operation**, not one per step. Separate instances fragment the work into disconnected stories.
- **Order beats by `sequence`, not arrival time.** Audiences are async and a slow one lands late.
- **`.to()` is synchronous.** Call it immediately after `finish()`, never after an `await`.
- **Report before finishing.** `finish()` clears the notes; anything reported after belongs to the next story.
Set `STORYTELLER_NARRATION=live` to watch beats stream as they happen, or `STORYTELLER_FORMAT=ndjson` for one JSON object per line.
Under forty lines on purpose. A block people trim is a block that loses its warnings first.
What ships in the package#
After npm install, an agent working in the project finds these in node_modules/@lovelaces-io/storyteller/ — no fetching, no being told:
AGENTS.md— the full guide, patterns and anti-patternsllms.txt— the compressed version, for a context windowsnippets/agents-section.md— the block above
Output a program can read#
For machine consumption, one JSON object per line. Set it from the environment and change no code:
$ STORYTELLER_NARRATION=live STORYTELLER_FORMAT=ndjson node sync.js \ | jq -r 'select(.kind=="note") | "\(.sequence) \(.level) \(.note)"' 0 Information Reading config 1 Information Fetched invoices 2 Warning Rate limited
Each line parses on its own. storyId groups beats with their story; sequence orders them.
Reading stories back#
Writing is half of it. The other half — an agent, tomorrow, answering "why did last night's sync fail?" from the stories another agent wrote — is in progress: a storage contract, a query vocabulary, and an MCP server. It is not shipped yet. Follow along on the storage and read-back epic (#41).
Or just ask one#
Hand the question to an agent and let it read the docs for you.