Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pretorin Developer & Agent Docs

Beta — Pretorin is currently in closed beta. Framework and control browsing works for authenticated users. Platform write features (evidence, narratives, monitoring) require a beta code. Sign up for early access.

Pretorin gives developers and AI agents direct access to compliance data, implementation context, and evidence workflows. The primary surfaces are the CLI, the MCP server, and skill-driven agent workflows. It supports 26 compliance frameworks and profiles, including NIST 800-53, NIST 800-171, FedRAMP, and CMMC.

The CLI and MCP tooling in this repository are open source under Apache-2.0. Access to Pretorin-hosted platform services, APIs, and account-scoped data is authenticated and governed separately by the applicable platform terms.

Start Here

Choose the path that matches how you work:

  • CLI-first — Use the Pretorin CLI directly for framework browsing, evidence workflows, reviews, scans, and agent execution.
  • AI-agent-first — Connect pretorin mcp-serve to Claude Code, Codex CLI, Cursor, or another MCP-compatible tool.
  • Hosted agent runtime — Use pretorin agent run when you want Pretorin-managed model execution with built-in skills.

Start with Installation, Authentication, and Quick Start if this is your first time here.

Core Paths

Pretorin is usually used in one of these modes:

  1. Bring-your-own-agent mode — Run pretorin mcp-serve and connect the MCP server to your existing AI tool (Claude Code, Codex CLI, Cursor, Windsurf, etc.). Your agent gets compliance tools without changing your workflow. Pair with pretorin skill install to give your agent explicit guidance for using pretorin (see “Bundled skill” below).

  2. Pretorin-hosted agent mode — Run pretorin agent run to use Pretorin’s built-in agent runtime when you don’t have your own local agent. Pretorin manages the AI runtime; you supply prompts.

  3. Direct CLI mode — Use pretorin subcommands directly for browsing frameworks, managing context, authoring evidence, updating narratives, and running scans. No agent involved.

Important architectural detail. The vast majority of the CLI and the entire MCP server are thin wrappers over the platform API — no LLM runs in pretorin in those paths. When you use mode 1 (your own agent via MCP), your local agent does all the reasoning. Pretorin is the tool surface. The only place pretorin runs its own LLM is pretorin agent run (mode 2), provided as a fallback for users without a local agent.

Bundled skill (pretorin skill install)

pretorin skill install copies a bundled skill into ~/.claude/skills/pretorin/ (or ~/.codex/skills/pretorin/). The skill is markdown + scripts that tell your agent how to use pretorin’s MCP tools effectively — which to call first, how to scope by system + framework, how to handle evidence and narrative writes. Highly recommended when using mode 1.

What You Can Do

  • Browse compliance frameworks — Query controls, families, metadata, and AI guidance from authoritative sources
  • Manage implementation context — Set an active system and framework, then track progress across controls
  • Create and manage evidence — Generate local evidence files, push them to the platform, and link them to controls
  • Write implementation narratives — Draft and push auditor-ready narratives for each control
  • Run AI-powered compliance tasks — Use the built-in Codex agent with bundled skills (gap-analysis, narrative-generation, evidence-collection, security-review, stig-scan, cci-assessment)
  • Run compliance recipes — Author or invoke recipe playbooks (markdown + scripts) that the calling agent executes for evidence capture, baseline scanning, and other procedures
  • Review code against controls — Analyze your codebase for control coverage
  • Track monitoring events — Record security scans, access reviews, configuration changes, and compliance checks
  • Generate compliance artifacts — Produce structured JSON artifacts documenting control implementations
  • Browse STIGs and CCIs — Look up STIG benchmarks, rules, and trace CCIs through the full control hierarchy
  • Manage vendors — Track third-party vendors, link evidence to vendor assessments, and upload vendor documents
  • Complete policy and scope questionnaires — Answer org-policy and scope questions through a guided workflow with AI-assisted generation and review

Architecture

Pretorin CLI is three things, with one shared API client at the bottom:

┌──────────────────────────────────────────────────────────────────────┐
│                            Developer                                  │
│                                                                       │
│   ┌──────────┐       ┌──────────────────┐      ┌──────────────────┐  │
│   │ Terminal │       │ Local AI Agent   │      │ pretorin agent   │  │
│   │ (direct  │       │ (Claude Code,    │      │ run              │  │
│   │  pretorin│       │  Codex CLI,      │      │ (Pretorin's own  │  │
│   │  cmds)   │       │  Cursor, ...)    │      │  CodexAgent — for│  │
│   │          │       │                  │      │  users w/o local │  │
│   │          │       │ + bundled        │      │  agent)          │  │
│   │          │       │   pretorin       │      │                  │  │
│   │          │       │   skill          │      │                  │  │
│   │          │       │   (optional)     │      │                  │  │
│   └────┬─────┘       └────────┬─────────┘      └────────┬─────────┘  │
│        │                      │                          │            │
│        │                      │ stdio                    │            │
│        │             ┌────────┴─────────┐                │            │
│        │             │  MCP Server      │                │            │
│        │             │  pretorin        │                │            │
│        │             │  mcp-serve       │                │            │
│        │             │                  │                │            │
│        │             │  Tool surface    │                │            │
│        │             │  (no LLM here)   │                │            │
│        │             └────────┬─────────┘                │            │
│        │                      │                          │            │
│        └──────────────┬───────┴──────────┬───────────────┘            │
│                       │                  │                            │
│              ┌────────┴──────────────────┴─────────┐                  │
│              │  Pretorin API Client                │                  │
│              │  (shared — only place that talks    │                  │
│              │   to the platform)                  │                  │
│              └────────────────┬────────────────────┘                  │
└───────────────────────────────┼───────────────────────────────────────┘
                                │
                       ┌────────┴─────────┐
                       │  Pretorin        │
                       │  Platform API    │
                       └──────────────────┘

Three things, one client:

  1. Direct CLIpretorin <command> runs synchronously, talks to the platform via the shared client. No LLM.
  2. MCP serverpretorin mcp-serve exposes the same platform features as MCP tools. The local agent does all the reasoning; pretorin is the tool surface. No LLM runs in pretorin in this path.
  3. pretorin agent run — Pretorin’s own LLM, used when the user doesn’t have a local agent. Calls the same platform-API tools as the MCP server, just over a Python in-process boundary.

The bundled skill (pretorin skill install) is content delivered to the local agent’s skill directory; it’s not a fourth path, it’s a way to give path 2 better instructions.

Installation

Pretorin CLI requires Python 3.10 or later.

uv installs the CLI as an isolated tool with its own dependencies:

uv tool install pretorin

pip

pip install pretorin

pipx

pipx provides isolated installation similar to uv:

pipx install pretorin

Docker

A multi-stage Dockerfile is included in the repository. The production target builds an image that runs the pretorin CLI as its entrypoint:

git clone https://github.com/pretorin-ai/pretorin-cli.git
cd pretorin-cli
docker build --target production -t pretorin .
docker run --rm pretorin --help

Mount your config directory to persist credentials between runs:

docker run --rm -v "$HOME/.pretorin:/home/pretorin/.pretorin" pretorin frameworks list

The included docker-compose.yml defines test, test-coverage, lint, typecheck, and integration services for contributors. Each is invoked with docker compose run --rm <service>, not docker compose up. See Contributing for development workflows.

Verify Installation

pretorin version

Expected output:

pretorin version 0.22.1

Updating

Check for and install the latest version:

pretorin update

The CLI also checks for updates automatically on startup and notifies you when a new version is available. To disable passive update notifications:

export PRETORIN_DISABLE_UPDATE_CHECK=1
# or
pretorin config set disable_update_check true

Development Installation

For contributing to Pretorin CLI:

git clone https://github.com/pretorin-ai/pretorin-cli.git
cd pretorin-cli
uv pip install -e ".[dev]"

This installs the package in editable mode with development dependencies (pytest, ruff, mypy, etc.).

Authentication

Getting an API Key

Get your API key from platform.pretorin.com.

Beta Note: Framework and control browsing works for authenticated users. Platform write features (evidence, narratives, monitoring) require a beta code. Systems can only be created on the platform, not through the CLI or MCP. Sign up for early access.

All hosted API access is account-scoped and authenticated. Access to Pretorin-hosted services and any returned account-scoped data is governed by the applicable platform terms in addition to the open-source license for this repository.

Login

pretorin login

Options:

FlagDescription
--api-key, -kAPI key (will prompt if not provided)
--api-urlCustom API base URL (for self-hosted instances)

You’ll be prompted to enter your API key. Credentials are stored in ~/.pretorin/config.json.

If you’re already authenticated, pretorin login validates your existing key against the API and skips the prompt. To re-authenticate with a different key, pass it explicitly:

pretorin login --api-key <new-key>

If you log into a different API endpoint or switch API keys, Pretorin clears the stored active system + framework context so stale scope does not bleed into the new environment.

Verify Authentication

$ pretorin whoami
╭──────────────────────────────── Your Session ────────────────────────────────╮
│ Status: Authenticated                                                        │
│ API Key: pretorin...9v7o                                                     │
│ API URL: https://platform.pretorin.com/api/v1/public                         │
│ Frameworks Available: 8                                                      │
╰──────────────────────────────────────────────────────────────────────────────╯

For machine-readable output, use the global --json flag:

pretorin --json whoami

Logout

Clear stored credentials:

pretorin logout

API Key via Environment Variable

You can set your API key via environment variable instead of pretorin login. The environment variable takes precedence over stored config:

export PRETORIN_API_KEY=pretorin_your_key_here

This is useful for CI/CD pipelines and containerized environments.

Customer-Managed Air-Gapped Installs

Use this guide when Pretorin is installed from a customer-managed or air-gapped deployment bundle and the CLI needs to talk to that private platform instead of the hosted Pretorin service.

The platform bundle owns Kubernetes install, data seeding, embedding, and AI provider validation. The CLI owns developer and agent access to the installed platform.

Security Notes

Do not paste secret values, decoded Kubernetes secrets, API tokens, model API keys, or full environment dumps into support tickets or chat. The commands below intentionally check non-secret configuration values or secret key names only.

When validating secrets, confirm that required keys exist without decoding their values:

kubectl get secret -n pretorin pretorin-platform-secrets \
  -o go-template='{{range $k, $_ := .data}}{{println $k}}{{end}}'

Platform Validation

After the platform is installed and object storage is initialized, run the bundle smoke test from the deployment bundle:

NAMESPACE=pretorin scripts/customer/validate-airgap-install.sh

The smoke test runs inside the deployed API pod and verifies:

  • database connectivity
  • seeded framework, package, source, document requirement, and crosswalk data
  • NIST 800-53 catalog presence
  • OSCAL control embedding coverage and vector dimensions
  • direct local AI provider chat and embedding calls
  • API-to-AI service connectivity
  • dashboard AI chat streaming and chat message persistence

For staged troubleshooting, skip chat first:

NAMESPACE=pretorin scripts/customer/validate-airgap-install.sh --skip-chat

That confirms data seeding, embeddings, provider reachability, and AI service health before testing the full streaming chat path.

CLI Configuration

Point the CLI at the private platform public API endpoint:

pretorin config set platform_api_base_url https://<platform-host>/api/v1/public
pretorin config set model_api_base_url https://<platform-host>/api/v1/public/model
pretorin whoami
pretorin frameworks list

pretorin config set platform_api_base_url prompts for an API key for the new endpoint and validates it before saving the change.

For non-interactive hosts, use environment variables:

export PRETORIN_API_KEY=<platform-api-token>
export PRETORIN_PLATFORM_API_BASE_URL=https://<platform-host>/api/v1/public
export PRETORIN_MODEL_API_BASE_URL=https://<platform-host>/api/v1/public/model

pretorin whoami
pretorin frameworks list

If you use MCP, the MCP server inherits the same CLI configuration:

pretorin mcp-serve

For Codex, Claude, Cursor, and other MCP client setup, see MCP Setup Guides.

Agent Runtime Notes

For pretorin agent run, model credential precedence is:

  1. OPENAI_API_KEY
  2. config.api_key
  3. config.openai_api_key

If you want the agent runtime to use the customer platform model endpoint, keep PRETORIN_MODEL_API_BASE_URL pointed at:

https://<platform-host>/api/v1/public/model

If OPENAI_API_KEY is set in the shell, it overrides stored Pretorin credentials for model calls. Unset it when you expect model calls to route through the customer Pretorin platform.

Common Issues

SymptomLikely causeFirst check or fix
pretorin whoami or pretorin frameworks list hits hosted PretorinCLI base URL is still using hosted configRun pretorin config list, then set platform_api_base_url to https://<platform-host>/api/v1/public.
CLI returns 401 or 403API key is for another endpoint, expired, or missing required scopesRun pretorin login or pretorin config set platform_api_base_url ... and enter a token from the customer platform.
pretorin agent run uses the wrong model credentialsShell OPENAI_API_KEY overrides stored CLI credentialsUnset shell OPENAI_API_KEY, or intentionally set PRETORIN_MODEL_API_BASE_URL for the customer endpoint.
AI provider validation failed during platform installOPENAI_BASE_URL is wrong, missing /v1, or unreachable from the clusterIn the deployment environment, run python -m app.cli.validate_ai_provider inside the API pod and confirm DNS, port, TLS, and provider path.
Provider calls time outNetworkPolicy blocks API or AI egress to the local model endpointAdd matching API and AI NetworkPolicy egress rules, then rerun Helm upgrade.
OPENAI_API_KEY is required or provider env is missingPlatform secret was not created or was created before env vars were exportedRerun scripts/customer/create-customer-secrets.sh, then restart API and AI deployments.
Embedding count is zero or below the control countThe embedding hook failed, is still running, or the API does not have the local embedding model envCheck pretorin-api-embedding-sync-* job logs and confirm API env includes OPENAI_BASE_URL and OPENAI_EMBEDDING_MODEL.
Embedding dimension mismatchLocal embedding model does not return 1536-dimensional vectorsUse a 1536-dimension embedding model for OPENAI_EMBEDDING_MODEL.
Data seeding fails or NIST 800-53 is missingFramework seed hook did not completeCheck pretorin-api-seed-frameworks-* job logs and rerun Helm upgrade after fixing the error.
Smoke test passes with --skip-chat but fails without itProvider supports basic calls but not streaming chat, tool calling, or the configured chat modelConfirm the model supports streaming chat completions and tool calls; update OPENAI_MODEL if needed.
Chat stream emits an error after a session is createdAI service can start the request but failed during agent executionCheck deployment/pretorin-ai logs around the smoke-test timestamp.

Useful platform-side checks:

kubectl get pods,jobs -n pretorin
kubectl logs -n pretorin job/<failed-job-name>

kubectl exec -n pretorin deployment/pretorin-api -- \
  sh -c 'env | grep -E "^(OPENAI_BASE_URL|OPENAI_MODEL|OPENAI_EMBEDDING_MODEL|OPENAI_USE_RESPONSES|AI_REQUIRE_LOCAL_PROVIDER|AI_PROVIDER_KIND|AI_PROVIDER_DEFAULT_PROFILE_ID|AI_PROVIDER_DEFAULT_PROFILE_NAME|AI_SERVICE_URL)="'

kubectl exec -n pretorin deployment/pretorin-ai -- \
  sh -c 'env | grep -E "^(OPENAI_BASE_URL|OPENAI_MODEL|OPENAI_EMBEDDING_MODEL|OPENAI_USE_RESPONSES|AI_REQUIRE_LOCAL_PROVIDER|AI_PROVIDER_KIND|AI_PROVIDER_DEFAULT_PROFILE_ID|AI_PROVIDER_DEFAULT_PROFILE_NAME|AI_SERVICE_URL)="'

What Passing Looks Like

A healthy install can:

  • return pretorin whoami
  • list seeded frameworks with pretorin frameworks list
  • run scripts/customer/validate-airgap-install.sh without failures
  • use chat in the web UI
  • route MCP tools through pretorin mcp-serve
  • route pretorin agent run through the configured customer model endpoint

Quick Start

After installing and authenticating, here are some common first steps.

Browse Frameworks

List all available compliance frameworks:

pretorin frameworks list

Get details on a specific control:

pretorin frameworks control nist-800-53-r5 ac-02

Set Up Context

Set your active system and framework for platform operations:

# Interactive selection
pretorin context set

# Or explicit
pretorin context set --system "My Application" --framework fedramp-moderate

Create Evidence

Create a local evidence file:

pretorin evidence create ac-02 fedramp-moderate \
  --description "Role-based access control in Azure AD" \
  --type configuration \
  --name "RBAC Configuration"

Push evidence to the platform:

pretorin evidence push

Run an Agent Task

Use the Codex agent for compliance analysis:

pretorin agent run "Assess AC-02 implementation gaps for my system"

Or use a predefined skill:

pretorin agent run --skill gap-analysis "Analyze my system compliance gaps"

Connect Your AI Tool

If you use Claude Code, Codex CLI, or another MCP-compatible AI tool:

# Install the skill (teaches your agent how to use Pretorin tools)
pretorin skill install

# Add the MCP server (Claude Code example)
claude mcp add --transport stdio pretorin -- pretorin mcp-serve

# Then ask your AI agent about compliance
# "What controls are in the Access Control family for FedRAMP Moderate?"

Check install status with pretorin skill status. See the MCP Setup Guides for other tools.

Run a Recipe

Recipes are markdown-plus-scripts playbooks that the calling agent invokes through MCP for evidence capture, baseline scanning, and other procedures:

# List available recipes (built-in + user + project)
pretorin recipe list

# Show one recipe's manifest and prose body
pretorin recipe show inspec-baseline

# Scaffold a new recipe in ~/.pretorin/recipes/<id>/
pretorin recipe new my-first-recipe

See Authoring Recipes for the full guide.

Browse STIGs and CCIs

Look up STIG benchmarks, rules, and CCI traceability:

# List available STIG benchmarks
pretorin stig list

# View STIG benchmark details
pretorin stig show <stig_id>

# Trace a CCI to its parent controls
pretorin cci chain <cci-id>

Run the Demo Walkthrough

An interactive demo script is included in the repository:

bash tools/demo-walkthrough.sh

Framework Browsing

The frameworks command group lets you browse compliance frameworks, control families, and individual controls. The browsing commands documented on this page are read-only and available to all authenticated users.

The same command group also exposes write-side commands for authoring, validating, uploading, forking, and rebasing custom frameworks — see Custom Framework Authoring at the bottom of this page for a quick index, or jump directly to the Custom Frameworks guide for the end-to-end workflow.

List All Frameworks

$ pretorin frameworks list
[°~°] Consulting the compliance archives...
                        Available Compliance Frameworks
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓
┃ ID          ┃ Title       ┃ Version     ┃ Tier         ┃ Families ┃ Controls ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━┩
│ cmmc-l1     │ CMMC 2.0    │ 2.0         │ tier1_essen… │        6 │       17 │
│             │ Level 1     │             │              │          │          │
│ cmmc-l2     │ CMMC 2.0    │ 2.0         │ tier1_essen… │       14 │      110 │
│             │ Level 2     │             │              │          │          │
│ ...         │             │             │              │          │          │
└─────────────┴─────────────┴─────────────┴──────────────┴──────────┴──────────┘

Total: 26 framework(s)

The ID column is what you use in all other commands.

The exact total and available framework set can vary as the platform catalog expands. Use pretorin frameworks list to see the live catalog available to your account.

Get Framework Details

$ pretorin frameworks get fedramp-moderate
[°~°] Gathering framework details...
╭───────────────── Framework: FedRAMP Rev 5 Moderate Baseline ─────────────────╮
│ ID: fedramp-moderate                                                         │
│ Title: FedRAMP Rev 5 Moderate Baseline                                       │
│ Version: fedramp2.1.0-oscal1.0.4                                             │
│ OSCAL Version: 1.0.4                                                         │
│ Tier: tier1_essential                                                        │
│ Category: government                                                         │
│ Published: 2024-09-24T02:24:00Z                                              │
╰──────────────────────────────────────────────────────────────────────────────╯

List Control Families

$ pretorin frameworks families nist-800-53-r5
[°~°] Gathering control families...
                       Control Families - nist-800-53-r5
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━┓
┃ ID                          ┃ Title                       ┃ Class ┃ Controls ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━┩
│ access-control              │ Access Control              │ ac    │       25 │
│ audit-and-accountability    │ Audit and Accountability    │ au    │       16 │
│ awareness-and-training      │ Awareness and Training      │ at    │        6 │
│ configuration-management    │ Configuration Management    │ cm    │       14 │
│ ...                         │                             │       │          │
└─────────────────────────────┴─────────────────────────────┴───────┴──────────┘

Important: Family IDs are slugs like access-control, not short codes like ac. The short code is shown in the Class column for reference, but commands require the full slug ID.

CMMC Family IDs

CMMC frameworks use level-specific family slugs:

$ pretorin frameworks families cmmc-l2

CMMC family IDs include the level suffix, e.g., access-control-level-2 instead of access-control.

Get Family Details

pretorin frameworks family nist-800-53-r5 access-control

List Controls

# Family filter via positional argument
pretorin frameworks controls nist-800-53-r5 access-control

# Family filter via flag (equivalent)
pretorin frameworks controls nist-800-53-r5 --family access-control --limit 10

# All controls in the framework (no family filter)
pretorin frameworks controls fedramp-moderate

The family filter is optional and may be passed as a positional argument or with --family/-f. Without --limit (default 0), all matching controls are shown.

Important: Control IDs are zero-padded — use ac-01, not ac-1. See Control ID Formats for details.

Get Control Details

$ pretorin frameworks control nist-800-53-r5 ac-02
[°~°] Looking up control details...
╭─────────────────────────────── Control: AC-02 ───────────────────────────────╮
│ ID: ac-02                                                                    │
│ Title: Account Management                                                    │
│ Class: SP800-53                                                              │
│ Type: organizational                                                         │
│                                                                              │
│ AI Guidance: Available                                                       │
╰──────────────────────────────────────────────────────────────────────────────╯

Parameters:
  - ac-02_odp.01: prerequisites and criteria
  - ac-02_odp.02: attributes (as required)
  - ac-02_odp.03: personnel or roles
  - ac-02_odp.04: policy, procedures, prerequisites, and criteria
  - ac-02_odp.05: personnel or roles

Brief Mode

By default, the full control is shown including statement, guidance, and references. Use --brief to show only the basic info panel:

$ pretorin frameworks control nist-800-53-r5 ac-02 --brief

The default (no flag) includes:

  • Statement — the formal control requirement text
  • Guidance — implementation guidance from the framework
  • Related Controls — other controls that relate to this one

Common Mistakes

Using the wrong ID format produces an error:

$ pretorin frameworks control nist-800-53-r5 ac-1
[°~°] Looking up control details...
[°︵°] Couldn't find control ac-1 in nist-800-53-r5
Try pretorin frameworks controls nist-800-53-r5 to see available controls.

Use zero-padded IDs: ac-01, not ac-1.

Framework Metadata

Get per-control metadata for a framework:

pretorin frameworks metadata nist-800-53-r5

Submit Artifacts

Submit a compliance artifact JSON file:

pretorin frameworks submit-artifact artifact.json

See Artifact Generation for the artifact schema.

JSON Output

All framework commands support JSON output for scripting and AI agents:

pretorin --json frameworks list
pretorin --json frameworks control nist-800-53-r5 ac-02

Custom Framework Authoring

In addition to the read-only browsing commands above, the frameworks group exposes the write-side commands that drive the custom-framework revision lifecycle. These let you author your own catalog, validate it locally, upload it as a draft revision, and fork or rebase against an upstream Pretorin-managed framework.

CommandDescription
pretorin frameworks init-custom <id>Scaffold a minimal valid unified.json for a new custom framework.
pretorin frameworks validate-custom <unified.json>Validate a unified.json artifact against the bundled JSON Schema.
pretorin frameworks build-custom <input> -f <id>Normalize an OSCAL or custom catalog into uploadable unified.json.
pretorin frameworks upload-custom <unified.json> [--publish]Upload as a draft revision, optionally publishing in one step.
pretorin frameworks fork-framework <upstream-id>Create a linked-fork draft from an upstream framework.
pretorin frameworks rebase-fork <fork-id>Create a rebase draft for a fork against the latest upstream revision.
pretorin frameworks revisions <framework-id>List all draft and published revisions for a framework.
pretorin frameworks export-oscal <unified.json>Regenerate an OSCAL catalog from a unified.json artifact.

See the Custom Frameworks guide for the full end-to-end workflow, the supported input shapes recognized by build-custom, and the linked-fork / rebase model.

Context Management

The context command group manages your active system and framework scope. Platform-backed compliance operations (evidence, narratives, notes, monitoring, control status) run inside exactly one system + framework pair at a time.

This works similarly to kubectl config use-context — set your scope once, then run commands within it.

List Available Systems

$ pretorin context list
[°~°] Fetching your systems...
                              Your Systems
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ System           ┃ Framework ID       ┃ Progress % ┃ Status    ┃
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ My Application   │ nist-800-53-r5     │        42% │ in_progress │
│ My Application   │ fedramp-moderate   │        28% │ in_progress │
│ Internal Tool    │ cmmc-l2            │        75% │ implemented │
└──────────────────┴────────────────────┴────────────┴───────────┘

Set Active Context

# Interactive — prompts for system and framework selection
pretorin context set

# Explicit
pretorin context set --system "My Application" --framework nist-800-53-r5

# Skip automatic source verification after setting context
pretorin context set --system "My Application" --framework nist-800-53-r5 --no-verify
OptionDescription
--system / -sSystem name or ID
--framework / -fFramework ID (e.g., fedramp-moderate)
--no-verifySkip source verification after setting context

Pretorin stores the canonical system ID for stability and also caches the last known system name for display. After setting context, source verification runs automatically unless --no-verify is passed. If you change API keys or platform endpoints with pretorin login, the stored active context is cleared automatically so old scope does not leak into the new environment.

Show Current Context

$ pretorin context show
╭──────────────────────── Active Context ─────────────────────────╮
│ System: My Application (sys-1234...)                           │
│ Framework: nist-800-53-r5                                      │
│ Progress: 42%                                                  │
│ Status: in_progress                                            │
╰─────────────────────────────────────────────────────────────────╯

# Compact summary for shell use
pretorin context show --quiet

# Fail fast if the stored context is missing, stale, or cannot be verified
pretorin context show --quiet --check

context show validates the stored system and framework against the platform when credentials are available. If the system has been deleted or the framework is no longer attached, the command reports that state explicitly instead of silently showing a stale context.

Verify Context

Verify the active context against source attestation:

# Full output
pretorin context verify

# Compact output with custom TTL
pretorin context verify --ttl 7200 --quiet
OptionDescription
--ttlVerification TTL in seconds (default: 3600)
--quiet / -qCompact output

Source Manifest

Show the resolved source manifest and evaluate it against detected sources:

pretorin context manifest
pretorin context manifest --quiet

Clear Context

pretorin context clear

Single-Scope Enforcement

All platform write operations must target exactly one system + framework pair. This includes:

  • Evidence creation and push
  • Narrative updates
  • Control notes
  • Monitoring events
  • Control status updates

If you need to work across multiple frameworks (e.g., fedramp-low and fedramp-moderate), run them as separate operations:

# Work on FedRAMP Moderate
pretorin context set --system "My App" --framework fedramp-moderate
pretorin evidence push

# Switch to FedRAMP Low
pretorin context set --system "My App" --framework fedramp-low
pretorin evidence push

Some commands also accept explicit --system and --framework flags, which override the stored context for that invocation.

Evidence Commands

The evidence command group manages local evidence files and syncs them to the Pretorin platform.

Create Local Evidence

pretorin evidence create ac-02 fedramp-moderate \
  --name "RBAC Configuration" \
  --description "Role-based access control in Azure AD" \
  --artifact-content "## Evidence\n\n- RBAC roles are configured in Azure AD." \
  --type configuration

Creates a markdown file under evidence/<framework>/<control>/ with YAML frontmatter containing metadata (control ID, framework, name, type, status, short description). The Markdown body is the evidence artifact that will be sent as artifact_content when pushed.

--type / -t is required — the CLI no longer defaults to policy_document. See Evidence Types below for the 13 canonical values.

List Local Evidence

# List all local evidence
pretorin evidence list

# Filter by framework
pretorin evidence list --framework fedramp-moderate

Push Evidence to Platform

pretorin evidence push

Pushes local evidence files to the platform using find-or-create upsert logic. Exact matches are reused and reported separately.

Requires an active single scope from pretorin context set, unless both --system and --framework are provided explicitly.

Search Platform Evidence

# Search by control
pretorin evidence search --control-id ac-02 --framework-id fedramp-moderate

# Search by system
pretorin evidence search --system "My Application" --framework-id fedramp-moderate --limit 100

Upload Evidence File

Upload a file directly as evidence:

pretorin evidence upload screenshot.png ac-02 fedramp-moderate \
  --name "MFA Screenshot" --type screenshot

pretorin evidence upload config.yaml ac-06 fedramp-moderate \
  --name "Auth Config" --type configuration --description "IdP auth config"

Creates an evidence record with the uploaded file and links it to the specified control. The file’s SHA-256 checksum is computed locally and verified server-side for integrity.

OptionDescription
--name / -nEvidence name (required)
--type / -tEvidence type (default: other)
--description / -dEvidence description
--system / -sSystem name or ID (uses active context if omitted)

Upsert Evidence

Find-or-create evidence and link it to a control:

pretorin evidence upsert ac-02 fedramp-moderate \
  --name "RBAC Configuration" \
  --description "Role mapping in IdP" \
  --artifact-content "## Evidence\n\n- Role mapping is enforced in the IdP export." \
  --type configuration

--description is the short human summary. --artifact-content is the Markdown evidence body that the platform materializes as the stored artifact. This searches for an exact match on (name + description + type + control + framework) within the active system scope. If found, it reuses the existing item; otherwise, it creates a new one. It then ensures the evidence is linked to the specified control.

Code Context Options

When upserting evidence, you can attach source code context:

OptionDescription
--code-filePath to source file
--code-linesLine range (e.g., 10-25)
--code-repoGit repository URL
--code-commitGit commit hash

If --code-repo or --code-commit are not provided, the CLI auto-populates them from the attested source verification snapshot when available.

Audit Sufficiency Options

For evidence whose auditor sufficiency depends on the period it covers or the query/filter that produced it (typical for log extracts, scan exports, and continuous-compliance feeds):

OptionDescription
--coverage-startISO 8601 start of the period the evidence content describes
--coverage-endISO 8601 end of the period; omit for point-in-time evidence
--capture-queryQuery / filter / command that produced the artifact (IPE reproducibility)
--cadence-daysRefresh cadence in days (1–365); evidence requires re-verification after this window. Server computes expires_at from this value.

Cadenced evidence transitions to expired automatically when expires_at lapses. Prefer evidence validate (below) so the CLI compares the fresh source-material hash before re-verifying.

Source Provenance Options

Audit-grade provenance metadata, captured into audit_metadata and used by evidence validate to detect drift against the original source material:

OptionDescription
--source-uriStable source path, URL, object id, report id, or export id used for provenance
--source-labelHuman-readable source title or section label for auditors
--source-locatorPrecise source locator, e.g. section id or lines 84-88
--source-excerptShort quoted excerpt or source material used to produce the evidence claim
--capture-methodSource capture method, e.g. repository_file_read, api_export, scanner_output

Link an existing platform evidence item to a control:

pretorin evidence link ev-abc123 ac-02
pretorin evidence link ev-abc123 ac-02 --framework-id fedramp-moderate --system "My System"

Options:

  • --framework-id / -f — Framework ID (uses active context if omitted)
  • --system / -s — System name or ID (uses active context if omitted)

Attach evidence to a per-system CCI implementation row:

pretorin evidence link-cci ev-abc123 <cci_implementation_id>
pretorin evidence link-cci ev-abc123 <cci_implementation_id> --system "My System"

Options:

  • --system / -s — System name or ID (uses active context if omitted)
  • --override-system-mismatch — Permit cross-system attachment (must be paired with --override-reason)
  • --override-reason TEXT — Justification recorded with the override

The CCI implementation UUID can be obtained from pretorin cci impl or from the CCI status rollup.

Attach remediation proof, mitigating-control documentation, or waiver-justification artifacts to a STIG rule:

pretorin evidence link-stig ev-abc123 <stig_rule_id>
pretorin evidence link-stig ev-abc123 <stig_rule_id> --system "My System"

The first link to a given (system, stig_rule) pair lazy-creates the workflow row on the platform.

Options match link-cci: --system, --override-system-mismatch, --override-reason.

Mark Evidence Current

Re-affirm that an evidence item is still current — bumps expires_at by the evidence’s refresh_cadence_days, transitions status from expired back to valid if needed, and auto-resolves any open evidence.expiring / evidence.expired monitoring events:

pretorin evidence mark-current ev-abc123
pretorin evidence mark-current ev-abc123 --system "My System"

Options:

  • --system / -s — System name or ID (uses active context if omitted)

Fails with HTTP 400 if the evidence has no refresh_cadence_days set — only cadenced evidence (set via evidence upsert --cadence-days N) can be marked current. This is the entry-point for the continuous-compliance refresh loop: cron jobs, recipes, and operators all call mark-current to confirm evidence is still representative without rewriting its body.

Validate Evidence Source

Validate file-backed evidence against its recorded audit_metadata.content_hash:

pretorin evidence validate ev-abc123 --system "My System"

# Resolve relative source_uri values against a specific repo checkout
pretorin evidence validate ev-abc123 --source-root /path/to/repo

