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
11 changes: 11 additions & 0 deletions skills/open-prose/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ render model, a smart judge model) alongside its deterministic tier-2 gate.
Checker (7 spec checks), 5 canonical scenarios, and a key-gated LLM judge panel
that is OFF in CI. Run offline: `pnpm test:eval:offline`.

### Intentional user-input pauses

- [interactive-grill](./interactive-grill/): the **counter-example** to the
all-non-interactive set. A `responsibility` whose `### Requires` entries are
deliberately left unbound so the VM fires `ask_user` at bind time. There is
**no `kind: service`**, **no `### Runtime: interactive: true`**, and no other
field that expresses interactivity. An unbound required input *is* the intent
signal. If the host cannot pause, the VM returns `unresolved-intent` rather
than guessing. This example teaches the author-facing pattern for grilling,
interview, or decision services that intentionally pause for human judgment.

---

## Named-parts & migrated corpus examples
Expand Down
111 changes: 111 additions & 0 deletions skills/open-prose/examples/interactive-grill/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# interactive-grill

**The standing goal:** challenge a feature brief with repository-grounded
questions and a deliberate human judgment step — the interactive counterpart
to `auto-pocock`'s non-interactive grilling.

**One-line scenario:** the operator supplies a `feature-brief`; the `grill`
responsibility pauses for the `target-repo` if unbound, inspects the repo for
evidence, recommends answers, and surfaces the questions that only a human can
resolve. Downstream `synthesize-brief` turns the output into a decision-ready
plan.

This is the smallest graph that teaches the **intentional `ask_user` pause**:
a `responsibility` whose `### Requires` entries are deliberately left unbound
so the VM fires `ask_user` at bind time. There is **no `kind: service`**, **no
`### Runtime: interactive: true`**, and no other field that expresses
interactivity. An unbound required input *is* the intent signal.

## The DAG

```
feature-brief (caller input) ──► grill (responsibility, input-driven)
target-repo (caller input) ──►
synthesize-brief (function)
```

One responsibility, one helper function, over **one interactive caller
interface**. The lesson is not topology — it is the bind-time pause contract.

## The beat arc

| beat | what happens |
| ----------- | ----------------------------------------------------- |
| **bind** | `feature-brief` and `target-repo` are unbound → VM |
| | fires `ask_user` for each, pauses, resumes with answers |
| **render** | `grill` reads the inputs, inspects the repo, writes |
| | `challenge-report`, `decision-records`, and |
| | `terminology-glossary` to the world-model |
| **skip** | re-run with the same inputs and no repo change → |
| | memo-skip at fresh 0 |

If the host cannot pause (CI, non-interactive shell), the VM returns
`unresolved-intent` with the missing inputs listed. A calling harness should
treat that as a contract signal: supply the value or halt, never guess.

## Run it with the Reactor harness

The `.prose.md` contracts work with any harness; these verbs steer to Reactor.

```bash
cd skills/open-prose/examples/interactive-grill
reactor doctor # honest health report
reactor compile --check # exits 1 (stale): recognized, not yet compiled
reactor compile # run the compile session -> IR cache
reactor topology # inspect the compiled DAG
reactor run # boot, drain, print dispositions + cost
reactor serve # browse the receipts + world-models locally
reactor receipts # read the per-node ledger
```

### Run with explicit inputs (non-interactive)

If you supply both inputs, the run does not pause:

```bash
prose run src/grill.prose.md \
--feature_brief "Add a dark mode toggle to the settings page" \
--target_repo "/path/to/your/repo"
```

### Run with missing inputs (interactive)

If you omit one or both inputs in a TTY, the VM prompts via `ask_user`:

```bash
prose run src/grill.prose.md
# VM: "feature-brief is required but not provided. What is the feature brief?"
# VM: "target-repo is required but not provided. What is the target repo path?"
```

In CI or a non-interactive shell, the same run returns `unresolved-intent`:

```bash
prose run src/grill.prose.md --feature_brief "..." --non_interactive
# unresolved-intent: missing inputs: target-repo
```

## What to try

- **Re-run with the same inputs** and watch the responsibility memo-**skip**;
the skip costs nothing and spawns nothing.
- **Omit an input in a TTY** and watch the VM pause for `ask_user`; provide the
value and the run resumes with the answer bound.
- **Omit an input in CI** and watch the run return `unresolved-intent`; a
harness should treat this as a contract signal, not a failure.
- **Move the contract** (edit `grill.prose.md`) and watch the responsibility
re-render and the fresh meter tick once.

