Skip to content

protean docs

The protean docs command group has two subcommands:

  • protean docs generate renders architecture documentation from a domain or an IR JSON file.
  • protean docs preview starts a live preview server for Protean documentation.

protean docs generate

Generate architecture diagrams and catalogs from a domain's Intermediate Representation.

# From a live domain
protean docs generate --domain=my_app --type=events

# From an IR JSON file
protean docs generate --ir=domain-ir.json --type=event-model

Options

  • --domain, -d: Path to the domain module. Mutually exclusive with --ir.
  • --ir: Path to an IR JSON file. Mutually exclusive with --domain.
  • --type, -t: What to generate. One of:
    • clusters: aggregate cluster class diagrams.
    • events: command-to-event flow diagrams plus downstream consumers.
    • handlers: handler wiring diagrams.
    • catalog: an event and command catalog (Markdown tables).
    • event-model: the EventModeling slice timeline (see below).
    • llms: a versioned llms.txt context pack (see below).
    • agents: a versioned AGENTS.md negative-constraint pack (see below).
    • all (default): every section except event-model, llms, and agents.
  • --format, -f: markdown (fenced code blocks, the default) or mermaid (raw diagram source). mermaid is not supported for catalog, llms, or agents.
  • --output, -o: Write to a file instead of stdout.
  • --cluster: Filter to a single cluster FQN (only with --type=clusters or --type=all).
  • --annotations: Path to an annotations TOML file (only with --type=event-model). Defaults to .protean/annotations.toml when present. See Annotating the event model.

The event model slice timeline

--type=event-model renders the domain as an EventModeling slice timeline. Each aggregate cluster becomes one slice that reads left to right:

  1. Command(s) that trigger the aggregate.
  2. Aggregate (state) that decides and holds state.
  3. Event(s) the aggregate raises (fact events are omitted).
  4. Read models and automations that consume those events. Projectors are read models, drawn as cylinders. Event handlers and process managers are automations, drawn as hexagons, so a read model and an automation are told apart at a glance. A process manager also shows its start/end lifecycle, both in its node label and on the edge from the event.

Consumers are matched to a slice's events by the event type, so the whole diagram is derived from the IR alone.

In Markdown output each slice leads with a structural Given-When-Then before the diagram:

  • Given the aggregate the slice is about.
  • When the cluster's commands (the triggers).
  • Then the cluster's non-fact events (the results).

The GWT is derived structurally from the IR. It is slice-level: a slice with several commands or events lists them all on the When and Then lines rather than pairing each command with the event it produces. Commands-only or events-only slices drop the line they have nothing for. This GWT is a Markdown-only lead; the --format=mermaid output stays a bare flowchart.

# One combined flowchart of all slices
protean docs generate --domain=my_app --type=event-model --format=mermaid

# One GWT-led diagram per slice, as Markdown
protean docs generate --domain=my_app --type=event-model

Unlike the other diagram types, event-model is its own view and is not included in --type=all.

The llms.txt context pack

--type=llms renders a versioned context pack in the llms.txt convention, aimed at an LLM agent working on a Protean project. It has two layers:

  • Framework layer (always present): an H1 naming Protean and the installed version, a one-line summary, and a "Core documentation" section of links to the published docs site for the core areas: aggregate clusters, events, command and event handlers, projections, and event evolution.
  • Project overlay (present only with a source): a section derived from the IR listing the project's own aggregate clusters (with their commands, events, and handlers) and its projections.

--type=llms is the one type that runs with no --domain and no --ir: with no source it emits the framework layer alone; with a source it adds the project overlay. Like event-model, it is its own view and is not part of --type=all.

The output is byte-stable: for a given Protean version and project structure, generating twice yields identical bytes. The overlay reads only structural parts of the IR, never volatile fields like the build timestamp, so it does not churn between runs.

# Framework layer only (no source needed)
protean docs generate --type=llms

# Framework layer plus the project overlay
protean docs generate --domain=my_app --type=llms --output=llms.txt

The AGENTS.md constraint pack

--type=agents renders a versioned AGENTS.md negative-constraint pack: the hard rules an agent working on a Protean project must not break. It has one rule for every error-level diagnostic code Protean can raise. Each rule is a prohibition built from that code's registered meaning, rationale, and fix, and it ends with the code so a reader can trace it back:

