Skip to content

Giving an Agent Domain Context

A coding agent working on your project needs to know your domain: the aggregate clusters, the commands and events, the handlers, the projections. There are two ways to give it that knowledge. This guide explains both and tells you which to reach for first.

Use the MCP server first

The protean mcp server is the recommended way. It exposes Protean's operations as tools an agent calls: validate, check, introspect, explain, and scaffold. Every tool answers live from the installed domain, so the agent always reasons about the code that is actually there, at the version of Protean the project actually runs.

Because the answers come from the domain itself, there is no copy to regenerate and commit, and no refresh step in your build. One thing to know: the server imports your domain modules on the first call and Python holds them in memory for the life of that process, so a server that is already running keeps answering from the version it imported. Restart it after you change the source, and the next introspect reflects the change.

Register the server in a .mcp.json file and point your agent at it. The protean mcp reference covers installation, transports, and registration.

When a committed snapshot makes sense

Some agents do not speak MCP. For those, a committed llms.txt snapshot is a legitimate fallback. protean docs generate --type=llms --domain=my_app renders a context pack from the domain's Intermediate Representation: the aggregate clusters with their commands, events, and handlers, and the projections. You commit that file, and the agent reads it like any other file in the repository.

Know the trade-off before you choose this path. A committed file is a copy. It was true when you generated it, and it goes stale the moment the domain moves ahead of it. The MCP server does not have this problem, because it never holds a copy. So use the snapshot only when the agent cannot use MCP, and put a drift check in place so a stale file does not pass unnoticed.

Keeping the snapshot fresh

protean docs generate --type=llms --check is the drift check. It renders the snapshot fresh in memory, compares it byte for byte to the committed file, writes nothing, and exits non-zero when they differ. A missing file counts as drift. The exit codes match protean dx check: 0 when the file is up to date, 1 on drift.

Set the snapshot path and the domain once, so neither command repeats the flags:

# pyproject.toml
[tool.protean.docs]
domain_snapshot = { path = "llms.txt", domain = "src/my_app/domain.py" }

domain takes the same value --domain takes. A project made by protean new keeps the domain in src/<package>/domain.py and leaves the package's __init__.py empty, so name the file: domain = "my_app" there points at a package with no domain in it and the command stops with a load error.

Then regenerate with protean docs generate --type=llms and check with protean docs generate --type=llms --check.

The path is read relative to the file that declares it, so both commands name the same snapshot from every directory that finds the config. The command looks in the directory you run it from and its two parents, so keep the key in a config file at the project root and run from the root or at most two levels below it.

A pre-commit recipe

To fail a commit when the snapshot is stale, wire the check as a local pre-commit hook. This is a recipe you choose to add; nothing installs it for you.

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: protean-llms-snapshot
        name: llms.txt is up to date
        entry: protean docs generate --type=llms --check
        language: system
        pass_filenames: false

A CI recipe

To fail a build when the snapshot is stale, run the same check as a CI step. It exits non-zero on drift, so the job fails on its own.

# in your CI workflow
- name: Check llms.txt is up to date
  run: protean docs generate --type=llms --check

Neither recipe is installed by protean new or protean dx. You add the one you want, when you want it.