## How it's built & exercised

- `src/grill.prose.md`: the intentionally interactive responsibility.
- `src/synthesize-brief.prose.md`: the stateless helper it `call`s.
- A run writes a keyless, chain-verifiable state-dir: a flat `receipts.json`,
`compile/topology.json` + `compile/labels.json`, and
`world-models/<hexNodeId>/{published.json, versions/sha256_*.bin}`.

The example is the canonical counter-example to `auto-pocock`'s non-interactive
adaptation. Where `auto-pocock` recommends answers to avoid prompting, this
example deliberately leaves required inputs unbound so the VM fires `ask_user`.
70 changes: 70 additions & 0 deletions skills/open-prose/examples/interactive-grill/src/grill.prose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
name: grill
kind: responsibility
version: 0.15.0
---

# Grill

> An intentionally interactive responsibility. It pauses mid-run to ask the
> operator a concrete question that cannot be grounded in repository evidence,
> then uses the answer to produce a grilling brief. This is the counter-example
> to `auto-pocock`: where `auto-pocock` recommends answers to avoid prompting,
> `grill` deliberately leaves required inputs unbound so the VM fires `ask_user`.

### Requires

- `feature-brief`: the original feature idea to challenge. If supplied via CLI
arg or config, the run proceeds without pausing. If **unbound**, the VM
pauses and prompts the operator for it via `ask_user`.
- `target-repo`: the repository to inspect for evidence. If supplied, the
grilling grounds its recommendations in repo evidence. If **unbound**, the
VM pauses and prompts the operator for it via `ask_user`.

### Maintains

The grilling brief, as this responsibility's maintained truth:

- `challenge-report`: a focused challenge report with questions, why they matter,
recommended answers grounded in repo evidence, risks, terminology corrections,
and unresolved unknowns.
- `decision-records`: a numbered list of `{question, recommended_answer,
confidence, source, residual_risk}` where `source` is one of `brief`, `repo`,
or `unresolved`.
- `terminology-glossary`: resolved domain terms with avoid-aliases, conflicts
flagged against the existing glossary.

### Continuity

input-driven: the grilling re-renders when `feature-brief` or `target-repo`
changes. A re-run with the same inputs and no repo change moves nothing, so the
memo key is a **hit** and the responsibility memo-**skips**.

### Shape

- `self`: challenge the plan, inspect the repository for discoverable answers,
recommend answers, and identify unresolved questions that require human judgment
- `prohibited`: making final product or implementation decisions, opening issues,
or inventing evidence when the repo cannot ground an answer

### Strategies

- Convert every would-be user question into a `decision-records` entry with a
recommended answer, confidence, source, and residual risk.
- If a question can be answered from the repository, mark `source: repo` and
cite the file; otherwise mark `source: brief` or `source: unresolved`.
- Use the existing domain glossary as the starting vocabulary; flag drift instead
of inventing terms.
- When a term is resolved, capture the resolution in `terminology-glossary` so
a downstream synthesis can commit it.

### Execution

1. Read the `feature-brief` and `target-repo` inputs.
2. Inspect the repository for evidence that grounds the questions in the brief.
3. For each ambiguity, produce a `decision-records` entry with a recommended
answer, confidence, source, and residual risk.
4. If a question cannot be grounded in evidence and confidence is below the
threshold, mark it `source: unresolved` so the operator can decide.
5. Write `challenge-report`, `decision-records`, and `terminology-glossary` to
the world-model and sign the receipt.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: synthesize-brief
kind: function
version: 0.15.0
---

# Synthesize Brief

> A stateless helper the `grill` responsibility calls to turn the challenge
> report into a decision-ready plan. A `function` declares `### Parameters ->
> ### Returns`, has no world-model, and no wake source — it is ephemeral and
> pure.

### Parameters

- `challenge-report`: focused challenge report with questions, recommended
answers, risks, terminology corrections, and unresolved unknowns.
- `decision-records`: numbered list of `{question, recommended_answer,
confidence, source, residual_risk}`.
- `terminology-glossary`: resolved domain terms with avoid-aliases.

