OpenProse
OpenProse

Contracts

The authored surface of OpenProse -- Markdown contracts, the five kinds, and the load-bearing sections that turn a declaration into a typed, subscribable node.

Contracts

A contract is the thing you author. It is a Markdown file -- *.prose.md -- with a small YAML frontmatter and a handful of ### sections. The Markdown, with its durable trail, is the public artifact: it can leave for any Prose-Complete harness with no lost semantics. The deployment's secrets and data stay private.

Authors are co-equal. A human and an agent write contracts the same way, read the same sections, and fork or compose each other's work without translation. Every section below is designed for both readers from the start.

This page is the authored surface only -- what you write. The runtime that serves these contracts (fingerprints, memoization, the continuity clock, receipts) is the Reactor harness's concern; see the Reactor section for that. Here, intent lives only in the contract.

Frontmatter and identity

A contract opens with YAML frontmatter. Two fields carry weight:

  • kind: -- which of the five contract kinds this is (below). This is the one field that changes how the file is executed.
  • name: -- the human-facing slug.

A third field, id:, is a durable identity (a base32 token) minted once by tooling and preserved across name: and filename renames. You do not hand-write id:; tooling manages it. name: is what you read; id: is what keys the node's world-model and receipt-ledger state on disk.

The five kinds

kind: selects one of five authored kinds. Each has a different run model.

KindRun modelPurpose
responsibilityServed (continuously reconciled)The headline kind: a mounted DAG node maintaining a standing truth over time.
functionCalled (one-shot)A stateless, ephemeral helper. Bind ### Parameters, run one render, return ### Returns. No Forme phase, no world-model.
gatewayMounted as external-drivenSugar for an external-driven responsibility. Compiles into a trigger registration for reactor serve; refuses a direct run.
testVia the test pathFixtures plus natural-language assertions against a subject responsibility or function.
patternInstantiated at compile timeA reusable coordination algorithm, expanded into nodes at compile time. Never directly run.

There is no kind: service (it was renamed to function) and no kind: system (deleted). Cross-node composition is a Forme-wired subscription between responsibilities; intra-node composition is an imperative call inside one render. There is never an internally-autowired graph kind.

The headline kind is responsibility. The rest of this page teaches its load-bearing sections, because those are what make a contract a typed, subscribable node rather than a bare prompt.

The load-bearing sections

Most ### sections mean exactly what their name says. ### Description is a human summary preserved for readers, not a contract. ### Goal is the render's one-sentence standing intent. These defer to the obvious.

Three sections carry the semantic load of a responsibility, and the contract must state them rather than defer: ### Requires, ### Maintains, and ### Continuity.

### Maintains -- the world-model schema (four jobs at once)

### Maintains is the schema for the truth this node keeps current. It does four jobs in one section:

  1. It types the maintained truth -- the shape of the world-model the node owns.
  2. It carries the canonicalization spec -- which fields are material and how they normalize. This compiles into the node's fingerprint, the cheap content identity the run phase compares.
  3. It declares facets (below) -- the named parts of the truth.
  4. It states postconditions -- the obligations a render must satisfy before it may commit.

There is no separate judge and no ### Criteria section. Satisfaction folds into ### Maintains: checked deterministically where it can be written as a validator, and self-attested by the render where it is semantic. A render that cannot satisfy its postconditions commits nothing -- the prior truth stands, and a failed receipt records why.

Facets make subscription structural