- **Do not** write code that causes this error: <meaning> <rationale> To
  comply: <fix> (`CODE`)

Beyond the fixed wording in that template (the lead-in, To comply:, and the code in backticks at the end), the generator adds nothing: it drops the three registry fields in as they are written, joined with single spaces. The rule reads as full sentences because each registry field already ends with a period.

Only error-level codes appear: advisory warning and info codes are style and design nudges, not hard rules, so they are excluded. The set of rules is coupled to the live registry, so adding or removing an error code changes the file.

--type=agents needs no --domain and no --ir: it derives only from the registry and the installed Protean version, so it runs with no source. Like llms and event-model, it is its own view and is not part of --type=all.

The output is byte-stable: for a given Protean version it renders identical bytes, because the header is version-stamped, the registry traversal is sorted, and nothing reads a timestamp.

A project generated with protean new already ships this file as AGENTS.md at its root, kept byte-identical to this command's output. Regenerate it after upgrading Protean:

# Print the constraint pack
protean docs generate --type=agents

# Refresh the project's AGENTS.md
protean docs generate --type=agents --output=AGENTS.md

Annotating the event model

The event model carries structure, not the business context a person adds: why a slice exists, the rule behind it, which team owns it. Those notes live outside the generated output, in .protean/annotations.toml, and merge back in on render. Keeping them out of the diagram means the model stays disposable and the notes stay under version control next to the code they describe. The file's shape is recorded in ADR-0032.

The file has a top-level [annotations] map keyed by element FQN. Each entry carries a required note (free text, the business context) and an optional owner (the team or person accountable). The FQN is the value protean.utils.fqn computes, module.QualifiedName; in a project generated from the canonical layout, an Order aggregate in src/myproj/example/aggregate.py has the FQN myproj.example.aggregate.Order. The FQN must be quoted, because TOML reads its dots as table separators otherwise:

[annotations."myproj.example.aggregate.Order"]
note = """
Orders are the fulfillment boundary. An order cannot ship until payment
clears, so PaymentConfirmed gates the shipment slice.
"""
owner = "Fulfillment"

On render, each note merges into every slice that draws the element it keys. A note on an aggregate, a command, an event, or a consumer (a projector, event handler, or process manager) shows after that slice's Given-When-Then and before its diagram. A consumer drawn in two slices shows its note in both. Because the key is the element's FQN, a note stays attached across a content change (adding a field, reordering elements, regenerating the model) and breaks on an identity change (renaming or moving the element), which changes the FQN.

A key that matches no drawn element is listed in an "Unmatched annotations" section at the end of the render, so a note orphaned by a rename or a typo stays visible and gets re-keyed. A fact event is filtered from the event model, so it draws no node; a note keyed to one is reported unmatched.

--annotations <path> reads the file from a non-default location. An explicit path that does not exist is an error, as is a malformed file: either aborts the command before any output is written. With no annotations file present, the default path absent and no --annotations given, the render is exactly what it is without the feature.

# Merge notes from the default .protean/annotations.toml
protean docs generate --domain=my_app --type=event-model

# Read notes from a non-default path
protean docs generate --domain=my_app --type=event-model \
    --annotations=docs/event-model-notes.toml

Notes are Markdown prose and cannot sit inside a raw flowchart, so --format=mermaid leaves the diagram body unchanged and appends only the unmatched-annotation report, the one piece that belongs at the end of the render.

protean docs preview

The protean docs preview command starts a live preview server for Protean documentation. This allows you to view changes in real-time as you edit.

Usage

protean docs preview [OPTIONS]

Options

  • --help: Shows the help message and exits.

Running a Preview Server

To start the live preview server for your project's documentation, run the command without any additional options:

protean docs preview

This will start a local server, usually accessible via a web browser at a URL such as http://localhost:8000. The exact URL will be displayed in your command line interface once the server is running:

INFO    -  Building documentation...
INFO    -  Cleaning site directory
INFO    -  Documentation built in 0.56 seconds
INFO    -  [09:45:08] Watching paths for changes: 'docs', 'mkdocs.yml'
INFO    -  [09:45:08] Serving on http://127.0.0.1:8000/