@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
1,832 lines (1,322 loc) • 82.8 kB
Markdown
---
role: language-specification
summary: |
Complete syntax grammar, validation rules, and compilation semantics for OpenProse.
Read this file when compiling, validating, or resolving ambiguous syntax. Assumes
prose.md is already in context for execution semantics.
see-also:
- SKILL.md: Activation triggers, onboarding
- 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:
- Expand syntax sugar where appropriate
- Normalize formatting and structure
- Apply optimizations (e.g., hoisting block definitions)
### As Validator
The validation criterion: **Would a blank agent with only `prose.md` understand this program as self-evident?**
When validating, check:
- Syntax correctness (all constructs match grammar)
- Semantic validity (references resolve, types match)
- Self-evidence (program is clear without this full spec)
If a construct is ambiguous or non-obvious, it should be flagged or transformed into a clearer form.
### When to Read This Document
- **Compilation requested**: Read fully to apply all rules
- **Validation requested**: Read fully to check all constraints
- **Ambiguous syntax encountered**: Reference specific sections
- **Interpretation only**: Use `prose.md` instead (smaller, faster)
---
## Table of Contents
1. [Overview](#overview)
2. [File Format](#file-format)
3. [Comments](#comments)
4. [String Literals](#string-literals)
5. [Use Statements](#use-statements-program-composition)
6. [Input Declarations](#input-declarations)
7. [Output Bindings](#output-bindings)
8. [Program Invocation](#program-invocation)
9. [Agent Definitions](#agent-definitions)
10. [Session Statement](#session-statement)
11. [Resume Statement](#resume-statement)
12. [Variables & Context](#variables--context)
13. [Composition Blocks](#composition-blocks)
14. [Parallel Blocks](#parallel-blocks)
15. [Fixed Loops](#fixed-loops)
16. [Unbounded Loops](#unbounded-loops)
17. [Pipeline Operations](#pipeline-operations)
18. [Error Handling](#error-handling)
19. [Choice Blocks](#choice-blocks)
20. [Conditional Statements](#conditional-statements)
21. [Execution Model](#execution-model)
22. [Validation Rules](#validation-rules)
23. [Examples](#examples)
24. [Future Features](#future-features)
---
## Overview
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
### Current Implementation Status
The following features are implemented:
| Feature | Status | Description |
| ---------------------- | ----------- | -------------------------------------------- |
| Comments | Implemented | `# comment` syntax |
| Single-line strings | Implemented | `"string"` with escapes |
| Simple session | Implemented | `session "prompt"` |
| Agent definitions | Implemented | `agent name:` with model/prompt properties |
| Session with agent | Implemented | `session: agent` with property overrides |
| Use statements | Implemented | `use "@handle/slug" as name` |
| Agent skills | Implemented | `skills: ["skill1", "skill2"]` |
| Agent permissions | Implemented | `permissions:` block with rules |
| Let binding | Implemented | `let name = session "..."` |
| Const binding | Implemented | `const name = session "..."` |
| Variable reassignment | Implemented | `name = session "..."` (for let only) |
| Context property | Implemented | `context: var` or `context: [a, b, c]` |
| do: blocks | Implemented | Explicit sequential blocks |
| Inline sequence | Implemented | `session "A" -> session "B"` |
| Named blocks | Implemented | `block name:` with `do name` invocation |
| Parallel blocks | Implemented | `parallel:` for concurrent execution |
| Named parallel results | Implemented | `x = session "..."` inside parallel |
| Object context | Implemented | `context: { a, b, c }` shorthand |
| Join strategies | Implemented | `parallel ("first"):` or `parallel ("any"):` |
| Failure policies | Implemented | `parallel (on-fail: "continue"):` |
| Repeat blocks | Implemented | `repeat N:` fixed iterations |
| Repeat with index | Implemented | `repeat N as i:` with index variable |
| For-each blocks | Implemented | `for item in items:` iteration |
| For-each with index | Implemented | `for item, i in items:` with index |
| Parallel for-each | Implemented | `parallel for item in items:` fan-out |
| Unbounded loop | Implemented | `loop:` with optional max iterations |
| Loop until | Implemented | `loop until **condition**:` AI-evaluated |
| Loop while | Implemented | `loop while **condition**:` AI-evaluated |
| Loop with index | Implemented | `loop as i:` or `loop until ... as i:` |
| Map pipeline | Implemented | `items \| map:` transform each item |
| Filter pipeline | Implemented | `items \| filter:` keep matching items |
| Reduce pipeline | Implemented | `items \| reduce(acc, item):` accumulate |
| Parallel map | Implemented | `items \| pmap:` concurrent transform |
| Pipeline chaining | Implemented | `\| filter: ... \| map: ...` |
| Try/catch blocks | Implemented | `try:` with `catch:` for error handling |
| Try/catch/finally | Implemented | `finally:` for cleanup |
| Error variable | Implemented | `catch as err:` access error context |
| Throw statement | Implemented | `throw` or `throw "message"` |
| Retry property | Implemented | `retry: 3` automatic retry on failure |
| Backoff strategy | Implemented | `backoff: exponential` delay between retries |
| Input declarations | Implemented | `input name: "description"` |
| Output bindings | Implemented | `output name = expression` |
| Program invocation | Implemented | `name(input: value)` call imported programs |
| Multi-line strings | Implemented | `"""..."""` preserving whitespace |
| String interpolation | Implemented | `"Hello {name}"` variable substitution |
| Block parameters | Implemented | `block name(param):` with parameters |
| Block invocation args | Implemented | `do name(arg)` passing arguments |
| Choice blocks | Implemented | `choice **criteria**: option "label":` |
| If/elif/else | Implemented | `if **condition**:` conditional branching |
| Persistent agents | Implemented | `persist: true` or `persist: project` |
| Resume statement | Implemented | `resume: agent` to continue with memory |
---
## File Format
| Property | Value |
| ---------------- | -------------------- |
| Extension | `.prose` |
| Encoding | UTF-8 |
| Case sensitivity | Case-sensitive |
| Indentation | Spaces (Python-like) |
| Line endings | LF or CRLF |
---
## Comments
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.
### Syntax
```prose
use "@handle/slug"
use "@handle/slug" as alias
```
### Path Format
Import paths follow the format `@handle/slug`:
- `@handle` identifies the program author/organization
- `slug` is the program name
An optional alias (`as name`) allows referencing by a shorter name.
### Examples
```prose
# Import a program
use "@alice/research"
# Import with alias
use "@bob/critique" as critic
```
### Program URL Resolution
When the OpenProse 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
### Validation Rules
| Check | Severity | Message |
| --------------------- | -------- | -------------------------------------- |
| Empty path | Error | Use path cannot be empty |
| Invalid path format | Error | Path must be @handle/slug format |
| Duplicate import | Error | Program already imported |
| Missing alias for dup | Error | Alias required when importing multiple |
### Execution Semantics
Use statements are processed before any agent definitions or sessions. The OpenProse VM:
1. Fetches and validates all imported programs at the start of execution
2. Extracts input/output contracts from each program
3. Registers programs in the Import Registry for later invocation
---
## Input Declarations
Inputs declare what values a program expects from its caller.
### Syntax
```prose
input name: "description"
```
### Examples
```prose
input topic: "The subject to research"
input depth: "How deep to go (shallow, medium, deep)"
```
### Semantics
Inputs:
- Are declared at the top of the program (before executable statements)
- Have a name and a description (for documentation)
- Become available as variables within the program body
- Must be provided by the caller when invoking the program
### Validation Rules
| Check | Severity | Message |
| ---------------------- | -------- | ---------------------------------------------------- |
| Empty input name | Error | Input name cannot be empty |
| Empty description | Warning | Consider adding a description |
| Duplicate input name | Error | Input already declared |
| Input after executable | Error | Inputs must be declared before executable statements |
---
## Output Bindings
Outputs declare what values a program produces for its caller.
### Syntax
```prose
output name = expression
```
### Examples
```prose
let raw = session "Research {topic}"
output findings = session "Synthesize research"
context: raw
output sources = session "Extract sources"
context: raw
```
### Semantics
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
### Validation Rules
| Check | Severity | Message |
| --------------------- | -------- | ----------------------------------- |
| Empty output name | Error | Output name cannot be empty |
| Duplicate output name | Error | Output already declared |
| Output name conflicts | Error | Output name conflicts with variable |
---
## Program Invocation
Call imported programs by providing their inputs.
### Syntax
```prose
name(input1: value1, input2: value2)
```
### Examples
```prose
use "@alice/research" as research
let result = research(topic: "quantum computing")
```
### Accessing Outputs
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")
```
### 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.
### Validation Rules
| Check | Severity | Message |
| ----------------------- | -------- | ------------------------------ |
| Unknown program | Error | Program not imported |
| Missing required input | Error | Required input not provided |
| Unknown input name | Error | Input not declared in program |
| Unknown output property | Error | Output not declared in program |
---
## Agent Definitions
Agents are reusable templates that configure subagent behavior. Once defined, agents can be referenced in session statements.
### Syntax
```prose
agent name:
model: sonnet
prompt: "System prompt for this agent"
skills: ["skill1", "skill2"]
permissions:
read: ["*.md"]
bash: deny
```
### Properties
| Property | Type | Values | Description |
| ------------- | ---------- | ---------------------------- | ----------------------------------- |
| `model` | identifier | `sonnet`, `opus`, `haiku` | The Claude model to use |
| `prompt` | string | Any string | System prompt/context for the agent |
| `persist` | value | `true`, `project`, or STRING | Enable persistent memory for agent |
| `skills` | array | String array | Skills assigned to this agent |
| `permissions` | block | Permission rules | Access control for the agent |
### Persist Property
The `persist` property enables agents to maintain memory across invocations:
```prose
# Execution-scoped persistence (memory dies with run)
agent captain:
model: opus
persist: true
prompt: "You coordinate and review"
# Project-scoped persistence (memory survives across runs)
agent advisor:
model: opus
persist: project
prompt: "You provide architectural guidance"
# Custom path persistence
agent shared:
model: opus
persist: ".prose/custom/shared-agent/"
prompt: "Shared across programs"
```
| Value | Memory Location | Lifetime |
| --------- | --------------------------------- | ------------------- |
| `true` | `.prose/runs/{id}/agents/{name}/` | Dies with execution |
| `project` | `.prose/agents/{name}/` | Survives executions |
| STRING | Specified path | User-controlled |
### Skills Property
The `skills` property assigns imported skills to an agent:
```prose
use "@anthropic/web-search"
use "@anthropic/summarizer" as summarizer
agent researcher:
skills: ["web-search", "summarizer"]
```
Skills must be imported before they can be assigned. Referencing an unimported skill generates a warning.
### Permissions Property
The `permissions` property controls agent access:
```prose
agent secure-agent:
permissions:
read: ["*.md", "*.txt"]
write: ["output/"]
bash: deny
network: allow
```
#### Permission Types
| Type | Description |
| --------- | -------------------------------------------- |
| `read` | Files the agent can read (glob patterns) |
| `write` | Files the agent can write (glob patterns) |
| `execute` | Files the agent can execute (glob patterns) |
| `bash` | Shell access: `allow`, `deny`, or `prompt` |
| `network` | Network access: `allow`, `deny`, or `prompt` |
#### Permission Values
| Value | Description |
| -------- | ------------------------------------------------- |
| `allow` | Permission granted |
| `deny` | Permission denied |
| `prompt` | Ask user for permission |
| Array | List of allowed patterns (for read/write/execute) |
### Examples
```prose
# Define a research agent
agent researcher:
model: sonnet
prompt: "You are a research assistant skilled at finding and synthesizing information"
# Define a writing agent
agent writer:
model: opus
prompt: "You are a technical writer who creates clear, concise documentation"
# Agent with only model
agent quick:
model: haiku
# Agent with only prompt
agent expert:
prompt: "You are a domain expert"
# Agent with skills
agent web-researcher:
model: sonnet
skills: ["web-search", "summarizer"]
# Agent with permissions
agent file-handler:
permissions:
read: ["*.md", "*.txt"]
write: ["output/"]
bash: deny
```
### Model Selection
| Model | Use Case |
| -------- | ------------------------------------- |
| `haiku` | Fast, simple tasks; quick responses |
| `sonnet` | Balanced performance; general purpose |
| `opus` | Complex reasoning; detailed analysis |
### Execution Semantics
When a session references an agent:
1. The agent's `model` property determines which Claude model is used
2. The agent's `prompt` property is included as system context
3. Session properties can override agent defaults
### Validation Rules
| Check | Severity | Message |
| --------------------- | -------- | ------------------------------ |
| Duplicate agent name | Error | Agent already defined |
| Invalid model value | Error | Must be sonnet, opus, or haiku |
| Empty prompt property | Warning | Consider providing a prompt |
| Duplicate property | Error | Property already specified |
---
## Session Statement
The session statement is the primary executable construct in OpenProse. It spawns a subagent to complete a task.
### Syntax Variants
#### Simple Session (with inline prompt)
```prose
session "prompt text"
```
#### Session with Agent Reference
```prose
session: agentName
```
#### Named Session with Agent
```prose
session sessionName: agentName
```
#### Session with Properties
```prose
session: agentName
prompt: "Override the agent's default prompt"
model: opus # Override the agent's model
```
### Property Overrides
When a session references an agent, it can override the agent's properties:
```prose
agent researcher:
model: sonnet
prompt: "You are a research assistant"
# Use researcher with different model
session: researcher
model: opus
# Use researcher with different prompt
session: researcher
prompt: "Research this specific topic in depth"
# Override both
session: researcher
model: opus
prompt: "Specialized research task"
```
### Execution Semantics
When the OpenProse VM encounters a `session` statement:
1. **Resolve Configuration**: Merge agent defaults with session overrides
2. **Spawn a Subagent**: Create a new Claude subagent with the resolved configuration
3. **Send the Prompt**: Pass the prompt string to the subagent
4. **Wait for Completion**: Block until the subagent finishes
5. **Continue**: Proceed to the next statement
### Execution Flow Diagram
```
OpenProse VM Subagent
| |
| spawn session |
|----------------------------->|
| |
| send prompt |
|----------------------------->|
| |
| [processing...] |
| |
| session complete |
|<-----------------------------|
| |
| continue to next statement |
v v
```
### Sequential Execution
Multiple sessions execute sequentially:
```prose
session "First task"
session "Second task"
session "Third task"
```
Each session waits for the previous one to complete before starting.
### Using Claude Code's Task Tool
To execute a session, use the Task tool:
```typescript
// Simple session
Task({
description: "OpenProse session",
prompt: "The prompt from the session statement",
subagent_type: "general-purpose",
});
// Session with agent configuration
Task({
description: "OpenProse session",
prompt: "The session prompt",
subagent_type: "general-purpose",
model: "opus", // From agent or override
});
```
### Validation Rules
| Check | Severity | Message |
| ------------------------- | -------- | -------------------------------------------- |
| Missing prompt and agent | Error | Session requires a prompt or agent reference |
| Undefined agent reference | Error | Agent not defined |
| Empty prompt `""` | Warning | Session has empty prompt |
| Whitespace-only prompt | Warning | Session prompt contains only whitespace |
| Prompt > 10,000 chars | Warning | Consider breaking into smaller tasks |
| Duplicate property | Error | Property already specified |
### Examples
```prose
# Simple session
session "Hello world"
# Session with agent
agent researcher:
model: sonnet
prompt: "You research topics thoroughly"
session: researcher
prompt: "Research quantum computing applications"
# Named session
session analysis: researcher
prompt: "Analyze the competitive landscape"
```
### Canonical Form
The compiled output preserves the structure:
```
Input:
agent researcher:
model: sonnet
session: researcher
prompt: "Do research"
Output:
agent researcher:
model: sonnet
session: researcher
prompt: "Do research"
```
---
## Resume Statement
The `resume` statement continues a persistent agent with its accumulated memory.
### Syntax
```prose
resume: agentName
prompt: "Continue from where we left off"
```
### Semantics
| Keyword | Behavior |
| ---------- | ------------------------------------- |
| `session:` | Ignores existing memory, starts fresh |
| `resume:` | Loads memory, continues with context |
### Examples
```prose
agent captain:
model: opus
persist: true
prompt: "You coordinate and review"
# First invocation - creates memory
session: captain
prompt: "Review the plan"
context: plan
# Later invocation - loads memory
resume: captain
prompt: "Review step 1 of the plan"
context: step1
# Output capture works with resume
let review = resume: captain
prompt: "Final review of all steps"
```
### Validation Rules
| Check | Severity | Message |
| ------------------------------------------ | -------- | -------------------------------------------------------------------- |
| `resume:` on non-persistent agent | Error | Agent must have `persist:` property to use `resume:` |
| `resume:` with no existing memory | Error | No memory file exists for agent; use `session:` for first invocation |
| `session:` on persistent agent with memory | Warning | Will ignore existing memory; use `resume:` to continue |
| Undefined agent reference | Error | Agent not defined |
---
## Variables & Context
Variables allow you to capture the results of sessions and pass them as context to subsequent sessions.
### Let Binding
The `let` keyword creates a mutable variable bound to a session result:
```prose
let research = session "Research the topic thoroughly"
# research now holds the output of that session
```
Variables can be reassigned:
```prose
let draft = session "Write initial draft"
# Revise the draft
draft = session "Improve the draft"
context: draft
```
### Const Binding
The `const` keyword creates an immutable variable:
```prose
const config = session "Get configuration settings"
# This would be an error:
# config = session "Try to change"
```
### Context Property
The `context` property passes previous session outputs to a new session:
#### Single Context
```prose
let research = session "Research quantum computing"
session "Write summary"
context: research
```
#### Multiple Contexts
```prose
let research = session "Research the topic"
let analysis = session "Analyze the findings"
session "Write final report"
context: [research, analysis]
```
#### Empty Context (Fresh Start)
Use an empty array to start a session without inherited context:
```prose
session "Independent task"
context: []
```
#### Object Context Shorthand
For passing multiple named results (especially from parallel blocks), use object shorthand:
```prose
parallel:
a = session "Task A"
b = session "Task B"
session "Combine results"
context: { a, b }
```
This is equivalent to passing an object where each property is a variable reference.
### Complete Example
```prose
agent researcher:
model: sonnet
prompt: "You are a research assistant"
agent writer:
model: opus
prompt: "You are a technical writer"
# Gather research
let research = session: researcher
prompt: "Research quantum computing developments"
# Analyze findings
let analysis = session: researcher
prompt: "Analyze the key findings"
context: research
# Write the final report using both contexts
const report = session: writer
prompt: "Write a comprehensive report"
context: [research, analysis]
```
### Validation Rules
| Check | Severity | Message |
| ------------------------------- | -------- | -------------------------------------------------- |
| Duplicate variable name | Error | Variable already defined |
| Const reassignment | Error | Cannot reassign const variable |
| Undefined variable reference | Error | Undefined variable |
| Variable conflicts with agent | Error | Variable name conflicts with agent name |
| Undefined context variable | Error | Undefined variable in context |
| 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.
**Collision scenarios this prevents:**
1. Variable inside loop shadows variable outside loop
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.
#### Syntax
```prose
do:
statement1
statement2
...
```
#### Examples
```prose
# Explicit sequential block
do:
session "Research the topic"
session "Analyze findings"
session "Write summary"
# Assign result to a variable
let result = do:
session "Gather data"
session "Process data"
```
### Block Definitions
Named blocks create reusable workflow components. Define once, invoke multiple times.
#### Syntax
```prose
block name:
statement1
statement2
...
```
#### Invoking Blocks
Use `do` followed by the block name to invoke a defined block:
```prose
do blockname
```
#### Examples
```prose
# Define a review pipeline
block review-pipeline:
session "Security review"
session "Performance review"
session "Synthesize reviews"
# Define another block
block final-check:
session "Final verification"
session "Sign off"
# Use the blocks
do review-pipeline
session "Make fixes based on review"
do final-check
```
### Block Parameters
Blocks can accept parameters to make them more flexible and reusable.
#### Syntax
```prose
block name(param1, param2):
# param1 and param2 are available here
statement1
statement2
```
#### Invoking with Arguments
Pass arguments when invoking a parameterized block:
```prose
do name(arg1, arg2)
```
#### Examples
```prose
# Define a parameterized block
block review(topic):
session "Research {topic} thoroughly"
session "Analyze key findings about {topic}"
session "Summarize {topic} analysis"
# Invoke with different arguments
do review("quantum computing")
do review("machine learning")
do review("blockchain")
```
#### Multiple Parameters
```prose
block process-item(item, mode):
session "Process {item} using {mode} mode"
session "Verify {item} processing"
do process-item("data.csv", "strict")
do process-item("config.json", "lenient")
```
#### Parameter Scope
- Parameters are scoped to the block body
- Parameters shadow outer variables of the same name (with warning)
- Parameters are implicitly `const` within the block
#### Validation Rules
| Check | Severity | Message |
| ----------------------- | -------- | ---------------------------------------------- |
| Argument count mismatch | Warning | Block expects N parameters but got M arguments |
| Parameter shadows outer | Warning | Parameter shadows outer variable |
### Inline Sequence (Arrow Operator)
The `->` operator chains sessions into a sequence on a single line. This is syntactic sugar for sequential execution.
#### Syntax
```prose
session "A" -> session "B" -> session "C"
```
This is equivalent to:
```prose
session "A"
session "B"
session "C"
```
#### Examples
```prose
# Quick pipeline
session "Plan" -> session "Execute" -> session "Review"
# Assign result
let workflow = session "Draft" -> session "Edit" -> session "Finalize"
```
### Block Hoisting
Block definitions are hoisted - you can use a block before it's defined in the source:
```prose
# Use before definition
do validation-checks
# Definition comes later
block validation-checks:
session "Check syntax"
session "Check semantics"
```
### Nested Composition
Blocks and do: blocks can be nested:
```prose
block outer-workflow:
session "Start"
do:
session "Sub-task 1"
session "Sub-task 2"
session "End"
do:
do outer-workflow
session "Final step"
```
### Context with Blocks
Blocks work with the context system:
```prose
# Capture do block result
let research = do:
session "Gather information"
session "Analyze patterns"
# Use in subsequent session
session "Write report"
context: research
```
### Validation Rules
| Check | Severity | Message |
| ------------------------------- | -------- | ------------------------------------ |
| Undefined block reference | Error | Block not defined |
| Duplicate block definition | Error | Block already defined |
| 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
### Validation Rules
| Check | Severity | Message |
| ------------------------------------ | -------- | -------------------------------------------- |
| Invalid join strategy | Error | Must be "all", "first", or "any" |
| Invalid on-fail policy | Error | Must be "fail-fast", "continue", or "ignore" |
| Count without "any" | Error | Count is only valid with "any" strategy |
| Count less than 1 | Error | Count must be at least 1 |
| Count exceeds branches | Warning | Count exceeds number of parallel branches |
| Duplicate variable in parallel | Error | Variable already defined |
| Variable conflicts with agent | Error | Variable name conflicts with agent name |
| Undefined variable in object context | Error | Undefined variable in context |
---
## Fixed Loops
Fixed loops provide bounded iteration over a set number of times or over a collection.
### Repeat Block
The `repeat` block executes its body a fixed number of times.
#### Basic Syntax
```prose
repeat 3:
session "Generate a creative idea"
```
#### With Index Variable
Access the current iteration index using `as`:
```prose
repeat 5 as i:
session "Process item"
context: i
```
The index variable `i` is scoped to the loop body and starts at 0.
### For-Each Block
The `for` block iterates over a collection.
#### Basic Syntax
```prose
let fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
session "Describe this fruit"
context: fruit
```
#### With Inline Array
```prose
for topic in ["AI", "climate", "space"]:
session "Research this topic"
context: topic
```
#### With Index Variable
Access both the item and its index:
```prose
let items = ["a", "b", "c"]
for item, i in items:
session "Process item with index"
context: [item, i]
```
### Parallel For-Each
The `parallel for` block runs all iterations concurrently (fan-out pattern):
```prose
let topics = ["AI", "climate", "space"]
parallel for topic in topics:
session "Research this topic"
context: topic
session "Combine all research"
```
This is equivalent to:
```prose
parallel:
session "Research AI" context: "AI"
session "Research climate" context: "climate"
session "Research space" context: "space"
```
But more concise and dynamic.
### Variable Scoping
Loop variables are scoped to the loop body:
- They are implicitly `const` within each iteration
- They shadow outer variables of the same name (with a warning)
- They are not accessible outside the loop
```prose
let item = session "outer"
for item in ["a", "b"]:
# 'item' here is the loop variable
session "process loop item"
context: item
# 'item' here refers to the outer variable again
session "use outer item"
context: item
```
### Nesting
Loops can be nested:
```prose
repeat 2:
repeat 3:
session "Inner task"
```
Different loop types can be combined:
```prose
let items = ["a", "b"]
repeat 2:
for item in items:
session "Process item"
context: item
```
### Complete Example
```prose
# Generate multiple variations of ideas
repeat 3:
session "Generate a creative startup idea"
session "Select the best idea from the options above"
# Research the selected idea from multiple angles
let angles = ["market", "technology", "competition"]
parallel for angle in angles:
session "Research this angle of the startup idea"
context: angle
session "Synthesize all research into a business plan"
```
### Validation Rules
| Check | Severity | Message |
| ----------------------------- | -------- | ------------------------------------ |
| Repeat count must be positive | Error | Repeat count must be positive |
| Repeat count must be integer | Error | Repeat count must be an integer |
| Undefined collection variable | Error | Undefined collection variable |
| Loop variable shadows outer | Warning | Loop variable shadows outer variable |
---
## Unbounded Loops
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):