A #### sub-heading inside ### Maintains declares a facet -- a named part of the truth. The facet's name is, at once, three things:

  • its fingerprint unit (the canonicalizer emits one token per facet, plus an always-on atomic token over the whole truth);
  • its subscription symbol (a consumer names it in ### Requires, and the reconciler wakes that consumer only when that facet's token moves -- Requires.<facet>Maintains.<facet>);
  • and its region of the world-model.

Declaring no parts is the atomic default -- one truth, one token -- and costs nothing. This adds no new grammar: it reuses the heading hierarchy and the Requires↔Maintains join. Structure is subscription. The way you shape the ### Maintains section is the way downstream nodes subscribe to it.

### Requires -- what the node subscribes to

### Requires names the upstream facets this node consumes. Forme matches each entry to a producer's ### Maintains facet and draws the subscription edge. ### Requires is the input side of the memo key: the run phase fingerprints the node's contract together with its subscribed inputs, and re-renders only when one of them moves.

### Continuity -- the wake source

### Continuity declares when, beyond a subscribed input changing, a node should re-render. It is not a schedule. A node is input-driven by default (it wakes when a subscribed facet moves). ### Continuity adds one of:

  • a self-driven cadence -- the truth goes stale on its own. A valid_until freshness state lives in the world-model as data; ### Continuity carries only the policy that reads it on a forecast cadence. When a valid_until lapses, the harness mechanically moves the facet's fingerprint and wakes the node -- no model call to decide that time has passed.
  • external-driven -- marks a kind: gateway so an ingress event wakes it.

The author writes these sections. The harness owns the fingerprinting, the forecast cadence, the receipts, and the subscription wiring. You declare the truth; the runtime decides, deterministically, when it is worth recomputing.

Functions: the call interface

A kind: function does not maintain a world-model and is not part of the DAG. It declares a plain call interface instead of the four-jobs schema:

  • ### Parameters -- the inputs bound at call time.
  • ### Returns -- the return value of its single render.

A lone function has no Forme phase: bind parameters, spawn one render, return ### Returns. It is the stateless, ephemeral helper.

A contract in full

Here is a real, migrated responsibility. Read it top to bottom: the > description states intent for a human, ### Requires names the single facet it subscribes to, ### Maintains types the CountSummary truth and declares one #### structured facet with an explicit postcondition, and ### Continuity declares it input-driven -- so it wakes only when its counts input moves.

count-summary.prose.md
---
name: count-summary
kind: responsibility
---

# Count Summary

> The single responsibility that turns the gateway's `counts` facet into a
> threshold-aware summary (U02). It reads its prior world-model by reference,
> writes a new `CountSummary`, and signs a receipt naming the upstream receipt it
> consumed. It skips when `counts` has not moved (U03).

### Requires

- `counts`: the numeric tallies. *(Maintained by `counter-events.counts`.)*

This is the only subscribed input. `count-summary` is **input-driven**: it wakes
iff the `counts` facet fingerprint moves. A metadata-only event moves only
`raw_events`, so this node stays dark while `Raw Event Auditor` wakes (U05).

### Maintains

The `CountSummary` world-model — the structured summary the alerting chain reads.

- `total` — the material event count.
- `by_kind` — the per-kind tallies.
- `threshold_crossed` — whether `total` reached the alert threshold.
- `explanation` — a short rationale string.

#### structured

The whole summary is material: any change to `total`, `by_kind`, or
`threshold_crossed` moves this node's truth and propagates to `Alert State`.

**Postcondition:** `total` equals the count of accepted material events;
`threshold_crossed` is true iff `total ≥ threshold`. Self-policed before signing —
no separate judge beat.

### Execution

Read the `counts` facet and the prior summary by reference, fold the per-kind
totals, set `threshold_crossed`, and commit the structured summary.

### Continuity

input-driven

And here is the gateway that feeds it. A gateway has no ### Requires (its input arrives from outside the graph). Notice how ### Maintains splits the ledger into two #### facets -- counts and raw_events -- so a metadata-only event moves raw_events (waking the auditor) without moving counts (the summary above stays dark). That split is the subscription wiring.

counter-events.prose.md
---
name: counter-events
kind: gateway
---

# Counter Events

> The gateway — the system's ingress. It has no `### Requires` (its input arrives
> from outside the graph), it `### Maintains` the canonical `CounterEventLedger`,
> and its `### Continuity` is **external-driven**, which is how Forme registers it
> as the single DAG entry point (U11).

### Continuity

external-driven

A webhook, a poll, or a manual kick becomes one external wake at the system's
edge. The gateway folds each accepted counter event into the canonical ledger and
projects two **independent facets** so a downstream subscriber wakes only on the
slice it actually depends on (U05). Replaying the same event id is a no-op — the
ledger dedups by id, so a re-delivery produces a byte-identical world-model and
the gateway memo-skips (U01/U03).

### Receives

- A counter event: `{ id, kind, value, material? }`. An event with
  `material: false` is **accepted into the audit trail but excluded from the
  tallies** — it is the metadata-only event that moves `raw_events` without moving
  `counts`.

### Maintains

The `CounterEventLedger` — the standing truth every downstream responsibility
subscribes to. Its canonicalization splits the truth into the two facets below, so
a change to one slice never spuriously wakes a subscriber of the other.

- `high_water_mark` — the running material event total.
- `counts_by_kind` — the per-kind material tallies.
- `accepted_event_ids` — the full accepted id set (material and metadata-only).
- `last_seen_at` — an immaterial monotone marker (it never appears in a facet, so
  it cannot wake a subscriber on its own).

#### counts

The numeric tallies (`high_water_mark`, `counts_by_kind`) over **material events
only**. Moves when a material event is accepted; does NOT move on a metadata-only
event. `Count Summary` and `Count Trend` subscribe here.

#### raw_events

The accepted-event id set plus duplicate / malformed flags. Moves whenever the
accepted set changes — including a metadata-only event. `Raw Event Auditor`
subscribes here.

### Emits

- count-summary
- raw-event-auditor
- count-trend

Forme keys the wake on the producing node; the subscribers above resolve their
edges to this gateway's `counts` / `raw_events` facets.

### Continuity recheck

A weekday 09:00 self-kick may re-scan even when no webhook fires; a byte-identical
re-scan memo-skips, so the self-kick costs nothing when nothing changed.

A bare prompt is any: it tells an agent what to do once and forgets. A contract is a typed function over the world -- a declared truth with a fingerprint, named facets others can subscribe to, and postconditions it must satisfy before it commits. That is the whole difference, and it is authored entirely in Markdown.

Where to go next

For the full language semantics, the authoritative source is the OpenProse spec (01-Language.md) and the loaded open-prose SKILL. If the docs and the skill ever disagree, trust the skill.

On this page