Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions skills/open-prose/contract-markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Forme and the Prose VM recognize these `###` sections case-insensitively:
| `### Fixtures` | test | Test inputs supplied without prompting |
| `### Expects` | test | Positive natural-language assertions |
| `### Expects Not` | test | Negative natural-language assertions |
| `### Patterns` | responsibility, function | Instantiates patterns into named nodes via fenced yaml (- name, pattern:, with:, config:). Expanded by Forme at compile time |
| `### Slots` | pattern | Responsibilities or functions a pattern requires from its caller |
| `### Config` | pattern | Pattern-level parameters and defaults |
| `### Delegation` | pattern | ProseScript or pseudocode describing slot interaction |
Expand Down Expand Up @@ -587,23 +588,50 @@ loop up to config.max_rounds:
```
````

A pattern is instantiated with a fenced `yaml` declaration. Use `with:` for slot
bindings and `config:` for pattern parameters:
A pattern is instantiated inside the canonical **`### Patterns`** section of a
responsibility or function using a fenced `yaml` block.

### Canonical Instantiation Syntax (`### Patterns`)

The declaration connects five elements:
1. **`use`**: Imports an external or standard library pattern at the file header (e.g. `use "std/patterns/worker-critic"`). When imported, the pattern can be referenced by its local alias (`worker-critic`). Alternatively, specify the full path directly in `pattern:`.
2. **`name:`**: Assigns a local identifier to the expanded node instance. This name is what ProseScript invokes via `call {name}` in `### Execution`, or what Forme wires as a node in the DAG.
3. **`pattern:`**: Identifies the pattern contract (`kind: pattern`). Can be a local contract filename, an imported alias, a standard library shorthand (`std/patterns/...`), or a full git host URI.
4. **`with:`**: Binds each slot declared in the pattern's `### Slots` to a concrete responsibility, function, or nested pattern instance. Forme validates that all required slots are filled.
5. **`config:`**: Supplies parameters that override the defaults declared in the pattern's `### Config`.

```markdown
use "std/patterns/worker-critic"

### Patterns

```yaml
- name: reviewed-draft
pattern: std/patterns/worker-critic
pattern: worker-critic
with:
worker: writer
critic: reviewer
config:
max_rounds: 3
```

`pattern:` names a `kind: pattern` file. `with:` binds slots to responsibilities,
functions, or nested pattern instances. After expansion, the named instance
behaves like a node. Nested pattern declarations are allowed only as slot values
inside another pattern instance's `with:` block.
### Execution

```prose
let final_doc = call reviewed-draft
topic: topic
guidelines: guidelines

return { document: final_doc }
```
```

### Slot Binding and Nesting

- `with:` binds slots to responsibilities, functions, or nested pattern instances.
- Nested pattern declarations are allowed only as slot values inside another pattern instance's `with:` block. Expansion proceeds inside-out.
- Recursive patterns are strictly prohibited.
- For compile-time resolution, slot validation, and expansion mechanics, see `forme.md#pattern-expansion`.

## Structured Blocks

Expand Down
50 changes: 50 additions & 0 deletions skills/open-prose/forme.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,56 @@ topology's `edges` to schedule and propagate.

---

## Pattern Expansion

Patterns are compile-time abstractions: slots, configuration parameters, invariants,
and delegation rules that describe how a set of roles or renders coordinate. By the
time the run-phase reconciler executes, patterns are completely expanded into
concrete nodes and delegation steps.

### The Expansion Lifecycle

When Forme processes a contract containing a `### Patterns` section, it performs
compile-time expansion in five steps:

1. **Resolution:**
Forme resolves the pattern named in `pattern:`. It first checks for a local
`kind: pattern` contract in the package or repository. If not found locally, it
resolves the pattern reference through installed dependencies (under
`<openprose-root>/deps/` per `deps.md`) or standard library shorthands
(`std/patterns/...`). An explicit `use "std/patterns/{name}"` declaration
establishes a local alias that `pattern:` may reference by its unqualified name.

