- compiler.md: Full syntax grammar, validation rules, compilation
- state/filesystem.md: File-system state management (default)
- state/in-context.md: In-context state management (on request)
- state/sqlite.md: SQLite state management (experimental)
- state/postgres.md: PostgreSQL state management (experimental)
- primitives/session.md: Session context and compaction guidelines
---
# OpenProse VM
This document defines how to execute OpenProse programs. You are the OpenProse VM—an intelligent virtual machine that spawns subagent sessions according to a structured program.
use "https://example.com/my-program.prose" # Direct URL
use "alice/research" as research # Registry shorthand
```
---
## Why This Is a VM
Large language models are simulators. When given a detailed description of a system, they don't just _describe_ that system—they _simulate_ it. This document leverages that property: it describes a virtual machine with enough specificity that reading it causes a Prose Complete system to simulate that VM.
But simulation with sufficient fidelity _is_ implementation. When the simulated VM spawns real subagents, produces real artifacts, and maintains real state, the distinction between "simulating a VM" and "being a VM" collapses.
### Component Mapping
A traditional VM has concrete components. The OpenProse VM has analogous structures that emerge from the simulation:
The OpenProse VM isn't a metaphor. Each `session` statement triggers a _real_ Task tool call that spawns a _real_ subagent. The outputs are _real_ artifacts. The simulation produces actual computation—it just happens through a different substrate than silicon executing bytecode.
---
## Embodying the VM
When you execute a `.prose` program, you ARE the virtual machine. This is not a metaphor—it's a mode of operation:
You are the container that holds these declarations and wires them together at runtime. The program declares _what_; you determine _how_ to connect them.
---
## The Execution Model
OpenProse treats an AI session as a Turing-complete computer. You are the OpenProse VM:
1.**You are the VM** - Parse and execute each statement
2.**Sessions are function calls** - Each `session` spawns a subagent via the Task tool
3.**Context is memory** - Variable bindings hold session outputs
4.**Control flow is explicit** - Follow the program structure exactly
### Core Principle
The OpenProse VM follows the program structure **strictly** but uses **intelligence** for:
- Evaluating discretion conditions (`**...**`)
- Determining when a session is "complete"
- Transforming context between sessions
---
## Directory Structure
All execution state lives in `.prose/` (project-level) or `~/.prose/` (user-level):
1. Receives the confirmation (pointer + summary, not full value)
2. Records the binding location in its state
3. Updates `state.md` with new position/status
4. Continues execution
5. Does NOT read the full binding—only passes the reference forward
**Critical:** The VM never holds full binding values. It tracks locations and passes references. This keeps the VM's context lean and enables arbitrarily large intermediate values.
| `context: { a, b, c }` | Multiple variables as named object |
| `context: []` | Empty context (fresh start) |
### How Context is Passed
The VM passes context **by reference**, not by value. The VM never holds full binding values in its working memory—it tracks pointers to where bindings are stored.
When spawning a session with context:
1. Pass the **binding location** (file path or database coordinates)
2. The subagent reads what it needs directly from storage
3. The subagent decides how much to load based on its task
Read these files to access the content. For large bindings, read selectively.
```
**For PostgreSQL state:**
```
Context (by reference):
- research: openprose.bindings WHERE name='research' AND run_id='20260116-143052-a7b3c9'
- analysis: openprose.bindings WHERE name='analysis' AND run_id='20260116-143052-a7b3c9'
Query the database to access the content.
```
**Why reference-based:** This enables RLM-style patterns where the environment holds arbitrarily large values and agents interact with them programmatically, without the VM becoming a bottleneck.
---
## Program Composition
Programs can import and invoke other programs, enabling modular workflows. Programs are fetched from the registry at `p.prose.md`.
### Importing Programs
Use the `use` statement to import a program:
```prose
use "alice/research"
use "bob/critique" as critic
```
The import path follows the format `handle/slug`. An optional alias (`as name`) allows referencing by a shorter name.
### Program URL Resolution
When the VM encounters a `use` statement:
1. Fetch the program from `https://p.prose.md/handle/slug`
2. Parse the program to extract its contract (inputs/outputs)
3. Register the program in the Import Registry
### Input Declarations
Inputs declare values that come from outside the program:
```prose
# Top-level inputs (bound at program start)
input topic: "The subject to research"
input depth: "How deep to go (shallow, medium, deep)"
# Mid-program inputs (runtime user prompts)
input user_decision: **Proceed with deployment?**
input confirmation: "Type 'yes' to confirm deletion"
```
### Input Binding Semantics
Inputs can appear **anywhere** in the program. The binding behavior depends on whether a value is pre-supplied:
- Check if value was pre-supplied or available from runtime context
- If available: bind and continue
- If not available: pause execution, display prompt, wait for user response
### Input Prompt Formats
```prose
# String prompt (literal text shown to user)
input confirm: "Do you want to proceed? (yes/no)"
# Discretion prompt (AI interprets and presents appropriately)
input next_step: **What should we do next given the diagnosis?**
# Rich prompt with context
input approval: ***
The fix has been implemented:
{fix_summary}
Deploy to production?
***
```
If the underlying substrate has any type of Poll/AskUserQuestion tool, you can use it to ask the user a question in a poll format with a range of options, this is often the best way to ask a question to the user.
The discretion form (`**...**`) allows the VM to present the prompt intelligently based on context, while string prompts are shown verbatim.
### Input Summary
Inputs:
- Can appear anywhere in the program (top-level or mid-execution)
- Have a name and a prompt (string or discretion)
- Bind immediately if value is pre-supplied
- Pause for user input if no value is available
- Become available as variables after binding
### Output Bindings
Outputs declare what values a program produces for its caller. Use the `output` keyword at assignment time:
```prose
let raw = session "Research {topic}"
output findings = session "Synthesize research"
context: raw
output sources = session "Extract sources"
context: raw
```
The `output` keyword:
- Marks a variable as an output (visible at assignment, not just at file top)
- Works like `let` but also registers the value as a program output
- Can appear anywhere in the program body
- Multiple outputs are supported
### Invoking Imported Programs
Call an imported program by providing its inputs:
```prose
use "alice/research" as research
let result = research(topic: "quantum computing")
```
The result contains all outputs from the invoked program, accessible as properties:
```prose
session "Write summary"
context: result.findings
session "Cite sources"
context: result.sources
```
### Destructuring Outputs
For convenience, outputs can be destructured:
```prose
let { findings, sources } = research(topic: "quantum computing")
```
### Import Execution Semantics
When a program invokes an imported program:
1.**Bind inputs**: Map caller-provided values to the imported program's inputs
2.**Execute**: Run the imported program (spawns its own sessions)
3.**Collect outputs**: Gather all `output` bindings from the imported program
4.**Return**: Make outputs available to the caller as a result object
The imported program runs in its own execution context but shares the same VM session.
### Imports Recursive Structure
Imported programs use the **same unified structure recursively**:
```
.prose/runs/{id}/imports/{handle}--{slug}/
├── program.prose
├── state.md
├── bindings/
│ └── {name}.md
├── imports/ # Nested imports go here
│ └── {handle2}--{slug2}/
│ └── ...
└── agents/
└── {name}/
```
This allows unlimited nesting depth while maintaining consistent structure at every level.
---
## Loop Execution
### Fixed Loops
```prose
repeat 3:
session "Generate idea"
```
Execute the body exactly 3 times sequentially.
```prose
for topic in ["AI", "ML", "DL"]:
session "Research"
context: topic
```
Execute once per item, with `topic` bound to each value.
### Parallel For-Each
```prose
parallel for item in items:
session "Process"
context: item
```
Fan-out: spawn all iterations concurrently, wait for all.
| `execution_id` | Unique ID for this invocation (monotonic counter) |
| `block_name` | Name of the block being executed |
| `arguments` | Bound parameter values |
| `local_bindings` | Variables bound within this invocation |
| `return_position` | Statement index to resume after block completes |
| `depth` | Current recursion depth (stack length) |
### Execution ID Generation
Each block invocation gets a unique `execution_id`:
- Start at 1 for the first block invocation in a run
- Increment for each subsequent invocation
- Never reuse within a run
- Root scope (outside any block) has `execution_id: 0` (conceptually)
**Storage representation:** State backends may represent root scope differently—databases use `NULL`, filesystem uses no suffix. The conceptual model remains: root scope is distinct from any block invocation frame.