Files
prose-contracts/docs/AUTHORING-GUIDE.md
T
root 436f583882 docs: add prose contract authoring guide (Anthropic-inspired)
NEW: docs/AUTHORING-GUIDE.md — canonical style guide for writing contracts

Patterns borrowed from Anthropic's frontend-design skill:
- Ground it in the subject: verify live state before writing
- Two-pass process: draft → critique → revise → ship
- Restraint: one contract, one concern; cut one thing before shipping
- Self-critique: would an agent trust this at 3am with 30s of context?

Core rules:
- Specificity over generality (lists rot, vagueness is invisible)
- Live-state fields in tables, not prose (grep-able)
- Descriptions answer: what, to what, why care?
- Templates for function and responsibility contracts
- CI enforces structure; author enforces quality

Linked from AGENTS.md for agent discovery.
2026-07-04 22:31:33 +00:00

10 KiB

Prose Contract Authoring Guide

Approach this as the lead systems engineer at a company where every contract is a production dependency — agents run from these every 15 minutes, every 6 hours, every Sunday at 3am. A stale IP or a missing section isn't a typo; it's an incident waiting to happen. Write every contract as if you'll be paged at 3am when it breaks.

Ground it in the subject

Before writing a single line of YAML, answer three questions and state them in the description:

  1. What system does this contract touch? Name the hosts, CTs, containers, and services explicitly. "The inference fleet" is vague. "GPU .8 (RTX 3090, qwen), .110 (RTX 5070, gemma), .15 (Strix Halo, ornith), and LiteLLM on CT 116" is specific.

  2. Who runs this contract, and when? State the agent, the trigger (cron, manual, event-driven), and the expected duration. "Runs every 6 hours via cron on abiba, takes ~120 seconds for a fleet scan."

  3. What breaks if this contract is wrong? Name the blast radius. "A stale CT ID causes a restart of the wrong container. A wrong Grafana URL breaks the monitoring dashboard for all agents."

If you cannot answer these three questions concretely, do not write the contract yet — go verify against the live system first.

Design principles

Specificity over generality

A contract that says "check all Docker containers" is worse than one that lists them by name. The list will go stale, but staleness is detectable (the CI lint catches it). Vagueness is invisible and uncatchable. Be specific, accept that specificity decays, and rely on the lint pipeline to catch the decay.

Ground truth is the live system, not the contract

Every IP, port, hostname, and container name in a contract is a live-state field. Mark them with VERIFY-BEFORE-USE in the access matrix. Before any mutating operation, verify against the live system. The contract is a hypothesis; the live system is the fact.

When you discover drift between contract and reality, do not "fix" the live system to match the contract. Halt. Ask which is correct. Document the answer in the contract and the knowledge graph. The .117→.19 incident was caused by fixing the wrong direction.

Contracts are typed — respect the kind

Kind When to use Must have Must NOT have
function Stateless, callable helper ## Parameters, ## Returns, ## Execution ## Maintains (no state)
responsibility Persistent duty with state ## Maintains, ## Continuity or ## Execution
gateway External-driven entry point ## Maintains ## Requires
pattern Reference/template/doctrine Flexible — prose-driven Don't prose run directly
test Verification only ## Execution Mutating operations

Use kind: architecture sparingly — it's not a standard OpenProse kind. Most architecture docs should be kind: pattern.

One contract, one concern

A contract that monitors health AND deploys exporters AND sends alerts is three contracts fighting for attention. Split them:

❌ infrastructure-monitoring.prose.md (deploy + export + alert)
✅ proxmox-monitor.prose.md (deploy exporters)
✅ gpu-monitor.prose.md (poll + dashboard)
✅ infrastructure-control.prose.md (reference topology)

The contract that deploys is a function. The contract that polls is a responsibility. The contract that maps the topology is a pattern. One concern per file, one kind per concern.

Live-state fields are declared, not hidden

Don't bury IPs and hostnames in prose paragraphs. Put them in tables, mark them with VERIFY-BEFORE-USE, and keep them in one place per contract:

| Host | IP | Role | Trust |
|------|----|------|-------|
| CT 116 | 192.168.68.116 | LiteLLM + Grafana | ⚠️ VERIFY-BEFORE-USE |

The lint pipeline scans for known stale values. Tables are grep-able; prose is not.

Process: verify, draft, lint, review, ship

Pass 1: Verify

Before writing, verify every live-state field you plan to include:

# Example: writing a contract about Zulip
curl -s http://192.168.68.19/api/v1/server_settings  # is .19 reachable?
ssh root@192.168.68.19 "docker ps"                     # is Zulip in Docker?
pct config 117 | grep net0                              # what IP does CT 117 have?

Write down the verified values. Use them in the contract. If you can't verify something, mark it explicitly: ⚠️ UNVERIFIED — needs live check.

Pass 2: Draft