# Provide the replacement artifact body and reviewer note up front when drift is expected
pretorin evidence validate ev-abc123 \
  --artifact-content "## Evidence\n\n- Updated RBAC mapping after Q2 IdP migration." \
  --description "RBAC mapping (post Q2 IdP migration)" \
  --drift-note "Source file rotated during Q2 IdP migration; review updated artifact."

If the fresh source-material hash is unchanged, the CLI calls mark-current and Pretorin records a re_verified lineage event. If the source changed, the CLI calls the artifact update endpoint with a new Markdown artifact and a drift_note so reviewers see the drift instead of silently marking stale evidence current.

OptionDescription
--system / -sSystem name or ID (uses active context if omitted)
--source-rootRoot directory for relative file-backed source_uri values (defaults to current directory)
--artifact-content / --artifactUpdated Markdown artifact body to use if drift is detected; otherwise an excerpt artifact is generated
--description / -dUpdated short summary to use if drift is detected
--drift-noteReviewer-facing note created when drift is detected (defaults to a generic review-needed prompt)

Delete Evidence

# Delete with confirmation prompt
pretorin evidence delete ev-abc123

# Skip confirmation (for automation)
pretorin evidence delete ev-abc123 --yes

# Explicit system scope
pretorin evidence delete ev-abc123 --system "My Application" --framework-id fedramp-moderate --yes

Permanently deletes an evidence item from the platform. This is system-scoped and requires WRITE access. Associated evidence embeddings are removed as part of the delete lifecycle.

OptionDescription
--system / -sSystem name or ID (uses active context if omitted)
--framework-id / -fFramework ID (uses active context if omitted)
--yes / -ySkip confirmation prompt

Evidence Types

Valid evidence types:

TypeDescription
policy_documentPolicy or procedure document
screenshotScreenshot evidence
screen_recordingScreen recording
log_fileLog file extract
configurationConfiguration file or setting
test_resultTest output or report
certificateCertificate or attestation document
attestationSigned attestation
code_snippetCode excerpt
repository_linkLink to source repository
scan_resultSecurity scan output
interview_notesInterview or assessment notes
otherOther evidence type

AI-Drift Normalization

Non-CLI write paths (MCP handlers, agent tools, upsert_evidence workflow, campaign apply) run a client-side normalizer before submitting evidence to the platform. It maps known AI-drift aliases to canonical types (e.g. audit_loglog_file, plural test_resultstest_result, screenshootscreenshot) and uses difflib fuzzy matching for novel typos before falling back to other. The CLI itself does not run the normalizer; users get a hard error listing all 13 canonical types and can self-correct.

Markdown Artifact Requirements

Evidence descriptions are short summaries. The actual evidence belongs in artifact_content / --artifact-content as Markdown:

  • Markdown headings are allowed in the artifact body.
  • Markdown images are not accepted on JSON evidence writes; upload files with evidence upload.
  • Source path, line, timestamp, and capture details belong in structured audit_metadata, not appended to the description.

These requirements are validated before push/upsert operations.

Narrative Commands

The narrative command group manages implementation narratives for controls. Narratives describe how a specific control is implemented within your system.

Create Local Narrative

pretorin narrative create ac-02 fedramp-moderate \
  -c "- RBAC enforced via IdP\n\n\`\`\`yaml\nroles:\n  admin: ...\n\`\`\`"

Creates a local markdown file at narratives/<framework>/<control>/<slug>.md with YAML frontmatter. Markdown is validated at create time (same rules as push).

Options:

  • --content / -c — Narrative content (required)
  • --name / -n — Custom name (defaults to <control>-<framework>)
  • --ai-generated — Mark the narrative as AI-generated

List Local Narratives

pretorin narrative list
pretorin narrative list --framework fedramp-moderate

Displays a table of local narrative files: Control, Framework, Name, AI Generated, Synced.

Push Narratives

pretorin narrative push --dry-run
pretorin narrative push

Batch-pushes all unsynced local narratives to the platform. After a successful push, the local file’s platform_synced frontmatter is set to true.

Get Current Narrative

pretorin narrative get ac-02 fedramp-moderate --system "My System"

Returns the current narrative text, status, and AI confidence metadata when present.

Push a Single File (Legacy)

pretorin narrative push-file ac-02 fedramp-moderate "My System" narrative-ac02.md

Reads a markdown or text file and submits it as the implementation narrative for the specified control.

Markdown Quality Requirements

Narratives must be auditor-ready markdown:

  • No markdown headings (#, ##, etc.)
  • At least two rich markdown elements (fenced code blocks, tables, lists, or links)
  • At least one structural element (code block, table, or list)
  • No markdown images (temporarily disabled pending platform image upload support)

These requirements are validated at create time and before push.

Generating Narratives with AI

To generate narratives using the agent runtime:

pretorin agent run --skill narrative-generation "Generate narrative for AC-02"

Or use the MCP server’s generate_control_artifacts tool for read-only drafts through your AI agent.

See Skills for more on agent-powered narrative generation.

No-Hallucination Requirements

Generated narratives must only document observable facts. For missing information, use TODO placeholder blocks:

  • Treat existing Pretorin narratives, notes, and status fields as a starting point, not proof that a control gap exists.
  • Before writing a narrative update or gap note, inspect the relevant implementation in the workspace and connected systems.
  • If observed implementation is stronger than the current platform record, update the narrative to match the observed implementation and record any remaining evidence gap separately.
[[PRETORIN_TODO]]
missing_item: <what is missing>
reason: Not observable from current workspace and connected MCP systems
required_manual_action: <what user must do on platform/integrations>
suggested_evidence_type: <policy_document|configuration|...>
[[/PRETORIN_TODO]]

Notes Commands

The notes command group manages control implementation notes. Notes are used to track gaps, manual follow-up items, and implementation context for specific controls.

Create Local Note

pretorin notes create ac-02 fedramp-moderate \
  -c "Gap: Missing SSO evidence. Manual next step: collect IdP configuration screenshots."

Creates a local markdown file at notes/<framework>/<control>/<slug>.md with YAML frontmatter. No markdown validation is applied to notes.

Options:

  • --content / -c — Note content (required)
  • --name / -n — Custom name (defaults to content summary)

Push Notes

pretorin notes push --dry-run
pretorin notes push

Batch-pushes all unsynced local notes to the platform. Notes are append-only on the platform. After a successful push, the local file’s platform_synced frontmatter is set to true.

List Local Notes

pretorin notes list --local
pretorin notes list --local --framework fedramp-moderate

Use the --local flag to list local note files instead of platform notes.

List Platform Notes

pretorin notes list ac-02 fedramp-moderate --system "My System"

Add a Note (Direct to Platform)

pretorin notes add ac-02 fedramp-moderate \
  --content "Gap: Missing SSO evidence. Manual next step: collect IdP configuration screenshots."

pretorin notes add ac-02 fedramp-moderate \
  --content "MFA verified" --system "My System"

Options:

  • --content / -c — Note content (required)
  • --system / -s — System name or ID (uses active context if omitted)

Resolve or Reopen a Note

# Resolve a note with an audit-trail justification
pretorin notes resolve ac-02 fedramp-moderate <note_id> --resolution-note "SSO config verified in IdP logs."

# Resolve with updated content and a resolution justification
pretorin notes resolve ac-02 fedramp-moderate <note_id> --resolution-note "SSO config verified." --content "Updated note text."

# Reopen a resolved note
pretorin notes resolve ac-02 fedramp-moderate <note_id> --reopen

Options:

  • --system / -s — System name or ID
  • --reopen — Reopen a resolved note instead of resolving it
  • --resolution-note / --justification — Required when resolving; stored as the closure audit trail
  • --content / -c — Optional updated note content
  • --pinned — Optional pinned state

Gap Note Format

When adding notes for unresolved gaps, use this structured format:

Gap: <short title>
Observed: <what was verifiably found>
Missing: <what could not be verified>
Why missing: <access/system limitation>
Manual next step: <explicit user/platform action>

This format ensures consistency across CLI, MCP, and agent workflows when documenting compliance gaps.

Monitoring Commands

The monitoring command group records security and compliance events against a system.

Push a Monitoring Event

pretorin monitoring push --system "My System" --title "Quarterly Access Review" \
  --event-type access_review --severity info

Event Types

TypeDescription
security_scanAutomated security scan result
configuration_changeInfrastructure or application configuration change
access_reviewPeriodic access review or audit
compliance_checkCompliance posture check or assessment

Severity Levels

SeverityDescription
criticalRequires immediate attention
highSignificant finding
mediumModerate finding
lowMinor finding
infoInformational event

Options

OptionDescription
--system / -sSystem name or ID (uses active context if omitted)
--framework / -fFramework ID (uses active context if omitted)
--title / -tEvent title (required)
--severityEvent severity (default: high)
--control / -cControl ID (e.g., sc-07, ac-02)
--description / -dDetailed event description
--event-typeEvent type (default: security_scan)
--update-control-statusAlso update the control status to in_progress

Context Requirement

The monitoring push command requires an active system context. Set it with pretorin context set or pass --system explicitly.

Campaign Workflows

The campaign command group runs bulk compliance operations across multiple controls, policies, or scope questions in a single coordinated run. Campaigns support an external-agent-first pattern with checkpoint persistence and lease-based concurrency for safe fan-out to multiple agents.

Campaign Domains and Modes

DomainModeDescription
controlsinitialDraft new narratives and evidence for controls
controlsnotes-fixAddress platform notes on existing controls
controlsreview-fixFix findings from a family review job
policyanswerGenerate answers for policy questions
policyreview-fixFix findings from a policy review
scopeanswerGenerate answers for scope questions
scopereview-fixFix findings from a scope review

Control Campaigns

Draft New Narratives for a Family

pretorin campaign controls --mode initial --family AC \
  --system "My System" --framework-id fedramp-moderate

Fix Controls with Platform Notes

pretorin campaign controls --mode notes-fix --family AC

Fix Controls after Family Review

pretorin campaign controls --mode review-fix --family AC --review-job <job-id>

Options

OptionDescription
--systemTarget system ID or name
--framework-idTarget framework ID
--familyControl family to target (e.g., AC, AU)
--controlsSpecific control IDs (comma-separated)
--all-controlsTarget all controls in the framework
--modeCampaign mode: initial, notes-fix, review-fix
--artifactsArtifact types to generate: narratives, evidence, or both (default: both)
--review-jobReview job ID (required for review-fix mode)
--concurrencyNumber of parallel workers
--max-retriesMaximum retry attempts per item
--checkpointPath to checkpoint file for resume
--applyApply proposals to platform after completion
--outputOutput mode: auto, live, compact, json

Policy Campaigns

Answer All Incomplete Policy Questions

pretorin campaign policy --mode answer --all-incomplete

Fix Policy Review Findings

pretorin campaign policy --mode review-fix --policies <policy-id>

Options

OptionDescription
--policiesSpecific policy IDs (comma-separated)
--all-incompleteTarget all incomplete policies
--modeCampaign mode: answer, review-fix
--systemOptional system context passthrough
--concurrencyNumber of parallel workers (default: 4)
--max-retriesMaximum retry attempts per item (default: 2)
--checkpointPath to checkpoint file for resume
--applyApply proposals to platform after completion
--outputOutput mode: auto, live, compact, json

Scope Campaigns

Answer Scope Questions

pretorin campaign scope --mode answer \
  --system "My System" --framework-id fedramp-moderate

Options

OptionDescription
--systemTarget system ID or name (required)
--framework-idTarget framework ID (required)
--modeCampaign mode: answer, review-fix
--concurrencyNumber of parallel workers (default: 4)
--max-retriesMaximum retry attempts per question (default: 2)
--checkpointPath to checkpoint file for resume
--applyApply proposals to platform after completion
--outputOutput mode: auto, live, compact, json

Checking Campaign Status

pretorin campaign status --checkpoint .pretorin/campaign-checkpoint.json

Campaign Lifecycle

  1. Prepare — The campaign snapshots platform state and creates a local checkpoint file
  2. Claim — Items are claimed with TTL-based leases (safe for multiple agents)
  3. Draft — Each item gets full context and drafting instructions
  4. Propose — Proposals are submitted without writing to the platform
  5. Apply — All accepted proposals are pushed to the platform in one operation

Use --apply to automatically apply after completion, or run campaign status to review before applying.

Vendor Management

The vendor command group manages vendor entities and their evidence documents. Vendors represent external providers (CSPs, SaaS, managed services) or internal teams whose controls your system inherits.

List Vendors

pretorin vendor list

Create a Vendor

pretorin vendor create "AWS" --type csp --description "Primary cloud provider" \
  --authorization-level "FedRAMP High P-ATO"

Vendor Types

TypeDescription
cspCloud Service Provider
saasSoftware as a Service
managed_serviceManaged service provider
internalInternal team or shared service

Get Vendor Details

pretorin vendor get <vendor_id>

Update a Vendor

pretorin vendor update <vendor_id> --name "AWS GovCloud" --authorization-level "FedRAMP High"

Delete a Vendor

pretorin vendor delete <vendor_id>
pretorin vendor delete <vendor_id> --force  # skip confirmation

Upload Vendor Documents

Upload SOC 2 reports, Customer Responsibility Matrices (CRMs), FedRAMP packages, or other vendor evidence:

pretorin vendor upload-doc <vendor_id> ./aws-soc2-report.pdf \
  --name "AWS SOC 2 Type II" \
  --description "Annual SOC 2 report covering 2025" \
  --attestation-type third_party_attestation

Attestation Types

TypeDescription
self_attestedVendor’s own assertion
third_party_attestationIndependent auditor report (SOC 2, FedRAMP)
vendor_providedDocumentation provided by vendor

List Vendor Documents

pretorin vendor list-docs <vendor_id>

Once vendors are created and documents uploaded, use the MCP tools or platform to set control responsibility edges:

  • set_control_responsibility — Mark controls as inherited/shared
  • generate_inheritance_narrative — AI-draft inheritance narratives from vendor docs
  • get_stale_edges / sync_stale_edges — Monitor and sync inheritance

Risk Management

The risk command group manages a system’s risk register: list and create risks, attach artifact links (controls / evidence / findings / vendors), record a mitigation strategy, and refresh AI summaries. The risk library is org-level; everything else is system-scoped — resolve the active system before any risk operation.

Workflow notes

  • Risks are system-scoped. Every command except risk library list takes a system as its first argument.
  • Auto-link is opt-in. risk create and risk seed only auto-link controls when --framework is supplied and the system has ControlImplementation rows for that framework. Without those, the risk is created with zero links — follow up with risk link add.
  • Mitigation is just risk update. There is no separate /mitigate endpoint. Set --treatment (one of mitigate / accept / transfer / avoid) plus --treatment-plan and --treatment-due-date through the update command.
  • AI refresh is best-effort. risk refresh-summary always re-scores. The AI summary regenerates only if the AI service is up and the org has quota; check whether ai_summary_generated_at advanced if you need to confirm AI ran.

List Risks

pretorin risk list <system_id>
pretorin risk list <system_id> --category availability --risk-level high --status open

Show a Risk

pretorin risk show <system_id> <risk_id>

Returns the full risk including eager-loaded artifact_links.

Create a Risk

Custom risk with no auto-linking:

pretorin risk create <system_id> \
  --title "Phishing campaign against ops team" \
  --category confidentiality

Custom risk with mitigation recorded in one call and control auto-linking against a framework:

pretorin risk create <system_id> \
  --title "Stolen credential abuse" \
  --category confidentiality \
  --treatment mitigate \
  --treatment-plan "Roll out hardware MFA to all admin accounts" \
  --treatment-due-date 2026-06-30 \
  --framework nist-800-53-r5 \
  --suggested-control-family AC \
  --suggested-control-family IA

Treatment values

ValueMeaning
mitigateReduce likelihood/impact through controls
acceptDocument and accept the residual risk
transferShift the risk (insurance, vendor SLA, etc.)
avoidEliminate the activity that causes the risk

Seed from the Library

Bulk-instantiate library templates against a system + framework:

pretorin risk seed <system_id> \
  --framework nist-800-53-r5 \
  --template-id tpl-phishing \
  --template-id tpl-insider \
  --template-id tpl-data-loss

Each template is scored against the system, and controls are auto-linked per the template’s suggested_control_families when matching ControlImplementation rows exist.

Update / Mitigate a Risk

This is the mitigation surface — record how a risk will be addressed by updating the same fields:

pretorin risk update <system_id> <risk_id> \
  --treatment mitigate \
  --treatment-plan "Hardware MFA deployed Q2 2026" \
  --treatment-due-date 2026-06-30

Any other risk fields (title, description, category, likelihood, impact, owner_id, status, review_frequency_days) can be updated through the same command.

Retiring a Risk

There is no public hard-delete endpoint for risks — risks are part of the audit chain. Retire a risk by setting its status to a terminal value:

pretorin risk update <system_id> <risk_id> --status closed

Conventional terminal values follow the platform’s other status-bearing models (FindingStatus, POAMItemStatus, RFIStatus): closed for risks that have been remediated or are no longer relevant. The risk list --status filter can then exclude them from active views (pretorin risk list <system_id> --status identified).

This is a CLI-side convention; the platform’s status field is currently an unconstrained string. A future platform-level lineage model is expected to formalize risk-retirement semantics across artifact types.

Risks can link to controls, evidence, findings, vendors, or monitoring events. Pass exactly one artifact flag.

# A control that mitigates this risk
pretorin risk link add <system_id> <risk_id> \
  --link-type mitigates_risk \
  --control AC-2 \
  --framework nist-800-53-r5

# Evidence demonstrating the risk is real
pretorin risk link add <system_id> <risk_id> \
  --link-type evidence_of_risk \
  --evidence <evidence_id>

# A vendor whose service introduces the risk
pretorin risk link add <system_id> <risk_id> \
  --link-type contributes_to_risk \
  --vendor <vendor_id>
ValueMeaning
contributes_to_riskArtifact increases the risk
mitigates_riskArtifact reduces the risk
evidence_of_riskArtifact demonstrates the risk has occurred or could
pretorin risk link rm <system_id> <risk_id> <link_id>

Refresh Score + AI Summary

Re-score the risk using the latest analytics and trigger a best-effort AI summary regeneration:

pretorin risk refresh-summary <system_id> <risk_id>

The endpoint always returns 200 with the updated entry. The score commits regardless of AI availability; the AI summary updates only if the AI service is reachable and the org has quota.

Risk Library

Browse the org-level template library to see what can be seeded. Templates expose a scenario (the risk description), category, cia_category, and suggested_control_families:

pretorin risk library list
pretorin risk library list --category "Access control"

Use --json to capture the full template payload, including suggested_control_families, for scripting.

Risk Assessment Report (RAR) generation is not exposed through the CLI or MCP — it remains a platform-only multi-section AI-assembled document. Drive RAR builds from the Pretorin platform UI when needed.

STIG & CCI Browsing

The stig and cci command groups let you browse STIG benchmarks, rules, and CCIs with full traceability from NIST 800-53 controls down to individual STIG check rules.

STIG Commands

List STIG Benchmarks

pretorin stig list
pretorin stig list --technology-area "Network"
pretorin stig list --product "Windows" --limit 10

Show STIG Details

pretorin stig show <stig_id>

Shows benchmark metadata including title, version, release info, and severity breakdown of rules.

List Rules for a STIG

pretorin stig rules <stig_id>
pretorin stig rules <stig_id> --severity cat_i
pretorin stig rules <stig_id> --cci CCI-000015 --limit 20

Show Applicable STIGs

# Uses active system context
pretorin stig applicable

# Explicit system
pretorin stig applicable --system "My System"

AI-Infer Applicable STIGs

pretorin stig infer
pretorin stig infer --system "My System"

Uses the system’s profile to recommend which STIG benchmarks should apply.

CCI Commands

CCIs (Control Correlation Identifiers) bridge NIST 800-53 controls to specific STIG rules via SRGs (Security Requirements Guides).

List CCIs

pretorin cci list
pretorin cci list --control ac-2
pretorin cci list --status draft --limit 50

Show CCI Details

pretorin cci show CCI-000015

Shows the CCI definition, linked SRGs, and linked STIG rules.

Full Traceability Chain

pretorin cci chain ac-2
pretorin cci chain ac-2 --system "My System"

Shows the complete chain: NIST 800-53 Control -> CCIs -> SRGs -> STIG rules (and test results when --system is provided).

This is useful for understanding exactly which technical checks validate a given control requirement.

Per-System CCI Implementation

pretorin cci impl <cci_uuid> --system "My System"

Reads the per-system CCI implementation row by (system, cci_uuid). Returns the live impl detail — status, status source, narrative (operator-authored or AI-generated draft), evidence count, conflict flag, and eMASS fields. A 404 means the impl row hasn’t been initialized yet for this system.

Use this when you already have the CCI catalog UUID (from cci show or upstream tooling) and want the system-specific compliance state without walking the full rollup.

STIG-to-CCI assignment is catalog-level. DISA defines the STIG-rule → CCI relationship in the catalog. There is no “assign STIG X to CCI Y on this system” operation — per-system applicability and per-system test results combine with the catalog mapping to produce the rollup. Use cci chain --system for the full picture.

STIG Scanning

Note: The legacy pretorin scan command was removed when the recipes system landed. Scanning now happens through recipes: each scanner ships as a built-in recipe that the calling AI agent (Claude Code, Codex CLI, custom MCP client, or pretorin agent) invokes through MCP.

If you have local automation that called pretorin scan run, switch it to invoke the recipe directly via the agent or use pretorin recipe list to discover the equivalent recipe.

Available Scanner Recipes

Recipe IDWrapsCLI requirement
inspec-baselineChef InSpecinspec
openscap-baselineOpenSCAPoscap
cloud-aws-baselineAWS APIs (boto3)aws
cloud-azure-baselineAzure APIsaz
manual-attestationHuman attestation (no external tool)

List them locally:

pretorin recipe list
pretorin recipe show inspec-baseline

How a Calling Agent Runs a Scan

The agent (running in your IDE or via pretorin agent run) opens a recipe context, calls the recipe’s run_scan script with a STIG id, and submits the returned per-rule results to the platform via submit_test_results.

The recipe body (the markdown under the frontmatter in recipe.md) is the prompt the agent reads to know what to do. You don’t run the recipe by hand; you ask the agent something like:

“Run an inspec-baseline scan against RHEL_9_STIG on this system.”

Behind the scenes the agent:

  1. Calls start_recipe(id="inspec-baseline", system_id=...).
  2. Calls the recipe’s run_scan tool with stig_id="RHEL_9_STIG".
  3. Reads the returned summary (per-rule pass/fail/error/not_applicable counts).
  4. Submits results via submit_test_results.
  5. Calls end_recipe(...).

Test Manifest

Browse what’s testable for a system without running anything:

pretorin stig applicable --system "My System"
pretorin cci chain ac-2 --system "My System"

The MCP equivalent is get_test_manifest — the calling agent uses this to figure out which rules apply before running a scan.

Authoring Your Own Scanner Recipe

Scanner recipes are just recipes. If you have an internal tool that produces STIG-style results, scaffold a recipe and drop it in ~/.pretorin/recipes/<id>/ (user) or <repo>/.pretorin/recipes/<id>/ (team):

pretorin recipe new my-scanner --location user

See the Authoring recipes docs for the full contract.

Submitting Results Manually

If you have raw scan output and want to push it without running the recipe flow, the platform endpoint is exposed directly:

submit_test_results(system_id, results)

via MCP. Each result needs rule_id, benchmark_id, status, and tool metadata — see the STIG / CCI workflow for schema details.

Review Commands

The review command group helps you review local code against framework controls.

Run a Review

# Uses active context for system/framework
pretorin review run --control-id ac-02 --path ./src

# Explicit system/framework override
pretorin review run --control-id ac-02 --framework-id nist-800-53-r5 --system "My System" --path ./src

# Local-only mode — saves control context as markdown, no system required
pretorin review run --control-id ac-02 --framework-id fedramp-moderate --local

# Custom output directory for local artifacts
pretorin review run --control-id ac-02 --framework-id fedramp-moderate --local --output-dir ./compliance-notes

pretorin review run does not push narratives or evidence to the platform. In normal mode, it fetches control requirements and current implementation details for comparison. In --local mode, it writes a markdown review artifact under .pretorin/reviews/ or the path specified with --output-dir.

Options

OptionDescription
--control-id / -cControl ID to review against (required)
--framework-id / -fFramework ID (uses active context if omitted)
--system / -sSystem name or ID (uses active context if omitted)
--path / -pPath to files to review (default: .)
--localForce local-only output (no API calls for implementation data)
--output-dir / -oOutput directory for local review artifacts (default: .pretorin/reviews)

Check Implementation Status

pretorin review status --control-id ac-02
pretorin review status --control-id sc-07 --framework-id fedramp-moderate --system my-system
OptionDescription
--control-id / -cControl ID (required)
--system / -sSystem name or ID (uses active context if omitted)
--framework-id / -fFramework ID (uses active context if omitted)

Configuration

The config command group manages CLI configuration stored at ~/.pretorin/config.json.

List Configuration

$ pretorin config list
         Pretorin Configuration
┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Key     ┃ Value           ┃ Source      ┃
┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ api_key │ pretorin...9v7o │ config file │
└─────────┴─────────────────┴─────────────┘

Config file: /home/user/.pretorin/config.json

Get a Config Value

pretorin config get api_key

Set a Config Value

pretorin config set api_base_url https://custom-api.example.com/api/v1

Show Config File Path

$ pretorin config path
/home/user/.pretorin/config.json

Config File Format

The config file is JSON:

{
  "api_key": "pretorin_...",
  "api_base_url": "https://platform.pretorin.com/api/v1/public",
  "platform_api_base_url": "https://platform.pretorin.com/api/v1/public",
  "model_api_base_url": "https://platform.pretorin.com/api/v1/public/model",
  "active_system_id": "sys-abc123",
  "active_system_name": "My Application",
  "active_framework_id": "nist-800-53-r5",
  "disable_update_check": false
}

Configuration Keys

KeyDescription
api_keyPretorin API key
api_base_urlPlatform REST API URL
platform_api_base_urlPlatform API base URL
model_api_base_urlModel API URL for agent runtime
openai_api_keyOptional model key for agent runtime
active_system_idCurrently active system ID
active_system_nameCached display name for the active system
active_framework_idCurrently active framework ID
disable_update_checkDisable passive update notifications

Environment Variable Overrides

Environment variables take precedence over config file values. See Environment Variables for the full list.

Customer-Managed Platforms

For air-gapped or customer-managed platform installs, point the CLI at the customer platform public API instead of hosted Pretorin:

pretorin config set platform_api_base_url https://<platform-host>/api/v1/public
pretorin config set model_api_base_url https://<platform-host>/api/v1/public/model

See Customer-Managed Air-Gapped Installs for the full setup and troubleshooting guide.

Complete Command Reference

Global Options

OptionDescription
--jsonJSON output mode for scripting and AI agents
--version, -VShow version and exit
--helpShow command help

Root Commands

CommandDescription
pretorin loginAuthenticate with the Pretorin API (--api-key/-k, --api-url)
pretorin logoutClear stored credentials
pretorin whoamiDisplay authentication status
pretorin versionShow CLI version
pretorin update [VERSION]Update to latest version, or a specific version
pretorin mcp-serveStart the MCP server (stdio transport)
pretorin mcp-smoke-testSmoke-test the cross-harness MCP tool surface (check_context, list_tools, get_instructions, get_workflow schema bundling); exits 1 on failure

Framework Commands

CommandDescription
pretorin frameworks listList all frameworks
pretorin frameworks get <id>Get framework details
pretorin frameworks families <id>List control families
pretorin frameworks family <fw> <family>Get control family details
pretorin frameworks controls <id> [FAMILY_ID]List controls (--family/-f, --limit/-n)
pretorin frameworks control <fw> <ctrl>Get control details (--brief/-b)
pretorin frameworks metadata <id>Get per-control framework metadata
pretorin frameworks submit-artifact <file>Submit a compliance artifact JSON file

Custom Frameworks

Subcommands of pretorin frameworks for authoring, validating, and uploading custom or forked frameworks. See Custom Frameworks for the full authoring workflow.

CommandDescription
pretorin frameworks init-custom <framework_id>Scaffold a minimal valid unified.json (--title/-t, --output/-o, --force/-f)
pretorin frameworks validate-custom <file>Validate a unified.json artifact against the bundled JSON Schema
pretorin frameworks build-custom <input>Normalize a source catalog (unified, OSCAL, or known custom) into uploadable unified.json (--framework-id/-f required, --output/-o, --force)
pretorin frameworks upload-custom <file>Upload a unified.json artifact as a draft revision (--framework-id/-f, --version-label/-v, --publish)
pretorin frameworks fork-framework <source_id> <new_id>Create a linked-fork draft from an upstream framework (--version-label/-v)
pretorin frameworks rebase-fork <framework_id>Create a rebase draft for a fork against the latest upstream revision (--version-label/-v)
pretorin frameworks revisions <framework_id>List all draft and published revisions for a framework
pretorin frameworks export-oscal <file>Regenerate an OSCAL catalog from a unified.json artifact (--output/-o, --force)

Context Commands

CommandDescription
pretorin context listList systems and frameworks with progress
pretorin context setSet active system/framework context (--system/-s, --framework/-f, --no-verify)
pretorin context showDisplay and validate current active context (--quiet/-q, --check)
pretorin context clearClear active context
pretorin context verifyVerify active context with source attestation (--ttl, --quiet/-q)
pretorin context manifestShow resolved source manifest and evaluate against detected sources (--quiet/-q)

Control Commands

CommandDescription
pretorin control status <ctrl> <status>Start/reopen control authoring; status must be in_progress (--framework-id/-f, --system/-s)
pretorin control context <ctrl>Get rich control context with AI guidance (--framework-id/-f, --system/-s)

Evidence Commands

CommandDescription
pretorin evidence create <ctrl> <fw>Create a local evidence file (--name/-n, --description/-d, --artifact-content, --type/-t)
pretorin evidence listList local evidence files (--framework/-f)
pretorin evidence pushPush local evidence to the platform (--dry-run)
pretorin evidence searchSearch platform evidence (--control-id/-c, --framework-id/-f, --system/-s, --limit/-n)
pretorin evidence upsert <ctrl> <fw>Find-or-create evidence and link it (--name/-n, --description/-d, --artifact-content, --type/-t, --system/-s, --code-file, --code-lines, --code-repo, --code-commit)
pretorin evidence upload <file> <ctrl> <fw>Upload a file as evidence (--name/-n, --type/-t, --description/-d, --system/-s)
pretorin evidence link <evidence_id> <ctrl>Link evidence to a control (--framework-id/-f, --system/-s)
pretorin evidence link-cci <evidence_id> <cci_implementation_id>Link evidence to a per-system CCI implementation row (--system/-s, --override-system-mismatch, --override-reason)
pretorin evidence link-stig <evidence_id> <stig_rule_id>Link evidence to a STIG rule workflow (lazy-creates the row) (--system/-s, --override-system-mismatch, --override-reason)
pretorin evidence mark-current <evidence_id>Re-affirm evidence freshness; bumps expires_at by the refresh cadence and resolves any expiring/expired monitoring events (--system/-s)
pretorin evidence validate <evidence_id>Compare recorded source-material hash before re-verifying or replacing a drifted Markdown artifact (--system/-s, --source-root)
pretorin evidence delete <evidence_id>Delete an evidence item (--system/-s, --framework-id/-f, --yes/-y)

Narrative Commands

CommandDescription
pretorin narrative create <ctrl> <fw>Create a local narrative file (--content/-c, --name/-n, --ai-generated)
pretorin narrative listList local narrative files (--framework/-f)
pretorin narrative pushPush local narratives to the platform (--dry-run)
pretorin narrative push-file <ctrl> <fw> <sys> <file>Push a single narrative file to the platform
pretorin narrative get <ctrl> <fw>Get current control narrative (--system/-s)

Notes Commands

CommandDescription
pretorin notes create <ctrl> <fw>Create a local note file (--content/-c, --name/-n)
pretorin notes list [ctrl] [fw]List notes — platform (--system/-s) or local (--local, --framework/-f)
pretorin notes pushPush local notes to the platform (--dry-run)
pretorin notes add <ctrl> <fw>Add a note directly on the platform (--content/-c, --system/-s)
pretorin notes resolve <ctrl> <fw> <note_id>Resolve or reopen a control note (--system/-s, --resolution-note/--justification, --reopen, --content/-c, --pinned)

Monitoring Commands

CommandDescription
pretorin monitoring pushPush a monitoring event (--system/-s, --framework/-f, --title/-t, --event-type, --severity, --control/-c, --description/-d, --update-control-status)

Policy Commands

CommandDescription
pretorin policy listList org policies available for questionnaire work
pretorin policy showShow persisted policy questionnaire state (--policy)
pretorin policy populateDraft policy questionnaire updates from the current workspace (--policy, --path/-p, --apply)

Scope Commands

CommandDescription
pretorin scope showShow scope questionnaire state and review findings (--system/-s, --framework-id/-f)
pretorin scope populateDraft scope questionnaire updates from the current workspace (--system/-s, --framework-id/-f, --path/-p, --apply)

Agent Commands

CommandDescription
pretorin agent run "<task>"Run a compliance task (--skill/-s, --model/-m, --base-url, --working-dir/-w, --no-stream, --legacy, --max-turns, --no-mcp)
pretorin agent doctorValidate Codex runtime setup
pretorin agent installDownload the pinned Codex binary
pretorin agent versionShow pinned Codex version and install status
pretorin agent skillsList available agent skills
pretorin agent mcp-listList configured MCP servers for the agent
pretorin agent mcp-add <name> <transport> <cmd>Add an MCP server configuration (--arg/-a, --scope)
pretorin agent mcp-remove <name>Remove an MCP server configuration

Skill Commands

CommandDescription
pretorin skill installInstall the Pretorin skill for AI coding agents (--agent/-a, --path/-p, --force/-f)
pretorin skill uninstallUninstall the Pretorin skill (--agent/-a, --path/-p)
pretorin skill statusShow installation status of the Pretorin skill
pretorin skill list-agentsList all known agents and their skill directories

Review Commands

CommandDescription
pretorin review runReview code against a control (--control-id/-c, --framework-id/-f, --system/-s, --path/-p, --local, --output-dir/-o)
pretorin review statusCheck implementation status for a control (--control-id/-c, --framework-id/-f, --system/-s)

Config Commands

CommandDescription
pretorin config listList all configuration
pretorin config get <key>Get a config value
pretorin config set <key> <value>Set a config value
pretorin config pathShow config file path

Campaign Commands

CommandDescription
pretorin campaign controlsRun bulk control narrative/evidence campaign (--system, --framework-id, --mode, --family, --controls, --all-controls, --artifacts, --review-job, --concurrency, --max-retries, --checkpoint, --apply, --output)
pretorin campaign policyRun bulk policy questionnaire campaign (--mode, --policies, --all-incomplete, --system, --concurrency, --max-retries, --checkpoint, --apply, --output)
pretorin campaign scopeRun bulk scope questionnaire campaign (--system, --framework-id, --mode, --concurrency, --max-retries, --checkpoint, --apply, --output)
pretorin campaign statusShow campaign progress from a checkpoint file (--checkpoint, --output)

Campaign Modes

DomainModeDescription
controlsinitialDraft new narratives and evidence for controls
controlsnotes-fixAddress platform notes on existing controls
controlsreview-fixFix findings from a family review job
policyanswerGenerate answers for policy questions
policyreview-fixFix findings from a policy review
scopeanswerGenerate answers for scope questions
scopereview-fixFix findings from a scope review

Vendor Commands

CommandDescription
pretorin vendor listList all vendors in the organization
pretorin vendor create <name>Create a vendor (--type/-t, --description/-d, --authorization-level/-a)
pretorin vendor get <vendor_id>Get vendor details
pretorin vendor update <vendor_id>Update vendor fields (--name, --description/-d, --type/-t, --authorization-level/-a)
pretorin vendor delete <vendor_id>Delete a vendor (--force/-f)
pretorin vendor upload-doc <vendor_id> <file>Upload a vendor evidence document (--name/-n, --description/-d, --attestation-type)
pretorin vendor list-docs <vendor_id>List documents linked to a vendor

Vendor Types

csp, saas, managed_service, internal

Risk Commands

Manage a system’s risk register. Risks are system-scoped except for the org-level risk library subgroup. See Risk Management for the full workflow.

CommandDescription
pretorin risk list <system_id>List risks for a system (--category, --risk-level, --status)
pretorin risk show <system_id> <risk_id>Show full risk including eager-loaded artifact links
pretorin risk create <system_id>Create a custom risk (--title, --category, --description, --treatment, --treatment-plan, --treatment-due-date, --framework, --suggested-control-family repeatable)
pretorin risk seed <system_id>Seed risks from library templates (--framework, --template-id repeatable)
pretorin risk update <system_id> <risk_id>Update fields including mitigation (--title, --description, --category, --likelihood, --impact, --owner-id, --status, --review-frequency-days, --treatment, --treatment-plan, --treatment-due-date)
pretorin risk link add <system_id> <risk_id>Attach an artifact (--link-type, exactly one of --control + --framework, --evidence, --finding, --vendor, --monitoring-event)
pretorin risk link rm <system_id> <risk_id> <link_id>Remove a risk artifact link
pretorin risk refresh-summary <system_id> <risk_id>Re-score risk and trigger best-effort AI summary regeneration
pretorin risk library listBrowse the org-level risk template library (--category)

Risk Treatment Values

mitigate, accept, transfer, avoid

contributes_to_risk, mitigates_risk, evidence_of_risk

STIG Commands

CommandDescription
pretorin stig listList STIG benchmarks (--technology-area/-t, --product/-p, --limit/-l)
pretorin stig show <stig_id>Show STIG benchmark detail with severity breakdown
pretorin stig rules <stig_id>List rules for a benchmark (--severity/-s, --cci, --limit/-l)
pretorin stig applicableShow applicable STIGs for the active system (--system/-s)
pretorin stig inferAI-infer applicable STIGs from system profile (--system/-s)

CCI Commands

CommandDescription
pretorin cci listList CCIs (--control/-c, --status, --limit/-l)
pretorin cci show <cci_id>Show CCI detail with linked SRGs and STIG rules (e.g., CCI-000015)
pretorin cci chain <control_id>Full traceability chain: Control -> CCIs -> SRGs -> STIG rules (--system/-s)
pretorin cci impl <cci_uuid>Show the per-system CCI implementation row (status, narrative, evidence_ids, eMASS fields) — 404 means uninitialized (--system/-s)

Recipe Commands

Recipes are markdown + script playbooks the calling AI agent executes. See Recipes for authoring guidance.

CommandDescription
pretorin recipe listList all loaded recipes with id, name, tier, author, and source path (--tier, --source)
pretorin recipe show <recipe_id>Display a recipe’s manifest, body, and (with --sources) all loader paths
pretorin recipe new <recipe_id>Scaffold a new recipe directory (--location user/project/builtin, --author, --name)
pretorin recipe validate <recipe_id>Validate a recipe’s manifest, scripts, and description quality (--path for path-based override)
pretorin recipe run <recipe_id>Run a recipe’s script locally for testing (--script/-s, --param/-p repeatable, --path, --system, --framework, --no-context)

Scanning

The legacy pretorin scan command was removed when the recipes system landed. Scanning now happens through built-in recipes that the calling AI agent invokes via MCP. See STIG Scanning for the recipe-based workflow.

Recipe IDWrapsCLI requirement
inspec-baselineChef InSpecinspec
openscap-baselineOpenSCAPoscap
cloud-aws-baselineAWS APIsaws
cloud-azure-baselineAzure APIsaz
manual-attestationHuman attestation

Deprecated Commands

CommandDescription
pretorin harness initDeprecated: initialize harness config
pretorin harness doctorDeprecated: validate harness setup
pretorin harness run "<task>"Deprecated: run task through harness backend

MCP Integration Overview

The Pretorin CLI includes a built-in Model Context Protocol (MCP) server that enables AI assistants to access compliance framework data directly during conversations.

Why MCP?

The Model Context Protocol allows AI assistants to:

  • Access real-time data — Query the latest compliance frameworks, controls, and requirements
  • Understand context — Get detailed control guidance and related controls for better recommendations
  • Reduce hallucination — Work with authoritative compliance data instead of training knowledge
  • Streamline workflows — No need to copy-paste control requirements or switch between tools

How It Works

The MCP server communicates via stdio (standard input/output) using JSON-RPC messages. When you start it with pretorin mcp-serve, your AI tool connects and gains access to 111 static compliance tools plus 11 dynamic recipe-script tools (122 total).

┌──────────────┐     stdio      ┌──────────────┐     HTTPS     ┌──────────────┐
│   AI Agent   │◄──────────────►│   Pretorin   │◄─────────────►│   Pretorin   │
│ (Claude,     │   JSON-RPC    │   MCP Server  │              │   Platform   │
│  Cursor,     │               │               │              │              │
│  Codex)      │               │               │              │              │
└──────────────┘               └──────────────┘              └──────────────┘

Scope

Scoped compliance execution tools on the MCP server run inside exactly one system + framework pair at a time. Set the active scope with pretorin context set, or pass both values explicitly. If a request spans multiple frameworks or systems, split it into separate runs.

Before running write-heavy MCP workflows from a shell or GUI wrapper, prefer validating the stored scope with:

pretorin context show --quiet --check

Tool Categories

The 111 static MCP tools are organized into categories. An additional 11 per-recipe-script tools (recipe_<id>__<script>) are registered dynamically from the recipe registry.

CategoryToolsAccess
Task Routing4Read-only, all users
Framework & Control Reference8Read-only, all users
Systems6Read-only
Evidence Management8Read/Write, requires beta
Implementation Context10Read/Write, requires beta
Monitoring1Write, requires beta
Workflow State & Analytics4Read-only
Family Operations4Read/Write, requires beta
Scope Workflow7Read/Write, requires beta
Policy Workflow10Read/Write, requires beta
Campaign Operations6Read/Write, requires beta
Risk Management9Read/Write, requires beta
Vendor Management8Read/Write, requires beta
Inheritance & Responsibility6Read/Write, requires beta
STIG & CCI13Read-only / Write mix
Recipes & Workflows7Read-only

See Tool Reference for the complete list.

Quick Setup

# 1. Install
uv tool install pretorin

# 2. Authenticate
pretorin login

# 3. Add to your AI tool (example: Claude Code)
claude mcp add --transport stdio pretorin -- pretorin mcp-serve

See Setup Guides for other AI tools.

Example Conversations

Getting Started with a Framework

You: What compliance frameworks are available for government systems?

Claude: Uses list_frameworks — I can see several frameworks available including NIST 800-53 Rev 5, NIST 800-171, and FedRAMP at various impact levels…

Understanding a Control

You: I need to implement Account Management for our FedRAMP Moderate system. What does it require?

Claude: Uses get_control and get_control_references — Account Management requires organizations to manage system accounts including identifying account types, establishing conditions for membership, and specifying authorized users…

Control Family Overview

You: Give me an overview of the Audit controls in NIST 800-53

Claude: Uses list_controls with family filter — The Audit and Accountability family contains controls for audit events, content, storage, review, and reporting…

MCP Setup Guides

Prerequisites

Install and authenticate the Pretorin CLI:

uv tool install pretorin
pretorin login

Install the Pretorin Skill

The skill teaches your AI agent how to use MCP tools for compliance workflows — control ID formats, narrative authoring rules, gap analysis methodology, and more. Install it before setting up MCP:

pretorin skill install                # both Claude Code and Codex CLI
pretorin skill install --agent claude # Claude Code only
pretorin skill install --agent codex  # Codex CLI only
pretorin skill status                 # check what's installed

The skill is copied to ~/.claude/skills/pretorin/ and/or ~/.codex/skills/pretorin/ and auto-discovered by each agent.

Claude Code

Quick setup — run a single command:

claude mcp add --transport stdio pretorin -- pretorin mcp-serve

This registers the server for your current project. To make it available across all your projects, add --scope user.

Team setup — add a .mcp.json file to your project root so every team member gets the server automatically:

{
  "mcpServers": {
    "pretorin": {
      "type": "stdio",
      "command": "pretorin",
      "args": ["mcp-serve"]
    }
  }
}

Claude Code detects the file automatically.

Claude Desktop

Add to your Claude Desktop configuration file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "pretorin": {
      "command": "pretorin",
      "args": ["mcp-serve"]
    }
  }
}