### Returns

The synthesized plan, as a stateless value:

- `grilled-plan`: clarified decisions, terminology, risks, and open questions
ready for downstream consumption.
- `chosen-terminology`: final glossary that downstream services must use verbatim.
- `open-questions`: questions intentionally left unresolved, each with the exact
plan risk the unresolved question creates.

### Shape

- `self`: synthesize the grilling output into a decision-ready artifact
- `prohibited`: making final product decisions, reopening a live user interview,
inventing evidence, or introducing new domain terms beyond `terminology-glossary`

### Invariants

- Decisions never silently drop a `decision-records` entry; unanswered ones
must appear in `open-questions` with explicit residual risk.

### Strategies

- Prefer the griller's recommended answer when it is grounded in repository
evidence or the original brief.
- Lock `chosen-terminology` before drafting the plan; do not coin new domain
terms here.
29 changes: 29 additions & 0 deletions skills/open-prose/guidance/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,35 @@ world-model, no `### Maintains`, and no `### Continuity`.
- Author functions rarely and call them constantly; most ship pre-built in
`std/`. They are the standard-library tier.

## Interactive Authoring

A render intentionally pauses for user input by leaving a required caller input
**unbound**. The VM's `ask_user` primitive fires at bind time for any `### Requires`
or `### Parameters` entry with no value from CLI args, config, or a calling render,
and resumes the run with the answer. This is the only sanctioned mechanism for
mid-run user prompting; there is **no `kind: service`**, **no `### Runtime:
interactive: true`**, and no other field that expresses interactivity.

- Declare the question in `### Requires` (for a `responsibility`) or `### Parameters`
(for a `function`). Leave it unbound — do not supply a default, do not fake a
config fallback. The absence of a value is the signal that this run is designed to
pause.
- When the host can satisfy `ask_user`, the VM prompts the user, binds the response,
and resumes. The bound value is written to `world-model/caller/{name}.md` and
available to the render as ordinary caller input.
- When the host **cannot** pause (non-interactive shell, CI, automation), the VM does
not guess or fabricate. It returns `unresolved-intent` with the missing inputs
listed, exactly as the `prose-author` std-op does when `interactive` is unavailable
(`packages/std/ops/prose-author.prose.md`). A downstream service should treat
`unresolved-intent` as a contract signal, not a failure.
- Use intentional pauses for genuine human judgment: a grilling/interview step,
a decision gate where the skill cannot ground an answer in repository evidence, or
a confirmation where the cost of a wrong guess exceeds the cost of asking.
- Do **not** use `ask_user` for values the render can derive, discover, or default
safely. Prefer `### Environment` for runtime config, `### Requires` bindings from
upstream nodes for data, and `### Strategies` for judgment guidance. Reserve
`ask_user` for decisions that are irreducibly human.

## Composition Authoring

Composition is no longer a separate kind. There are exactly two forms:
Expand Down
25 changes: 25 additions & 0 deletions skills/open-prose/prose.md
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,31 @@ At run start, the VM resolves each `requires` entry:
| Value provided by a calling render (nested invocation) | Bind immediately |
| No value available | Prompt user via `ask_user`, bind response |

### Intentional `ask_user` Pauses

`ask_user` is the VM's only mechanism for pausing a run to collect human input.
It fires automatically when a required caller input is unbound, but it is also the
**intended** mechanism for authors who want a render to pause mid-run for a
decision that cannot be derived from evidence or defaults.

To author an intentional pause:

1. Declare the input in `### Requires` (responsibility) or `### Parameters`
(function).
2. Do **not** supply a value via CLI arg, config, or calling render.
3. The VM pauses at bind time, prompts the user via `ask_user`, binds the response,
and resumes.

There is **no `kind: service`**, **no `### Runtime: interactive: true`**, and no
other field that expresses interactivity. An unbound required input *is* the intent
signal.

When the host cannot satisfy `ask_user` (non-interactive shell, CI, automation),
the VM does **not** guess or fabricate. It returns `unresolved-intent` with the
missing inputs listed, exactly as the `prose-author` std-op does when `interactive`
is unavailable. A calling render or harness should treat `unresolved-intent` as a
contract signal and either supply the missing value or halt rather than invent one.

### Writing Input Bindings

Write each input to the active backend binding store. Filesystem runs use
Expand Down