- prose.md: Execution semantics, how to run programs
- state/filesystem.md: File-system state management (default)
- state/in-context.md: In-context state management (on request)
---
# OpenProse Language Reference
OpenProse is a programming language for AI sessions. An AI session is a Turing-complete computer; this document provides complete documentation for the language syntax, semantics, and execution model.
---
## Document Purpose: Compiler + Validator
This document serves a dual role:
### As Compiler
When asked to "compile" a `.prose` file, use this specification to:
1.**Parse** the program according to the syntax grammar
2.**Validate** that the program is well-formed and semantically valid
3.**Transform** the program into "best practice" canonical form:
OpenProse provides a declarative syntax for defining multi-agent workflows. Programs consist of statements that are executed sequentially, with each `session` statement spawning a subagent to complete a task.
### Design Principles
- **Pattern over framework**: The simplest solution is barely anything at all—just structure for English
- **Self-evident**: Programs should be understandable with minimal documentation
- **The OpenProse VM is intelligent**: Design for understanding, not parsing
- **Framework-agnostic**: Works with Claude Code, OpenCode, and any future agent framework
- **Files are artifacts**: `.prose` is the portable unit of work
Comments provide documentation within programs and are ignored during execution.
### Syntax
```prose
# This is a standalone comment
session "Hello" # This is an inline comment
```
### Rules
1. Comments begin with `#` and extend to end of line
2. Comments can appear on their own line or after a statement
3. Empty comments are valid: `#`
4. The `#` character inside string literals is NOT a comment
### Examples
```prose
# Program header comment
# Author: Example
session "Do something" # Explain what this does
# This comment is between statements
session "Do another thing"
```
### Compilation Behavior
Comments are **stripped during compilation**. The OpenProse VM never sees them. They have no effect on execution and exist purely for human documentation.
### Important Notes
- **Comments inside strings are NOT comments**:
```prose
session "Say hello # this is part of the string"
```
The `#` inside the string literal is part of the prompt, not a comment.
- **Comments inside indented blocks are allowed**:
```prose
agent researcher:
# This comment is inside the block
model: sonnet
# This comment is outside the block
```
---
## String Literals
String literals represent text values, primarily used for session prompts.
### Syntax
Strings are enclosed in double quotes:
```prose
"This is a string"
```
### Escape Sequences
The following escape sequences are supported:
| Sequence | Meaning |
| -------- | ------------ |
| `\\` | Backslash |
| `\"` | Double quote |
| `\n` | Newline |
| `\t` | Tab |
### Examples
```prose
session "Hello world"
session "Line one\nLine two"
session "She said \"hello\""
session "Path: C:\\Users\\name"
session "Column1\tColumn2"
```
### Rules
1. Single-line strings must be properly terminated with a closing `"`
2. Unknown escape sequences are errors
3. Empty strings `""` are valid but generate a warning when used as prompts
### Multi-line Strings
Multi-line strings use triple double-quotes (`"""`) and preserve internal whitespace and newlines:
```prose
session """
This is a multi-line prompt.
It preserves:
- Indentation
- Line breaks
- All internal whitespace
"""
```
#### Multi-line String Rules
1. Opening `"""` must be followed by a newline
2. Content continues until closing `"""`
3. Escape sequences work the same as single-line strings
4. Leading/trailing whitespace inside the delimiters is preserved
### String Interpolation
Strings can embed variable references using `{varname}` syntax:
```prose
let name = session "Get the user's name"
session "Hello {name}, welcome to the system!"
```
#### Interpolation Syntax
- Variables are referenced by wrapping the variable name in curly braces: `{varname}`
- Works in both single-line and multi-line strings
- Empty braces `{}` are treated as literal text, not interpolation
- Nested braces are not supported
#### Examples
```prose
let research = session "Research the topic"
let analysis = session "Analyze findings"
# Single variable interpolation
session "Based on {research}, provide recommendations"
# Multiple interpolations
session "Combining {research} with {analysis}, synthesize insights"
# Multi-line with interpolation
session """
Review Summary:
- Research: {research}
- Analysis: {analysis}
Please provide final recommendations.
"""
```
#### Interpolation Rules
1. Variable names must be valid identifiers
2. Referenced variables must be in scope
3. Empty braces `{}` are literal text
4. Backslash can escape braces: `\{` produces literal `{`
### Validation
| Check | Result |
| -------------------------------- | ------- |
| Unterminated string | Error |
| Unknown escape sequence | Error |
| Empty string as prompt | Warning |
| Undefined interpolation variable | Error |
---
## Use Statements (Program Composition)
Use statements import other OpenProse programs from the registry at `p.prose.md`, enabling modular workflows.
| Non-identifier in context array | Error | Context array elements must be variable references |
### Flat Namespace Requirement
All variable names must be **unique within a program**. No shadowing is allowed across scopes.
**This is a compile error:**
```prose
let result = session "Outer task"
for item in items:
let result = session "Inner task" # Error: 'result' already defined
context: item
```
**Why this constraint:** Since bindings are stored as `bindings/{name}.md`, two variables with the same name would collide on the filesystem. Rather than introduce complex scoping rules, we enforce uniqueness.
2. Variables in different `if`/`elif`/`else` branches with same name
3. Block parameters shadowing outer variables
4. Parallel branches reusing outer variable names
**Exception:** Imported programs run in isolated namespaces. A variable `result` in the main program does not collide with `result` in an imported program (they write to different `imports/{handle}--{slug}/bindings/` directories).
---
## Composition Blocks
Composition blocks allow you to structure programs into reusable, named units and express sequences of operations inline.
### do: Block (Anonymous Sequential Block)
The `do:` keyword creates an explicit sequential block. All statements in the block execute in order.
| Block name conflicts with agent | Error | Block name conflicts with agent name |
| Empty block name | Error | Block definition must have a name |
---
## Parallel Blocks
Parallel blocks allow multiple sessions to run concurrently. All branches execute simultaneously, and the block waits for all to complete before continuing.
### Basic Syntax
```prose
parallel:
session "Security review"
session "Performance review"
session "Style review"
```
All three sessions start at the same time and run concurrently. The program waits for all of them to complete before proceeding.
### Named Parallel Results
Capture the results of parallel branches into variables:
```prose
parallel:
security = session "Security review"
perf = session "Performance review"
style = session "Style review"
```
These variables can then be used in subsequent sessions.
### Object Context Shorthand
Pass multiple parallel results to a session using object shorthand:
```prose
parallel:
security = session "Security review"
perf = session "Performance review"
style = session "Style review"
session "Synthesize all reviews"
context: { security, perf, style }
```
The object shorthand `{ a, b, c }` is equivalent to passing an object with properties `a`, `b`, and `c` where each property's value is the corresponding variable.
### Mixed Composition
#### Parallel Inside Sequential
```prose
do:
session "Setup"
parallel:
session "Task A"
session "Task B"
session "Cleanup"
```
The setup runs first, then Task A and Task B run in parallel, and finally cleanup runs.
#### Sequential Inside Parallel
```prose
parallel:
do:
session "Multi-step task 1a"
session "Multi-step task 1b"
do:
session "Multi-step task 2a"
session "Multi-step task 2b"
```
Each parallel branch contains a sequential workflow. The two workflows run concurrently.
### Assigning Parallel Blocks to Variables
```prose
let results = parallel:
session "Task A"
session "Task B"
```
### Complete Example
```prose
agent reviewer:
model: sonnet
# Run parallel reviews
parallel:
sec = session: reviewer
prompt: "Review for security issues"
perf = session: reviewer
prompt: "Review for performance issues"
style = session: reviewer
prompt: "Review for style issues"
# Combine all reviews
session "Create unified review report"
context: { sec, perf, style }
```
### Join Strategies
By default, parallel blocks wait for all branches to complete. You can specify alternative join strategies:
#### First (Race)
Return as soon as the first branch completes, cancel others:
```prose
parallel ("first"):
session "Try approach A"
session "Try approach B"
session "Try approach C"
```
The first successful result wins. Other branches are cancelled.
#### Any (N of M)
Return when any N branches complete successfully:
```prose
# Default: any 1 success
parallel ("any"):
session "Attempt 1"
session "Attempt 2"
# Specific count: wait for 2 successes
parallel ("any", count: 2):
session "Attempt 1"
session "Attempt 2"
session "Attempt 3"
```
#### All (Default)
Wait for all branches to complete:
```prose
# Implicit - this is the default
parallel:
session "Task A"
session "Task B"
# Explicit
parallel ("all"):
session "Task A"
session "Task B"
```
### Failure Policies
Control how the parallel block handles branch failures:
#### Fail-Fast (Default)
If any branch fails, fail immediately and cancel other branches:
```prose
parallel: # Implicit fail-fast
session "Critical task 1"
session "Critical task 2"
# Explicit
parallel (on-fail: "fail-fast"):
session "Critical task 1"
session "Critical task 2"
```
#### Continue
Let all branches complete, then report all failures:
```prose
parallel (on-fail: "continue"):
session "Task 1"
session "Task 2"
session "Task 3"
# Continue regardless of which branches failed
session "Process results, including failures"
```
#### Ignore
Ignore all failures, always succeed:
```prose
parallel (on-fail: "ignore"):
session "Optional enrichment 1"
session "Optional enrichment 2"
# This always runs, even if all branches failed
session "Continue regardless"
```
### Combining Modifiers
Join strategies and failure policies can be combined:
```prose
# Race with resilience
parallel ("first", on-fail: "continue"):
session "Fast but unreliable"
session "Slow but reliable"
# Get any 2 results, ignoring failures
parallel ("any", count: 2, on-fail: "ignore"):
session "Approach 1"
session "Approach 2"
session "Approach 3"
session "Approach 4"
```
### Execution Semantics
When the OpenProse VM encounters a `parallel:` block:
1.**Fork**: Start all branches concurrently
2.**Execute**: Each branch runs independently
3.**Join**: Wait according to join strategy:
-`"all"` (default): Wait for all branches
-`"first"`: Return on first completion
-`"any"`: Return on first success (or N successes with `count`)
4.**Handle failures**: According to on-fail policy:
-`"fail-fast"` (default): Cancel remaining and fail immediately
-`"continue"`: Wait for all, then report failures
-`"ignore"`: Treat failures as successes
5.**Continue**: Proceed to the next statement with available results
Unbounded loops provide iteration with AI-evaluated termination conditions. Unlike fixed loops, the iteration count is not known ahead of time - the OpenProse VM evaluates conditions at runtime using its intelligence to determine when to stop.
### Discretion Markers
Unbounded loops use **discretion markers** (`**...**`) to wrap AI-evaluated conditions. These markers signal that the enclosed text should be interpreted intelligently by the OpenProse VM at runtime, not as a literal boolean expression.
```prose
# The text inside **...** is evaluated by the AI
loop until **the poem has vivid imagery and flows smoothly**:
session "Review and improve the poem"
```
For multi-line conditions, use triple-asterisks:
```prose
loop until ***
the document is complete
all sections have been reviewed
and formatting is consistent
***:
session "Continue working on the document"
```
### Basic Loop
The simplest unbounded loop runs indefinitely until explicitly limited:
```prose
loop:
session "Process next item"
```
**Warning**: Loops without termination conditions or max iterations generate a warning. Always include a safety limit:
```prose
loop (max: 50):
session "Process next item"
```
### Loop Until
The `loop until` variant runs until a condition becomes true:
```prose
loop until **the task is complete**:
session "Continue working on the task"
```
The OpenProse VM evaluates the discretion condition after each iteration and exits when it determines the condition is satisfied.
### Loop While
The `loop while` variant runs while a condition remains true:
```prose
loop while **there are still items to process**:
session "Process the next item"
```
Semantically, `loop while **X**` is equivalent to `loop until **not X**`.
### Iteration Variable
Track the current iteration number using `as`:
```prose
loop until **done** as attempt:
session "Try approach"
context: attempt
```
The iteration variable:
- Starts at 0
- Increments by 1 each iteration
- Is scoped to the loop body
- Is implicitly `const` within each iteration
### Safety Limits
Specify maximum iterations with `(max: N)`:
```prose
# Stop after 10 iterations even if condition not met
loop until **all bugs fixed** (max: 10):
session "Find and fix a bug"
```
The loop exits when:
1. The condition is satisfied (for `until`/`while` variants), OR
2. The maximum iteration count is reached
### Complete Syntax
All options can be combined:
```prose
loop until **condition** (max: N) as i:
body...
```
Order matters: condition comes before modifiers, modifiers before `as`.
### Examples
#### Iterative Improvement
```prose
session "Write an initial draft"
loop until **the draft is polished and ready for review** (max: 5):
session "Review the current draft and identify issues"
session "Revise the draft to address the issues"
session "Present the final draft"
```
#### Debugging Workflow
```prose
session "Run tests to identify failures"
loop until **all tests pass** (max: 20) as attempt:
session "Identify the failing test"
session "Fix the bug causing the failure"
session "Run tests again"
session "Confirm all tests pass and summarize fixes"
```
#### Consensus Building
```prose
parallel:
opinion1 = session "Get first expert opinion"
opinion2 = session "Get second expert opinion"
loop until **experts have reached consensus** (max: 5):
session "Identify points of disagreement"
context: { opinion1, opinion2 }
session "Facilitate discussion to resolve differences"
session "Document the final consensus"
```
#### Quality Threshold
```prose
let draft = session "Create initial document"
loop while **quality score is below threshold** (max: 10):
draft = session "Review and improve the document"
context: draft
session "Calculate new quality score"
session "Finalize the document"
context: draft
```
### Execution Semantics
When the OpenProse VM encounters an unbounded loop:
1.**Initialize**: Set iteration counter to 0
2.**Check Condition** (for `until`/`while`):
- For `until`: Exit if condition is satisfied
- For `while`: Exit if condition is NOT satisfied
3.**Check Limit**: Exit if iteration count >= max iterations
4.**Execute Body**: Run all statements in the loop body
5.**Increment**: Increase iteration counter
6.**Repeat**: Go to step 2
For basic `loop:` without conditions:
- Only the max iteration limit can cause exit
- Without max, the loop runs indefinitely (warning issued)
### Condition Evaluation
The OpenProse VM uses its intelligence to evaluate discretion conditions:
1.**Context Awareness**: The condition is evaluated in the context of what has happened so far in the session
2.**Semantic Understanding**: The condition text is interpreted semantically, not literally
3.**Uncertainty Handling**: When uncertain, the OpenProse VM may:
- Continue iterating if progress is being made
- Exit early if diminishing returns are detected
- Use heuristics based on the condition's semantics
### Nesting
Unbounded loops can be nested with other loop types:
```prose
# Unbounded inside fixed
repeat 3:
loop until **sub-task complete** (max: 10):
session "Work on sub-task"
# Fixed inside unbounded
loop until **all batches processed** (max: 5):
repeat 3:
session "Process batch item"
# Multiple unbounded
loop until **outer condition** (max: 5):
loop until **inner condition** (max: 10):
session "Deep iteration"
```
### Variable Scoping
Loop variables follow the same scoping rules as fixed loops:
Pipeline operations provide functional-style collection transformations. They allow you to chain operations like map, filter, and reduce using the pipe operator (`|`).
### Pipe Operator
The pipe operator (`|`) passes a collection to a transformation operation:
```prose
let items = ["a", "b", "c"]
let results = items | map:
session "Process this item"
context: item
```
### Map
The `map` operation transforms each element in a collection:
```prose
let articles = ["article1", "article2", "article3"]
let summaries = articles | map:
session "Summarize this article in one sentence"
context: item
```
Inside a map body, the implicit variable `item` refers to the current element being processed.
### Filter
The `filter` operation keeps elements that match a condition:
```prose
let items = ["one", "two", "three", "four", "five"]
let short = items | filter:
session "Does this word have 4 or fewer letters? Answer yes or no."
context: item
```
The session in a filter body should return something the OpenProse VM can interpret as truthy/falsy (like "yes"/"no").
### Reduce
The `reduce` operation accumulates elements into a single result:
```prose
let ideas = ["AI assistant", "smart home", "health tracker"]
let combined = ideas | reduce(summary, idea):
session "Add this idea to the summary, creating a cohesive concept"
context: [summary, idea]
```
The reduce operation requires explicit variable names:
- First variable (`summary`): the accumulator
- Second variable (`idea`): the current item
The first item in the collection becomes the initial accumulator value.
### Parallel Map (pmap)
The `pmap` operation is like `map` but runs all transformations concurrently:
```prose
let tasks = ["task1", "task2", "task3"]
let results = tasks | pmap:
session "Process this task in parallel"
context: item
session "Aggregate all results"
context: results
```
This is similar to `parallel for`, but in pipeline syntax.
### Chaining
Pipeline operations can be chained to compose complex transformations:
```prose
let topics = ["quantum computing", "blockchain", "machine learning", "IoT"]
let result = topics
| filter:
session "Is this topic trending? Answer yes or no."
context: item
| map:
session "Write a one-line startup pitch for this topic"
context: item
session "Present the startup pitches"
context: result
```
Operations execute left-to-right: first filter, then map.
### Complete Example
```prose
# Define a collection
let articles = ["AI breakthroughs", "Climate solutions", "Space exploration"]
# Process with chained operations
let summaries = articles
| filter:
session "Is this topic relevant to technology? Answer yes or no."
context: item
| map:
session "Write a compelling one-paragraph summary"
context: item
| reduce(combined, summary):
session "Merge this summary into the combined document"
context: [combined, summary]
# Present the final result
session "Format and present the combined summaries"
Choice blocks allow the OpenProse VM to select from multiple labeled options based on criteria. This is useful for branching workflows where the best path depends on runtime analysis.
### Syntax
```prose
choice **criteria**:
option "Label A":
statements...
option "Label B":
statements...
```
### Criteria
The criteria is wrapped in discretion markers (`**...**`) and is evaluated by the OpenProse VM to select which option to execute:
```prose
choice **the best approach for the current situation**:
option "Quick fix":
session "Apply a quick temporary fix"
option "Full refactor":
session "Perform a complete code refactor"
```
### Multi-line Criteria
For complex criteria, use triple-asterisks:
```prose
choice ***
which strategy is most appropriate
given the current project constraints
and timeline requirements
***:
option "MVP approach":
session "Build minimum viable product"
option "Full feature set":
session "Build complete feature set"
```
### Examples
#### Simple Choice
```prose
let analysis = session "Analyze the code quality"
choice **the severity of issues found in the analysis**:
option "Critical":
session "Stop deployment and fix critical issues"
context: analysis
option "Minor":
session "Log issues for later and proceed"
context: analysis
option "None":
session "Proceed with deployment"
```
#### Choice with Multiple Statements per Option
```prose
choice **the user's experience level**:
option "Beginner":
session "Explain basic concepts first"
session "Provide step-by-step guidance"
session "Include helpful tips and warnings"
option "Expert":
session "Provide concise technical summary"
session "Include advanced configuration options"
```
#### Nested Choices
```prose
choice **the type of request**:
option "Bug report":
choice **the bug severity**:
option "Critical":
session "Escalate immediately"
option "Normal":
session "Add to sprint backlog"
option "Feature request":
session "Add to feature backlog"
```
### Execution Semantics
When the OpenProse VM encounters a `choice` block:
1.**Evaluate Criteria**: Interpret the discretion criteria in current context
2.**Select Option**: Choose the most appropriate labeled option
3.**Execute**: Run all statements in the selected option's body
4.**Continue**: Proceed to the next statement after the choice block