309 lines
10 KiB
Markdown
309 lines
10 KiB
Markdown
# 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:
|
|
|
|
```markdown
|
|
| 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:
|
|
|
|
```bash
|
|
# 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
|
|
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 6 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:
|
|
|
|
```markdown
|
|
## 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
|
|
|
|
```markdown
|
|
---
|
|
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
|
|
|
|
```markdown
|
|
---
|
|
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.
|