From 42b3387f6712308d81d8449eceba5e76179962df Mon Sep 17 00:00:00 2001 From: Raymond Weitekamp Date: Tue, 19 May 2026 12:42:29 -0400 Subject: [PATCH] docs: document OpenProse host capability modes --- packages/std/ops/prose-author.prose.md | 19 +++++++---- skills/open-prose/SKILL.md | 7 ++++ skills/open-prose/contract-markdown.md | 6 ++++ skills/open-prose/guidance/authoring.md | 31 +++++++++++++++++ skills/open-prose/help.md | 13 +++++++ skills/open-prose/prose.md | 45 ++++++++++++++++++++++--- 6 files changed, 111 insertions(+), 10 deletions(-) diff --git a/packages/std/ops/prose-author.prose.md b/packages/std/ops/prose-author.prose.md index 8d47ac4b..8b8f65d7 100644 --- a/packages/std/ops/prose-author.prose.md +++ b/packages/std/ops/prose-author.prose.md @@ -18,12 +18,19 @@ Use this when a caller knows the workflow they want but has not yet written the folder-shaped package with an `index.prose.md` system and nearby service, gateway, responsibility, pattern, or test files. -Direct in-harness `prose write` is interactive by default: after a read-only -landscape scan and initial shape/root decision, ask a small number of targeted -questions when the host can satisfy the OpenProse `ask_user` primitive. The -shell CLI may mark the run non-interactive because it can only pass argv/stdin -up front; in that mode, return `unresolved-intent` with concrete missing -decisions instead of guessing. +### Invocation Modes + +`prose-author` is one contract with explicit mode inputs, not separate hidden +behaviors per surface. Direct in-harness `prose write` is interactive by default +when the host can satisfy `ask_user`. + +| Invocation surface | Mode inputs | Behavior | +|--------------------|-------------|----------| +| Direct in-harness `prose write` | `interactive` defaults to `true` when the host can satisfy `ask_user` | After a read-only landscape scan and initial shape/root decision, ask a small number of targeted questions when they unblock safe source planning | +| Shell CLI `prose write` | wrapper passes `interactive: false`, `output_mode: source-package-only`, `apply: false`, `run_state: in-context`, and `terminal_summary: required` | Read request text from argv or piped stdin only; return a validated source package or `unresolved-intent` with concrete missing decisions | + +Non-interactive authoring must not guess when a missing decision could change +the generated source shape, root, path, side effects, or safety boundary. ### Services diff --git a/skills/open-prose/SKILL.md b/skills/open-prose/SKILL.md index 29da8615..1e05ccaa 100644 --- a/skills/open-prose/SKILL.md +++ b/skills/open-prose/SKILL.md @@ -158,6 +158,13 @@ the current host must map onto its available tools: | `copy_binding` | Publish declared outputs through the active backend | Filesystem backend copies from `workspace/` to `bindings/`; database backends write records/attachments; never publish undeclared scratch files | | `check_env` | Verify an environment variable exists | Check only presence; never reveal or log raw values | +When a host capability changes contract behavior, pass it as an explicit mode +input rather than relying on implicit host inference. For example, shell +wrappers for `prose write` pass `interactive: false` because they can supply +argv/stdin up front but cannot pause and resume a running authoring contract for +follow-up questions. See `prose.md#host-capability-modes` and +`guidance/authoring.md#mode-aware-contracts`. + ## Format Detection | Format | Extension | Primary Docs | Execution Path | diff --git a/skills/open-prose/contract-markdown.md b/skills/open-prose/contract-markdown.md index 07a7aca9..0915cfd0 100644 --- a/skills/open-prose/contract-markdown.md +++ b/skills/open-prose/contract-markdown.md @@ -449,6 +449,12 @@ requirements: they do not satisfy `### Requires`, do not create Forme dependency-graph edges, and do not grant or restrict tool use. Use `### Shape` for service boundaries and prohibited actions. +`### Tools` is for concrete host tools such as CLI executables and MCP servers, +not VM primitives. Do not list `ask_user`, `spawn_session`, `read_state`, +`write_state`, or `copy_binding` here. When primitive availability changes +behavior, declare an explicit mode input such as `interactive` in +`### Requires` and document the mode-specific behavior in the contract. + ```markdown ### Tools diff --git a/skills/open-prose/guidance/authoring.md b/skills/open-prose/guidance/authoring.md index e0f1b336..e08cbb57 100644 --- a/skills/open-prose/guidance/authoring.md +++ b/skills/open-prose/guidance/authoring.md @@ -31,6 +31,37 @@ Use this file when writing or reviewing OpenProse author-facing artifacts: - Express hard boundaries as contracts, especially `Shape.prohibited`, environment declarations, and pattern invariants. +## Mode-Aware Contracts + +When a service or system behaves differently based on the caller or host +environment, make the mode an explicit contract input. Do not hide that choice +in command names, prose-only caveats, or implicit host assumptions. + +- Put capability and mode inputs in `### Requires`, such as `interactive`, + `apply`, `run_state`, `network_allowed`, `persistence_scope`, or + `tools_available`. +- Document each mode's observable behavior in `### Ensures`, especially which + bindings are produced, which side effects are allowed, and which degraded + outputs are valid. +- Declare fallback errors in `### Errors` when a missing capability blocks safe + execution. Prefer a specific error such as `unresolved-intent` over guessing. +- Use `### Invariants` to prohibit unavailable primitives in a mode. For + example, a non-interactive run must not rely on `ask_user`, `gate()`, or any + other mid-run caller interaction. +- Keep the same command and contract honest: different hosts may pass different + mode inputs, but the contract should make that difference visible and + testable. +- Add tests or semantic lint checks for each important mode branch, especially + any branch that suppresses side effects, avoids persistence, or returns a + declared fallback instead of normal output. + +`prose write` is the model case. Direct in-harness authoring can be +interactive when the host supports `ask_user`; the shell CLI path passes +`interactive: false` because all user input must arrive up front through argv +or stdin. Both paths invoke the same authoring contract. The mode input, not +hidden CLI magic, selects whether the system may ask follow-up questions or +must return `unresolved-intent`. + ## Service Authoring - A service is an atomic execution boundary: one contract, one session, one diff --git a/skills/open-prose/help.md b/skills/open-prose/help.md index 6623a654..fe286f70 100644 --- a/skills/open-prose/help.md +++ b/skills/open-prose/help.md @@ -111,6 +111,19 @@ runs a sufficiently intelligent model and supports the OpenProse primitives (`spawn_session`, filesystem state, tool calls, and user input) is considered "Prose Complete". +### Why does shell `prose write` not ask follow-up questions? + +The shell CLI path is single-shot: it can pass argv and piped stdin before the +authoring run starts, but it cannot safely pause and resume the running +OpenProse contract for more input. Shell wrappers therefore invoke +`std/ops/prose-author` with `interactive: false`. Pass all required authoring +context up front; when safe generation needs another decision, the command +returns `unresolved-intent` with a retry hint. + +Direct in-harness `prose write` can be interactive when the host can satisfy +the OpenProse `ask_user` primitive. Both paths use the same authoring contract; +the explicit `interactive` input selects the allowed behavior. + ### How is this a VM? LLMs are simulators -- when given a detailed system description, they don't just diff --git a/skills/open-prose/prose.md b/skills/open-prose/prose.md index 4968998e..afda694c 100644 --- a/skills/open-prose/prose.md +++ b/skills/open-prose/prose.md @@ -45,7 +45,7 @@ codex exec "prose run system.prose.md" | `prose run /` | Reserved for the OpenProse registry (future home at `p.prose.md`) | | `prose run ...@` | Pin to a SHA or tag; require that version in `/deps/` | | `prose run ... --offline` | Require disk-only resolution; error if not in `/deps/` | -| `prose write [request...]` | Interactive-by-default authoring through `std/ops/prose-author`, asking targeted shape/root questions when supported and returning a validated source package | +| `prose write [request...]` | Author through `std/ops/prose-author`; interactive hosts may ask targeted shape/root questions, while non-interactive callers must pass all context up front and receive `unresolved-intent` when more detail is required | | `prose lint ` | Validate structure, schema, shapes, and contracts | | `prose preflight ` | Check dependencies, declared tools, and environment variables | | `prose test ` | Run test(s) and report results | @@ -231,6 +231,41 @@ own tools: --- +## Host Capability Modes + +OpenProse contracts may need different behavior in different host +environments: a chat or TUI host can pause for `ask_user`, while a shell wrapper +may only be able to pass argv and stdin before execution starts. The VM does +not infer those modes mid-run. The caller or host declares them as explicit +contract inputs or activation context, and the service or system treats those +inputs as part of its public contract. + +Use this pattern for environment-dependent behavior: + +1. The host determines what it can support by construction or configuration. +2. The invocation passes that capability as a named input, such as + `interactive: false`, `apply: false`, `run_state: in-context`, + `network_allowed: false`, or `tools_available: [...]`. +3. The contract declares the mode input in `### Requires` and documents the + mode-specific obligations in `### Ensures`, `### Errors`, and + `### Invariants`. +4. Execution branches on the declared mode. If a required primitive is + unavailable, return a declared error or degraded output instead of stalling, + guessing, or pretending the primitive exists. + +For example, the shell CLI path for `prose write` can pass request text from +argv or piped stdin, but it cannot pause and safely resume the running +authoring contract for follow-up questions. That wrapper therefore invokes the +same `std/ops/prose-author` contract with `interactive: false`. Direct +in-harness authoring may leave `interactive` true when the host can satisfy +`ask_user`. + +Capability modes are contract-level obligations. They make behavior explicit +and testable, but they are not a hard static type system unless a caller adds a +separate checker for the mode-specific output shape. + +--- + ## Directory Structure Load `state/README.md` and the selected backend spec before execution. Durable @@ -339,7 +374,8 @@ The manifest's Caller Interface lists what the system requires. Bind these value | CLI arguments (`prose run system.prose.md --question "..."`) | Bind immediately | | Config file (`/.env` or system-level config) | Bind immediately | | Pre-supplied by calling system (if this is a nested invocation) | Bind immediately | -| No value available | Pause execution, prompt user via `ask_user` | +| No value available and current mode permits `ask_user` | Pause execution, prompt user via `ask_user` | +| No value available and current mode cannot use `ask_user` | Return a declared missing-input error or mode-specific fallback | Write each bound input to `bindings/caller/{name}.md`: @@ -881,7 +917,8 @@ At system start, the VM resolves each `requires` entry: | Value provided via CLI arg (`--question "..."`) | Bind immediately | | Value provided via config file | Bind immediately | | Value provided by calling system (nested invocation) | Bind immediately | -| No value available | Prompt user via `ask_user`, bind response | +| No value available and current mode permits `ask_user` | Prompt user via `ask_user`, bind response | +| No value available and current mode cannot use `ask_user` | Return a declared missing-input error or mode-specific fallback | ### Writing Input Bindings @@ -1211,7 +1248,7 @@ function execute(manifest, inputs?): 2. Bind caller inputs: - From CLI args, config, or calling system - For run-typed inputs (run / run[]): validate existence, structure, completion; emit staleness warning if source system changed - - Prompt user (`ask_user`) for any missing required inputs + - Prompt user (`ask_user`) for missing required inputs only when the current mode permits it; otherwise return the declared missing-input error or mode-specific fallback - Write each to the active backend binding store (filesystem: bindings/caller/{name}.md with structured metadata for run types) - Record upstream in the backend event header for any run-typed inputs 3. Initialize backend storage for each service (filesystem: workspace/ and bindings/ directories)