Restart Claude Desktop after saving.

Cursor

Add to ~/.cursor/mcp.json:

{
  "mcpServers": {
    "pretorin": {
      "command": "pretorin",
      "args": ["mcp-serve"]
    }
  }
}

Restart Cursor after saving.

OpenAI Codex CLI

Add to ~/.codex/config.toml:

[mcp_servers.pretorin]
command = "pretorin"
args = ["mcp-serve"]

If you installed Pretorin with uv tool install or pipx, prefer pinning the absolute path from command -v pretorin to avoid PATH drift between shells and GUI apps.

Windsurf

Add to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "pretorin": {
      "command": "pretorin",
      "args": ["mcp-serve"]
    }
  }
}

Restart Windsurf after saving.

Other MCP Clients

The Pretorin MCP server follows the standard Model Context Protocol and works with any MCP-compatible client. The server communicates via stdio.

To test the server manually:

pretorin mcp-serve

The server accepts JSON-RPC messages on stdin and responds on stdout.

PATH Considerations

If your AI tool can’t find the pretorin command, use the full path:

# Find the full path
command -v pretorin

Then use that path in your configuration:

{
  "mcpServers": {
    "pretorin": {
      "command": "/home/user/.local/bin/pretorin",
      "args": ["mcp-serve"]
    }
  }
}

This is especially important for uv tool and pipx installations where the binary may not be on the PATH available to GUI applications.

Before debugging scoped MCP write failures, validate the active CLI scope:

pretorin context show --quiet --check

MCP Tool Reference

The MCP server provides 122 tools organized by category.

