From 6d2e7113dc62ee9085ae00e67f18e8263acc3477 Mon Sep 17 00:00:00 2001 From: PayalVerma1 Date: Thu, 6 Aug 2026 20:14:46 +0530 Subject: [PATCH] docs: add guidance for intentional ask_user pauses --- skills/open-prose/examples/README.md | 11 ++ .../examples/interactive-grill/README.md | 111 ++++++++++++++++++ .../interactive-grill/src/grill.prose.md | 70 +++++++++++ .../src/synthesize-brief.prose.md | 48 ++++++++ skills/open-prose/guidance/authoring.md | 29 +++++ skills/open-prose/prose.md | 25 ++++ 6 files changed, 294 insertions(+) create mode 100644 skills/open-prose/examples/interactive-grill/README.md create mode 100644 skills/open-prose/examples/interactive-grill/src/grill.prose.md create mode 100644 skills/open-prose/examples/interactive-grill/src/synthesize-brief.prose.md diff --git a/skills/open-prose/examples/README.md b/skills/open-prose/examples/README.md index b51fe606..28624b80 100644 --- a/skills/open-prose/examples/README.md +++ b/skills/open-prose/examples/README.md @@ -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 diff --git a/skills/open-prose/examples/interactive-grill/README.md b/skills/open-prose/examples/interactive-grill/README.md new file mode 100644 index 00000000..14147473 --- /dev/null +++ b/skills/open-prose/examples/interactive-grill/README.md @@ -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//{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`. diff --git a/skills/open-prose/examples/interactive-grill/src/grill.prose.md b/skills/open-prose/examples/interactive-grill/src/grill.prose.md new file mode 100644 index 00000000..299fe3c6 --- /dev/null +++ b/skills/open-prose/examples/interactive-grill/src/grill.prose.md @@ -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. diff --git a/skills/open-prose/examples/interactive-grill/src/synthesize-brief.prose.md b/skills/open-prose/examples/interactive-grill/src/synthesize-brief.prose.md new file mode 100644 index 00000000..7f2039b2 --- /dev/null +++ b/skills/open-prose/examples/interactive-grill/src/synthesize-brief.prose.md @@ -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. diff --git a/skills/open-prose/guidance/authoring.md b/skills/open-prose/guidance/authoring.md index 8d165be4..30c25abd 100644 --- a/skills/open-prose/guidance/authoring.md +++ b/skills/open-prose/guidance/authoring.md @@ -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: diff --git a/skills/open-prose/prose.md b/skills/open-prose/prose.md index c5e1c7ed..d72a2416 100644 --- a/skills/open-prose/prose.md +++ b/skills/open-prose/prose.md @@ -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