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
19 changes: 13 additions & 6 deletions packages/std/ops/prose-author.prose.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions skills/open-prose/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
6 changes: 6 additions & 0 deletions skills/open-prose/contract-markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
31 changes: 31 additions & 0 deletions skills/open-prose/guidance/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions skills/open-prose/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 41 additions & 4 deletions skills/open-prose/prose.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ codex exec "prose run system.prose.md"
| `prose run <owner>/<repo>` | Reserved for the OpenProse registry (future home at `p.prose.md`) |
| `prose run ...@<version>` | Pin to a SHA or tag; require that version in `<openprose-root>/deps/` |
| `prose run ... --offline` | Require disk-only resolution; error if not in `<openprose-root>/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 <file.prose.md>` | Validate structure, schema, shapes, and contracts |
| `prose preflight <file.prose.md>` | Check dependencies, declared tools, and environment variables |
| `prose test <path>` | Run test(s) and report results |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 (`<openprose-root>/.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`:

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
Loading