Cross-Harness Discovery (RFC #113)

Three small tools let any MCP client — Claude Code, Cursor, Codex, vanilla Agents SDK — ground itself and reach the routing layer without depending on the initialize instructions block. Call them at session start.

check_context

Cheap, unauthenticated probe of session grounding. Reads local CLI config only — no platform calls. Returns:

{
  "connected": true,
  "active_system": {"id": "sys-1", "name": "Primary"},
  "active_framework_id": "nist-800-53-r5",
  "suggested_next": "Ready. Call `start_task` with an `intent_verb` to route the user's request to a workflow.",
  "pending_attention": {}
}

suggested_next is deterministic and tells the agent exactly what to do given current state — call pretorin login, call list_systems, set a framework, or proceed with start_task.

Parameters: None.

When to call: Once at session start. Always safe; never makes a platform call.


list_tools

Compact catalog of every available tool. Returns one short record per tool:

{
  "total": 122,
  "tier_counts": {"default": 8, "workflow": 12, "reference": 91, "recipe": 11},
  "tools": [
    {"name": "check_context", "purpose": "Cheap, unauthenticated probe of session grounding", "tier": "default", "requires_workflow": false},
    ...
  ]
}

The full payload fits in roughly 100 lines — far smaller than fetching every tool’s inputSchema. Tier classification:

TierMeaning
defaultAlways advertised. Minimum surface to ground a session and route a task.
referenceRead-only browsing of frameworks, controls, systems, recipes. Safe without prior routing.
workflowRequires prior start_task context. Calling without it raises WorkflowRoutingError.
recipeDynamic per-recipe-script tools (recipe_<id>__<script>).

Parameters: None.

When to call: Once at session start (or anytime “what’s available?” comes up).


get_instructions

Returns the server’s routing instructions as a regular tool response. Mirrors the text the MCP initialize handshake advertises in the instructions field, for harnesses (Cursor, Codex, vanilla Agents SDK) that don’t render that text to the agent.

Parameters: None.

When to call: If the agent didn’t receive routing rules via the initialize handshake, or wants to re-read them mid-session.


Routing Errors (errors-as-instructions)

Workflow-tier tools require an active routing context — either an active system/framework set via pretorin context set, or the context that start_task resolves. Calling a workflow write without context returns a structured error payload, not just plain text:

{
  "error": "workflow_required",
  "message": "No active system/framework context. Call start_task with an intent_verb to route the request to the right workflow, or run `pretorin context set --system <id> --framework <id>` in the terminal.",
  "routing_hint": {
    "reason": "no_active_context",
    "next_action": "call_start_task",
    "missing": ["system_id", "framework_id"],
    "suggested_intent_verb": "collect_evidence"
  }
}

The response carries isError=true so agents that only check the error flag still surface the failure, but the parseable body tells them exactly which start_task call to make. This means routing rules live in the protocol, not the preamble — harnesses that ignore instructions still see the rules in the error payload they have to read anyway.

Evidence and narrative producer writes add one more guardrail: create_evidence, create_evidence_batch, and update_narrative require recipe_context_id from start_recipe. Missing recipe context returns recipe_required with a recipe_gap before scope resolution, so agents converge on start_task → source preflight → start_recipe → write.

suggested_intent_verb is set when the called tool has a registered mapping (see _TOOL_TO_INTENT_VERB in src/pretorin/mcp/server.py). Every tool in WORKFLOW_TIER has one; tools added to that set without a mapping will fail the sync test in CI.


Workflow Schema Bundling

When the agent calls get_workflow, the response includes required_tool_schemas — the full MCP Tool definitions for every tool the workflow body references:

{
  "id": "single-control",
  "manifest": { "..." },
  "body": "## Single Control Update\n\n...",
  "required_tool_schemas": [
    {"name": "create_evidence", "description": "...", "inputSchema": {...}},
    {"name": "update_narrative", "description": "...", "inputSchema": {...}},
    ...
  ]
}

Single round trip equips the agent. Schemas are response data — agents can read them as reference regardless of whether their harness supports dynamic tool registration.


Telemetry

The server emits structured telemetry events to stderr so MCP hosts can capture them through their existing log pipeline. Each event is one line:

PRETORIN_TELEMETRY_EVENT {"ts": 1747249200.12, "event_type": "workflow_routing_required", "tool": "create_evidence", "reason": "no_active_context", "missing": ["system_id", "framework_id"], "suggested_intent_verb": "collect_evidence"}
PRETORIN_TELEMETRY_EVENT {"ts": 1747249200.99, "event_type": "recipe_required", "tool": "create_evidence", "reason": "agent write attempted without recipe_context_id", "requested_evidence_type": "code_snippet", "suggested_next": "call_start_task_then_start_recipe"}

Events emitted:

event_typeWhen
start_task_succeededA start_task call completed successfully (the canonical-path signal).
workflow_routing_requiredA write tool raised WorkflowRoutingError (the bypass signal).
recipe_requiredAn evidence or narrative producer write returned the structured recipe-context guardrail before scope resolution.

For the post-recipe producer path, compute the combined bypass rate as:

(workflow_routing_required + recipe_required) / start_task_succeeded

When your log pipeline can group by MCP session, count only bypass events that did not have the expected preceding canonical calls in that same session: start_task for workflow_routing_required, and start_task → source preflight → start_recipe for recipe_required. The recipe_required event carries only non-content shape data such as evidence type, item count, narrative length, and suggested_next; it must not include artifact content, narrative text, evidence ids, control ids, or source excerpts.

Per RFC #113 and issue #121, this combined bypass data gates any phase-4 default-surface cull or capability-negotiation work.

Opt-out: set PRETORIN_MCP_TELEMETRY_DISABLED=1 in the environment before starting the MCP server. Local-only — no PII or content leaves the user’s machine.


Common Write-Side Parameters

Most write-side tools (evidence, narratives, notes, control status, monitoring events) accept two shared audit-trail flags. Both default to false and should only be set when the calling agent has a deliberate reason to bypass the corresponding guardrail:

  • allow_scope_override — Permit a write outside the active system/framework context.
  • allow_unverified_sources — Permit a write when source attestation shows a mismatch.

Where listed below as “(audit-trail flags)”, a tool accepts both fields. MCP agent evidence and narrative writes require recipe_context_id returned by start_recipe; writes without one return a structured recipe_required error.

Task Routing

start_task

Route a user prompt to the right workflow. Call this FIRST whenever the user references compliance work (a control, system, framework, questionnaire, or campaign). The calling agent extracts entities from the user prompt and supplies them as structured args; pretorin applies deterministic rules to pick a workflow and bundles the platform read-state (workflow_state, compliance_status, pending items) into the response. The agent then reads the selected workflow’s body via get_workflow and follows it.

The one exception is pure reference questions (“show me AC-2”, “list frameworks”) — those go directly to the read-side tools without start_task.

Parameters:

  • entities (required) — Structured entities extracted from the user prompt. Required sub-fields: intent_verb (one of work_on, collect_evidence, draft_narrative, answer, campaign, inspect_status) and raw_prompt (original verbatim text). Optional: system_id, framework_id, control_ids, scope_question_ids, policy_question_ids.
  • active_system_id (optional) — The user’s active CLI context system_id, if any. Used to detect cross-system writes.
  • active_framework_id (optional) — The user’s active CLI context framework_id, if any. Used by inspect_status when the user asks for current status without naming a framework.
  • skip_inspect (optional) — Skip the server-side platform reads when the calling agent already has fresh state. Default: false

Returns: Selected workflow id, resolved scope (system/framework/items), platform-state bundle, and suggested_capture_plan for source/recipe preflight when the workflow produces evidence or narratives.


Framework & Control Reference

These tools are read-only and available to all authenticated users.

list_frameworks

List all available compliance frameworks.

Parameters: None

Returns: List of frameworks with ID, title, version, tier, and control counts.


get_framework

Get detailed metadata about a specific framework including AI context (purpose, target audience, regulatory context).

Parameters:

  • framework_id (required) — e.g., nist-800-53-r5, fedramp-moderate

Returns: Framework details including description, version, OSCAL version, and dates.


list_control_families

List control families for a framework with AI context (domain summary, risk context, implementation priority).

Parameters:

  • framework_id (required)

Returns: List of control families with ID, title, class, and control count.


list_controls

List controls for a framework, optionally filtered by family.

Parameters:

  • framework_id (required)
  • family_id (optional) — Family IDs are slugs like access-control, not short codes. CMMC families include a level suffix (e.g., access-control-level-2).

Returns: List of controls with ID, title, and family.


get_control

Get detailed control information including AI guidance (summary, control intent, evidence expectations, implementation considerations, common failures, complexity).

Parameters:

  • framework_id (required)
  • control_id (required) — NIST/FedRAMP: zero-padded (ac-01). CMMC: dotted (AC.L2-3.1.1).

Returns: Control details including parameters, parts, and enhancement count.


get_controls_batch

Get detailed control data for many controls in one framework-scoped request.

Parameters:

  • framework_id (required)
  • control_ids (optional) — List of canonical control IDs; omit to retrieve all controls in the framework

Returns: Full control detail records for the requested controls.


get_control_references

Get reference information including statement, guidance, objectives, and related controls.

Parameters:

  • framework_id (required)
  • control_id (required)

Returns: Statement, guidance, objectives, parameters, and related controls.


Systems

list_systems

List systems in the current organization.

Parameters: None

Returns: System IDs, names, and summary metadata.


get_cli_status

Return the local Pretorin CLI version status, including update availability and upgrade guidance for MCP hosts and agents.

Parameters:

  • force (optional) — Bypass local cache and re-check PyPI for the latest version. Default: false

Returns: Current version, latest version, update available flag, and upgrade instructions.


get_source_manifest

Get the resolved source manifest for a system and evaluate it against currently detected sources. Shows which external sources (git, cloud, HRIS, etc.) are required, recommended, or optional, and whether each is currently satisfied.

Parameters:

  • system_id (optional) — System ID or name

Returns: Source manifest with per-source satisfaction status. Returns null manifest if none is configured.


get_system

Get system metadata including attached frameworks and security impact level.

Parameters:

  • system_id (required) — System ID or name

Returns: System metadata.


list_connected_sources

List source connections bound to a system for recipe preflight. Older platform deployments return availability: "unknown" instead of blocking recipe discovery.

Parameters:

  • system_id (optional) — Defaults to active scope

Returns: Source availability plus normalized source rows (kind, connected, identifier, binding_role, last_checked, probe_status).


get_compliance_status

Get framework progress and implementation posture for a system.

Parameters:

  • system_id (required) — System ID or friendly system name

Returns: Framework status summaries and progress metrics.


Evidence Management

search_evidence

Search current evidence items.

Parameters:

  • system_id (optional) — System ID or friendly system name. When omitted, the active CLI scope is used if available.
  • control_id (optional) — Filter by control
  • framework_id (optional) — Filter by framework
  • limit (optional) — Maximum number of results. Default: 20

Returns: Matching evidence items. The server uses the same compatibility search path as the CLI for deployments that only expose system-scoped evidence routes.


create_evidence

Upsert evidence on the platform (find-or-create by default). If dedupe is true, exact matching evidence in the active system/framework scope is reused; otherwise a new record is created.

Parameters:

  • name (required)
  • description (required) — Short human summary of what the evidence demonstrates
  • artifact_content (required) — Markdown body containing the actual evidence artifact
  • evidence_type (required) — Must be one of the canonical evidence types
  • system_id (optional) — Defaults to active scope
  • control_id (optional)
  • framework_id (optional)
  • dedupe (optional) — Default: true
  • code_file_path (optional) — Path to source file (relative to workspace root)
  • code_line_numbers (optional) — Line range (e.g., 10-25)
  • code_snippet (optional) — Relevant code excerpt
  • code_repository (optional) — Git repository URL
  • code_commit_hash (optional) — Git commit hash
  • source_uri, source_label, source_locator, source_excerpt, capture_method (optional) — Structured provenance inputs. If omitted, the handler derives safe defaults from code context or active scope.
  • recipe_context_id (required) — Active recipe execution context from start_recipe; evidence is stamped producer_kind='recipe' automatically.
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns:

  • evidence_id
  • created — true if new, false if reused
  • linked — whether control/system link succeeded
  • match_basisexact_name_desc_type_control_framework or none

create_evidence_batch

Create and link multiple evidence items within one system/framework scope in a single request.

Parameters:

  • items (required) — Array of evidence payloads. Each item: name, description, artifact_content, control_id, evidence_type (required); optional relevance_notes, code_file_path, code_line_numbers, code_snippet, code_repository, code_commit_hash, source_uri, source_label, source_locator, source_excerpt, capture_method.
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • recipe_context_id (required) — Active recipe execution context from start_recipe; every item in the batch is stamped producer_kind='recipe'. All items share the same context — per-item context variation is not supported in v1.
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: Batch creation summary with per-item results and created evidence IDs.


Link an existing evidence item to a control.

Parameters:

  • evidence_id (required)
  • control_id (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional)
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: Link confirmation.


Link an existing evidence item to a per-system CCI implementation row. Use when you already have the CCI implementation UUID (from get_cci_implementation or get_cci_status).

Parameters:

  • evidence_id (required)
  • cci_implementation_id (required)
  • system_id (optional) — Defaults to active scope
  • override_system_mismatch (optional, default false) — Permit cross-system attachment
  • override_reason (optional) — Required when override_system_mismatch is true

Returns: Link confirmation with link_summary.cci_implementation_id.


Link an existing evidence item to a STIG rule workflow. Lazy-creates the workflow row if none exists yet for (system, stig_rule). Use to attach remediation proof, mitigating-control documentation, or waiver-justification artifacts to a failing rule.

Parameters:

  • evidence_id (required)
  • stig_rule_id (required) — STIG catalog rule UUID
  • system_id (optional) — Defaults to active scope
  • override_system_mismatch (optional, default false)
  • override_reason (optional) — Required when override_system_mismatch is true

Returns: Link confirmation with link_summary.stig_rule_workflow_id.


upload_evidence

Upload a file as evidence to the platform (system-scoped, requires WRITE access).

Parameters:

  • file_path (required) — Absolute path to the file to upload
  • name (required) — Evidence name
  • system_id (optional) — Defaults to active scope
  • evidence_type (optional) — Default: other
  • description (optional) — Evidence description
  • control_id (optional)
  • framework_id (optional)
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: Uploaded evidence record.


delete_evidence

Delete an evidence item from the platform (system-scoped, requires WRITE access).

Parameters:

  • evidence_id (required) — The evidence item ID to delete
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • allow_scope_override (optional, audit-trail flag)

Returns: Deletion confirmation.


get_narrative

Get the current narrative record for a control.

Parameters:

  • control_id (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: Narrative text, status, and AI confidence metadata.


Implementation Context

get_control_context

Get rich context for a control including AI guidance, statement, objectives, scope status, and current implementation details.

Parameters:

  • control_id (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope

Returns: Combined control metadata and implementation details.


get_scope

Get system scope and policy information including excluded controls and Q&A responses.

Parameters:

  • system_id (required)
  • framework_id (required)

Returns: Scope narrative, excluded controls, Q&A responses, and scope status.


patch_scope_qa

Apply partial scope questionnaire updates for a system/framework.

Parameters:

  • system_id (required)
  • framework_id (required)
  • updates (required) — Non-empty list of {question_id, answer} objects

Returns: Updated scope questionnaire state, including the saved responses.


list_org_policies

List organization policies available for questionnaire work.

Parameters: None

Returns: Policy summaries including id, name, template linkage, and questionnaire status.


get_org_policy_questionnaire

Get the canonical questionnaire state for one organization policy.

Parameters:

  • policy_id (required)

Returns: Policy metadata, saved answers, and the merged template/question set when available.


patch_org_policy_qa

Apply partial questionnaire updates for one organization policy.

Parameters:

  • policy_id (required)
  • updates (required) — Non-empty list of {question_id, answer} objects

Returns: Updated organization policy questionnaire state.


get_control_implementation

Get implementation details for a control in a system.

Parameters:

  • control_id (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: Current status, narrative, evidence count, and notes metadata.


get_control_notes

Get notes for a control implementation.

Parameters:

  • control_id (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional)

Returns: Note list with total count.


update_narrative

Push a narrative text update produced by a narrative recipe.

Parameters:

  • control_id (required)
  • narrative (required) — Must be auditor-ready markdown (no headings, 2+ rich elements, 1+ structural element, no images)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • is_ai_generated (optional) — Default: false
  • recipe_context_id (required) — Active narrative-producing recipe context from start_recipe
  • evidence_ids (required) — Evidence ids cited by the narrative
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: Update confirmation.


add_control_note

Add a note for unresolved gaps or manual follow-up actions. Content is plain text (no markdown validation required).

Parameters:

  • control_id (required)
  • content (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: The created note record.


resolve_control_note

Resolve, unresolve, or update an existing control note. Use this to clear blocking notes so control status can advance. Resolving a note requires a resolution_note justification for the audit trail.

Parameters:

  • control_id (required)
  • note_id (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • is_resolved (optional) — Default: true
  • resolution_note (required when resolving) — Explain why the note can be closed; stored as the resolution audit trail and cascaded to linked RFIs/findings
  • content (optional) — Updated note content
  • is_pinned (optional)
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: The updated note record.


Compliance Updates

update_control_status

Start or reopen authoring for a control.

Parameters:

  • control_id (required)
  • status (required) — in_progress
  • system_id (optional) — Defaults to active scope
  • framework_id (optional)
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

CLI/MCP callers cannot set ready_to_approve, implemented, or not_applicable; those decisions happen in the Pretorin UI by a human.

Returns: Status update confirmation.


push_monitoring_event

Create a monitoring event for a system.

Parameters:

  • title (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • severity (optional) — critical, high, medium, low, info. Default: medium
  • event_type (optional) — security_scan, configuration_change, access_review, compliance_check. Default: security_scan
  • control_id (optional)
  • description (optional)
  • allow_scope_override, allow_unverified_sources (optional, audit-trail flags)

Returns: The created monitoring event.


generate_control_artifacts

Generate read-only AI drafts for a control narrative and evidence-gap assessment.

Parameters:

  • system_id (required)
  • control_id (required)
  • framework_id (required)
  • working_directory (optional) — Local workspace path for code-aware drafting
  • model (optional) — Model override

Returns: Draft narrative text plus evidence-gap analysis. Does not write to the platform.

To persist approved evidence or narrative changes, first open the matching recipe context; create_evidence and update_narrative require recipe_context_id.


Workflow State & Analytics

get_workflow_state

Get the lifecycle state for a system+framework, showing which stage needs work next (scope, policies, controls, evidence).

Parameters:

  • system_id (required)
  • framework_id (required)

Returns: Current workflow stage, completion percentages, and next recommended action.


get_analytics_summary

Get a lightweight system progress snapshot.

Parameters:

  • system_id (required)
  • framework_id (required)

Returns: Scope completion, policy completion, control coverage, and evidence gaps.


get_family_analytics

Get per-family breakdown with narrative coverage, evidence coverage, and status distribution.

Parameters:

  • system_id (required)
  • framework_id (required)

Returns: Per-family metrics.


get_policy_analytics

Get per-policy breakdown with answer completion and review status.

Parameters:

  • policy_id (required)

Returns: Per-policy completion metrics.


Family Operations

get_pending_families

Identify which control families need work.

Parameters:

  • system_id (required)
  • framework_id (required)

Returns: Families with counts of pending vs total controls.


get_family_bundle

Get all controls in one family with status, narrative presence, evidence presence, and note counts.

Parameters:

  • system_id (required)
  • family_id (required)
  • framework_id (required)

Returns: Complete family bundle with per-control details.


trigger_family_review

Trigger AI review of all controls in a family. Takes 2-4 minutes for large families.

Parameters:

  • system_id (required)
  • family_id (required)
  • framework_id (required)

Returns: Review job ID for polling.


get_family_review_results

Poll family review results.

Parameters:

  • system_id (required)
  • job_id (required)

Returns: Aggregated findings with severity, affected control IDs, and recommended fixes.


Scope Workflow

get_pending_scope_questions

Get only unanswered scope questions (lightweight).

Parameters:

  • system_id (required)
  • framework_id (required)

Returns: List of unanswered questions with IDs.


get_scope_question_detail

Get guidance, tips, and example responses for a specific scope question.

Parameters:

  • system_id (required)
  • question_id (required)
  • framework_id (required)

Returns: Question text, guidance, tips, and example answers.


answer_scope_question

Answer one scope question.

Parameters:

  • system_id (required)
  • question_id (required)
  • answer (required)
  • framework_id (required)

Returns: Updated question state.


trigger_scope_generation

Trigger AI generation of scope document from answered questions. By default the same durable job also runs an AI review after generation.

Parameters:

  • system_id (required)
  • framework_id (required)
  • include_review (optional) — Run AI review after generation in the same job. Default: true

Returns: Generation job ID for polling.


trigger_scope_review

Trigger AI review of scope answers.

Parameters:

  • system_id (required)
  • framework_id (required)

Returns: Review job ID for polling.


get_scope_review_results

Poll for structured scope review findings.

Parameters:

  • system_id (required)
  • job_id (required)

Returns: Findings with severity levels and recommended fixes.


Policy Workflow

get_pending_policy_questions

Get only unanswered policy questions.

Parameters:

  • policy_id (required)

Returns: List of unanswered questions.


get_policy_question_detail

Get guidance, tips, and examples for a specific policy question.

Parameters:

  • policy_id (required)
  • question_id (required)

Returns: Question text, guidance, and example answers.


answer_policy_question

Answer one policy question.

Parameters:

  • policy_id (required)
  • question_id (required)
  • answer (required)

Returns: Updated question state.


get_policy_workflow_state

Get per-policy workflow state including completion, generation, and review status.

Parameters:

  • policy_id (required)

Returns: Policy workflow state.


trigger_policy_generation

Trigger AI generation of policy document from answered questions. By default the same durable job also runs an AI review after generation.

Parameters:

  • policy_id (required)
  • system_id (optional) — System ID for scope context
  • include_review (optional) — Run AI review after generation in the same job. Default: true

Returns: Generation job status.


trigger_policy_review

Trigger AI review of policy answers/document.

Parameters:

  • policy_id (required)

Returns: Review job ID for polling.


get_policy_review_results

Poll for structured policy review findings.

Parameters:

  • policy_id (required)
  • job_id (required)

Returns: Findings with severity levels and recommended fixes.


Campaign Operations

Campaigns enable bulk compliance operations with checkpoint persistence and lease-based concurrency.

prepare_campaign

Prepare a workflow-aligned campaign run with a platform state snapshot.

Parameters:

  • domain (required) — controls, policy, or scope
  • mode (required) — Campaign mode for the selected domain
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope
  • family_id (optional) — Target family for control campaigns
  • control_ids (optional) — Explicit control IDs to include
  • all_controls (optional) — Include all controls. Default: false
  • artifacts (optional) — Artifact type: narratives, evidence, or both. Default: both
  • review_job (optional) — Family review job ID for review-fix mode
  • policy_ids (optional) — Explicit policy IDs to include
  • all_incomplete (optional) — Include all incomplete items. Default: false
  • apply (optional) — Apply proposals immediately. Default: false
  • output (optional) — Output format: auto, live, compact, json. Default: json
  • checkpoint_path (optional) — Local checkpoint file path
  • working_directory (optional) — Working directory for executors
  • concurrency (optional) — Parallel execution limit. Default: 4
  • max_retries (optional) — Retry limit per item. Default: 2

Returns: Campaign checkpoint with item list and metadata.


claim_campaign_items

Claim items for drafting with TTL-based leases. Safe for fan-out to multiple agents.

Parameters:

  • checkpoint_path (required) — Local campaign checkpoint path
  • lease_owner (optional) — Stable identifier for the claiming agent
  • max_items (optional) — Number of items to claim. Default: 1
  • lease_ttl_seconds (optional) — Lease time-to-live. Default: 300

Returns: Claimed items with lease metadata.


get_campaign_item_context

Get full item context plus drafting instructions for a claimed item.

Parameters:

  • checkpoint_path (required)
  • item_id (required)

Returns: Control/policy/scope context, current state, and drafting guidance.


submit_campaign_proposal

Submit an external agent’s proposal without applying it to the platform.

Parameters:

  • checkpoint_path (required)
  • item_id (required)
  • proposal (required) — Campaign proposal payload object

Returns: Proposal acceptance confirmation.


apply_campaign

Push stored proposals to the platform.

Parameters:

  • checkpoint_path (required)
  • item_ids (optional) — Subset of item IDs to apply; omit to apply all

Returns: Apply results with per-item status.


get_campaign_status

Get structured campaign status with a stable transcript snapshot.

Parameters:

  • checkpoint_path (required)

Returns: Campaign progress, item states, and transcript.


Risk Management

Risks are system-scoped (except list_risk_library, which is org-level). Mitigation is recorded via update_risk — there is no separate /mitigate endpoint. Control auto-link on create/seed is opt-in: it requires framework_id plus matching ControlImplementation rows on the system.

list_risks

List risks for a system with optional filters.

Parameters:

  • system_id (required)
  • category (optional) — Filter by risk category (confidentiality, integrity, availability, etc.)
  • risk_level (optional) — Filter by overall risk level (critical, high, medium, low)
  • status (optional) — Filter by lifecycle status

Returns: Risk list with summary metadata.


get_risk

Get the full risk detail including eager-loaded artifact_links (controls, evidence, findings, vendors, monitoring events).

Parameters:

  • system_id (required)
  • risk_id (required)

Returns: Full risk record with artifact_links.


create_risk

Create a custom risk for a system. Control auto-link is opt-in: pass framework_id plus suggested_control_families and the platform will only auto-link controls that already have ControlImplementation rows for that framework.

Parameters:

  • system_id (required)
  • title (required)
  • category (required) — confidentiality, integrity, availability, etc.
  • description (optional)
  • cia_category (optional)
  • likelihood (optional)
  • impact (optional)
  • owner_id (optional)
  • treatment (optional) — mitigate, accept, transfer, avoid
  • treatment_plan (optional)
  • treatment_due_date (optional) — ISO date string
  • review_frequency_days (optional) — How often the risk should be re-reviewed
  • framework_id (optional) — Required for control auto-link
  • suggested_control_families (optional) — List of family short codes (e.g., ["AC", "IA"])

Returns: The created risk including artifact_links.


seed_risks

Bulk-instantiate library templates against a system + framework. Each template is scored against the system, and controls are auto-linked per the template’s suggested_control_families when matching ControlImplementation rows exist.

Parameters:

  • system_id (required)
  • framework_id (required)
  • template_ids (required) — Non-empty list of risk template IDs

Returns: Seed summary with the created risks.


update_risk

Update risk fields. This is the mitigation surface — set treatment, treatment_plan, and treatment_due_date here rather than via a separate endpoint. Set status to a terminal value (e.g., closed) to retire a risk; there is no public hard-delete because risks are part of the audit chain.

Parameters:

  • system_id (required)
  • risk_id (required)
  • title (optional)
  • description (optional)
  • category (optional)
  • cia_category (optional)
  • likelihood (optional)
  • impact (optional)
  • owner_id (optional)
  • status (optional)
  • review_frequency_days (optional)
  • treatment (optional) — mitigate, accept, transfer, avoid
  • treatment_plan (optional)
  • treatment_due_date (optional)

Returns: Updated risk record.


Attach an artifact (control, evidence, finding, vendor, or monitoring event) to a risk. Pass exactly one artifact reference.

Parameters:

  • system_id (required)
  • risk_id (required)
  • link_type (required) — contributes_to_risk, mitigates_risk, evidence_of_risk
  • control_id (optional) — Pair with framework_id
  • framework_id (optional) — Required when linking a control
  • evidence_id (optional)
  • finding_id (optional)
  • vendor_id (optional)
  • monitoring_event_id (optional)

Returns: The created artifact link record.


Remove a previously attached artifact link.

Parameters:

  • system_id (required)
  • risk_id (required)
  • link_id (required)

Returns: Removal confirmation.


refresh_risk_summary

Re-score the risk using the latest analytics and trigger a best-effort AI summary regeneration. The endpoint always returns 200 with the updated entry. The score commits regardless of AI availability; the AI summary updates only if the AI service is reachable and the org has quota — check whether ai_summary_generated_at advanced to confirm AI ran.

Parameters:

  • system_id (required)
  • risk_id (required)

Returns: Updated risk entry.


list_risk_library

Browse the org-level risk template library. Templates expose a scenario, category, cia_category, and suggested_control_families.

Parameters:

  • category (optional) — Filter templates by category

Returns: Library template list.


Vendor Management

list_vendors

List all vendor entities in the organization.

Parameters: None

Returns: Vendor list with IDs, names, types, and authorization levels.


create_vendor

Create a new vendor entity.

Parameters:

  • name (required)
  • provider_type (required) — csp, saas, managed_service, internal
  • description (optional)
  • authorization_level (optional)

Returns: Created vendor record.


get_vendor

Get vendor details.

Parameters:

  • vendor_id (required)

Returns: Vendor metadata and linked documents.


update_vendor

Update vendor fields.

Parameters:

  • vendor_id (required)
  • name (optional)
  • description (optional)
  • provider_type (optional) — csp, saas, managed_service, internal
  • authorization_level (optional)

Returns: Updated vendor record.


delete_vendor

Delete a vendor entity.

Parameters:

  • vendor_id (required)

Returns: Deletion confirmation.


upload_vendor_document

Upload vendor evidence documents (SOC 2 reports, CRMs, FedRAMP packages).

Parameters:

  • vendor_id (required)
  • file_path (required)
  • name (optional)
  • description (optional)
  • attestation_type (optional) — self_attested, third_party_attestation, vendor_provided. Default: vendor_provided

Returns: Uploaded document record.


list_vendor_documents

List documents linked to a vendor.

Parameters:

  • vendor_id (required)

Returns: Document list with metadata.


Link an evidence item to a vendor with attestation type. Set vendor_id to null to unlink.

Parameters:

  • evidence_id (required)
  • vendor_id (optional) — Vendor ID; null to unlink
  • attestation_type (optional) — self_attested, third_party_attestation, vendor_provided

Returns: Link confirmation.


Inheritance & Responsibility

set_control_responsibility

Create or update an inheritance edge for a control.

Parameters:

  • system_id (required)
  • control_id (required)
  • framework_id (required)
  • responsibility_mode (required) — inherited or shared
  • source_type (optional) — provider, internal, or hybrid
  • vendor_id (optional) — Vendor providing the inherited control

Returns: Created responsibility edge.


get_control_responsibility

Check if a control is inherited and from where.

Parameters:

  • system_id (required)
  • control_id (required)
  • framework_id (required)

Returns: Responsibility edge details or null.


remove_control_responsibility

Convert an inherited control back to system-specific.

Parameters:

  • system_id (required)
  • control_id (required)
  • framework_id (required)

Returns: Removal confirmation.


generate_inheritance_narrative

AI-generate an inheritance narrative grounded in vendor documentation.

Parameters:

  • system_id (required)
  • control_id (required)
  • framework_id (required)

Returns: Draft inheritance narrative text.


get_stale_edges

Identify controls where the source narrative changed but the inherited control hasn’t been updated.

Parameters:

  • system_id (required)

Returns: List of stale inheritance edges with source change timestamps.


sync_stale_edges

Bulk update inherited controls by regenerating narratives from latest source.

Parameters:

  • system_id (required)

Returns: Sync results with per-control status.


STIG & CCI

list_stigs

List STIG benchmarks with optional filters.

Parameters:

  • technology_area (optional) — Filter by technology area
  • product (optional) — Filter by product name
  • limit (optional) — Default: 100
  • offset (optional) — Pagination offset. Default: 0

Returns: STIG benchmark list with IDs, titles, and rule counts.


get_stig

Get STIG benchmark detail.

Parameters:

  • stig_id (required)

Returns: Benchmark metadata including title, version, release info, and severity breakdown.


list_stig_rules

List rules for a STIG benchmark.

Parameters:

  • stig_id (required)
  • severity (optional) — Filter by severity (high, medium, low)
  • cci_id (optional) — Filter by CCI identifier
  • limit (optional) — Default: 100
  • offset (optional) — Pagination offset. Default: 0

Returns: Rule list with IDs, titles, severities, and linked CCIs.


get_stig_rule

Get full STIG rule detail.

Parameters:

  • stig_id (required)
  • rule_id (required)

Returns: Check text, fix text, discussion, and linked CCIs.


list_ccis

List CCIs with optional filters.

Parameters:

  • nist_control_id (optional) — Filter by NIST 800-53 control ID (e.g., AC-2)
  • status (optional)
  • limit (optional) — Default: 100
  • offset (optional) — Pagination offset. Default: 0

Returns: CCI list with definitions and linked controls.


get_cci

Get CCI detail with linked SRGs and STIG rules.

Parameters:

  • cci_id (required) — e.g., CCI-000015

Returns: CCI definition, linked SRGs, and linked STIG rules.


get_cci_chain

Get the full traceability chain: Control -> CCIs -> SRGs -> STIG rules.

Parameters:

  • nist_control_id (required) — NIST 800-53 control ID (e.g., AC-2)

Returns: Complete traceability from control requirements to technical checks.


get_cci_status

Get CCI-level compliance rollup for a system.

Parameters:

  • system_id (required)
  • nist_control_id (optional) — Filter by NIST control ID (e.g., AC-2)

Returns: Per-CCI pass/fail status.


get_cci_implementation

Read a single per-system CCI implementation row by (system_id, cci_uuid). Returns the live impl detail (status, status_source, narrative, ai_generated_narrative, evidence_ids, eMASS fields, has_status_conflict). 404 means the row hasn’t been initialized for this system yet.

Parameters:

  • system_id (required)
  • cci_uuid (required) — The CCI catalog UUID (the unique catalog row id, not the CCI-000XXX label)

Returns: Full CCI implementation detail.


get_stig_applicability

Get which STIGs apply to a system based on its profile.

Parameters:

  • system_id (required)

Returns: List of applicable STIG benchmarks.


infer_stigs

AI-infer applicable STIGs from the system’s profile.

Parameters:

  • system_id (required)

Returns: Recommended STIG benchmarks with reasoning.


get_test_manifest

Fetch the test manifest (applicable STIGs + rules) for a system.

Parameters:

  • system_id (required)
  • stig_id (optional) — Scope manifest to a specific STIG benchmark

Returns: Test manifest with applicable rules and scanner assignments.


submit_test_results

Upload STIG scan results to the platform.

Parameters:

  • system_id (required)
  • cli_run_id (required) — CLI scan run identifier
  • results (required) — Array of test result objects
  • cli_version (optional) — CLI version string

Returns: Submission confirmation with per-result status.


Recipes & Workflows

Recipes are markdown playbooks the calling agent reads and executes; workflows describe how to iterate items in a domain and which recipes to pick per item. See RFC 0001 for the contract spec and docs/src/recipes/ for authoring guides.

list_recipes

List loaded recipes with their summary metadata (id, name, tier, description, use_when, produces). When system_id is supplied, recipes missing required connected sources are hidden by default. Use this to discover which recipes are available, then call get_recipe(id) to read the full body.

Parameters:

  • tier (optional) — Filter to one tier: official, partner, or community
  • produces (optional) — Filter by what the recipe produces: evidence, narrative, both, or answers
  • system_id (optional) — Filter to recipes runnable against the system’s connected sources
  • include_unavailable (optional) — Include recipes missing required sources with unavailable reasons. Default: false

Returns: Recipe summaries with manifest metadata, required sources, and per-recipe source availability.


get_recipe

Return one recipe’s full manifest and body. The body is the markdown playbook the calling agent reads to understand the procedure.

Parameters:

  • recipe_id (required) — Recipe id to fetch

Returns: Recipe manifest plus the markdown body.


check_sources

Preflight source reachability and candidate recipes for one workflow/control. Use after start_task and before opening recipe contexts when the agent needs fresh source/recipe detail.

Parameters:

  • workflow_id (required) — Workflow id returned by start_task
  • control_id (required)
  • system_id (optional) — Defaults to active scope
  • framework_id (optional) — Defaults to active scope

Returns: Capture-plan items with source kind, connected state, candidate recipes, selected recipe, and structured recipe_gap entries.


start_recipe

Open a recipe execution context. Returns a context_id the caller passes on subsequent platform-API write tool calls so audit metadata is stamped with producer_kind='recipe' automatically. One recipe per session at a time (nesting forbidden in v1). Contexts auto-expire after 1 hour of inactivity.

Parameters:

  • recipe_id (required) — Recipe id (must be loadable from the registry)
  • recipe_version (required) — Recipe version the caller intends to run. Cross-checked against the loaded recipe; mismatch is an error.
  • params (optional) — Inputs the calling agent supplies, validated against the recipe’s params schema
  • selection (optional) — Structured RecipeSelection record from the engagement layer, stored on the context for the eventual RecipeResult
  • evidence_ids (optional) — Evidence ids supplied as inputs for narrative-producing recipes

Returns: Context id and the resolved recipe manifest snapshot.


end_recipe

Close a recipe execution context and return the RecipeResult summary (status, evidence and narrative counts, errors, elapsed time). Must be called once the recipe’s work is complete; failure to call leaves the context in place until the 1-hour expiry sweep.

Parameters:

  • context_id (required) — Context id returned by start_recipe
  • status (optional) — Caller-supplied disposition: pass, fail, or needs_input. Default: pass

Returns: RecipeResult summary.


list_workflows

List loaded workflow playbooks (single-control, scope-question, policy-question, campaign). Each workflow describes how to iterate items in its domain and which recipes to pick per item. Use this before picking a recipe so the agent works at the right granularity.

Parameters:

  • iterates_over (optional) — Filter to one item-iteration shape: single_control, scope_questions, policy_questions, or campaign_items

Returns: Workflow summaries with manifest metadata.


get_workflow

Return one workflow’s full manifest and body. The body is the markdown playbook the calling agent reads to know how to iterate items and pick recipes per item in this workflow’s domain.

Parameters:

  • workflow_id (required) — Workflow id to fetch

Returns: Workflow manifest plus the markdown body.

MCP Resources

The MCP server exposes read-only resources across three URI schemes: analysis://, status://, and workflow://.

Available Resources

Resource URIDescription
analysis://schemaJSON schema for compliance artifacts
analysis://guide/{framework_id}Analysis guide for a specific framework
analysis://control/{framework_id}/{control_id}Analysis guidance for a specific control within one framework scope
status://cliCurrent CLI version, update availability, and upgrade guidance
workflow://recipe/{recipe_id}Step-by-step workflow recipe for common compliance tasks

Usage

Access these resources via ReadMcpResourceTool with server: "pretorin" in your MCP client.

Analysis Schema

analysis://schema

Returns the JSON schema for structured compliance artifacts. Use this when generating artifact JSON to ensure correct structure. See Artifact Schema for documentation.

Framework Analysis Guide

analysis://guide/{framework_id}

Available framework guides:

  • analysis://guide/fedramp-moderate
  • analysis://guide/nist-800-53-r5
  • analysis://guide/nist-800-171-r3

Returns framework-specific analysis guidance including purpose, target audience, scope, and assessment methodology.

Control Analysis Guidance

analysis://control/{framework_id}/{control_id}

Example: analysis://control/fedramp-moderate/ac-02

Returns control-specific analysis guidance including search patterns, evidence examples, and assessment criteria for one framework scope.

CLI Status

status://cli

Returns the current CLI version, latest available version, whether an update is available, passive notification status, check state (verified/unverified), and the upgrade command. MCP hosts can inspect this resource to surface update guidance.

Workflow Recipes

workflow://recipe/{recipe_id}

Returns a step-by-step workflow recipe for common compliance tasks. Available recipes are listed dynamically via the MCP list_resources method.

MCP Troubleshooting

“Not authenticated” Error

Ensure you’ve logged in:

pretorin login
pretorin whoami  # Verify authentication

MCP Server Not Found

  1. Verify pretorin is installed and in your PATH:

    which pretorin
    pretorin --version
    
  2. Try using the full path in your configuration:

    {
      "mcpServers": {
        "pretorin": {
          "command": "/path/to/pretorin",
          "args": ["mcp-serve"]
        }
      }
    }
    
  3. For uv tool or pipx installations, find the path:

    command -v pretorin
    
  4. If the MCP client can talk to Pretorin but scoped write tools behave strangely, validate the stored CLI context:

    pretorin context show --quiet --check
    

    This catches deleted systems, detached frameworks, and other stale local scope before you debug the MCP client itself.

Server Crashes or Hangs

Check the MCP server logs:

pretorin mcp-serve 2>&1 | tee mcp-debug.log

Ensure your API key is valid:

pretorin whoami

Framework or Control Not Found

  • Verify the framework ID exists: pretorin frameworks list
  • Verify the control ID exists: pretorin frameworks controls <framework_id>
  • Check Control ID Formats for correct formatting

Common ID Mistakes

ErrorFix
ac-1 not foundUse zero-padded: ac-01
ac family not foundUse slug: access-control
AC.l2-3.1.1 not foundCMMC is case-sensitive: AC.L2-3.1.1
3.1.1 control not found800-171 needs leading zeros: 03.01.01

No Systems Found

If list_systems returns no systems, you need a beta code to create one on the Pretorin platform. Systems cannot be created through the CLI or MCP. Sign up for early access.

Rate Limiting

The API uses rate limiting. If you receive 429 Too Many Requests errors, the client automatically retries with exponential backoff. For persistent issues, reduce request frequency.

Support

Agent Overview

The agent command group runs autonomous compliance tasks using the Codex agent runtime. This is the Pretorin-hosted model mode — Pretorin manages the AI runtime and routes model calls through its /v1 endpoints.

If you already use another AI agent (Claude Code, Cursor, etc.), use the MCP mode instead (pretorin mcp-serve) and connect Pretorin tools to your existing agent.

Running a Compliance Task

# Free-form task
pretorin agent run "Assess AC-02 implementation gaps for my system"

# Use a predefined skill
pretorin agent run --skill gap-analysis "Analyze my system compliance gaps"

Options

OptionDescription
--skill/-s <name>Use a predefined skill template
--model/-m <model>Model override
--base-url <url>Custom model API endpoint
--working-dir/-w <path>Working directory for code analysis
--no-streamDisable streaming output
--legacyUse legacy OpenAI Agents SDK (deprecated)
--max-turns <n>Maximum agent turns (legacy mode only, default 15)
--no-mcpDisable external MCP servers (legacy mode only)

Hosted Model Setup

Use this setup when you want pretorin agent run to call Pretorin-hosted model endpoints.

# 1. Login with your Pretorin API key
pretorin login

# 2. Optional: override the default model proxy endpoint
#    (default: https://platform.pretorin.com/api/v1/public/model)
pretorin config set model_api_base_url https://your-proxy.example.com/v1

# 3. Validate runtime
pretorin agent doctor
pretorin agent install

# 4. Run a task
pretorin agent run "Assess AC-02 implementation gaps for my system"

Model Key Precedence

The Codex agent resolves API keys in this order:

  1. config.api_key (from pretorin login) — used as bearer key for the platform model proxy
  2. OPENAI_API_KEY environment variable
  3. config.openai_api_key

When --base-url is explicitly provided (non-platform endpoint), the order changes to prefer OPENAI_API_KEY first, then falls back to config keys.

Custom Model Endpoints

The agent supports any OpenAI-spec LLM endpoint, including:

  • Azure OpenAI
  • vLLM
  • LiteLLM
  • Ollama

Configure via --base-url flag or the model_api_base_url config key.

How It Works

The agent runtime uses the Codex SDK with a pinned binary in ~/.pretorin/bin/ and an isolated CODEX_HOME at ~/.pretorin/codex/. The agent:

  1. Downloads and pins a specific Codex binary version
  2. Runs in an isolated CODEX_HOME environment (never touches ~/.codex/)
  3. Automatically injects the Pretorin MCP server for compliance tool access
  4. Streams events and output in real-time (unless --no-stream is passed)

See Agent Runtime Management for the full set of pretorin agent lifecycle commands.

Agent Skills

Skills are predefined task templates that guide the agent through specific compliance workflows.

Available Skills

SkillDescriptionMax Turns
gap-analysisAnalyze system compliance gaps across frameworks20
narrative-generationGenerate implementation narratives for controls15
evidence-collectionCollect and map evidence from codebase to controls20
security-reviewReview codebase for security controls and compliance posture25
stig-scanRun STIG compliance scans against a system15
cci-assessmentAssess CCI compliance for a specific control15

max_turns is enforced only in --legacy mode. The Codex runtime governs turn count via its own session loop.

Using Skills

# Gap analysis
pretorin agent run --skill gap-analysis "Analyze my system compliance gaps"

# Narrative generation
pretorin agent run --skill narrative-generation "Generate narratives for all AC controls"

# Evidence collection
pretorin agent run --skill evidence-collection "Collect evidence for AC-02 in this repo"

# Security review
pretorin agent run --skill security-review "Review this codebase for AC-02 coverage"

# STIG scan
pretorin agent run --skill stig-scan "Check STIG applicability for my system"

# CCI assessment
pretorin agent run --skill cci-assessment "Assess CCI compliance for AC-02"

List Skills

pretorin agent skills

Skill Details

Gap Analysis

Read-only platform analysis that identifies controls without complete implementation. The agent:

  1. Lists systems and their associated frameworks
  2. Checks the compliance status for each system
  3. Identifies controls that are not yet implemented or only partially implemented
  4. Prioritizes gaps by risk level (controls in higher-impact families first)
  5. Provides actionable recommendations for closing each gap

This skill does not write to the platform — it produces a structured report with sections for each framework. To capture findings as evidence or update narratives, follow up with evidence-collection or narrative-generation.

See Gap Analysis Workflow for the broader methodology that combines this skill with codebase search.

Narrative Generation

Generates control implementation narratives that meet auditor-readiness requirements:

  • No markdown headings
  • At least two rich markdown elements (code blocks, tables, lists, links)
  • At least one structural element (code block, table, or list)
  • TODO placeholders for missing information
  • Only documents observable facts (no hallucination)

Evidence Collection

Searches the codebase for evidence that maps to specific controls:

  • Identifies relevant files and code patterns
  • Creates evidence items with auditor-ready descriptions
  • Links evidence to controls via the platform
  • Flags gaps where evidence is missing

Security Review

Reviews the codebase against specific controls and records findings on the platform:

  • Analyzes code for control coverage
  • Identifies implementation strengths and weaknesses
  • Documents findings with file paths and line numbers
  • Pushes monitoring events for critical or high-severity findings
  • Reopens control authoring with in_progress and drafts narratives based on findings
  • Adds notes for findings that require manual remediation
  • Produces remediation recommendations

This is the broadest write-side skill — it can call push_monitoring_event, update_control_status (only in_progress), update_narrative, create_evidence, link_evidence, and add_control_note/resolve_control_note in addition to the read-side platform tools.

STIG Scan

Runs STIG compliance scans against a system:

  • Checks which STIGs apply to the system (applicability)
  • Gets the test manifest (rules to evaluate)
  • Reports available scanners and rule coverage
  • Summarizes the scan plan and gaps in automated coverage

CCI Assessment

Assesses CCI-level compliance for a specific control:

  • Gets control context and implementation status
  • Lists CCIs for the target control
  • Checks CCI-level test results (pass/fail/not tested)
  • Identifies CCIs with no test coverage
  • Presents results as a traceability chain: Control -> CCIs -> SRGs -> STIG rules

Agent Runtime Management

The agent runtime uses a managed Codex binary with isolated configuration.

Check Runtime Health

pretorin agent doctor

Validates that the Codex runtime is properly installed and configured.

Install Codex Binary

pretorin agent install

Downloads the pinned Codex binary to ~/.pretorin/bin/. The version is pinned by the CLI to ensure compatibility.

Check Version

pretorin agent version

Shows the pinned Codex version and whether it’s currently installed.

Manage MCP Servers

The agent can connect to additional MCP servers beyond Pretorin. This lets the agent access other tools (filesystem, databases, etc.) during compliance tasks.

List Configured Servers

pretorin agent mcp-list

Add a Server

# stdio transport
pretorin agent mcp-add <name> stdio <command> --arg <arg1> --arg <arg2>

# http transport
pretorin agent mcp-add <name> http <url>

Options:

OptionDescription
--arg/-a <arg>Additional args for stdio transport (repeatable)
--scope <scope>Config scope: project (default, .pretorin-mcp.json) or global (~/.pretorin/mcp.json)

Examples:

pretorin agent mcp-add github stdio uvx --arg mcp-server-github
pretorin agent mcp-add aws http https://mcp.example.com/aws
pretorin agent mcp-add tools stdio node --arg /path/to/server --scope global

Remove a Server

pretorin agent mcp-remove <name>

Runtime Architecture

The Codex runtime is fully isolated:

  • Binary location: ~/.pretorin/bin/
  • Configuration: ~/.pretorin/codex/ (CODEX_HOME)
  • Version pinning: The CLI pins a specific Codex version for compatibility
  • MCP injection: Pretorin MCP server is automatically available to the agent

This isolation ensures the agent runtime never interferes with any user-installed Codex instances.

Authoring Recipes

Recipes are markdown-plus-Python playbooks that the calling AI agent invokes through MCP. Each recipe is a directory with a recipe.md (frontmatter + prose body) and one or more script files. The agent reads the body to understand what the recipe does, picks one when its use_when matches the task, and calls the recipe’s scripts as MCP tools.

If you’ve ever written a Claude Code skill, the shape will feel familiar — recipes are the same idea, scoped to compliance work and stamped with audit metadata automatically.

Why You Might Write One

Three concrete reasons:

  • Your team has a non-obvious procedure for capturing a particular kind of evidence (e.g., “pull the latest IAM policy from the prod account, redact customer ARNs, attach as a configuration record”). Encoding it as a recipe means every teammate’s agent does the same steps the same way.
  • You wrap an internal scanner that produces STIG-style results. A recipe exposes it next to the built-in inspec-baseline / openscap-baseline so the calling agent can pick it for the rules it covers.
  • You’re contributing back upstream. First-party scanner recipes (inspec-baseline, openscap-baseline, etc.) live under src/pretorin/recipes/_recipes/. New contributions follow the same shape.

Write Your First Recipe in 10 Minutes

# 1. Scaffold a fresh recipe in your user folder.
pretorin recipe new my-first-recipe

# 2. The scaffolder drops a directory at ~/.pretorin/recipes/my-first-recipe/
#    with recipe.md, scripts/main.py, README.md, tests/test_recipe.py.

# 3. Edit the description, use_when, and the body of recipe.md.
$EDITOR ~/.pretorin/recipes/my-first-recipe/recipe.md

# 4. Edit scripts/main.py — implement `async def run(ctx, **params)`.
$EDITOR ~/.pretorin/recipes/my-first-recipe/scripts/main.py

# 5. Validate.
pretorin recipe validate my-first-recipe

# 6. Run it locally to test (no agent / MCP boundary involved).
pretorin recipe run my-first-recipe --param key=value

If validate passes and run prints the result you expect, the recipe is in the registry. Restart your MCP client and the agent can use it on the next task.

pretorin recipe run is a local-testing path: writes go through your authenticated PretorianClient directly, so audit-metadata stamping requires explicit audit_metadata on each create_evidence call (see Writer tools). The agent path through MCP stamps automatically.

What Ships in v1

  • Four loader paths with clear precedence: explicit > project > user > built-in. See Loader paths below.
  • pretorin recipe list / show / new / validate / run CLI commands.
  • list_recipes / get_recipe MCP discovery tools, including source-aware filtering with list_recipes(system_id=...).
  • list_connected_sources / check_sources MCP preflight tools for capture planning.
  • Per-script MCP tools auto-registered as recipe_<safe_id>__<script_name>.
  • Audit-metadata stamping: the calling agent opens a recipe execution context with start_recipe(...); every platform write inside the context is stamped with producer_kind="recipe", the recipe id, and the recipe version. The platform records the full chain.
  • Workflow playbooks (list_workflows / get_workflow) for the four item-iteration shapes: single-control, scope-question, policy-question, campaign. Workflows describe how to iterate; recipes describe what to do per item.
  • Recipe-only MCP writes for evidence and narratives: agents pass recipe_context_id on write tools, and narrative recipes also pass cited evidence_ids.

Loader Paths

SourcePathUse it when
Built-insrc/pretorin/recipes/_recipes/<id>/First-party recipes shipped with pretorin-cli.
User folder~/.pretorin/recipes/<id>/Your local recipes — survives across projects.
Project folder<repo>/.pretorin/recipes/<id>/Team-shared recipes checked into the compliance repo.
Explicit pathpretorin recipe show --path /abs/...Testing a recipe while authoring.

If the same id appears in two paths, the higher-precedence one wins. pretorin recipe show <id> --sources lists every location and marks which is active.

Tier

Each loaded recipe gets a tier set by the loader from its source path:

  • official — built-in, shipped with pretorin-cli (forced regardless of what the manifest says).
  • community — anything from the user/project folders or explicit paths.
  • partner — reserved for installed packages (v1.5).

The calling agent sees the tier in list_recipes output and can factor it in when picking. Community recipes are first-class — the tier is signal, not a permission gate.

  • Manifest reference — every frontmatter field with examples.
  • Script contract — the async def run(ctx, **params) -> dict signature.
  • Writer tools — the platform-API tools your scripts call, and how audit metadata gets stamped.
  • Testing — pytest fixtures and patterns.
  • Publishing — how to share a community recipe or PR an official one.
  • Workflows — the layer above recipes: how the calling agent picks a workflow, then picks recipes per item.
  • Worked example — a community recipe walked through end-to-end.

Manifest Reference

The manifest is the YAML frontmatter at the top of recipe.md. It’s the public contract between recipe authors and pretorin. The schema is defined by the pydantic models in src/pretorin/recipes/manifest.py and frozen per contract_version.

Minimal Example

---
id: my-first-recipe
version: 0.1.0
name: "My First Recipe"
description: "Capture the active IAM role's trust policy and attach it as a configuration record."
use_when: "The agent needs evidence that an IAM role's trust policy meets least-privilege requirements."
produces: evidence
author: "Jane Doe"
license: Apache-2.0
requires:
  sources:
    - kind: aws_account
      binding_role: primary
      probe: "aws sts get-caller-identity"
      source_version_kind: cloud_account_scan_time
scripts:
  capture:
    path: scripts/capture.py
    description: "Pull the trust policy from AWS and return it as a redacted dict."
---

# My First Recipe Body

The agent reads everything below the closing `---` to understand what to do.

Required Fields

FieldTypeNotes
idstringKebab-case (^[a-z][a-z0-9-]*$). Globally unique across loader paths.
versionstringSemVer-ish. Bumped when behavior changes. Stamped on evidence.
namestringDisplay name shown in pretorin recipe list.
descriptionstring≥ 50 chars. The text the agent reads to decide if this recipe fits.
use_whenstring≥ 30 chars. Explicit “when the agent has X and needs Y” guidance.
producesenumevidence / narrative / both / answers. What the recipe writes back to the platform.
authorstringAttribution. Stamped in evidence provenance.

Optional Fields

FieldTypeDefaultNotes
tierenumcommunityofficial / partner / community. Loader overrides from source path.
licensestringApache-2.0SPDX identifier. Required for any recipe shared publicly.
paramsmap{}Recipe-level inputs. See Params.
requiresobject{}Connected sources, CLI binaries, and env vars the recipe needs. See Requires.
attestslist[][{control, framework}] hints. Filter, not binding.
scriptsmap{}tool_name → ScriptDecl. Each becomes an MCP tool. See Scripts.
contract_versionint1Frontmatter shape version. Bumps only on breaking changes.
recipe_schema_versionstring"1.0"Schema this recipe is written against.
min_pretorin_versionstringnullLoader refuses to load if running pretorin is older.

Tier

tier is set by the loader from the recipe’s source path, overriding whatever the manifest declares:

  • Built-in path → official
  • User folder, project folder, explicit path → community
  • partner → reserved for installed packages (v1.5)

Authors should still declare a tier — it documents intent, and any manifest-internal validation runs against the declared value.

Params

Recipe-level params are inputs the calling agent supplies via start_recipe(...). They flow through to scripts as kwargs.

params:
  stig_id:
    type: string
    description: "STIG benchmark id targeting Linux baseline controls"
    required: true
  target:
    type: string
    description: "Optional connection string (e.g., 'ssh://host')"
    default: "local"

Supported types: string, integer, number, boolean, array. For array, declare items: { type: ... } so the MCP renderer can emit a real JSON Schema.

Requires

Document the runtime environment and connected system sources the recipe needs. sources is the MCP preflight contract; cli and env remain local runtime requirements for recipe scripts.

requires:
  sources:
    - kind: github_org
      binding_role: primary
      probe: "gh auth status"
      source_version_kind: github_api_etag
  cli:
    - { name: inspec, probe: "inspec --version" }
    - { name: jq, probe: "jq --version" }
  env:
    - AWS_PROFILE
    - INSPEC_TARGET

Supported source kinds: workspace_repo, github_org, aws_account, azure_tenant, k8s_cluster, slack_workspace, and emass.

source_version_kind tells the agent/platform what counts as the source anchor for evidence emitted by this recipe: for example git_commit, github_api_etag, cloud_account_scan_time, or capture_timestamp. When list_recipes(system_id=...) is called, recipes whose required sources are not connected are hidden unless include_unavailable=true.

Attests

attests is a list of {control, framework} entries the recipe is likely relevant to. It’s a hint for filtering, never a binding — the agent picks recipes by reading description and use_when, not by joining on attests.

attests:
  - { control: AC-2, framework: nist-800-53-r5 }
  - { control: AC-6, framework: nist-800-53-r5 }

Scripts

Each ScriptDecl becomes an MCP tool:

scripts:
  run_scan:
    path: scripts/run_scan.py
    description: "Pull the manifest, run the scan, return per-rule results."
    params:
      stig_id:
        type: string
        description: "STIG benchmark id"
        required: true
      target:
        type: string
        description: "Connection string"
    timeout_seconds: 600
    writes_evidence: false
FieldNotes
pathRelative to the recipe directory. The validator checks the file exists and contains async def run.
descriptionOne-liner the agent sees on the tool. Be specific — this is the tool’s “tooltip”.
paramsPer-script JSON Schema input. Independent of recipe-level params.
timeout_secondsWall-clock cap for one invocation. Default 300.
writes_evidenceDeclared intent. The trust gate for community recipes considers it.

The MCP tool name for this script is recipe_<safe_id>__run_scan, where safe_id is the recipe id with hyphens converted to underscores (MCP tool names can’t contain hyphens). The recipe author doesn’t construct this name — pretorin does.

Contract and Schema Versioning

Two version fields exist for forward compatibility:

  • contract_version — bumps only on backwards-incompatible shape changes to the frontmatter itself. Most recipes pin to 1.
  • recipe_schema_version — the schema this specific recipe is written against. The loader refuses to load a recipe whose schema is newer than what the running pretorin supports, with a hint to upgrade.

min_pretorin_version lets a recipe author require a specific runtime version (e.g., when a recipe uses a writer tool that only exists in pretorin ≥ 0.18).

What the Loader Does to Your Manifest

  1. Parses the YAML frontmatter.
  2. Validates against the pydantic schema. A failure raises RecipeManifestError for that recipe — the registry keeps loading other recipes.
  3. Overrides tier from the source path (built-in → official, otherwise community).
  4. Caches the parsed manifest by (path, mtime). If you edit the file, the next load re-parses it.

Script Contract

Every script declared under scripts: in recipe.md must export a single async function:

async def run(ctx, **params) -> dict:
    ...

That’s the entire contract. The recipe runner imports the module, calls run, awaits the result, and hands it back to the calling agent as the MCP tool response.

The Signature

from typing import Any

async def run(ctx: Any, *, stig_id: str, target: str = "local") -> dict[str, Any]:
    """Run a baseline scan and return per-rule results."""
    ...
ArgumentTypeWhat it is
ctxRecipeScriptContextPer-invocation execution context (see below).
**paramsvariesThe keyword args the agent supplied, validated against scripts.<name>.params in the manifest.

The function must be async. Use await for I/O. Pretorin’s writer tools are async; using sync HTTP or sync subprocess for slow operations will block the recipe runner’s event loop.

The return value must be a JSON-serializable dict. Anything with tuple, set, datetime, or custom classes will fail to serialize back through MCP.

The ctx Argument

RecipeScriptContext (defined in src/pretorin/recipes/runner.py):

@dataclass
class RecipeScriptContext:
    system_id: str | None
    framework_id: str | None
    api_client: Any           # PretorianClient — see writer-tools.md
    logger: logging.Logger
    recipe_id: str
    recipe_version: str
    recipe_context_id: str | None

The two you’ll use most:

  • ctx.api_client — the authenticated PretorianClient. Use this to call platform-API methods (ctx.api_client.create_evidence(...), ctx.api_client.get_test_manifest(...), etc.). See Writer tools for the full surface.
  • ctx.logger — a logging.Logger named for the recipe. Prefer this over print so the calling agent’s logs stay structured.

ctx.system_id is set when the calling agent specified a system at start_recipe time. Use it for any per-system platform call. If your recipe doesn’t make sense without a system, raise early with a clear error.

ctx.recipe_context_id is the active execution context id. The MCP write handlers read it from the session automatically — you only need to pass it explicitly if your script makes a platform write outside the MCP boundary (e.g., a direct httpx call against a custom internal endpoint).

Returning Results

Whatever your script returns becomes the MCP tool response the calling agent reads. Keep it structured: nested dicts the agent can inspect, with clear keys.

async def run(ctx, *, stig_id: str) -> dict[str, Any]:
    rules = await fetch_rules(ctx.api_client, ctx.system_id, stig_id)
    results = await scan(rules)
    return {
        "stig_id": stig_id,
        "summary": {
            "total": len(results),
            "passed": sum(1 for r in results if r.status == "pass"),
            "failed": sum(1 for r in results if r.status == "fail"),
        },
        "rules": [r.to_dict() for r in results],
    }

Return shapes the agent can pattern-match are easier to act on than freeform prose. Save the prose for the recipe body — let the script return data.

Imports Inside scripts/

The runner adds the recipe’s scripts/ directory to sys.path for the duration of the call, so a sibling module is reachable as a top-level import:

# scripts/run_scan.py
from helpers import normalize_results   # reaches scripts/helpers.py

The path is removed after the call returns. You don’t have to __init__.py-decorate the directory.

Error Handling

Don’t swallow exceptions inside run. Let them propagate — the runner catches them, logs them, and returns a structured error to the calling agent. Catching and returning a string error makes the agent think the call succeeded.

# Bad
async def run(ctx, *, stig_id: str) -> dict[str, Any]:
    try:
        return await fetch(ctx, stig_id)
    except Exception as e:
        return {"error": str(e)}        # agent sees a "successful" call

# Good
async def run(ctx, *, stig_id: str) -> dict[str, Any]:
    return await fetch(ctx, stig_id)    # exceptions surface as tool errors

Patterns

Three shapes cover most recipes:

Capture-from-source

The recipe pulls a thing (a config file, a snippet of code, a query result), redacts it, and registers an evidence record.

async def run(ctx, *, file_path: str, line_range: str | None = None) -> dict[str, Any]:
    text = (Path(file_path).read_text()).split("\n")
    snippet = _slice(text, line_range)
    redacted, summary = redact_secrets(snippet)
    composed = compose_audit_markdown(redacted, file_path=file_path, line_range=line_range)
    evidence_id = await ctx.api_client.create_evidence(
        system_id=ctx.system_id,
        ...
    )
    return {"evidence_id": evidence_id, "redaction_summary": summary.to_dict()}

Wrap-a-scanner

The recipe wraps an external tool (oscap, inspec, az, aws), runs it against the platform’s test manifest, and returns per-rule results.

async def run(ctx, *, stig_id: str, target: str = "local") -> dict[str, Any]:
    manifest = await fetch_test_manifest(ctx.api_client, ctx.system_id, stig_id=stig_id)
    rules = rules_for_stig(manifest, stig_id)
    scanner = InSpecScanner()
    results = await scanner.execute(rules, config={"target": target})
    return {
        "stig_id": stig_id,
        "summary": summarize_results(results),
    }

The five built-in scanner recipes are exactly this shape — each is a thin adapter over a pretorin.scanners.* class.

Q-and-A attestation

The recipe is the agent collecting human attestations interactively, with no external tool involved. Inputs are structured answers; the recipe just records them.

async def run(ctx, *, stig_id: str, attestations: list[dict]) -> dict[str, Any]:
    scanner = ManualScanner()
    results = await scanner.execute(rules, config={"attestations": attestations})
    return {"stig_id": stig_id, "summary": summarize_results(results)}

When to Write Multiple Scripts

If your recipe has steps that the calling agent might want to interleave with reasoning (e.g., “redact, show me, then compose”), expose each step as its own script. The agent can then call them as separate MCP tools and inspect the intermediate output.

The code-evidence-capture recipe ships two scripts (redact_secrets and compose_snippet) for exactly this reason.

Writer Tools

Recipe scripts call the platform API through ctx.api_client, which is the same PretorianClient the rest of pretorin uses. This page covers the common write paths and how audit metadata gets stamped automatically when your recipe runs inside an execution context.

How Audit Metadata Gets Stamped

The calling agent opens an execution context with start_recipe(recipe_id, recipe_version, params) and gets back a context_id. Subsequent platform writes from that MCP session pick up the context automatically and stamp:

{
  "producer_kind": "recipe",
  "producer_id": "<recipe_id>",
  "producer_version": "<recipe_version>",
  "recipe_context_id": "<context_id>"
}

When the calling agent makes an MCP-routed write — i.e. the write goes through create_evidence, create_evidence_batch, or update_narrative — it must pass recipe_context_id. The MCP handler reads that context and builds the metadata. Direct agent writes without a recipe context return a structured recipe_required error.

When your script makes a write directly through ctx.api_client (skipping MCP), you have to pass audit_metadata yourself. The helper to build it lives at pretorin.evidence.audit_metadata:

from pretorin.evidence.audit_metadata import build_recipe_metadata

audit_metadata = build_recipe_metadata(
    body=source_excerpt,
    source_uri="file://config/rbac.yaml",
    source_type="repo_file",
    recipe_id=ctx.recipe_id,
    recipe_version=ctx.recipe_version,
    source_label="RBAC configuration",
    source_locator="lines 12-30",
    source_excerpt=source_excerpt,
    capture_method="repository_file_read",
    redaction_summary=...,
)

evidence = EvidenceCreate(
    control_id="ac-2",
    framework_id="nist-800-53-r5",
    name="...",
    evidence_type="configuration",
    description="RBAC role mapping is enforced in the IdP configuration.",
    artifact_content=composed_markdown,
    audit_metadata=audit_metadata,
)
await ctx.api_client.create_evidence(ctx.system_id, evidence)

Most recipes don’t need the direct path — write through MCP and let the handler stamp.

Read-Side Helpers

You’ll often need to read platform state before writing. The most useful methods on ctx.api_client:

MethodReturnsUse
list_systems()list[dict]Find a system id when one wasn’t passed in ctx.system_id.
get_test_manifest(system_id, stig_id=None)dictThe applicable rules for a system, optionally narrowed to one STIG.
get_control(framework_id, control_id)ControlDetailFull control text + family + status.
get_controls_batch(framework_id, control_ids)dictBatch fetch — cheaper than N calls.
get_control_implementation(...)dictCurrent narrative + status for one control.
get_stig_applicability(system_id)dictWhich STIGs apply to a system.
get_compliance_status(system_id, framework_id)dictHigh-level coverage rollup.
get_source_manifest(system_id)dictVerified-sources state for the system.

Manifest helpers for scanner recipes

If you’re building a scanner recipe, three helpers in pretorin.scanners.manifest cover the common shape:

from pretorin.scanners.manifest import (
    fetch_test_manifest,
    rules_for_stig,
    summarize_results,
)

manifest = await fetch_test_manifest(ctx.api_client, ctx.system_id, stig_id="RHEL_9_STIG")
rules = rules_for_stig(manifest, "RHEL_9_STIG")
results = await my_scanner.execute(rules, config={"target": "local"})
summary = summarize_results(results)

Every built-in scanner recipe uses exactly this pattern. If you’re wrapping a new scanner, copy inspec-baseline/scripts/run_scan.py as a starting point.

Write-Side: Evidence

from pretorin.client.models import EvidenceCreate

evidence = EvidenceCreate(
    control_id="ac-2",
    framework_id="nist-800-53-r5",
    name="RBAC configuration excerpt",
    evidence_type="configuration",
    description="RBAC role mapping is enforced in the IdP configuration.",
    artifact_content=composed_markdown,
    audit_metadata=audit_metadata,
)
result = await ctx.api_client.create_evidence(ctx.system_id, evidence)

For batched writes (much faster when you have ≥ 10):

from pretorin.client.models import EvidenceBatchItemCreate

items = [
    EvidenceBatchItemCreate(
        control_id="ac-2",
        name="...",
        description="Short summary",
        artifact_content=composed_markdown,
        evidence_type="...",
        audit_metadata=audit_metadata,
    )
    for _ in batch
]
response = await ctx.api_client.create_evidence_batch(
    ctx.system_id,
    framework_id="nist-800-53-r5",
    items=items,
)

For binary/unstructured artifacts (a screenshot, a PDF, an exported report), upload the file:

result = await ctx.api_client.upload_evidence(
    system_id=ctx.system_id,
    file_path="/tmp/my-screenshot.png",
    name="Console screenshot — IAM users page",
    evidence_type="screenshot",
    control_id="ac-2",
)

Write-Side: Narratives

await ctx.api_client.update_narrative(
    system_id=ctx.system_id,
    framework_id="nist-800-53-r5",
    control_id="ac-2",
    narrative=composed_text,
    recipe_context_id=ctx.recipe_context_id,
    evidence_ids=["ev-123"],
)

Narrative-producing recipes must cite evidence ids. Open the context with start_recipe(..., evidence_ids=[...]), compose only from those evidence items, then pass the same ids to update_narrative.

Write-Side: Test Results

For scanner recipes:

await ctx.api_client.submit_test_results(
    system_id=ctx.system_id,
    benchmark_id="RHEL_9_STIG",
    results=[...],   # list of TestResult dicts
)

Redaction and Markdown Composition

Two helpers from pretorin.evidence:

from pretorin.evidence.redact import redact_secrets
from pretorin.evidence.markdown import compose

redacted_text, redaction = redact_secrets(raw_text)
markdown = compose(
    prose="The IAM trust policy is configured to require MFA for assume-role.",
    snippet=redacted_text,
    snippet_lang="json",
    file_path="iam/trust-policy.json",
)

redact_secrets returns a RedactionResult with counts per secret type (AWS access keys, GitHub tokens, JWTs, etc.). compose turns prose + snippet into the audit-grade markdown body the platform expects on evidence records.

These two are the building blocks of the code-evidence-capture built-in recipe — read its source under src/pretorin/recipes/_recipes/code-evidence-capture/ for a real-world usage pattern.

What Recipe Scripts Should Not Do

  • Don’t bypass ctx.api_client to talk to the platform via raw HTTP. You’ll skip the auth, retry, and error-handling logic the client provides.
  • Don’t construct audit_metadata from scratch. Use build_recipe_metadata / build_recipe_metadata_from_context so the shape stays consistent.
  • Don’t call start_recipe from inside a script. The recipe context is already open — that’s how the script got invoked. Nesting is forbidden.
  • Don’t assume ctx.system_id is set. If your recipe requires a system, raise early with a clear message rather than passing None through to the API.

Testing Recipes

Recipes are real Python modules — test them like any other Python code. The scaffolder drops a tests/test_recipe.py stub when you run pretorin recipe new <id>; this page covers what to put in it.

Three Layers Worth Testing

A recipe has three layers and each rewards a different test style:

  1. Pure helpers inside scripts/ — redaction, normalization, parsers. Plain unit tests with no fixtures. Fastest feedback loop; most coverage per line of test code.
  2. run against a fake ctx — the script’s main entry point. Mock ctx.api_client so you don’t hit the network.
  3. End-to-end through the recipe runner — load the recipe, call its script through the runner, assert the result. Slower but proves the manifest, the importlib-based dispatch, and the script all line up.

Unit-Testing Helpers

If you’ve factored out helpers into scripts/redact.py or scripts/normalize.py, import them directly:

# tests/test_helpers.py
from scripts.redact import redact_aws_keys

def test_redact_aws_keys_replaces_full_key() -> None:
    text = "AKIAIOSFODNN7EXAMPLE"
    redacted = redact_aws_keys(text)
    assert "AKIA" not in redacted
    assert "[REDACTED:AWS_KEY]" in redacted

The recipe runner adds the scripts/ directory to sys.path. In tests, make sure your pytest.ini or pyproject.toml does the same:

[tool.pytest.ini_options]
pythonpath = ["scripts"]

Testing run with a Fake ctx

The script’s run function takes a ctx argument typed as Any (loose intentionally — see Script contract). A MagicMock with AsyncMock for the I/O methods is enough:

# tests/test_run.py
from unittest.mock import AsyncMock, MagicMock
import pytest

from scripts.run_scan import run

@pytest.mark.asyncio
async def test_run_returns_summary_for_no_rules() -> None:
    ctx = MagicMock()
    ctx.system_id = "sys-1"
    ctx.api_client = MagicMock()
    ctx.api_client.get_test_manifest = AsyncMock(
        return_value={"applicable_stigs": []}
    )

    result = await run(ctx, stig_id="EMPTY_STIG")

    assert result["stig_id"] == "EMPTY_STIG"
    assert result["summary"]["total"] == 0

The four scanner recipe tests in pretorin-cli’s test suite use exactly this shape — patch get_test_manifest, call run, assert on the returned summary.

End-to-End Through the Runner

The strongest test exercises the full path: registry loads the manifest, runner imports the script, script runs against a fake API client. This is what tests/recipes/test_code_evidence_capture.py does for the code-evidence-capture recipe and it’s the regression-test pattern to copy.

Sketch:

import pytest
from pretorin.recipes import loader as loader_module
from pretorin.recipes.loader import clear_cache
from pretorin.recipes.registry import RecipeRegistry
from pretorin.recipes.runner import RecipeScriptContext, run_script
from unittest.mock import AsyncMock, MagicMock

@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    clear_cache()
    monkeypatch.setattr(loader_module, "_user_recipes_root", lambda: tmp_path / "u")
    monkeypatch.setattr(loader_module, "_project_recipes_root", lambda start=None: None)

@pytest.mark.asyncio
async def test_my_recipe_end_to_end() -> None:
    registry = RecipeRegistry()
    entry = registry.get("my-recipe")
    assert entry is not None

    api_client = MagicMock()
    api_client.create_evidence = AsyncMock(return_value={"id": "ev-1"})
    ctx = RecipeScriptContext(
        system_id="sys-1",
        framework_id="nist-800-53-r5",
        api_client=api_client,
        logger=MagicMock(),
        recipe_id="my-recipe",
        recipe_version="0.1.0",
        recipe_context_id="ctx-test",
    )

    result = await run_script(
        recipe=entry.active,
        script_name="capture",
        ctx=ctx,
        params={"control_id": "ac-2"},
    )

    assert result["evidence_id"] == "ev-1"

For a community recipe outside the pretorin source tree, point the loader at your recipe’s directory:

monkeypatch.setattr(loader_module, "_user_recipes_root", lambda: my_recipe_parent)

Run It Locally

Before wiring the recipe into an agent, exercise it directly:

pretorin recipe run my-recipe --param key=value --param limit=20

pretorin recipe run loads the recipe through the registry (or --path for a not-yet-registered directory), opens a recipe execution context, calls the script, prints the return value, and closes the context. It bypasses the MCP boundary, so:

  • Use it for fast iteration on the script itself.
  • Pure transformation recipes (return data, don’t write to the platform) work end-to-end.
  • Recipes that do write through ctx.api_client need explicit audit_metadata on each call — the MCP boundary stamps automatically; this command does not. See Writer tools.

--no-context skips opening the execution context for recipes that don’t need it.

Validate as a Smoke Test

pretorin recipe validate <id> runs the manifest schema check, the script existence check, and the description-quality check. Add it to your CI as a shell-out smoke test:

- name: Validate recipes
  run: |
    pretorin recipe validate my-recipe
    pretorin recipe validate my-other-recipe

This catches “you renamed the script and forgot to update the manifest” faster than any pytest assertion will.

What Not to Test

  • Don’t test the platform API. Your recipe is an adapter; testing what create_evidence does is pretorin’s job, not yours. Mock the client.
  • Don’t test pydantic validation of the manifest. That’s already covered by pretorin’s loader tests. If your manifest is malformed, pretorin recipe validate will tell you.
  • Don’t test redaction patterns. Use pretorin.evidence.redact’s helpers and trust them.

Publishing Recipes

Recipes can ship at three tiers. The tier is set by the loader from the recipe’s source path, not by what the manifest declares. Where you put the recipe directory determines who can run it and how.

Three Distribution Models

1. Personal — User Folder

~/.pretorin/recipes/<id>/

Drop a recipe directory there and pretorin recipe list shows it immediately. Loaded as tier: community. No further setup.

Use this when:

  • You’re prototyping a recipe.
  • The recipe encodes a personal workflow no one else needs.
  • You’re working through the 10-minute walkthrough.

The user folder isn’t synced anywhere — it lives on your machine.

2. Team — Project Folder

<repo>/.pretorin/recipes/<id>/

Checked into your team’s compliance repo (the same repo with pretorin.yaml or your team’s CLAUDE.md). The loader walks up from CWD looking for a .pretorin/recipes/ directory, so any teammate working in the repo gets the recipes automatically.

Use this when:

  • The recipe encodes a team-specific procedure (e.g., “pull our internal IAM audit log endpoint”).
  • You want your CI to validate the recipes alongside the rest of the compliance repo.
  • The recipe is too domain-specific to belong upstream but everyone on the team needs it.

Loaded as tier: community. The project folder has higher precedence than the user folder, so a team-shared recipe shadows a teammate’s personal copy with the same id.

3. Upstream — First-Party in pretorin-cli

src/pretorin/recipes/_recipes/<id>/

Forced to tier: official by the loader. Distributed with every pretorin-cli install.

Use this when:

  • The recipe is broadly useful to anyone running pretorin (a new scanner wrapper, a generic capture pattern).
  • You’re willing to maintain it — official recipes get tested in CI and block releases on regressions.
  • The procedure is stable enough that a SemVer bump with backwards- incompatible changes would be unusual.

How to Submit a First-Party Recipe

  1. Open an issue first. Describe what the recipe does and why it belongs in the upstream set rather than as a community recipe. The first-party set is intentionally small — it’s a curation surface, not a catch-all.

  2. Scaffold under _recipes/ — clone pretorin-cli and create the recipe directory:

    pretorin recipe new my-new-scanner --location builtin
    

    This drops the recipe under src/pretorin/recipes/_recipes/<id>/ with the right structure.

  3. Set tier: official in the manifest. The loader will override from the source path anyway, but it documents intent.

  4. Add a smoke test. Every built-in recipe has at least one test under tests/recipes/ that loads it through the registry and verifies the basic shape. Copy the pattern from tests/recipes/test_builtin_scanner_recipes.py.

  5. Run quality gates locally:

    pretorin recipe validate my-new-scanner
    pytest tests/recipes/
    ./tools/check.sh quick
    
  6. PR the recipe. Include in the description: what the recipe does, when an agent should pick it (your use_when text is a good start), and what scanners or platform APIs it depends on. Reference the issue from step 1.

  7. Maintenance commitment. Once merged, the recipe ships with every pretorin-cli release. Be prepared to respond to issues against it.

How to Share a Community Recipe

There’s no central registry yet (pretorin’s recipe install <pkg> is v1.5). For now, share a community recipe by:

  • Posting the recipe directory in a gist or repo. Anyone can clone it into their ~/.pretorin/recipes/ or <repo>/.pretorin/recipes/ and use it.
  • Opening a PR against your team’s compliance repo. The recipe lands under <repo>/.pretorin/recipes/<id>/ and is loaded automatically by every teammate.

When you publish a community recipe, fill out:

  • license — required for anything you share publicly. SPDX identifier (Apache-2.0, MIT, etc.).
  • author — your name or your team’s name. Stamped in the audit metadata of every evidence record the recipe writes.
  • description — clear enough that someone else’s agent can decide whether to pick it without needing to read your team’s wiki.

Tier and Trust

The calling agent sees tier on every recipe in list_recipes output. v1 doesn’t gate execution by tier — a community recipe runs just like an official one. What tier does is:

  • Audit signal. Every evidence record stamped by a community recipe carries the recipe id, version, and author. A reviewer can trace which recipes contributed to a system’s evidence set.
  • Selection signal. When two recipes both fit a task, the agent prefers the official one unless the community one’s description makes a stronger case.

partner tier is reserved for recipes shipped via installed Python packages (deferred to v1.5). The shape is: a Python package declares an entry point pointing at a recipe directory inside the package; the loader picks it up at install time.

Versioning Your Recipe

Bump version in the manifest when:

  • The recipe’s behavior changes in a way that would surprise a previous consumer (different output shape, different side effects, different redaction rules).
  • A bug fix changes results materially.

Don’t bump for cosmetic edits to the body or doc-only changes. Recipe version stamps every evidence record, so a version bump means the audit trail reflects “results from a different procedure.”

recipe_schema_version is independent — it tracks the frontmatter shape, not the recipe’s behavior. Most recipes pin this to "1.0" and never touch it.

Workflows

Workflows sit one layer above recipes. A workflow is a playbook the calling agent reads to learn how to iterate items in a domain (one control, all pending scope questions, the entire campaign). Recipes describe what to do per item; workflows describe how to walk the items.

Three-layer routing model:

engagement (deterministic Python rules)
  → workflow (markdown playbook the calling agent reads)
    → recipe (calling agent picks per item from the menu)

The engagement layer (start_task) lands later — see the roadmap. v1 ships the workflow registry and the per-workflow playbook markdown so the agent can navigate manually.

What Ships in v1

Four built-in workflows:

IDIterates overPick when
single-controlone controlThe user names exactly one control id and the work fits in a single focused pass.
scope-questionscope questionnaire itemsThe user references the scope questionnaire or scope is the active workflow-state blocker.
policy-questionpolicy questionnaire itemsThe user references an org policy questionnaire or policy is the active blocker.
campaignmany controls (server-side)Bulk control work — drafting narratives or capturing evidence for a family or framework.

Browse them:

pretorin recipe list  # for recipes (CLI)

There’s no pretorin workflow list CLI yet — workflows are discovered through MCP only:

  • list_workflows — summary metadata for every loaded workflow.
  • get_workflow(workflow_id) — full manifest plus the markdown body.

How a Workflow’s Body Looks

Every workflow body has the same shape: a brief intent statement, a description of the iteration shape, a step-by-step block, and a “what to avoid” closing section. Read one of the built-ins as a template:

cat src/pretorin/workflows_lib/_workflows/single-control/workflow.md

The frontmatter declares:

FieldNotes
idkebab-case, globally unique
versionSemVer-ish
namedisplay name
description≥ 50 chars, what the engagement layer matches against
use_when≥ 30 chars, explicit trigger guidance
producesevidence / narrative / answers / mixed
iterates_oversingle_control / scope_questions / policy_questions / campaign_items
recipes_commonly_usedhint list of recipe ids the agent often picks

Why Workflows Matter

Without workflows, the calling agent would freelance the iteration pattern for every task. That’s drift-prone — different agents hit the same questionnaire and follow different orders, producing inconsistent audit trails. The workflow body fixes the pattern: load pending items, filter, iterate, pick a recipe per item, submit through the audit boundary, optionally trigger review.

recipes_commonly_used is a hint, not a binding. The agent reads list_recipes(system_id=...) at runtime and picks per-item by matching use_when strings against recipes whose required sources are connected. When no recipe fits, the workflow surfaces a structured recipe_gap instead of writing directly.

Server-Side vs Calling-Agent Iteration

Three of the four workflows are calling-agent iteration: the agent loops items in its own context window, calling MCP tools per item. Fine for small sets (one control, ~50 questionnaire items).

campaign is server-side iteration: pretorin’s own CodexAgent walks items inside pretorin, calling the same recipe surface. The calling agent kicks off the campaign and observes status — it doesn’t iterate items in its own context. This is what makes thousand-control campaigns tractable without overwhelming the calling agent’s context window.

Authoring a New Workflow

v1 doesn’t ship a workflow scaffolder — workflows are first-party only. If you need a new iteration shape, open an issue describing:

  • What domain it iterates (controls? questions? something else?).
  • Why the existing four don’t fit.
  • The recipes the workflow would commonly use.

Community workflows land in v1.5 once the engagement layer is in place and the routing rules can include community contributions safely.

What’s Already Wired

  • Engagement layer (start_task) — picks the workflow from the user’s prompt entities. See Engagement Layer.
  • Capture preflightstart_task returns suggested_capture_plan and workflows can refresh it with check_sources. Evidence and narrative writes then proceed through recipe contexts.

Roadmap

  • Richer recipe execution — add more first-party and community recipes for operational systems such as GitHub, Kubernetes, and eMASS.
  • Community workflows — third loader path, scaffolder, validator. v1.5.

Engagement Layer

The engagement layer is pretorin’s routing boundary. When the user says “draft AC-2 for system X” or “answer my scope questionnaire” or “work through the AC family”, the calling agent’s first move is start_task. Pretorin picks the workflow; the agent then loads the workflow body and follows it.

This is the third layer in the routing model:

engagement      ← start_task (deterministic Python rules)
  workflow      ← get_workflow(selected) → markdown playbook
    recipe      ← list_recipes / start_recipe

Recipes are the leaf — what to do per item. Workflows are the trunk — how to iterate items. Engagement is the root — what kind of work we’re doing in the first place.

Why a Routing Layer

Without engagement, the calling agent guesses. Pattern-matching on nouns sends the agent into evidence/narrative write tools the moment the user says “AC-2”, which produces wrong-framework writes when the user wasn’t explicit and silently-cross-system writes when the active context shifted. The audit chain breaks.

The engagement layer fixes this with deterministic rules that run in pretorin (no LLM here). The calling agent extracts entities; the rules pick a workflow; the response carries the platform read-state the workflow needs. One round-trip, one routing decision, no drift.

What start_task Does

Three things:

  1. Validates the entities the calling agent extracted. Hallucinated control ids, unknown frameworks, unresolvable system names — these fail loud as MCP errors. The calling agent surfaces the error and asks the user to clarify.
  2. Cross-checks coherence. If the named control exists in some framework but not the one the user named, that’s an ambiguity. If the resolved system doesn’t match the active CLI context, that’s ambiguity too. The response is ambiguous: true with a reason — the calling agent surfaces it before any writes.
  3. Picks a workflow and seeds capture preflight. The rule cascade (in priority order):
    • intent_verb == "inspect_status" → no workflow, just inspect output.
    • intent_verb == "campaign" → campaign.
    • scope question ids OR (intent=answer AND pending scope) → scope-question.
    • policy question ids OR (intent=answer AND pending policy) → policy-question.
    • exactly one control id → single-control.
    • multiple control ids → campaign with control filter.
    • framework set, no controls → campaign over the framework.
    • else → ambiguous; ask the user.

The rule_matched field on the response records which rule fired so the audit trail captures the routing decision.

For evidence-producing workflows, start_task also returns suggested_capture_plan: source kinds, connected-source status, candidate recipes, selected recipe ids, and structured recipe_gap entries. The calling agent presents this plan before opening recipe contexts or writing evidence/narratives.

Entity Shape

The calling agent’s LLM extracts:

{
    "intent_verb": "work_on" | "collect_evidence" | "draft_narrative"
                 | "answer" | "campaign" | "inspect_status",
    "raw_prompt": "<user's verbatim prompt>",
    "system_id": "<id or name, or None>",
    "framework_id": "<id, or None>",
    "control_ids": ["ac-2", "ac-3"],
    "scope_question_ids": [],
    "policy_question_ids": [],
}

raw_prompt is audit-only — pretorin doesn’t parse it. Everything else either resolves cleanly or fails the cross-check.

Inspect Bundle

When the call succeeds, the response carries a bundled snapshot of the platform state the workflow will need:

  • workflow_state (per-system stage rollup)
  • compliance_status (overall posture)
  • pending_families (controls that still need work)
  • pending_scope_questions / pending_policy_questions
  • org_policies

The calling agent doesn’t have to issue separate reads for each — one round-trip yields the routing decision plus the context the workflow will reference.

Inspect is best-effort: if any one platform call fails, that section carries an error field but the rest of the payload still populates. Pass skip_inspect: true when you already have fresh state.

Three Response Shapes

  1. MCP error — entity validation or cross-check hard failure. The calling agent shows the error and stops.
  2. selected_workflow set, ambiguous: false — routed. Calling agent reads the workflow body and follows it.
  3. ambiguous: true with ambiguity_reason — coherence problem. Calling agent surfaces the reason to the user, gets clarification, and retries with disambiguated entities.

There’s no fourth shape. The router never produces a confidence score or alternatives — the rule either matched or didn’t.

Active System Context

Pass active_system_id (the user’s CLI context system) so the cross- check catches cross-system writes. When the resolved system doesn’t match, the response is ambiguous regardless of what the rules would say. This is the small extra friction that eliminates the silent wrong-system-write class of error.

Where the Code Lives

  • src/pretorin/engagement/entities.pyEngagementEntities pydantic model.
  • src/pretorin/engagement/selection.pyEngagementSelection response model.
  • src/pretorin/engagement/rules.py — pure-function rule cascade.
  • src/pretorin/engagement/cross_check.py — platform-state coherence checks.
  • src/pretorin/engagement/inspect.py — bundles platform reads into the response.
  • src/pretorin/mcp/handlers/engagement.py — the start_task MCP handler.

The rule cascade is testable in pure isolation — same inputs always produce the same output. Drift impossible by construction.

Worked Example: a Community Recipe

This walkthrough builds a community recipe end-to-end, the same way you would. The recipe captures the most recent N entries from a structured audit log file as evidence for an access-control review.

The recipe doesn’t ship with pretorin-cli — it’s a teaching artifact. Drop it under ~/.pretorin/recipes/audit-log-capture/ to actually run it; the files are reproduced below for reference.

What This Recipe Does

The audit team needs evidence that an admin’s recent actions are being logged. They have a structured log file (one JSON event per line). The recipe:

  1. Reads the last N entries from the log.
  2. Filters to events matching a username.
  3. Composes a markdown evidence body with the events as a code block.
  4. Returns the composed text to the calling agent so the agent can hand it to create_evidence.

The recipe doesn’t write evidence itself — it returns structured data the agent submits through the MCP write boundary. That’s where audit metadata gets stamped automatically.

Directory Layout

~/.pretorin/recipes/audit-log-capture/
├── recipe.md
├── README.md
└── scripts/
    └── capture.py

recipe.md

---
id: audit-log-capture
version: 0.1.0
name: "Audit Log Capture"
description: "Capture the most recent admin events from a JSONL audit log and return a formatted markdown body for evidence submission."
use_when: "The auditor needs evidence that admin actions are logged. You have a JSONL audit log file path and a username to filter on."
produces: evidence
author: "Example Team"
license: Apache-2.0
attests:
  - { control: AU-2, framework: nist-800-53-r5 }
  - { control: AU-3, framework: nist-800-53-r5 }
params:
  log_path:
    type: string
    description: "Absolute path to the JSONL audit log file"
    required: true
  username:
    type: string
    description: "Admin username to filter events for"
    required: true
  limit:
    type: integer
    description: "Maximum number of events to include"
    default: 20
scripts:
  capture:
    path: scripts/capture.py
    description: "Read the audit log, filter by username, return composed markdown."
    params:
      log_path:
        type: string
        description: "Absolute path to JSONL log"
        required: true
      username:
        type: string
        description: "Admin username"
        required: true
      limit:
        type: integer
        description: "Max events"
        default: 20
---

# Audit Log Capture

Reads the tail of a JSONL audit log, filters to events for one admin
user, and returns a composed markdown body the calling agent can submit
as a configuration evidence record.

The agent should attach the result to the relevant `AU-2` / `AU-3`
implementation narrative for the system.

scripts/capture.py

"""Capture recent audit events for a specific admin user."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from pretorin.evidence.markdown import compose


async def run(
    ctx: Any,
    *,
    log_path: str,
    username: str,
    limit: int = 20,
) -> dict[str, Any]:
    """Read tail of JSONL log, filter to one user, compose evidence body."""
    path = Path(log_path)
    if not path.is_file():
        raise FileNotFoundError(f"audit log not found: {log_path}")

    matched: list[dict[str, Any]] = []
    for raw in path.read_text(encoding="utf-8").splitlines():
        if not raw.strip():
            continue
        try:
            event = json.loads(raw)
        except json.JSONDecodeError:
            continue
        if event.get("user") == username:
            matched.append(event)
        if len(matched) >= limit:
            break

    snippet = "\n".join(json.dumps(e, sort_keys=True) for e in matched)
    body = compose(
        prose=(
            f"The {len(matched)} most recent audit events for user "
            f"{username!r} from {path.name}. Each line is one structured "
            "event (timestamp, action, target, source IP)."
        ),
        snippet=snippet,
        snippet_lang="json",
        file_path=str(path),
    )
    return {
        "username": username,
        "event_count": len(matched),
        "evidence_body": body,
    }

README.md

# audit-log-capture

A community recipe that captures admin audit events as markdown evidence.

## Try it

1. Drop this directory under ~/.pretorin/recipes/audit-log-capture/
2. Run `pretorin recipe validate audit-log-capture`
3. From your AI agent (Claude Code, Codex CLI), ask:
   > "Capture the last 20 audit events for admin alice from /var/log/audit.jsonl
   > as evidence for AU-2."

Walking Through It

Why split into manifest + script

Everything inside the frontmatter is what the calling agent reads to decide whether to use the recipe. Everything in scripts/capture.py is how the work happens. Keep the description sharp — the agent picks based on it.

Why use compose from pretorin.evidence.markdown

compose produces an audit-grade markdown body: prose explaining the context, a fenced code block with the snippet, an italic provenance footer with the file path and timestamp. Doing this by hand drifts; using the helper keeps every evidence record consistent.

Why no create_evidence call

The recipe returns the composed body and lets the calling agent submit it via create_evidence. This is the right shape for two reasons:

  1. Audit metadata gets stamped at the MCP write boundary. The recipe context is open in the calling agent’s session; routing through MCP means the handler reads the context and stamps producer_kind="recipe" automatically.
  2. The agent stays in the loop. The agent can review the composed body, ask the user to confirm before submitting, or attach extra metadata it pulled from elsewhere.

A recipe that takes raw input and produces structured output the agent hands to a writer tool is the most useful shape. Recipes that perform their own writes are sometimes appropriate (the scanner recipes do, via submit_test_results) but the default should be: return data, let the agent submit.

How the agent invokes it

The MCP tool name is recipe_audit_log_capture__capture (the hyphens in the recipe id become underscores; the script name is the suffix after __).

The agent’s call sequence:

start_recipe(id="audit-log-capture", version="0.1.0",
                      params={"log_path": "/var/log/audit.jsonl",
                              "username": "alice", "limit": 20})
  → returns context_id

recipe_audit_log_capture__capture(
    log_path="/var/log/audit.jsonl",
    username="alice",
    limit=20,
)
  → returns {"username": "alice", "event_count": 20, "evidence_body": "..."}

create_evidence(
    system_id="...",
    control_id="au-2",
    framework_id="nist-800-53-r5",
    name="Recent admin audit events for alice",
    evidence_type="configuration",
    description="Recent admin audit events were captured for alice.",
    artifact_content=<the evidence_body from the previous call>,
    source_excerpt=<the log lines used to compose the evidence>,
    capture_method="repository_file_read",
    recipe_context_id=<context_id>,
)
  → platform stamps producer_kind="recipe", producer_id="audit-log-capture",
                    producer_version="0.1.0"

end_recipe(context_id=<context_id>, status="pass")

What the agent should not do

The recipe doesn’t include a submit script that calls create_evidence directly. Why: every evidence record submitted from inside the recipe context picks up audit metadata at the MCP boundary; moving the write into the script would require the script to build metadata itself, which is one more place for the audit trail to drift.

When in doubt, return data, let the agent write.

Supported Frameworks

Pretorin provides access to 26 compliance frameworks and profiles spanning federal, contractor, defense industrial base, intelligence community, regulatory, and industry-specific compliance requirements.

Representative Frameworks

The table below highlights a representative subset of commonly used frameworks in Pretorin. Always call pretorin frameworks list to get the current catalog from the API for your environment. Control counts reflect the full catalog (base controls plus enhancements) as exposed by the platform.

IDTitleVersionTierFamiliesControls
nist-800-53-r5NIST SP 800-53 Rev 55.2.0tier1_essential201150
nist-800-171-r3NIST SP 800-171 Revision 31.0.0tier1_essential1797
fedramp-lowFedRAMP Rev 5 Low Baselinefedramp2.1.0-oscal1.0.4tier1_essential18156
fedramp-moderateFedRAMP Rev 5 Moderate Baselinefedramp2.1.0-oscal1.0.4tier1_essential18323
fedramp-highFedRAMP Rev 5 High Baselinefedramp2.1.0-oscal1.0.4tier1_essential18410
cmmc-l1CMMC 2.0 Level 1 (Foundational)2.0tier1_essential617
cmmc-l2CMMC 2.0 Level 2 (Advanced)2.0tier1_essential14110
cmmc-l3CMMC 2.0 Level 3 (Expert)2.0tier1_essential1024

Framework Tiers

Each framework has a tier classification displayed in the pretorin frameworks list output:

TierDescription
tier1_essentialCore frameworks most teams encounter first: NIST 800-53, NIST 800-171, the FedRAMP baselines, and all three CMMC levels.
tier2_importantSector-specific and adjacent baselines: DoD Cloud SRG / On-Prem, FIPS 140-3, GDPR, HIPAA, ICD-503, IoT Federal, ISO 27001, ISO 42001, MITRE ATLAS, NIST 800-218, NSS-IC, OT/ICS, PCI-DSS 4.0, and SOC 2.

Framework Relationships

Understanding how frameworks relate helps with cross-compliance:

NIST 800-53 Rev 5 (full catalog including enhancements, ~1150 controls)
├── FedRAMP Low/Moderate/High (800-53 subset + cloud requirements)
├── DoD Cloud IL2/IL4/IL5 + DoD On-Prem (FedRAMP + DoD additions)
├── NIST 800-171 Rev 3 (800-53 subset for CUI in non-federal systems)
│   └── CMMC Level 2 (maps to 800-171 requirements)
└── CMMC Level 3 (advanced controls beyond 800-171)

If an organization is already compliant with a parent framework, many child framework controls are already satisfied.

NIST SP 800-53 Rev 5

The foundational catalog for federal information systems. Includes 20 control families covering all aspects of information security. All other US government frameworks derive from it. The platform exposes the full catalog (base controls plus enhancements), which pretorin frameworks list reports as ~1150 controls.

Target audience: Federal agencies

NIST SP 800-171 Rev 3

Protects Controlled Unclassified Information (CUI) in non-federal systems. A focused subset of 800-53 with 97 requirements in the platform’s catalog.

Target audience: Federal contractors, universities, and other non-federal entities handling CUI under DFARS 252.204-7012 or similar requirements.

FedRAMP

Based on NIST 800-53 with additional cloud-specific requirements. Required for cloud services used by federal agencies.

Impact levels:

LevelIDControlsUse When
Lowfedramp-low156Public, non-sensitive data. Limited adverse effect from loss.
Moderatefedramp-moderate323CUI, PII, sensitive data. Serious adverse effect from loss. Most common level.
Highfedramp-high410Life-safety, financial, law enforcement data. Severe/catastrophic effect from loss.

Target audience: Cloud service providers to government

CMMC 2.0

Cybersecurity Maturity Model Certification for defense contractors. Required by DoD contracts.

LevelIDControlsUse When
Level 1cmmc-l117Handles only Federal Contract Information (FCI). Basic cyber hygiene.
Level 2cmmc-l2110Handles CUI. Aligns with NIST 800-171. Most defense contractors need this.
Level 3cmmc-l324Highest sensitivity CUI. Advanced practices on top of Level 2.

Target audience: Defense industrial base organizations

Note: CMMC Level 3 controls are in addition to Level 2. An organization at Level 3 must also satisfy all Level 2 controls.

Custom and Forked Frameworks

If your organization needs to track a framework that isn’t in the built-in catalog (e.g., an internal control set, a tailored ISO/SOC 2 mapping, an industry-specific regulation), you can author one yourself or fork an existing Pretorin-managed framework. The pretorin frameworks group exposes the full revision lifecycle:

  • Author from scratchinit-custom, validate-custom, upload-custom
  • Convert from OSCAL or 12 known custom catalog shapesbuild-custom
  • Fork an existing frameworkfork-framework, rebase-fork
  • Inspect draftsrevisions
  • Round-trip back to OSCALexport-oscal

See the Custom Frameworks guide for the end-to-end workflow.

See Framework Selection Guide for help choosing the right framework.

Control ID Formats

Correct ID formatting is critical. The Pretorin API returns errors on malformed IDs. When unsure, discover IDs first with pretorin frameworks families <id> or pretorin frameworks controls <id>.

NIST 800-53 Rev 5 / FedRAMP

Framework IDs: nist-800-53-r5, fedramp-low, fedramp-moderate, fedramp-high

Family IDs

Family IDs are lowercase slugs, not short codes:

CorrectIncorrect
access-controlac
audit-and-accountabilityau
identification-and-authenticationia
system-and-communications-protectionsc
configuration-managementcm
incident-responseir
risk-assessmentra

Control IDs

Control IDs are zero-padded with a hyphen:

CorrectIncorrect
ac-01ac-1, AC-1, ac1
ac-02ac-2, AC-2, ac2
au-02au-2, AU-2
sc-07sc-7, SC-7

Enhancement IDs append a dot-suffix or parenthetical suffix:

FormatExample
Dot notationac-02.1
Parentheticalac-02(1)

CMMC 2.0

Framework IDs: cmmc-l1, cmmc-l2, cmmc-l3

Family IDs

CMMC family IDs include a level suffix:

CorrectIncorrect
access-control-level-1access-control, ac
access-control-level-2access-control, ac-l2
incident-response-level-2incident-response, ir
system-and-communications-protection-level-3sc, sc-l3

Control IDs

CMMC control IDs use dotted notation with a level prefix and are case-sensitive:

CorrectIncorrect
AC.L2-3.1.1ac-01, 3.1.1
SC.L3-3.13.2sc-07, 3.13.2
AC.L1-3.1.22ac.l1-3.1.22

Use uppercase for the family prefix (e.g., AC, not ac).

NIST 800-171 Rev 3

Framework ID: nist-800-171-r3

Family IDs

Family IDs use the same lowercase slug convention as NIST 800-53:

CorrectIncorrect
access-controlac, 3.1
incident-responseir, 3.6
identification-and-authenticationia, 3.5

Control IDs

Control IDs use dotted notation with leading zeros:

CorrectIncorrect
03.01.013.1.1, ac-01
03.01.023.1.2, ac-02
03.13.013.13.1, sc-01

Auto-Normalization

The CLI and MCP tools automatically normalize NIST 800-53 and FedRAMP control IDs: uppercase is lowered and single-digit numbers are zero-padded. For example, AC-2 becomes ac-02 and SC-7.1 becomes sc-07.1. CMMC and NIST 800-171 IDs are passed through unchanged — use the exact format shown above.

Discovery Workflow

When a user provides an informal control reference (e.g., “AC-2” or “access control”):

  1. Call pretorin frameworks families <framework_id> to find the correct family slug
  2. Call pretorin frameworks controls <framework_id> --family <family_slug> to find the correct control ID
  3. Use the discovered ID in subsequent calls

Quick Reference

FrameworkFamily FormatControl FormatExample
NIST 800-53access-controlac-01pretorin frameworks control nist-800-53-r5 ac-02
FedRAMPaccess-controlac-01pretorin frameworks control fedramp-moderate ac-02
CMMCaccess-control-level-2AC.L2-3.1.1pretorin frameworks control cmmc-l2 AC.L2-3.1.1
800-171access-control03.01.01pretorin frameworks control nist-800-171-r3 03.01.01

Framework Selection Guide

Use this decision tree to identify the right compliance framework for your situation.

Decision Tree

1. Federal Agency (US Government)

Use: NIST 800-53 Rev 5 (nist-800-53-r5)

The foundational catalog for federal information systems. All other US government frameworks derive from it. Spans 20 control families covering all aspects of information security; the platform exposes the full catalog (base controls plus enhancements), which pretorin frameworks list reports as ~1150 controls. Use this when the organization IS a federal agency and needs the full control catalog.

2. Federal Contractor Handling CUI

Use: NIST 800-171 Rev 3 (nist-800-171-r3)

Protects Controlled Unclassified Information (CUI) in non-federal systems. A focused subset of 800-53 with 97 requirements in the platform’s catalog. Use this when the organization is a contractor, university, or other non-federal entity that handles CUI under DFARS 252.204-7012 or similar requirements.

3. Cloud Service Provider to Government

Use: FedRAMP (fedramp-low, fedramp-moderate, fedramp-high)

Based on NIST 800-53 with additional cloud-specific requirements. Required for cloud services used by federal agencies.

LevelIDControlsUse When
Lowfedramp-low156Public, non-sensitive data. Loss would have limited adverse effect.
Moderatefedramp-moderate323CUI, PII, sensitive but not critical data. Loss would have serious adverse effect. Most common level.
Highfedramp-high410Life-safety, financial, law enforcement, or emergency services data. Loss would have severe or catastrophic effect.

When unsure, FedRAMP Moderate is the most common starting point for cloud services handling government data.

4. Defense Industrial Base (DIB)

Use: CMMC (cmmc-l1, cmmc-l2, cmmc-l3)

Cybersecurity Maturity Model Certification for defense contractors. Required by DoD contracts.

LevelIDControlsUse When
Level 1cmmc-l117Handles only Federal Contract Information (FCI). Basic cyber hygiene.
Level 2cmmc-l2110Handles CUI. Aligns with NIST 800-171. Most defense contractors need this.
Level 3cmmc-l324Highest sensitivity CUI. Advanced/progressive practices on top of Level 2.

Note: CMMC Level 3 controls are in addition to Level 2.

5. None of the above fits — bring your own framework

If the built-in catalog doesn’t cover your obligation (e.g., an internal control set, a tailored mapping, an industry-specific regulation), you can author a custom framework or fork an existing one. See the Custom Frameworks guide for the pretorin frameworks init-custom / build-custom / upload-custom / fork-framework workflow.

Quick Reference

SituationFrameworkID
We’re a federal agencyNIST 800-53nist-800-53-r5
We handle CUI as a contractorNIST 800-171nist-800-171-r3
We’re a cloud service for governmentFedRAMPfedramp-moderate
We have a DoD contractCMMCcmmc-l2
We need to handle both CUI and cloudFedRAMP + 800-171Start with fedramp-moderate
We’re not sure yetStart with NIST 800-53nist-800-53-r5
Our framework isn’t in the catalogAuthor or fork your ownSee Custom Frameworks

Using AI Context for Selection

Call get_framework (MCP) or pretorin frameworks get <id> (CLI) to get AI context including purpose, target audience, regulatory context, scope, and key concepts. This helps confirm whether a framework is the right fit.

Custom Frameworks

The pretorin frameworks command group supports authoring, validating, and uploading custom and forked compliance frameworks through the platform’s revision-lifecycle endpoints.

The canonical artifact is unified.json. _index.json is not an upload format — it’s an internal index file used elsewhere in the data pipeline, but the platform’s revision lifecycle expects the full unified.json.

End-to-end workflow

init-custom ──► (edit) ──► validate-custom ──► upload-custom [--publish] ──► revisions
                                                          │
build-custom ◄────── (OSCAL or custom catalog as input) ──┘

Starting from scratch

Scaffold a minimal valid unified.json:

pretorin frameworks init-custom acme-soc2-tailored
# → writes unified.json with one sample family + one sample control

Edit the file, fill in your metadata, families, and controls, then run a local pre-flight check:

pretorin frameworks validate-custom unified.json

The bundled JSON Schema validator catches structural errors fast. The platform runs the authoritative validator on upload — additional issues may surface there.

Starting from OSCAL

Already have an OSCAL catalog? Convert it to unified.json:

pretorin frameworks build-custom my-oscal-catalog.json -f acme-iso27001 -o unified.json

The CLI auto-detects the input shape. OSCAL → unified preserves the _oscal blocks for lossless regeneration; you can round-trip back via export-oscal.

Starting from a custom catalog

Many compliance catalogs ship in custom (non-OSCAL) JSON shapes — control_families + controls, CIS-style nested safeguards, ISO control_themes, PCI-DSS, CSA-CCM domains, NIST AI RMF governance requirements, FIPS 140-3, DISA STIG wrappers, MITRE ATLAS, and more.

build-custom recognizes 12 known custom shapes and normalizes them all to unified.json:

pretorin frameworks build-custom catalog.json -f acme-soc2 -o unified.json

If the input shape isn’t recognized, the CLI tells you which shapes are supported. Use init-custom to scaffold instead and copy your data over manually.

Uploading

Upload a draft revision to the platform:

pretorin frameworks upload-custom unified.json

The platform validates synchronously. On a validation failure (HTTP 400) the CLI renders the platform’s structured validation_report as a readable table — you’ll see exact paths and messages for what to fix.

To upload and publish in one step:

pretorin frameworks upload-custom unified.json --publish

You can override the framework ID baked into the artifact and add a version label:

pretorin frameworks upload-custom unified.json -f acme-soc2-v2 -v "2026-Q1"

Linked forks

Fork a Pretorin-managed framework into your own draft. The platform records the lineage so you can rebase later when upstream advances.

pretorin frameworks fork-framework nist-800-53-r5 acme-nist-tailored
pretorin frameworks fork-framework fedramp-moderate acme-fedramp-mod -v initial

The fork starts as a draft anchored on the upstream’s current revision. Edit the resulting unified.json (export it from the platform UI or use revisions to find the draft), then re-upload via upload-custom.

Rebasing a fork

When upstream has advanced and you want to bring your fork forward:

pretorin frameworks rebase-fork acme-nist-tailored

The platform creates a fresh draft anchored on the latest upstream. You resolve any divergence locally and re-upload.

Listing revisions

See all drafts and published revisions for a framework:

pretorin frameworks revisions acme-nist-tailored

Exporting OSCAL

Regenerate an OSCAL catalog from a unified artifact:

pretorin frameworks export-oscal unified.json -o catalog.json

When the unified artifact retains the _oscal blocks (i.e., it was originally converted from OSCAL via build-custom), regeneration is lossless — props, parts, links, and back-matter are restored verbatim.

Command reference

CommandTalks to platform?Purpose
init-custom <id> [-t title] [-o path] [--force]NoScaffold a minimal valid unified.json
validate-custom <path>NoLocal JSON Schema pre-flight
build-custom <input> -f <id> [-o path] [--force]NoNormalize OSCAL / custom catalog → unified.json
upload-custom <path> [-f id] [-v label] [--publish]YesUpload draft revision; optionally publish
fork-framework <upstream_id> <new_id> [-v label]YesCreate linked-fork draft
rebase-fork <id> [-v label]YesCreate rebase draft against latest upstream
revisions <id>YesList drafts + published revisions
export-oscal <path> [-o path] [--force]NoRegenerate OSCAL catalog from unified.json

All commands respect the global --json flag for machine-readable output.

Notes for tool authors

The vendored conversion and validation primitives are exposed at pretorin.frameworks and can be imported directly:

from pretorin.frameworks import (
    custom_to_unified,
    oscal_to_unified,
    unified_to_oscal,
)
from pretorin.frameworks.validate import validate_unified
from pretorin.frameworks.templates import minimal_unified

These are pure-data functions — no I/O, no platform calls. Good for embedding in CI pipelines, agent tools, or your own automation.

Narrative & Evidence Workflow

This is the core workflow for updating control implementations on the platform. Follow this sequence for any control update.

Workflow Steps

1. Resolve the Target

Identify the system_id, control_id, and framework_id for your update. Set the active context:

pretorin context set --system "My Application" --framework fedramp-moderate

2. Read Current State

Before making changes, understand what’s already there:

# Get full control context (requirements + current implementation)
pretorin control context ac-02 --framework-id fedramp-moderate
# Via MCP: get_control_context

# Get current narrative
pretorin narrative get ac-02 fedramp-moderate

# Search existing evidence
pretorin evidence search --control-id ac-02 --framework-id fedramp-moderate

# List existing notes
pretorin notes list ac-02 fedramp-moderate

3. Collect Observable Facts

Search your codebase and connected systems for evidence. Only document what is directly observable — never assume or fabricate implementation details.

Treat existing Pretorin narratives, notes, and status fields as a starting point, not proof that a control gap exists. Before writing a narrative update or gap note, inspect the relevant implementation in the workspace and connected systems. If those sources show stronger implementation than the current platform record, update the narrative to reflect the observed implementation and record any remaining evidence gap separately.

4. Draft Updates

Prepare three types of updates:

Narrative — How the control is implemented. Include TODO placeholders for unknowns:

[[PRETORIN_TODO]]
missing_item: SSO configuration details
reason: Not observable from current workspace and connected MCP systems
required_manual_action: Export IdP SAML configuration
suggested_evidence_type: configuration
[[/PRETORIN_TODO]]

Evidence — Specific artifacts demonstrating implementation (config files, code, policies).

Gap Notes — Unresolved items requiring manual follow-up:

Gap: Missing MFA enforcement evidence
Observed: TOTP library imported in auth module
Missing: MFA policy enforcement configuration
Why missing: IdP admin console not accessible via codebase
Manual next step: Screenshot MFA policy from Azure AD admin portal

5. Push Updates

# Push a single narrative file
pretorin narrative push-file ac-02 fedramp-moderate "My Application" narrative-ac02.md

# Upsert evidence (finds or creates, then links)
pretorin evidence upsert ac-02 fedramp-moderate \
  --name "RBAC Configuration" \
  --description "Role mapping in IdP" \
  --artifact-content "## Evidence\n\n- Role mapping is enforced in the IdP export." \
  --type configuration

# Cadenced evidence with audit-sufficiency metadata
pretorin evidence upsert ac-02 fedramp-moderate \
  --name "Quarterly Access Review" \
  --description "Output of quarterly access review query" \
  --artifact-content "## Evidence\n\n- Quarterly access review query output is attached as Markdown." \
  --type attestation \
  --coverage-start 2026-01-01 --coverage-end 2026-03-31 \
  --capture-query "SELECT user_id, last_login FROM users WHERE ..." \
  --cadence-days 90

# Add gap notes
pretorin notes add ac-02 fedramp-moderate \
  --content "Gap: Missing MFA evidence..."

# Start/reopen implementation authoring
pretorin control status ac-02 in_progress \
  --framework-id fedramp-moderate

CLI and MCP callers may only set in_progress. Stage-for-approval, approval, and not-applicable decisions happen in the Pretorin UI by a human.

Read-Only Draft Workflow

When you want AI drafts before any platform writes:

  1. Resolve scope (system, control, framework)
  2. Read current state (context, narrative, evidence, notes)
  3. Generate drafts via pretorin agent run --skill narrative-generation or the MCP generate_control_artifacts tool
  4. Review the draft — clearly separate candidate narrative, evidence gaps, and manual follow-up actions
  5. Only push to the platform after explicit approval

Markdown Quality Rules

All narratives and evidence must pass markdown quality validation:

Narratives

  • No markdown headings (#, ##, etc.)
  • At least 2 rich markdown elements (code blocks, tables, lists, links)
  • At least 1 structural element (code block, table, or list)
  • No markdown images

Evidence

  • No markdown headings
  • At least 1 rich markdown element
  • No markdown images

Continuous Compliance & Cadenced Evidence

Evidence created with --cadence-days carries a refresh cadence; the platform stores expires_at = created_at + cadence_days and emits evidence.expiring / evidence.expired monitoring events as the deadline approaches and passes.

To re-affirm that cadenced evidence is still current, prefer validate so the CLI compares the fresh source-material hash before marking current:

pretorin evidence validate <evidence_id>
# If unchanged, records re_verified; if changed, replaces the Markdown artifact
# with a drift note instead of silently marking stale evidence current.

This fails with HTTP 400 if the evidence has no cadence set. The --coverage-start / --coverage-end flags describe the period the evidence content covers (point-in-time if --coverage-end is omitted). The --capture-query flag records the query, filter, or command that produced the artifact — auditors use this for IPE (Information Produced by the Entity) reproducibility.

Linking Evidence to CCI Implementations

To attach evidence to a per-system CCI implementation row (rather than the control as a whole), use link-cci:

pretorin evidence link-cci <evidence_id> <cci_implementation_uuid>

The CCI implementation UUID comes from pretorin cci impl <cci_uuid>; the row must already exist on the platform. Add --override-system-mismatch --override-reason "<why>" to permit cross-system attachment.

Evidence Deduplication

pretorin evidence upsert and the MCP create_evidence tool use find-or-create logic by default (dedupe: true):

  1. Search for an exact match on (name + description + type + control + framework) within the active system scope
  2. If found, reuse the existing evidence item
  3. If not found, create a new one
  4. Ensure the evidence is linked to the specified control

The response indicates whether the evidence was created (new) or reused, along with the match_basis.

Campaign Workflows

Campaigns are the recommended way to run bulk compliance operations across multiple controls, policies, or scope questions. They replace manual one-at-a-time updates with a coordinated prepare-claim-propose-apply lifecycle.

When to Use Campaigns

  • Initial control implementation — Draft narratives and evidence for an entire control family
  • Fixing review findings — Address issues flagged by family or policy reviews
  • Answering questionnaires — Bulk-answer policy or scope questions
  • Notes remediation — Fix controls flagged by platform notes

Campaign Lifecycle

Prepare → Claim → Draft → Propose → Apply
  1. Prepare — Snapshot platform state and create a checkpoint file. This captures the current state of all target items so the campaign works from a consistent baseline.

  2. Claim — Lease items for drafting. TTL-based leases prevent concurrent editing when multiple agents are working in parallel.

  3. Draft — For each claimed item, get full context (control requirements, current state, guidance) and produce a draft.

  4. Propose — Submit drafts as proposals without writing to the platform. This provides a review opportunity before any changes are persisted.

  5. Apply — Push all accepted proposals to the platform as a single operation.

External Agent Pattern

Campaigns are designed for external agents (Claude Code, Codex, Cursor, etc.) operating through MCP:

Agent A: prepare_campaign → claim_campaign_items → get_campaign_item_context → submit_campaign_proposal
Agent B: claim_campaign_items → get_campaign_item_context → submit_campaign_proposal
...
Coordinator: get_campaign_status → apply_campaign

The checkpoint file enables independent agent execution. Agents can claim non-overlapping items and work in parallel.

CLI Usage

Control Campaign

# Draft narratives for the Access Control family
pretorin campaign controls --mode initial --family AC \
  --system "My System" --framework-id fedramp-moderate

# Fix controls flagged by notes
pretorin campaign controls --mode notes-fix --family AC \
  --system "My System" --framework-id fedramp-moderate

# Fix controls flagged by review
pretorin campaign controls --mode review-fix --family AC --review-job <job-id> \
  --system "My System" --framework-id fedramp-moderate

# Auto-apply after completion
pretorin campaign controls --mode initial --family AC --apply \
  --system "My System" --framework-id fedramp-moderate

Policy Campaign

# Answer all incomplete policy questions
pretorin campaign policy --mode answer --all-incomplete

# Fix review findings for a specific policy
pretorin campaign policy --mode review-fix --policies <policy-id>

Scope Campaign

pretorin campaign scope --mode answer \
  --system "My System" --framework-id fedramp-moderate

Check Status

pretorin campaign status --checkpoint .pretorin/campaign-checkpoint.json

MCP Tool Sequence

For AI agents working through MCP:

  1. get_workflow_state — Understand what needs work
  2. get_pending_families — Identify target families
  3. prepare_campaign — Create the campaign
  4. claim_campaign_items — Claim items
  5. get_campaign_item_context — Get context per item
  6. submit_campaign_proposal — Submit drafts
  7. get_campaign_status — Review progress
  8. apply_campaign — Push to platform

Policy & Scope Questionnaires

Pretorin uses questionnaire workflows to capture organizational policy information and system scope details. Both follow a similar lifecycle: answer questions, generate documents, review, and iterate.

Policy Questionnaire Workflow

Organization policies (e.g., Access Control Policy, Incident Response Policy) are defined at the org level and apply across systems.

1. List Available Policies

pretorin policy list

Or via MCP: list_org_policies

2. View Current State

# Show questionnaire state and saved review findings
pretorin policy show --policy <policy-id-or-name>

Or via MCP:

get_pending_policy_questions  # lightweight — only unanswered
get_policy_question_detail    # guidance and examples per question

3. Answer Questions

Via CLI — Draft answers from your workspace:

# Preview proposed answers
pretorin policy populate --policy <policy-id>

# Apply answers to the platform
pretorin policy populate --policy <policy-id> --apply

Via MCP — Answer individually for precise control:

answer_policy_question(policy_id, question_id, answer)

Or batch-update multiple answers:

patch_org_policy_qa(policy_id, updates=[{question_id, answer}, ...])

4. Generate Policy Document

Once questions are answered, trigger AI document generation:

trigger_policy_generation(policy_id)

5. Review

Trigger an AI review of the policy:

trigger_policy_review(policy_id)
get_policy_review_results(policy_id)  # poll for results

Review results include findings with severity levels, affected sections, and recommended fixes.

6. Track Status

get_policy_workflow_state(policy_id)
get_policy_analytics(policy_id)

Scope Questionnaire Workflow

Scope questionnaires are system+framework specific. They define what’s in scope, what’s excluded, and system boundary details.

1. View Current State

# Show scope questionnaire state and review findings
pretorin scope show --system "My System" --framework-id fedramp-moderate

Or via MCP:

get_pending_scope_questions(system_id, framework_id)
get_scope_question_detail(system_id, framework_id, question_id)

2. Answer Questions

Via CLI — Draft answers from your workspace:

# Preview proposed answers
pretorin scope populate --system "My System" --framework-id fedramp-moderate

# Apply answers to the platform
pretorin scope populate --system "My System" --framework-id fedramp-moderate --apply

Via MCP — Answer individually:

answer_scope_question(system_id, framework_id, question_id, answer)

Or batch-update:

patch_scope_qa(system_id, framework_id, updates=[{question_id, answer}, ...])

3. Generate Scope Document

trigger_scope_generation(system_id, framework_id)

4. Review

trigger_scope_review(system_id, framework_id)
get_scope_review_results(system_id, framework_id)

5. View Full Scope

get_scope(system_id, framework_id)

Returns scope narrative, excluded controls, and Q&A responses.

Bulk Questionnaire Campaigns

For answering many questions at once, use campaigns:

# Answer all incomplete policy questions
pretorin campaign policy --mode answer --all-incomplete

# Answer scope questions
pretorin campaign scope --mode answer --system "My System" --framework-id fedramp-moderate

# Fix review findings
pretorin campaign policy --mode review-fix --policies <policy-id>

See Campaign Workflows for details on the campaign lifecycle.

Vendor Inheritance

Many compliance controls are partially or fully satisfied by external providers (cloud platforms, SaaS tools, managed services). Pretorin tracks these inheritance relationships and keeps inherited narratives in sync with vendor documentation.

Concepts

  • Vendor — An external provider or internal shared service (CSP, SaaS, managed service, internal)
  • Responsibility edge — A link between a control and a vendor indicating the control is inherited or shared
  • Stale edge — A responsibility edge where the source narrative has changed but the inherited control hasn’t been updated

Workflow

1. Create Vendor Entities

pretorin vendor create "AWS GovCloud" --type csp \
  --description "Primary cloud infrastructure" \
  --authorization-level "FedRAMP High P-ATO"

pretorin vendor create "Okta" --type saas \
  --description "Identity and access management"

2. Upload Vendor Documentation

pretorin vendor upload-doc <vendor_id> ./aws-crm.pdf \
  --name "AWS Customer Responsibility Matrix" \
  --attestation-type vendor_provided

pretorin vendor upload-doc <vendor_id> ./okta-soc2.pdf \
  --name "Okta SOC 2 Type II Report" \
  --attestation-type third_party_attestation

3. Set Control Responsibility

Via MCP tools:

set_control_responsibility(
  system_id,
  control_id,
  framework_id,
  responsibility_mode,  # "inherited" or "shared"
  source_type,          # "provider", "internal", or "hybrid"
  vendor_id,            # optional — vendor providing the inherited control
)

Responsibility modes:

  • inherited — Fully satisfied by the vendor
  • shared — Partially satisfied; your system handles the remainder

4. Generate Inheritance Narratives

generate_inheritance_narrative(system_id, control_id, framework_id)

AI generates a narrative grounded in the vendor’s uploaded documentation (resolved via the responsibility edge), explaining how the vendor satisfies the control requirements.

5. Monitor Staleness

Over time, vendor documentation or source narratives may be updated. Check for stale inheritance:

get_stale_edges(system_id)

Returns controls where the source has changed but the inherited narrative hasn’t been refreshed.

6. Sync Stale Edges

sync_stale_edges(system_id)

Bulk updates inherited controls by regenerating narratives from the latest source.

Linking Evidence to Vendors

link_evidence_to_vendor(evidence_id, vendor_id, attestation_type)

Attestation types: self_attested, third_party_attestation, vendor_provided

Gap Analysis Workflow

A systematic approach to assessing a codebase against a compliance framework’s controls.

Step 1: Scope the Assessment

Determine which framework and control families to assess.

# List frameworks if not specified
pretorin frameworks list

# List control families for the chosen framework
pretorin frameworks families fedramp-moderate

Not all families will have code evidence. Prioritize based on evidence likelihood.

Step 2: Prioritize Control Families

High Priority (Direct Code Evidence)

These families typically have strong evidence in source code:

FamilyWhat to Search For
Access Control (AC)Authentication systems, RBAC/ABAC, session management, user provisioning
Audit & Accountability (AU)Logging frameworks, audit trails, log retention, structured logging
Identification & Authentication (IA)Login flows, MFA, password hashing, credential storage, OAuth/SAML
System & Communications Protection (SC)TLS config, encryption, network boundaries, CORS, API security
Configuration Management (CM)Config files, env handling, version pinning, baseline settings, IaC

Medium Priority (Mixed Code/Policy)

FamilyWhat to Search For
System Acquisition (SA)Secure development practices, dependency management, SAST/DAST configs
System Integrity (SI)Input validation, error handling, malware protection configs
Assessment (CA)Security testing configs, vulnerability scanning, CI/CD security gates

Lower Priority (Mostly Policy)

Primarily documentation-based, unlikely to have code evidence:

  • Awareness & Training (AT)
  • Planning (PL)
  • Personnel Security (PS)
  • Physical Protection (PE)
  • Program Management (PM)

Step 3: Collect Evidence

For each high-priority family:

  1. List controls filtered by family:

    pretorin frameworks controls fedramp-moderate --family access-control
    
  2. For each relevant control, get AI guidance:

    pretorin frameworks control fedramp-moderate ac-02
    

    References and AI guidance are shown by default. The ai_guidance field provides evidence expectations, implementation considerations, and common failures. Use --brief to show only the basic info panel.

  3. Search the codebase using guidance-informed patterns:

File patterns:

**/auth/**          **/users/**         **/accounts/**
**/logging/**       **/audit/**         **/security/**
**/config/**        **/settings/**      **/crypto/**
**/identity/**      **/iam/**           **/rbac/**
**/middleware/**     **/terraform/**     **/k8s/**

Keyword patterns:

authenticate, authorize, permission, role, session
log, audit, event, trace, record
encrypt, tls, ssl, https, certificate
config, setting, baseline, default
password, credential, hash, mfa, token
  1. For each piece of evidence, note the file path, line numbers, and what it demonstrates.

Step 4: Assess Implementation Status

For each control, assign a status:

StatusCriteria
ImplementedFull requirements met with clear code evidence
PartialSome requirements met, others missing or incomplete
PlannedArchitecture supports it but feature not built yet
Not ApplicableControl doesn’t apply to this component
GapControl requirements not addressed at all

Use ai_guidance.common_failures to calibrate your assessment — if the codebase exhibits a known failure pattern, it’s likely a gap or partial implementation.

Step 5: Produce the Report

Structure the gap analysis output as:

Summary

  • Framework assessed and total controls in scope
  • Counts by status (implemented, partial, planned, not applicable, gap)
  • Overall compliance posture assessment

Family-by-Family Findings

For each assessed family:

  • Family name and total controls
  • Status breakdown
  • Key findings with evidence references
  • Gaps with remediation recommendations

Priority Remediation Items

Rank gaps by:

  1. Controls with the highest security impact
  2. Controls that are prerequisites for other controls (check related controls)
  3. Controls that are easiest to implement (quick wins)

Evidence Summary

For each assessed control:

  • Control ID and title
  • Implementation status
  • Evidence file paths and descriptions
  • Recommendations if partial or gap

Example Output

See the example gap analysis for a complete sample report.

Tips

  • Start broad (family level) and drill into specific controls where evidence exists
  • Use pretorin frameworks control <fw> <ctrl> for AI guidance — it provides the richest context (references are included by default; use --brief to skip them)
  • Check related controls to identify dependencies
  • For infrastructure evidence, look at Terraform, CloudFormation, Dockerfiles, Helm charts, and CI/CD configs
  • For application evidence, focus on auth, logging, crypto, and configuration code

Example: Gap Analysis Report

This example shows a gap analysis for a hypothetical web application assessed against FedRAMP Moderate.

Gap Analysis: Acme Web Platform — FedRAMP Moderate

Summary

MetricValue
FrameworkFedRAMP Rev 5 Moderate (fedramp-moderate)
ComponentAcme Web Platform
Families Assessed5 of 18 (high-priority code-evidenced families)
Controls in Scope47
Implemented18 (38%)
Partial14 (30%)
Planned3 (6%)
Not Applicable4 (9%)
Gap8 (17%)

Overall Posture: Partial compliance. Strong authentication and logging foundations, but gaps in account lifecycle management, boundary protection, and baseline configuration documentation.


Access Control (AC) — 12 controls assessed

Status: 5 implemented, 4 partial, 1 planned, 2 gap

Key Findings:

  • AC-02 (Account Management) — Partial. User creation with role assignment exists in src/auth/users.py:45-72, but no account expiration, dormant account handling, or manager approval workflow.
  • AC-03 (Access Enforcement) — Implemented. RBAC middleware in src/middleware/auth.py:12-38 enforces role-based access on all API routes.
  • AC-07 (Unsuccessful Logon Attempts) — Implemented. Account lockout after 5 failed attempts in src/auth/login.py:89-105.
  • AC-17 (Remote Access) — Gap. No VPN or remote access controls documented.

Recommendations:

  1. Add account expiration and dormant account cleanup (addresses AC-02 gaps)
  2. Implement remote access policy and controls for administrative access (addresses AC-17)

Audit & Accountability (AU) — 8 controls assessed

Status: 5 implemented, 2 partial, 1 gap

Key Findings:

  • AU-02 (Audit Events) — Implemented. Structured JSON logging for auth events, data access, and admin actions.
  • AU-03 (Content of Audit Records) — Implemented. Logs include timestamp, user ID, action, outcome, and source IP.
  • AU-06 (Audit Record Review) — Gap. No automated log review or alerting configured.

Recommendations:

  1. Configure CloudWatch alarms for security events (addresses AU-06)
  2. Add log review procedures and alerting rules

System & Communications Protection (SC) — 10 controls assessed

Status: 2 implemented, 4 partial, 2 planned, 2 not applicable

Key Findings:

  • SC-07 (Boundary Protection) — Partial. TLS 1.3 and CORS configured, but security groups allow broad ingress.
  • SC-08 (Transmission Confidentiality) — Implemented. All traffic encrypted via TLS 1.3 with HSTS.
  • SC-28 (Protection of Information at Rest) — Planned. Database encryption not yet enabled.

Priority Remediation

PriorityControlGapEffort
1SC-28Enable RDS encryption at restLow — Terraform change
2AU-06Add CloudWatch alerting for security eventsMedium — alerting rules
3AC-02Account lifecycle managementMedium — new feature
4CM-02/CM-06Baseline configuration documentationMedium — documentation
5AC-17Remote access controls for admin accessHigh — new infrastructure

Artifact Generation

Compliance artifacts are structured JSON documents that describe how a specific control is implemented within a component.

Generating Artifacts

Via Agent

pretorin agent run --skill evidence-collection "Generate artifact for AC-02 in my system"

Via MCP

Use the generate_control_artifacts tool for read-only AI drafts.

Submit to Platform

pretorin frameworks submit-artifact artifact.json

Artifact Schema

{
  "framework_id": "fedramp-moderate",
  "control_id": "ac-02",
  "component": {
    "component_id": "my-application",
    "title": "My Application",
    "description": "A web application that handles user data",
    "type": "software",
    "control_implementations": [
      {
        "control_id": "ac-02",
        "description": "2-3 sentence narrative explaining HOW the control is implemented",
        "implementation_status": "implemented",
        "responsible_roles": ["System Administrator", "Security Team"],
        "evidence": [
          {
            "description": "What this evidence demonstrates",
            "file_path": "src/auth/users.py",
            "line_numbers": "45-72",
            "code_snippet": "def create_user(username, role):\n    ..."
          }
        ],
        "remarks": "Optional additional context"
      }
    ]
  },
  "confidence": "high"
}

See Artifact Schema Reference for the full field documentation.

Implementation Status Values

StatusCriteria
implementedFully implemented and operational. Clear, direct code evidence.
partialSome aspects implemented, others pending.
plannedNot yet implemented but scheduled. Architecture supports it.
not-applicableControl doesn’t apply to this component.

Confidence Levels

LevelCriteria
highClear, direct evidence in code. Specific file paths and line numbers.
mediumReasonable evidence with some inference required.
lowLimited evidence. Significant assumptions made.

Evidence Quality

Good evidence shows HOW a control is implemented with specifics. Weak evidence merely shows that relevant code exists.

Good:

User creation requires role assignment and manager approval via the create_user() function which validates roles against an allowlist and triggers an approval workflow.

Weak:

Has a User class in the models file.

Guidelines

  • Call pretorin frameworks control <fw> <ctrl> first — the AI guidance describes exactly what evidence assessors expect
  • Include specific file paths and line numbers
  • Keep code snippets brief (under 10 lines)
  • Focus on the most relevant evidence, not exhaustive listing
  • Describe what the evidence demonstrates in relation to the control requirement

Example: Good Artifact

{
  "framework_id": "fedramp-moderate",
  "control_id": "ac-02",
  "component": {
    "component_id": "acme-web-platform",
    "title": "Acme Web Platform",
    "description": "A web application with multi-tenant user management",
    "type": "software",
    "control_implementations": [
      {
        "control_id": "ac-02",
        "description": "The application implements account management through a provisioning system that requires role assignment during user creation, enforces manager approval for elevated roles, and automatically disables accounts after 90 days of inactivity.",
        "implementation_status": "implemented",
        "responsible_roles": ["System Administrator", "Security Team", "Team Managers"],
        "evidence": [
          {
            "description": "User creation requires role assignment and manager approval for admin roles",
            "file_path": "src/users/provisioning.py",
            "line_numbers": "45-72",
            "code_snippet": "def create_user(username, role, manager_id):\n    validate_role(role)\n    if role in ELEVATED_ROLES:\n        require_approval(manager_id)\n    user = User.create(username=username, role=role)"
          },
          {
            "description": "Automated dormant account detection and deactivation after 90 days",
            "file_path": "src/users/lifecycle.py",
            "line_numbers": "120-145",
            "code_snippet": "def check_dormant_accounts():\n    threshold = datetime.utcnow() - timedelta(days=90)\n    dormant = User.query.filter(User.last_login < threshold)"
          }
        ],
        "remarks": "Account removal via soft delete to maintain audit trail."
      }
    ]
  },
  "confidence": "high"
}

Example: Partial Implementation

{
  "framework_id": "fedramp-moderate",
  "control_id": "sc-07",
  "component": {
    "component_id": "acme-web-platform",
    "title": "Acme Web Platform",
    "description": "A web application with multi-tenant user management",
    "type": "software",
    "control_implementations": [
      {
        "control_id": "sc-07",
        "description": "TLS 1.3 enforced and CORS restricted to specific origins. However, security group ingress allows broad access from 0.0.0.0/0 on port 443, and no WAF is configured.",
        "implementation_status": "partial",
        "responsible_roles": ["System Administrator", "DevOps Team"],
        "evidence": [
          {
            "description": "CORS restricted to application origins only",
            "file_path": "src/api/middleware.py",
            "line_numbers": "8-15",
            "code_snippet": "app.add_middleware(\n    CORSMiddleware,\n    allow_origins=['https://app.acme.com'])"
          },
          {
            "description": "Security group allows unrestricted ingress — overly permissive",
            "file_path": "terraform/security.tf",
            "line_numbers": "12-25",
            "code_snippet": "ingress {\n    from_port = 443\n    cidr_blocks = [\"0.0.0.0/0\"]\n}"
          }
        ],
        "remarks": "Recommend restricting security group ingress and adding WAF."
      }
    ]
  },
  "confidence": "medium"
}

Asset Inventory & System Spec

Auditors require five “system spec” artifacts: an asset inventory plus four snapshot kinds (data flow, network topology, threat model, hardware/software baseline). The platform exposes these as typed entities so the inventory can evolve over time and each snapshot can be attested independently.

This page covers the CLI / MCP surface for the asset inventory specifically. The four snapshot kinds are authored in the platform UI today; toggling them required/optional is the only CLI touchpoint.

CLI commands

All commands live under pretorin scope artifacts. They wrap the public /api/v1/public/systems/{id}/spec/* endpoints and require a token with the system_spec.read (reads) or system_spec.write (writes) scope.

# 1. List the 5 artifact kinds with their required / toggle / attest state.
pretorin scope artifacts list

# 2. Inspect the current asset inventory (or replay history).
pretorin scope artifacts inventory show
pretorin scope artifacts inventory show --as-of 2026-04-01T00:00:00Z

# 3. Upload a CSV.
#    The CLI fetches the current platform inventory first, classifies your CSV
#    rows into added / modified / decommissioned, shows a diff preview, and
#    posts a single diff.
pretorin scope artifacts inventory upload assets.csv

# 4. Scan a source and apply the resulting diff.
pretorin scope artifacts inventory scan aws            # live AWS account
pretorin scope artifacts inventory scan azure          # live Azure subscription
pretorin scope artifacts inventory scan k8s            # live K8s via kubectl
pretorin scope artifacts inventory scan iac-workspace  # local .tf / k8s YAML files
pretorin scope artifacts inventory scan aws --dry-run  # show diff without applying

# 5. Toggle an artifact kind required/optional.
#    Rationale is required when toggling off.
pretorin scope artifacts toggle threat_model --optional \
    --rationale "single-tenant scope; threat model lives in linked vendor SSP"

Scan sources

Each inventory scan <source> runs a built-in recipe that lives under src/pretorin/recipes/_recipes/asset-inventory-<source>/:

SourceRecipeWhat it reads
awsasset-inventory-aws-baselineLive AWS API (boto3) — EC2 instances in v1
azureasset-inventory-azure-baselineLive Azure Resource Manager — Compute VMs in v1
k8sasset-inventory-k8s-baselinekubectl get against the active context — nodes + Deployment/StatefulSet/DaemonSet
iac-workspaceasset-inventory-iac-workspaceStatic parse of .tf, .tf.json, K8s YAML, and cloudformation.{json,yaml} files in the cwd

The iac-workspace recipe is the generalized “look at the IaC checked into this repo” path — it does not call any cloud API and does not require any cloud credentials. Use it when you want the inventory to match the desired state in source control rather than current cloud state.

Adding a new source means adding a new recipe directory and updating SCAN_SOURCE_TO_RECIPE_ID in src/pretorin/spec.py. Community recipes can live under ~/.pretorin/recipes/ and override built-ins by id.

Decommission semantics

The diff endpoint never silently revives a decommissioned asset. If you upload a CSV (or scan) that contains an external_id matching a previously-decommissioned row, the platform returns an outcome: "not_found" item with a message telling you to re-activate via the UI rather than re-scan. The CLI surfaces this clearly in the diff response — look for the yellow “Some rows did not apply cleanly” block.

MCP tools

Three tools mirror the read + diff endpoints for AI agents:

  • list_artifact_requirements(system_id) — wraps the kinds endpoint.
  • get_asset_inventory(system_id, as_of?) — wraps the inventory read.
  • submit_asset_inventory_diff(system_id, recipe_id, added?, modified?, decommissioned?, idempotency_key?, recipe_context_id?) — posts the diff. recipe_id is a free-form string (the platform records cli:<recipe_id> as per-row provenance). idempotency_key defaults to sha256(system_id, recipe_id, scan_timestamp)[:32]; pass your own when replaying a scan. recipe_context_id is optional — set it when the diff is being produced inside an active recipe context opened via start_recipe.

The diff endpoint accepts recipe_context_id cleanly but does not require it, unlike the create_evidence write surface. There’s no 11-field audit metadata envelope required for inventory diffs.

Cross-Framework Mapping

Map controls across related frameworks to identify overlaps, reduce duplicate work, and understand framework relationships.

When to Use Cross-Framework Mapping

  • Dual compliance — Organization needs FedRAMP + CMMC. Map overlapping controls to avoid duplicate work.
  • Framework migration — Moving from 800-171 to FedRAMP. Identify which controls already satisfy FedRAMP requirements.
  • Gap identification — Already compliant with 800-53 and need CMMC. Find the delta.
  • Audit preparation — Show auditors how controls in one framework map to another.

Workflow

Step 1: Start with the Source Control

Query the control with references to discover relationships:

pretorin frameworks control nist-800-53-r5 ac-02

References are shown by default. The Related Controls field reveals connections to other controls and frameworks.

Step 2: Build the Mapping

Look up the equivalent control in each target framework:

FrameworkControl IDTitle
NIST 800-53 Rev 5ac-02Account Management
FedRAMP Moderateac-02Account Management
NIST 800-171 Rev 303.01.01Account Management
CMMC Level 2AC.L2-3.1.1Authorized Access Control

Step 3: Compare Requirements

Get details for each framework’s version of the control:

pretorin frameworks control nist-800-53-r5 ac-02
pretorin frameworks control fedramp-moderate ac-02
pretorin frameworks control nist-800-171-r3 03.01.01
pretorin frameworks control cmmc-l2 AC.L2-3.1.1

Compare what each framework emphasizes. For Account Management:

  • NIST 800-53 — Full control with 13 enhancements. Covers account types, conditions, authorized users, managers, CRUD, monitoring, and atypical usage.
  • FedRAMP Moderate — Same base control with FedRAMP-specific parameter values (e.g., specific timeframes for disabling inactive accounts).
  • NIST 800-171 — Streamlined from 800-53. Core requirements: defining types, assigning managers, establishing conditions, authorizing access, monitoring.
  • CMMC Level 2 — Maps directly to 800-171 03.01.01. Same core requirements framed as maturity practices.

Step 4: Identify Gaps and Overlaps

NIST 800-53 AC-02 (most comprehensive)
  ├── Includes all FedRAMP Moderate AC-02 requirements ✓
  ├── Includes all NIST 800-171 03.01.01 requirements ✓
  └── Includes all CMMC L2 AC.L2-3.1.1 requirements ✓

FedRAMP Moderate AC-02
  ├── Satisfies NIST 800-171 03.01.01 ✓
  └── Satisfies CMMC L2 AC.L2-3.1.1 ✓

NIST 800-171 03.01.01
  └── Satisfies CMMC L2 AC.L2-3.1.1 ✓

Key insight: Compliance with a parent framework generally satisfies the child framework’s corresponding control. Always verify with pretorin frameworks control <fw> <ctrl> to check for framework-specific parameters or additional requirements (references are included by default).

Using MCP for Cross-Framework Mapping

With an MCP-connected AI agent, ask questions like:

“Map Account Management controls across NIST 800-53, FedRAMP Moderate, and CMMC Level 2. Show me the overlaps and any unique requirements.”

The agent will use get_control and get_control_references to discover and compare related controls across frameworks.

STIG Compliance Scanning

Pretorin integrates STIG (Security Technical Implementation Guide) scanning to verify technical control implementations. The scanning workflow connects NIST 800-53 controls to specific technical checks via the CCI (Control Correlation Identifier) chain.

Traceability Chain

NIST 800-53 Control → CCIs → SRGs → STIG Rules → Scanner Results
  • CCI — Control Correlation Identifier: bridges a control requirement to testable items
  • SRG — Security Requirements Guide: technology-neutral security requirements
  • STIG Rule — Technology-specific check with detailed test and fix procedures

Browse the Chain

Find Applicable STIGs

# Show STIGs applicable to your system
pretorin stig applicable --system "My System"

# AI-infer STIGs from system profile
pretorin stig infer --system "My System"

Explore the Traceability

# Full chain from a NIST control to STIG rules
pretorin cci chain ac-2 --system "My System"

# Browse CCIs for a control
pretorin cci list --control ac-2

# See what a specific CCI requires
pretorin cci show CCI-000015

# Browse STIG rules
pretorin stig rules <stig_id> --severity cat_i

Scanning Workflow

Scanning is driven by recipes that the calling AI agent invokes through MCP. Each scanner ships as a built-in recipe (inspec-baseline, openscap-baseline, cloud-aws-baseline, cloud-azure-baseline, manual-attestation).

1. Discover Available Recipes

pretorin recipe list
pretorin recipe show inspec-baseline

2. Review Test Manifest

The agent uses get_test_manifest (MCP) to see which STIGs and rules apply to a system before running a scan. From the CLI you can browse the relationships directly:

pretorin stig applicable --system "My System"
pretorin cci chain ac-2 --system "My System"

3. Ask the Agent to Run the Scan

Inside Claude Code, Codex CLI, or pretorin agent run, ask:

“Run an inspec-baseline scan against RHEL_9_STIG on this system.”

The agent will open a recipe context, call the recipe’s run_scan script, and submit results through submit_test_results. There is no direct CLI command for executing a scan — the recipe layer is the contract surface.

4. Submit Results Manually

If you have raw scanner output and want to upload it without running through a recipe, push it directly via MCP:

submit_test_results(system_id, results)

5. Attach Evidence to a Failing Rule

When a rule fails, attach remediation proof, mitigating-control documentation, or waiver-justification artifacts to the rule’s per-system workflow row. The workflow row is lazy-created on first attachment.

# Create the evidence
pretorin evidence upsert ac-02 fedramp-moderate \
  --name "RHEL hardening playbook output" \
  --description "Ansible run output applying CAT-I remediations" \
  --artifact-content "## Evidence\n\n- Ansible run output applying CAT-I remediations." \
  --type configuration

# Link it to the STIG rule by catalog rule UUID
pretorin evidence link-stig <evidence_id> <stig_rule_uuid>

Add --override-system-mismatch --override-reason "<why>" to permit cross-system attachment when the evidence belongs to a different system than the active context.

MCP Tools for STIG/CCI

ToolDescription
list_stigsList benchmarks with filters
get_stigBenchmark detail
list_stig_rulesRules with severity/CCI filters
get_stig_ruleFull rule: check text, fix text, CCIs
list_ccisCCIs with control filter
get_cciCCI detail with linked rules
get_cci_chainFull traceability chain
get_cci_statusCCI compliance rollup
get_stig_applicabilityApplicable STIGs for a system
infer_stigsAI-infer applicable STIGs
get_test_manifestTest manifest for a system
submit_test_resultsUpload scan results

Environment Variables

Environment variables override stored configuration values.

Authentication & API

VariableDescriptionDefault
PRETORIN_API_KEYAPI key for platform access. Overrides api_key in config file.
PRETORIN_PLATFORM_API_BASE_URLPlatform REST API base URLhttps://platform.pretorin.com/api/v1/public
PRETORIN_API_BASE_URLBackward-compatible alias for PRETORIN_PLATFORM_API_BASE_URL
PRETORIN_MODEL_API_BASE_URLModel API URL for agent runtimehttps://platform.pretorin.com/api/v1/public/model

Context

VariableDescriptionDefault
PRETORIN_SYSTEM_IDActive system ID. Overrides the system set via pretorin context set.
PRETORIN_FRAMEWORK_IDActive framework ID. Overrides the framework set via pretorin context set.

Agent Runtime

VariableDescriptionDefault
OPENAI_API_KEYModel key override for agent runtime. Takes precedence over stored Pretorin login key.
OPENAI_BASE_URLBase URL for the model API endpoint. Overrides openai_base_url in config file.
OPENAI_MODELModel name for the agent runtime.gpt-4o

Source Attestation

VariableDescriptionDefault
PRETORIN_SOURCE_PROVIDERSJSON array of source provider configurations. Overrides source_providers in config file.
PRETORIN_SOURCE_MANIFESTJSON string or file path to a source manifest. Falls back to .pretorin/source-manifest.json in the git repo root, then ~/.pretorin/source-manifest-{system_id}.json, then the source_manifest config key.

Behavior

VariableDescriptionDefault
PRETORIN_DISABLE_UPDATE_CHECKSet to a truthy value (1, true, yes, on) to disable passive update notifications.
PRETORIN_LOG_LEVELLogging level (DEBUG, INFO, WARNING, ERROR)WARNING
PRETORIN_MCP_TELEMETRY_DISABLEDSet to any non-empty value to suppress the PRETORIN_TELEMETRY_EVENT JSON lines that pretorin mcp-serve emits on stderr for tool-routing observability.

Precedence

For the API key:

  1. PRETORIN_API_KEY environment variable (highest)
  2. api_key in ~/.pretorin/config.json

For the platform API URL:

  1. PRETORIN_PLATFORM_API_BASE_URL environment variable (highest)
  2. PRETORIN_API_BASE_URL environment variable (legacy alias)
  3. platform_api_base_url in ~/.pretorin/config.json
  4. api_base_url in ~/.pretorin/config.json (legacy)
  5. https://platform.pretorin.com/api/v1/public default

For the model key (agent runtime):

  1. OPENAI_API_KEY environment variable (highest)
  2. config.api_key (from pretorin login)
  3. config.openai_api_key

For the model name:

  1. OPENAI_MODEL environment variable (highest)
  2. openai_model in ~/.pretorin/config.json
  3. Org AI settings from the platform (cached)
  4. gpt-4o default

For the source manifest:

  1. PRETORIN_SOURCE_MANIFEST environment variable (highest) — JSON string or file path
  2. .pretorin/source-manifest.json in the git repo root
  3. ~/.pretorin/source-manifest-{system_id}.json
  4. source_manifest key in ~/.pretorin/config.json

CI/CD Example

export PRETORIN_API_KEY=pretorin_your_key_here
export PRETORIN_DISABLE_UPDATE_CHECK=1
export PRETORIN_SYSTEM_ID=your_system_id

pretorin frameworks list
pretorin evidence push

Artifact Schema Reference

Complete field reference for compliance artifact JSON documents.

Top-Level Fields

FieldTypeRequiredDescription
framework_idstringYesThe compliance framework (e.g., fedramp-moderate, nist-800-53-r5)
control_idstringYesThe control being addressed (e.g., ac-02, au-02)
componentobjectYesThe system component being assessed
confidencestringYesConfidence in the analysis: high, medium, or low

Component Fields

FieldTypeRequiredDescription
component_idstringYesSource identifier (repository name, package name)
titlestringYesHuman-readable component name
descriptionstringYesBrief description of what the component does
typestringYesOne of: software, hardware, service, policy, process
control_implementationsarrayYesHow the control is implemented

Control Implementation Fields

FieldTypeRequiredDescription
control_idstringYesMust match parent control_id
descriptionstringYes2-3 sentence narrative of HOW the control is implemented
implementation_statusstringYesimplemented, partial, planned, or not-applicable
responsible_rolesarrayNoRoles responsible (default: ["System Administrator"])
evidencearrayNoSupporting evidence items
remarksstringNoAdditional notes or caveats

Evidence Fields

FieldTypeRequiredDescription
descriptionstringYesNarrative of what this evidence shows
file_pathstringNoPath to the source file
line_numbersstringNoLine range (e.g., "10-25")
code_snippetstringNoRelevant code excerpt (keep under 10 lines)

Implementation Status Definitions

StatusDefinition
implementedControl is fully implemented and operational. Clear, direct evidence exists in the codebase.
partialSome aspects are implemented, others are pending. Example: user CRUD exists but no account expiration or manager approval.
plannedNot yet implemented but scheduled. The architecture supports it but the feature isn’t built.
not-applicableControl doesn’t apply to this component. Example: a pure API service with no user accounts doesn’t need account management controls.

Confidence Levels

LevelDefinition
highClear, direct evidence in code. Well-documented implementations with specific file paths and line numbers.
mediumReasonable evidence but some inference required. The implementation likely satisfies the control but some aspects aren’t explicitly documented.
lowLimited evidence. Significant assumptions made. The codebase has relevant code but the connection to the control requirement is indirect.

Contributing

Thank you for your interest in contributing to the Pretorin CLI!

We welcome contributions to the CLI, MCP server, docs, scanners, developer workflows, and local tooling. This repository is open source under Apache-2.0, while Pretorin-hosted platform services, authenticated API access, and account-scoped data are governed separately by the applicable platform terms.

Scope

Good fits for this repository:

  • CLI commands and output improvements
  • MCP tools, prompts, and local agent integrations
  • Scanner integrations and developer workflow automation
  • Documentation, examples, and tests

Out of scope for public contributions:

  • Customer data, exported platform data, or private operational runbooks
  • Secrets, internal credentials, or private environment details
  • Changes that imply trademark rights or suggest an unofficial fork is an official Pretorin service

For brand usage guidance, see Trademarks and Service Terms.

Getting Started

  1. Fork the repository
  2. Clone your fork:
    git clone https://github.com/YOUR_USERNAME/pretorin-cli.git
    cd pretorin-cli
    
  3. Install development dependencies:
    uv pip install -e ".[dev]"
    

Development Workflow

Running Tests

pytest

Integration tests require an API key and are marked with @pytest.mark.integration:

pytest -m integration

Integration tests require a valid API key tied to an account that has accepted the platform terms.

Type Checking

mypy src/pretorin

Linting

ruff check src/pretorin
ruff format src/pretorin

Full CI Check

Run the same checks as the CI pipeline:

ruff check src/pretorin && ruff format --check src/pretorin && mypy src/pretorin && pytest

Submitting Changes

  1. Create a feature branch from main
  2. Make your changes
  3. Ensure tests pass and code is properly formatted
  4. Add a sign-off to each commit with git commit -s
  5. Submit a pull request

By submitting a contribution, you certify that:

  • You have the right to submit the code, docs, or other materials.
  • Your contribution may be distributed under the Apache License, Version 2.0.
  • You are not including confidential information, customer data, or material that is governed by separate platform terms.

Code Style

  • Follow PEP 8 guidelines
  • Use type hints for all function signatures
  • Write docstrings for public functions and classes
  • Keep functions focused and small

CI Pipeline

The CI pipeline runs on Python 3.10, 3.11, and 3.12:

  • Lint — Ruff check + format
  • Audit — pip-audit (dependency vulnerability scan)
  • Type check — mypy strict mode
  • Test — pytest
  • The source code in this repository is licensed under Apache-2.0.
  • The Pretorin name, logos, and other brand assets remain subject to trademark rights and are not licensed for reuse except for nominative/reference use. See Trademarks and Service Terms.
  • Access to Pretorin-hosted APIs, services, and account-scoped data is authenticated and governed by separate platform terms.

Reporting Issues

Use GitHub Issues to report bugs or request features. Include:

  • Clear description of the issue
  • Steps to reproduce (for bugs)
  • Expected vs actual behavior
  • CLI version (pretorin version)

Questions?

Trademarks and Service Terms

Pretorin, the Pretorin logo, and related brand assets are trademarks or registered trademarks of Pretorin, Inc.

The Apache-2.0 license for this repository covers the source code and documentation in this repo. It does not grant permission to use Pretorin trademarks, logos, or branding for derivative products or services in a way that suggests sponsorship, endorsement, or official status.

Permitted uses generally include truthful, referential statements such as:

  • Saying that your project is based on or compatible with Pretorin CLI
  • Linking to this repository or to Pretorin documentation
  • Describing changes you made in a fork, as long as you do not imply the fork is an official Pretorin release or hosted service

Not permitted without separate permission:

  • Shipping a fork under the Pretorin name as if it were the official product
  • Reusing Pretorin logos, trade dress, or marketing assets for another hosted service
  • Suggesting endorsement, partnership, certification, or official support where none exists

Access to Pretorin-hosted platform services, APIs, and any account-scoped data returned by those services is governed by the applicable platform terms and account agreements, which are separate from the open-source license for this repository.

Changelog

All notable changes to the Pretorin CLI are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

0.22.1 - 2026-05-21

Fixed

  • inventory show always reported empty (#133 follow-up): the CLI read the response under payload["assets"] but the platform returns asset rows under "items". Same bug caused inventory scan to misclassify every row as added (it diffed against an empty existing inventory). Both paths now read payload["items"].
  • artifacts toggle always 422’d (#133 follow-up): the client posted {"optional": ...} but the server’s PATCH schema is keyed on toggled_off. Renamed the wire field while keeping the user-facing --optional/--required flag unchanged. The artifacts list renderer now reads either toggled_off or legacy optional defensively.

0.22.0 - 2026-05-21

Added

  • System spec CLI + MCP surface (#133): new pretorin scope artifacts ... command group wraps the public /api/v1/public/systems/{id}/spec/* endpoints from monorepo PR #859. Operators can artifacts list, inventory show [--as-of T], inventory upload <csv>, inventory scan <source>, and artifacts toggle <kind> --optional --rationale "...". Three matching MCP tools (list_artifact_requirements, get_asset_inventory, submit_asset_inventory_diff) expose the same surface to AI agents. The diff endpoint accepts recipe_context_id but does not require it — the 11-field audit-metadata envelope is reserved for evidence writes.
  • Four asset-inventory recipes: asset-inventory-aws-baseline (live EC2 via boto3), asset-inventory-azure-baseline (live Compute VMs via azure-mgmt-compute), asset-inventory-k8s-baseline (kubectl-driven enumeration of nodes + Deployment/StatefulSet/DaemonSet), and asset-inventory-iac-workspace (static parse of .tf / .tf.json / K8s YAML / CloudFormation files in the cwd — no cloud credentials required). All four ship as tier: official.

0.21.4 - 2026-05-19

Fixed

  • MCP start-task routing and capture preflight (#126, #127): inspect_status now returns a bounded no-workflow status bundle without running the full routing cross-check, and platform validation outages now surface as structured upstream errors instead of false “not found” messages.
  • AI-guidance evidence expectations (#127): start_task.suggested_capture_plan now falls back from control context to get_control(...).ai_guidance.evidence_expectations, so enriched controls such as AU-04 retain their recipe preflight plan even when the system control-context endpoint is unavailable.

Changed

  • Workspace capture fallback (#127): workspace-capture is now the generic workspace evaluation/capture fallback for broad or unclassified evidence expectations, and capture-plan metadata identifies whether a recipe came from an expectation match or the fallback path.

Removed

  • Legacy document requirements API: removed the obsolete get_document_requirements client/MCP surface and pretorin frameworks documents command. Evidence requirements are derived from AI guidance.

0.21.3 - 2026-05-19

Fixed

  • pretorin update works on uv tool and pipx installs: the update command now detects how pretorin was installed by inspecting sys.executable and dispatches to uv tool upgrade pretorin or pipx upgrade pretorin when appropriate. Previously it always shelled out to python -m pip install --upgrade, which fails with No module named pip on recent uv versions because tool venvs no longer ship pip. Pinned upgrades (pretorin update X.Y.Z) route to uv tool install --force / pipx install --force so they work on isolated tool venvs too. Failure paths and the post-upgrade “ran but version unchanged” hint now name the right installer.

0.21.2 - 2026-05-18

Changed

  • MCP recipe-required telemetry (#121): evidence and narrative producer guardrails now emit a PRETORIN_TELEMETRY_EVENT with event_type="recipe_required" and non-content shape metadata, letting operators compute the combined workflow/recipe bypass rate for the post-v0.21 trigger watch.

0.21.1 - 2026-05-16

Maintenance

  • Automated maintenance + documentation sync pass (#122): lint/type-check fixes, test coverage improvements, dead-code removal, dependency vulnerability patches, version/registration consistency, and a repository-wide doc resync against the v0.21 surface (CLI/MCP/agent references, llms.txt manifests, and a fresh mdBook rebuild).

0.21.0 - 2026-05-15

Added

  • Recipe/source MCP producer surface (#118): recipes can declare requires.sources, MCP exposes list_connected_sources and check_sources, start_task returns suggested_capture_plan, and list_recipes(system_id=...) filters to source-eligible recipes while failing open on older platform deployments.
  • Narrative recipe support (#118): start_recipe accepts evidence_ids, update_narrative requires a narrative-producing recipe context with cited evidence ids, and the built-in evidence-narrative-compose recipe provides the canonical narrative path.
  • Workspace capture floor recipe (#118): workspace-capture generalizes code capture for readable workspace files such as runbooks, policy drafts, scripts, configs, and exported reports.

Changed

  • Recipe-only MCP writes (#118): MCP create_evidence, create_evidence_batch, and update_narrative now reject agent writes without recipe_context_id using a structured recipe_required error.

0.20.1 - 2026-05-15

Fixed

  • Control note resolution parity (#760): MCP, CLI, and built-in agent note-resolution tools now expose and forward resolution_note, matching the platform UI’s audit-trail requirement for closing notes. pretorin notes resolve accepts --resolution-note / --justification, and local validation prevents closing a note without a justification.

0.20.0 - 2026-05-14

Changed

  • MCP tool prefix dropped (#113, phase 3): every server-side tool name lost its leading pretorin_. Hosts see mcp__pretorin__check_context instead of mcp__pretorin__pretorin_check_context. Recipe-script tools follow the same rule (recipe_<id>__<script> instead of pretorin_recipe_<id>__<script>). Breaking change for any agent that hardcoded the old names — re-install the bundled skill (pretorin skill install) or update local references. Tier metadata, the intent-verb map, and the workflow-body schema-bundling regex all moved with the rename. The handler function names (handle_create_evidence, etc.) are unchanged — this only affects the wire-level tool identifier.

Added

  • Cross-harness MCP tool surface (#113, phases 0-2): the MCP server now ships a small set of cross-harness discovery + grounding tools so Cursor, Codex, vanilla Agents SDK, and any other client can ground a session without depending on the initialize instructions block.
    • check_context — cheap, unauthenticated probe. Returns {connected, active_system, active_framework_id, suggested_next, pending_attention} with a deterministic next-step hint. Call once at session start.
    • list_tools — compact catalog. One short record per tool (name, purpose, tier, requires_workflow) plus tier counts. Cross-harness alternative to fetching every tool’s full schema just to browse. Tiers: default, reference, workflow, recipe.
    • get_instructions — callable mirror of the server’s instructions block, for harnesses that don’t render it.
    • Errors-as-instructions: write tools that fail because there’s no active routing context now return a structured {error: "workflow_required", message, routing_hint} payload (still isError=true) instead of plain-text errors. routing_hint.suggested_intent_verb tells the agent the exact start_task call to make. Backed by a new WorkflowRoutingError exception class.
    • Workflow schema bundling: get_workflow now bundles required_tool_schemas — the full MCP Tool definitions for every tool the workflow body references. One round trip equips the agent.
    • Telemetry: structured single-line JSON events emitted on stderr (PRETORIN_TELEMETRY_EVENT {...}) on successful start_task and on WorkflowRoutingError raises. Feeds the phase-4 trigger decision in the RFC. Opt out with PRETORIN_MCP_TELEMETRY_DISABLED=1.
    • pretorin mcp-smoke-test command: end-to-end harness that exercises every new behavior in-process — useful for verifying an install or PR.

0.19.0 - 2026-05-13

Added

  • Markdown evidence artifacts and structured provenance (#112): JSON evidence writes now send short description summaries plus standalone Markdown artifact_content, with source/capture context in audit_metadata (source_label, source_locator, source_excerpt, content_hash, capture_method, and related fields). Batch evidence follows the same per-item contract. Added pretorin evidence validate to compare fresh source-material hashes before re-verifying; drifted sources update the existing evidence artifact with a drift_note instead of silently calling mark-current.

0.18.2 - 2026-05-09

Maintenance

  • Automated maintenance + documentation sync pass (#111): lint/type-check fixes, test coverage improvements, dead-code removal, dependency vulnerability patches, version/registration consistency, and a repository-wide doc resync against the v0.18 surface (CLI/MCP/agent references, llms.txt manifests, and a fresh mdBook rebuild).

0.18.1 - 2026-05-09

Added

  • Continuous compliance — --cadence-days flag and mark-current command (#108 PR B): pretorin evidence upsert accepts --cadence-days <int> to opt new evidence into a refresh cadence; the platform then computes expires_at server-side and includes the row in the daily freshness sweep. New pretorin evidence mark-current <id> subcommand re-affirms that evidence is still current — bumps expires_at by the cadence, transitions expired→valid, writes a re_verified lineage row, and auto-resolves any open evidence.expiring/evidence.expired monitoring events. EvidenceCreate carries the new refresh_cadence_days field. PretorianClient.mark_evidence_current() is the corresponding API client method.

0.18.0 - 2026-05-08

Added

  • Auditor sufficiency fields on evidence writes (#108): pretorin evidence upsert gains --coverage-start, --coverage-end, and --capture-query flags so callers can populate the new auditor sufficiency columns. The MCP create_evidence tool accepts the same arguments. EvidenceCreate and EvidenceBatchItemCreate now carry data_coverage_start_at, data_coverage_end_at, and capture_query. Pairs with the platform-side schema; auditors get clear answers to the seven sufficiency questions (source-system, capture-vs-coverage timestamps, producer authority, capture context, in-scope binding, control mapping, reliability) without walking attestation chains.

0.17.8 - 2026-05-08

Fixed

  • Evidence audit metadata serialization: pretorin evidence upsert and MCP evidence writes now serialize audit_metadata.captured_at using Pydantic JSON mode before handing payloads to httpx. Previously, recipe/agent-stamped evidence failed locally with TypeError: Object of type datetime is not JSON serializable before the platform request was sent.
  • Source verification JSON safety: evidence create and batch-create now normalize source-verification snapshots to JSON primitives, so attested contexts with datetime values do not break evidence writes.

0.17.7 - 2026-05-07

Fixed

  • MCP recipe-script context resolution (#104): scanner recipes invoked over MCP (manual-attestation, inspec-baseline, openscap-baseline, cloud-aws-baseline, cloud-azure-baseline) now correctly receive the active system_id / framework_id. The dispatcher previously read these from PretorianClient instead of Config, so every script ran with ctx.system_id == None and the platform returned System not found. As a bonus, PRETORIN_SYSTEM_ID / PRETORIN_FRAMEWORK_ID env-var overrides now flow through end-to-end.
  • Recipe import error in scope/policy questionnaire redactors (#103): scope-q-answer and policy-q-answer no longer fail at import with cannot import name 'redact_secrets'. Both scripts now use the public redact() helper from pretorin.evidence.redact and unpack the (str, RedactionResult) return shape.

Documentation

  • Customer-managed air-gapped install guide: new page walking operators of customer-managed / air-gapped Pretorin platform deployments through pointing the CLI at their private platform — non-secret platform validation (smoke test, embedding readiness, AI provider checks), CLI configuration via PRETORIN_PLATFORM_API_BASE_URL / pretorin login --base-url, and tenant-scoped CLI smoke tests. Linked from the configuration reference. See Customer-managed air-gapped installs.

0.17.6 - 2026-05-06

Added

  • Risk-management CLI + MCP surface (#100): you can now populate a system’s risk register directly from the CLI or from any MCP-connected agent — list, create, seed from library templates, update with mitigation, link controls/evidence/vendors as artifacts, and refresh the AI-generated summary. End-to-end wrappers around the platform’s public /systems/{system_id}/risks* endpoints. New pretorin risk command group: list, show, create, seed, update, refresh-summary, link add/link rm, and library list. Matching MCP tools: list_risks, get_risk, create_risk, seed_risks, update_risk, link_risk_artifact, unlink_risk_artifact, refresh_risk_summary, list_risk_library. Tool descriptions encode workflow gotchas — risks are system-scoped, control auto-link is opt-in (requires framework_id + matching ControlImplementation rows), mitigation is recorded via update_risk (no separate /mitigate endpoint), and AI summary refresh is best-effort.

0.17.5 - 2026-05-06

Fixed

  • pretorin cci impl panel now surfaces the impl row id (the id field in the platform response) so agents can chain directly into evidence link-cci without re-querying.
  • Panel header now displays the CCI human label (CCI-000007) by reading the platform’s cci_identifier field. The earlier code read a non-existent cci_uuid field and silently fell back to the URL arg.
  • Removed dead-code rendering loop for emass_* fields that the platform does not return.

0.17.4 - 2026-05-06

Added

  • CCI implementation read endpoint (#97): pretorin cci impl <cci_uuid> and MCP tool get_cci_implementation wrap the new platform GET /systems/{system_id}/cci-implementations/{cci_uuid} endpoint, returning the live per-system impl row.
  • Evidence link target-type extensions (#97): new sibling commands pretorin evidence link-cci and pretorin evidence link-stig plus MCP tools link_evidence_to_cci_implementation and link_evidence_to_stig_rule_workflow. Both honor the platform’s override_system_mismatch + override_reason gate; the STIG variant lazy-creates the workflow row when none exists.
  • Agent guidance on STIG-to-CCI traceability: SKILL.md and the single-control workflow playbook clarify that the STIG-rule → CCI relationship is catalog-level (DISA-defined). Use get_cci_chain(nist_control_id, system_id) for “what tests this CCI on this system.”

0.17.3 - 2026-05-05

Fixed

  • Scope and policy generation MCP tools now request AI review in the same durable generation job by default, matching the platform workflow while preserving an include_review=false opt-out.

0.17.2 - 2026-05-02

Documentation

  • Repository-wide documentation sync to current v0.17 surfaces: README recipes table, getting-started, CLI/MCP reference, frameworks selection + custom-framework authoring, recipes/workflows, agent overview, env-vars reference, llms.txt manifests, and a fresh mdBook rebuild.

Fixed

  • Test isolation: test_install_default_writes_to_all_known_agents now performs filesystem assertions inside the Path.home() patch context so CI runs do not depend on the runner’s real home directory.

0.17.1 - 2026-04-30

Added

  • Custom framework authoring CLI (#90): end-to-end build / validate / upload workflow around the platform’s unified.json revision-lifecycle endpoints. New pretorin frameworks commands: init-custom, validate-custom, build-custom, upload-custom (--publish to ship immediately), fork-framework, rebase-fork, revisions, export-oscal.
  • Vendored unified-framework toolchain at pretorin.frameworks: bundled JSON Schema validator, OSCAL ↔ unified converters with lossless round-trip, and the 12-format custom-catalog converter ported from the monorepo data/tools/.
  • Framework revision lifecycle client methods on PretorianClient: create_custom_draft, publish_draft, fork_framework, create_rebase_draft, list_revisions. Structured validation_report is preserved through PretorianClientError.details on 400.
  • jsonschema>=4.0.0 added as a runtime dependency.

Documentation

0.17.0 - 2026-04-30

Added

  • Recipe extensibility system (RFC 0001): full implementation of the three-layer routing model — engagement → workflow → recipe. Calling AI agents now route through deterministic Python rules to a workflow playbook, then pick recipes per item from a discoverable menu instead of freelancing.
  • start_task MCP tool: pure-function rule cascade over agent-extracted entities. Cross-checks against platform state (hallucinated control ids → hard error; wrong-framework / cross-system writes → ambiguous response). Bundles inspect summary into the response.
  • Workflow registry + 4 built-in playbooks: single-control, scope-question, policy-question, campaign. list_workflows and get_workflow MCP tools.
  • Recipe registry + 8 built-in recipes: code-evidence-capture, inspec-baseline, openscap-baseline, cloud-aws-baseline, cloud-azure-baseline, manual-attestation, scope-q-answer, policy-q-answer.
  • Recipe authoring surface: pretorin recipe list / show / new / validate / run CLI commands. Four loader paths with clear precedence: explicit > project > user > built-in. Per-script MCP tools auto-registered as recipe_<safe_id>__<script>.
  • Recipe execution context: start_recipe / end_recipe; every platform write inside the context auto-stamps producer_kind="recipe", recipe id, and recipe version.
  • Audit-trail metadata: EvidenceAuditMetadata is stamped on every CLI / agent / MCP / campaign-apply evidence write. Build helpers at pretorin.evidence.audit_metadata are the single construction surface.
  • Recipe selection on every drafting call: draft_control_artifacts consults the recipe registry before falling through to freelance. The decision is recorded as RecipeSelection on the response.
  • pretorin.evidence.redact + pretorin.evidence.markdown: shared primitives for secret redaction and audit-grade markdown composition.
  • Bundled pretorin skill v0.17.0: teaches the calling agent about the routing model. New “Engagement (Routing)” section flags start_task as the FIRST call.
  • Authoring docs at docs/src/recipes/: index, manifest reference, script contract, writer tools, testing, publishing, workflows, engagement, worked example.

Changed (BREAKING)

  • pretorin scan CLI command removed. All scanner functionality moved to recipes. Migrate to pretorin recipe run <recipe-id> (e.g., pretorin recipe run inspec-baseline --param stig_id=RHEL_9_STIG) or invoke via MCP.
  • ScanOrchestrator removed. Manifest fetch + rule filter + summary helpers extracted to pretorin.scanners.manifest and shared across scanner recipes.

Removed

  • src/pretorin/cli/scan.py and src/pretorin/scanners/orchestrator.py.
  • The deprecated rejected_invalid_type campaign-apply telemetry counter (deprecated in 0.16.0).

0.16.3 - 2026-04-26

Fixed

  • CCI chain test fix: test_cci_chain_with_system_status now correctly mocks resolve_execution_context so CCI status rendering is exercised. No production code changes.

0.16.2 - 2026-04-21

Fixed

  • pretorin campaign controls --family case-insensitive resolution (#84): --family cc6 now resolves to canonical CC6 before hitting the backend. Unknown families raise a structured error listing available families and pointing at pretorin frameworks families <framework-id>. Same fix applied to prepare_campaign MCP handler.

0.16.1 - 2026-04-21

Added

  • Gap questions for policy and scope Q&A: MCP tool descriptions guide agents through answer-first workflow with structured gap questions for organizational knowledge gaps.

0.16.0 - 2026-04-21

Changed (BREAKING)

  • evidence_type is now required on every CLI, MCP, agent, and workflow write path (#79). CLI paths hard-error when the user omits -t/--type; every other path runs a client-side normalizer before submission.

Added

  • Evidence provenance fields: CLI sends code_file_path, code_line_numbers, code_snippet, code_repository, code_commit_hash on all evidence creation paths. Auditors can trace evidence to source files and commits.
  • Source verification mapping: Attested source identities mapped to platform’s SourceVerificationPayload with source_type and source_role.
  • pretorin evidence upload: Upload files (screenshots, PDFs, configs) as evidence with SHA-256 integrity verification.
  • upload_evidence MCP tool: Agents and recipes can upload evidence files via MCP.
  • File reference validation: Campaign apply reads actual file content as canonical snippet, validates paths and line ranges.
  • Code provenance on local evidence: Frontmatter supports code_* fields for local evidence create and push.
  • pretorin.evidence.types module: canonical 13-type enum, AI-drift alias map, and normalize_evidence_type() with fuzzy matching.

Changed

  • Evidence models include code provenance fields. Campaign extracts code_* and relevance_notes from AI recommendations.
  • upsert_evidence() creates enriched evidence as new record when provenance fields are provided.
  • AI generation prompt requests code file paths and line numbers in evidence recommendations.

Fixed

  • SOC2 campaign batches with non-canonical evidence_type strings now succeed end-to-end via the normalizer.
  • Non-campaign write paths can no longer silently tag missing-type evidence as policy_document.

0.15.5 - 2026-04-20

Fixed

  • Campaign --apply runs no longer flood the evidence locker with AI-authored summaries typed as policy_document (issue #77). The pipeline now wires recommended_notes through to the platform as real gap notes, rejects evidence recommendations with missing or invalid evidence_type (turning them into synthesized gap notes), and emits a structured campaign.apply.control telemetry line for post-ship measurement.
  • Partial failures in the per-control notes write now raise PretorianClientError with the failing indexes, mirroring the existing evidence-batch behavior so checkpoint resumes are idempotent.
  • Evidence batch result mapping now aligns offsets to the original recommendation index via the accepted-items list and asserts length match, fixing a latent index-drift bug that appeared once any recommendation was rejected mid-loop.
  • Completion note now fires when all pending work has landed across runs, not only when something new was written in the current run.

Changed

  • evidence_type is now required on EvidenceBatchItemCreate. The campaign batch write path no longer silently tags missing types as policy_document; pydantic validation raises instead. Other evidence write paths (CLI, MCP, direct API) keep their existing defaults.
  • Agent drafting prompts (_build_generation_task, _draft_control_fix, _WORKFLOW_GUARDRAILS, codex system prompt, [[PRETORIN_TODO]] template) now list all 13 valid evidence types verbatim and state that an empty evidence_recommendations list is a valid result — gaps belong in recommended_notes.
  • _WORKFLOW_GUARDRAILS merged in the evidence-collection skill’s “concrete, auditable artifacts” language so narrative-generation skill callers inherit the same rules.

0.15.4 - 2026-04-18

Changed

  • Updated 6 dependencies to resolve 7 known vulnerabilities (cryptography, pygments, pyjwt, pytest, python-multipart, requests)
  • Added CLAUDE.md and AGENTS.md for AI agent context

0.15.3 - 2026-04-18

Fixed

  • pretorin update now checks PyPI before running pip, skipping reinstall when already current
  • pretorin update verifies the installed version after pip runs, detecting silent failures in pipx/uv-managed environments

Added

  • pretorin update [VERSION] accepts an optional version argument to install a specific release

0.15.2 - 2026-04-18

Changed

  • Documentation sync: rebuilt all docs to match current codebase

0.15.1 - 2026-04-17

Added

  • pretorin evidence delete <evidence-id> command with --yes flag for non-interactive workflows
  • MCP tool delete_evidence for programmatic evidence deletion within system scope
  • API client method delete_evidence for the public DELETE endpoint

0.15.0 - 2026-04-16

Added

  • Source manifest requirement policy: declare which external sources a system expects and gate compliance writes on their presence
  • pretorin context manifest command for viewing the resolved manifest and evaluating it against detected sources
  • Manifest loading from four layered sources: env var, repo-local .pretorin/source-manifest.json, per-system user config, or inline config key
  • Family-level source requirements with three requirement levels (required/recommended/optional) and write blocking on missing required sources
  • Manifest evaluation results in write provenance (manifest_status and missing_required_sources fields)

Changed

  • _enforce_source_attestation now evaluates manifest requirements after the existing MISMATCH check
  • resolve_execution_context and build_write_provenance accept optional control_id for family-level manifest enforcement

0.14.0 - 2026-04-10

Changed

  • MCP and agent write workflows now treat the active CLI context as a strict execution boundary by default, with an explicit allow_scope_override escape hatch for intentional cross-scope writes
  • Control-scoped MCP and agent workflows now route through one shared scope-validation path so exact control lookup happens in the resolved framework before any write proceeds
  • Agent guidance now tells built-in workflows to resolve an exact user-supplied control in the active framework before doing broader discovery
  • pretorin mcp-serve now emits a non-blocking stderr update prompt when a newer CLI release is available, so MCP-only users can discover upgrades without interrupting active tool calls

Fixed

  • apply_campaign now reports apply: true after a successful apply run and persists that state back to the checkpoint summary
  • Stored active context and campaign checkpoints are now validated against the current API environment before campaign reads or writes proceed
  • Control-scoped MCP and agent updates now refuse silent remaps like cm-04.02 to a different control when the exact control does not resolve in the active framework

Added

  • get_cli_status and the status://cli MCP resource expose local CLI version, update availability, and upgrade guidance to MCP hosts and agents

0.13.1 - 2026-04-07

Added

  • get_stig MCP tool for STIG benchmark detail
  • get_cci_chain MCP tool for full Control → CCI → SRG → STIG rule traceability

0.13.0 - 2026-04-07

Added

  • Complete STIG/CCI MCP tools: list_stigs, get_stig, list_stig_rules, get_stig_rule, list_ccis, get_cci, get_cci_chain, get_cci_status, get_stig_applicability, infer_stigs, get_test_manifest, submit_test_results
  • STIG/CCI agent tools for OpenAI Agents SDK
  • pretorin stig CLI group: list, show, rules, applicable, infer
  • pretorin cci CLI group: list, show, chain
  • pretorin scan CLI group: doctor, manifest, run, results
  • Scanner orchestration module with support for OpenSCAP, InSpec, AWS/Azure Cloud Scanners, and Manual review

0.12.0 - 2026-04-04

Added

  • Vendor management CLI: pretorin vendor list/create/get/update/delete/upload-doc/list-docs
  • MCP vendor tools: list_vendors, create_vendor, get_vendor, update_vendor, delete_vendor, upload_vendor_document, list_vendor_documents, link_evidence_to_vendor
  • Inheritance/responsibility MCP tools: set_control_responsibility, get_control_responsibility, remove_control_responsibility, generate_inheritance_narrative, get_stale_edges, sync_stale_edges

0.11.0 - 2026-04-02

Added

  • Campaign CLI: pretorin campaign controls/policy/scope/status
  • Campaign MCP tools: prepare_campaign, claim_campaign_items, get_campaign_item_context, submit_campaign_proposal, apply_campaign, get_campaign_status
  • External-agent-first campaign pattern with checkpoint persistence and lease-based concurrency
  • Campaign builtin executor for local execution

0.10.0 - 2026-03-28

Added

  • Workflow state and analytics MCP tools: get_workflow_state, get_analytics_summary, get_family_analytics, get_policy_analytics
  • Family operations MCP tools: get_pending_families, get_family_bundle, trigger_family_review, get_family_review_results
  • Policy workflow MCP tools: get_pending_policy_questions, get_policy_question_detail, answer_policy_question, get_policy_workflow_state, trigger_policy_generation, trigger_policy_review, get_policy_review_results
  • Scope workflow MCP tools: get_pending_scope_questions, get_scope_question_detail, answer_scope_question, trigger_scope_generation, trigger_scope_review, get_scope_review_results
  • ExecutionScope for thread-safe parallel agent execution

0.9.7 - 2026-03-25

Fixed

  • Aligned CLI control status validation with the platform status enum set used by that release
  • Aligned MCP control status validation with the live platform status enum set to match public API behavior
  • Synced package version metadata and release notes so PyPI builds publish a consistent CLI version

Changed

  • Updated CLI and MCP coverage tests to reflect the platform control status contract used by public control workflows

0.8.7 - 2026-03-23

Added

  • MCP questionnaire tooling for scope and organization policy workflows

Changed

  • MCP documentation now reflects the full 29-tool surface, including batch evidence support

0.8.6 - 2026-03-23

Added

  • pretorin context show --quiet for compact shell-friendly context checks
  • pretorin context show --check to fail fast when stored scope is missing, stale, or unverified

Changed

  • context show caches the last known system name so offline and stale context output stays human-friendly

Fixed

  • context show validates stored context against the platform instead of silently treating deleted systems as active

0.8.5 - 2026-03-23

Fixed

  • Reset active system/framework context when logging into a different API endpoint or with a different API key
  • Model API base URL now follows the configured platform public API endpoint during login
  • scope populate --json --apply and policy populate --json --apply now persist questionnaire updates
  • Larger Codex subprocess line buffer for policy questionnaire responses

0.8.0 - 2026-03-07

Added

  • MCP generate_control_artifacts for read-only AI drafting of control narratives and evidence-gap assessments
  • Shared AI drafting workflow helper for structured MCP/CLI parity

Changed

  • MCP system-scoped tools now resolve friendly system names the same way the CLI does
  • Codex Desktop MCP configuration can be pinned to the UV-managed Pretorin wrapper

0.7.0 - 2026-03-07

Fixed

  • Control implementation parsing tolerant of notes: null deployments
  • Compatibility fallback for control note reads when /notes endpoint returns 405
  • Compatibility fallback for evidence search on system-scoped evidence routes
  • Agent --no-stream crash on literal [[PRETORIN_TODO]] blocks

Changed

  • MCP and legacy agent evidence search tools accept optional system_id context

0.6.1 - 2026-03-05

Fixed

  • Added required MCP registry ownership marker for PyPI validation

0.6.0 - 2026-03-05

Added

  • Shared markdown quality validator for auditor-readable artifacts
  • Dedicated tests for markdown quality guardrails
  • CLI/MCP/agent parity for reading notes via dedicated endpoint

Changed

  • Narrative and evidence update flows enforce markdown quality checks before push/upsert
  • Agent prompts require auditor-ready markdown (lists/tables/code/links)
  • Source tagging normalized to cli across write paths

Removed

  • Markdown image usage from narrative/evidence authoring contract (temporarily)

0.5.4 - 2026-03-05

Added

  • pretorin narrative get to read current control narratives
  • pretorin notes list and pretorin notes add for control-note management
  • pretorin evidence search for platform evidence visibility
  • pretorin evidence upsert for find-or-create evidence with control linking
  • Shared compliance workflow helpers (system resolution, evidence dedupe/upsert, TODO blocks, gap notes)
  • MCP get_control_notes tool

Changed

  • create_evidence now upserts by default (dedupe: true)
  • pretorin evidence push uses find-or-create upsert logic
  • Agent skill prompts include no-hallucination guidance and gap note format

Removed

  • Automatic control status updates from CLI evidence push workflow

0.5.3 - 2026-03-02

Fixed

  • CI lint failure formatting
  • CLI model key precedence: OPENAI_API_KEYconfig.api_keyconfig.openai_api_key

0.5.2 - 2026-02-27

Fixed

  • Rich markup MarkupError crash in login flow
  • Evidence type mismatch (documentationpolicy_document)
  • CMMC control ID casing preserved (no longer incorrectly lowercased)
  • monitoring push checks active context before requiring --system
  • pretorin login skips prompt when already authenticated
  • Demo script --json flag position and stdin handling

Changed

  • Default evidence type changed to policy_document
  • Valid evidence types aligned with API
  • Added .pretorin/ and evidence/ to .gitignore

0.5.0 - 2026-02-27

Added

  • Context management (context list/set/show/clear)
  • Evidence commands (evidence create/list/push/search/upsert)
  • Narrative push (narrative push)
  • Monitoring events (monitoring push)
  • Codex agent runtime (agent run with skills, agent doctor/install/version/skills)
  • Agent MCP management (agent mcp-list/mcp-add/mcp-remove)
  • Code review (review run/status)
  • 14 new MCP tools for system, evidence, narrative, monitoring, notes, and control operations
  • Control ID normalization (zero-padding)
  • Interactive demo walkthrough script
  • Beta messaging across CLI, MCP, and README

Changed

  • Platform API base URL changed to /api/v1/public
  • Evidence and linking scoped to system
  • update_control_status() changed from PATCH to POST

Removed

  • pretorin narrative generate — use pretorin agent run --skill narrative-generation
  • pretorin_generate_narrative MCP tool

Security

  • MCP mutation handler parameter validation
  • Client-side enum validation
  • Path traversal protection in evidence writer
  • TOML injection prevention in Codex config writer
  • Connection error URL display

0.2.0 - 2026-02-06

Added

  • --json flag for machine-readable output
  • pretorin frameworks family/metadata/submit-artifact commands
  • Full AI Guidance rendering on control detail view
  • .mcp.json for Claude Code auto-discovery
  • Usage examples in command docstrings

Changed

  • Control references shown by default (replaced --references with --brief)
  • Default controls limit changed to 0 (show all)

0.1.0 - 2025-02-03

Added

  • Initial public release
  • CLI commands for browsing compliance frameworks
  • Authentication commands (login, logout, whoami)
  • Configuration management
  • MCP server with 7 tools and analysis resources
  • Self-update functionality
  • Rich terminal output with Rome-bot mascot
  • Docker support
  • GitHub Actions CI/CD
  • Integration test suite

Supported Frameworks

  • NIST SP 800-53 Rev 5
  • NIST SP 800-171 Rev 2/3
  • FedRAMP (Low, Moderate, High)
  • CMMC Level 1, 2, and 3