2. **Slot Validation:**
Forme inspects the target pattern's declared `### Slots`. Every declared slot
must be bound in the instance's `with:` block. Forme verifies that each bound
slot references a valid responsibility, function, or nested pattern instance
available in scope. Unbound or mismatched slots are surfaced as compile-time
diagnostics (`unsatisfied-slot`).

3. **Config Validation:**
Forme validates the instance's `config:` block against the pattern's declared
`### Config` section. Any declared defaults in `### Config` are applied for
omitted optional parameters; missing required values or type mismatches trigger
compile-time validation errors.

4. **Instantiation & Delegation Lowering:**
The instance's `name:` becomes the assigned identifier for the expanded unit.
Forme lowers the pattern's `### Delegation` block (ProseScript) into the
concrete execution logic of the expanded unit, substituting the bound slot
references and validated config values. In `### Execution`, callers invoke
this expanded unit via ProseScript `call {name}`.

5. **Nesting and Cycle Prevention:**
Patterns nest cleanly: a slot in `with:` may be filled by another inline
pattern instantiation. Forme resolves and expands nested patterns **inside-out**
(the innermost leaf pattern expands first, then supplies its expanded node to
the outer slot). **Recursive patterns are strictly prohibited:** Forme checks
that pattern instantiations form a directed acyclic hierarchy and halts
compilation if any pattern directly or transitively references itself.

---

## What Forme retired

Forme used to be a SKILL-phase dependency-injection container that wired
Expand Down
19 changes: 12 additions & 7 deletions skills/open-prose/prose.md
Original file line number Diff line number Diff line change
Expand Up @@ -1265,15 +1265,20 @@ A pattern file declares its pattern with Contract Markdown sections. Understandi

### Instantiation

Authors instantiate patterns with explicit slot-filling: `pattern:` names the
pattern, `with:` binds slots, and `config:` sets pattern parameters. Instances
Authors instantiate patterns inside the canonical `### Patterns` section with
explicit slot-filling: `name:` assigns the instance identifier, `pattern:` names
the pattern, `with:` binds slots, and `config:` sets pattern parameters. Instances
are declared in contract files and expanded by Forme at compile time into
concrete nodes. Nested pattern declarations may appear only as
slot values inside a pattern instance's `with:` block.
For instantiation syntax, see `contract-markdown.md` (Patterns) and `forme.md`,
Pattern Expansion. No shorthand pattern syntax is accepted at runtime.
concrete nodes. Nested pattern declarations may appear only as slot values inside
a pattern instance's `with:` block.

Patterns nest — a slot can be filled by another pattern instantiation. Expansion proceeds inside-out. Recursive patterns are prohibited. For nesting examples, see `forme.md`, Pattern Expansion.
For instantiation syntax, `use` aliases, and complete examples, see
`contract-markdown.md` (Patterns) and `forme.md`, Pattern Expansion. No shorthand
pattern syntax is accepted at runtime.

Patterns nest — a slot can be filled by another pattern instantiation. Expansion
proceeds inside-out. Recursive patterns are prohibited. For nesting examples and
expansion rules, see `forme.md`, Pattern Expansion.

### Patterns in the Manifest

Expand Down
9 changes: 7 additions & 2 deletions spec/01-Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ Canonical sections include:
| `### Shape` | responsibility, function | Capability boundaries: self, delegates, prohibited work |
| `### Execution` | responsibility, function | ProseScript render body that pins choreography |
| `### Fixtures` / `### Expects` / `### Expects Not` | test | Test data and assertions |
| `### Patterns` | responsibility, function | Pattern instantiation declarations (- name:, pattern:, with:, config:) |
| `### Slots` / `### Config` / `### Delegation` | pattern | Pattern interface and algorithm |
| `### Schedule` / `### Receives` / `### Emits` / `### Payload` | gateway | Time/event ingress declarations |

Expand All @@ -459,8 +460,11 @@ Header hierarchy is part of the language, and the `####` level is now load-beari
Composition has exactly two forms (there is no `system` graph kind): **intra-node**
choreography is a ProseScript `call` inside one render's `### Execution`;
**cross-node** composition is a Forme-wired `### Requires` ↔ `### Maintains`
subscription between responsibilities. Pattern instances remain current YAML
syntax, declared in a responsibility's slots:
subscription between responsibilities. Pattern instances are declared in the
canonical `### Patterns` section with fenced YAML syntax:

```markdown
### Patterns

```yaml
- name: reviewed-output
Expand All @@ -471,6 +475,7 @@ syntax, declared in a responsibility's slots:
config:
max_rounds: 3
```
```

### Forme

Expand Down
83 changes: 83 additions & 0 deletions tests/open-prose/contract-markdown/patterns.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";

const repoRoot = fileURLToPath(new URL("../../../", import.meta.url));

function read(rel: string): string {
return readFileSync(join(repoRoot, rel), "utf8");
}

function flat(rel: string): string {
return read(rel).replace(/\s+/g, " ");
}

const CONTRACT_MD = "skills/open-prose/contract-markdown.md";
const FORME_MD = "skills/open-prose/forme.md";
const PROSE_MD = "skills/open-prose/prose.md";
const SPEC_LANG = "spec/01-Language.md";
const SMOKE_PATTERN = "tests/open-prose/smoke/09-local-pattern.prose.md";

describe("pattern reference and instantiation syntax canonicalization (Issue #170)", () => {
it("includes ### Patterns in Canonical Sections table of contract-markdown.md", () => {
const source = read(CONTRACT_MD);
const canonicalTable = source.slice(
source.indexOf("## Canonical Sections"),
source.indexOf("### Folded and deleted sections"),
);
expect(canonicalTable).toContain("### Patterns");
expect(canonicalTable).toMatch(/responsibility,\s*function/);
expect(canonicalTable).toMatch(/fenced yaml/i);
});

it("includes ### Patterns in Canonical Sections table of spec/01-Language.md", () => {
const source = read(SPEC_LANG);
const canonicalTable = source.slice(
source.indexOf("Canonical sections include:"),
source.indexOf("The retired judge-era sections"),
);
expect(canonicalTable).toContain("### Patterns");
expect(canonicalTable).toMatch(/Pattern instantiation declarations/i);
});

it("defines use, pattern:, with:, and config: together with an end-to-end example", () => {
const doc = flat(CONTRACT_MD);
expect(doc).toMatch(/### Canonical Instantiation Syntax \(`### Patterns`\)/i);
expect(doc).toContain("use");
expect(doc).toContain("pattern:");
expect(doc).toContain("with:");
expect(doc).toContain("config:");
expect(doc).toMatch(/call reviewed-draft/);
expect(doc).toMatch(/forme\.md#pattern-expansion/);
});

it("repairs broken Forme references: forme.md contains ## Pattern Expansion", () => {
const formeDoc = read(FORME_MD);
expect(formeDoc).toContain("## Pattern Expansion");

const flatForme = flat(FORME_MD);
expect(flatForme).toMatch(/Resolution:/i);
expect(flatForme).toMatch(/Slot Validation:/i);
expect(flatForme).toMatch(/Config Validation:/i);
expect(flatForme).toMatch(/Instantiation & Delegation Lowering:/i);
expect(flatForme).toMatch(/inside-out/i);
expect(flatForme).toMatch(/Recursive patterns are strictly prohibited/i);
});

it("prose.md cross-references resolve to contract-markdown.md and forme.md", () => {
const proseDoc = flat(PROSE_MD);
expect(proseDoc).toMatch(/canonical `### Patterns` section/i);
expect(proseDoc).toMatch(/`contract-markdown\.md`\s*\(Patterns\)/i);
expect(proseDoc).toMatch(/`forme\.md`,\s*Pattern Expansion/i);
});

it("smoke test 09-local-pattern.prose.md conforms to canonical ### Patterns section", () => {
const smokeDoc = read(SMOKE_PATTERN);
expect(smokeDoc).toContain("### Patterns");
expect(smokeDoc).toContain("pattern: worker-critic");
expect(smokeDoc).toContain("with:");
expect(smokeDoc).toContain("config:");
expect(smokeDoc).toContain("call reviewed-result");
});
});