Write the contract following the structure for its kind. Fill every required section. Use the templates below. Write descriptions that name the subject: "GPU fleet health check across RTX 3090 (.8), RTX 5070 (.110), and Strix Halo (.15)" not "monitoring check."

Pass 3: Lint locally

Before pushing, run the same checks the CI will run:

bash scripts/prose-lint.sh

Fix everything that fails. The lint checks for:

  • Valid YAML frontmatter
  • Required sections per kind
  • Regression patterns (/grafana/ route, CT 122/123)
  • Missing Maintains/Parameters/Returns

Pass 4: Self-critique

Read your contract and ask:

  1. Could an agent run this at 3am without context? If it needs tribal knowledge, add it to the description or a comment.
  2. Does any value contradict the infrastructure-control pattern? Check CT IDs, IPs, hostnames, nginx routes, Grafana access.
  3. Is anything in here that was previously fixed and reverted? Check the lint regression rules. If you're unsure, grep the git log for similar changes.
  4. Am I saying the same thing as another contract? If yes, link to it instead of duplicating. One source of truth.

Pass 5: Open PR, let CI review

Push to a branch, open a PR. The pipeline runs:

auth → validate → lint → ai-review → gate

The AI review compares your diff against the infrastructure-control ground truth. It catches contradictions you might miss. If it flags something, don't argue — verify and fix. The AI is reading the same ground truth you should have read before writing.

Restraint and self-critique

Spend your complexity budget in one place. A contract that deploys Prometheus, configures Grafana, provisions dashboards, and sets up exporters is a function that does four things — each of which can fail independently. Split them.

Don't add a ### Requires that you haven't verified works. A dependency on "SSH access to .116" is only valid if you've tested it from the agent that will run the contract.

Cut sections that don't serve the runner. A 200-line appendix of curl commands is documentation, not execution. Put it in a wiki or a README, not in the contract. The contract is for the agent running it; the README is for the human reading about it.

Before shipping, take one thing out. Like Chanel's advice: before leaving the house, remove one accessory. Is there a section, a parameter, a check that doesn't pull its weight? Cut it. The contract that ships with 5 sections will be maintained. The one with 15 will rot.

Writing in contracts

Descriptions

A description is not a label. It's the first thing an agent reads before deciding whether to run this contract. Make it answer: what does this do, to what, and why should I care?

❌ "Monitors infrastructure health"
✅ "Scans all 5 Proxmox nodes and 19 CTs for disk pressure, checks Docker
   container health on .7/.116/.17, alerts via Telegram DM on RED/CRITICAL"

Field names

Name things by what they control, not by how the system is built:

❌ prometheus_scrape_interval_seconds
✅ check_every_seconds

The runner doesn't care that Prometheus is doing the scraping. They care how often the check runs.

Error messages and alerts

Errors tell the operator what went wrong and what to do about it. Be specific:

❌ "Health check failed"
✅ "GPU .15 (Strix Halo) unreachable at :8080 — firewalled to .116 only.
   Check router /health/unified at http://192.168.68.116/health/unified instead."

Comments

Comments in contracts explain WHY, not WHAT. The execution steps say what to do. Comments say why we do it this way and not another:

## Execution

1. **Check GPU health via router** — cannot probe .15:8080 directly
   (firewalled to .116 only as of 2026-07-01 Vulkan rebuild)
2. **Skip sidecar check** — JSON exporters at :8090 were never deployed
   on any GPU host; router falls back to GPU /health direct probe

Templates

Function contract template

---
kind: function
name: contract-name
description: >
  What this does, to what, and why. Be specific about hosts/services.
version: 1.0.0
---

## Parameters

- param_one: type — description (default: "value")
- param_two: type — description (default: "value")

## Returns

- result_field: type — description

## Requires

- Dependency one — how to verify it's available
- Dependency two

## Execution

1. **Step one** — what and why
2. **Step two** — what and why
3. **Compile and return** — assemble the output

Responsibility contract template

---
kind: responsibility
name: contract-name
description: >
  What this recurring duty monitors/maintains. Name the systems, the
  cadence, the blast radius.
version: 1.0.0
---

## Maintains

- state_field: { current: value, last_check: timestamp }
- another_field: description of maintained state

## Parameters

- param: type — description (default: "value")

## Requires

- Dependency one
- Dependency two

## Continuity

- Self-driven: check every N seconds/minutes/hours
- Also wakes on: trigger description

## Execution

1. **Check** — verify current state
2. **Evaluate** — compare against thresholds
3. **Remediate or escalate** — fix or alert
4. **Record** — update world-model state

Enforcement

This guide is aspirational, not gated. The CI pipeline enforces structure (frontmatter, required sections) and regression (known stale patterns). It does not enforce prose quality or specificity. That's on you, the author.

Before you ship: read your contract as if you're the agent who will run it at 3am with 30 seconds of context. If you wouldn't trust it, rewrite it.