O-Lang Syntax
The complete, accurate reference for .ol files — derived directly from the kernel parser and runtime.
O-Lang uses a format called Linear Declarative Workflow Form (LDWF). Workflows are read and executed top-to-bottom. Each statement expresses intent — the kernel handles execution, validation, resolver dispatch, and cryptographic audit logging.
linear
top-to-bottom execution
declarative
intent, not mechanics
.ol
file extension
This reference is derived directly from the kernel parser and runtime source. Every keyword and syntax pattern shown here is verified against actual parser regex and handler logic.
Every .ol file has three regions in this order. Blank lines between steps are required for readability and encouraged by the spec. Indentation is semantic-neutral — execution never depends on it.
Workflow name and input variables. Parsed first.
Explicit allowlist. Only listed resolvers may be invoked. Required.
Execution steps in monotonically increasing numeric order.
# ── 1. DECLARATION ──────────────────────────────────── Workflow "Workflow Name" with input_a, input_b # ── 2. RESOLVER ALLOWLIST ───────────────────────────── Allow resolvers: ToolA ToolB # ── 3. STEPS ────────────────────────────────────────── Step 1: Ask ToolA "{input_a}" Save as result_a Step 2: Ask ToolB "{result_a.field} and {input_b}" Save as result_b Return result_b
All keywords verified against the kernel parser. Keywords are case-insensitive in the parser (regex uses /i flag throughout) but the convention is to write them with capital first letters as shown.
| Keyword / Statement | Region | Purpose |
|---|---|---|
| Workflow ... with ... | Header | Declares workflow name and comma-separated input variable names. |
| Allow resolvers: | Header | Opens the resolver allowlist block. Required. Only resolvers listed here may run. |
| - ResolverName | Allowlist | Adds a resolver to the allowlist. Parser accepts both '- Name' and bare 'Name' entries. |
| Constraint: key = value | Header | Workflow-level constraint. Currently: max_generations = N. |
| Step N: Ask Resolver "..." | Steps | Numbered step. Calls a resolver with an interpolated prompt. N must increase monotonically. |
| Save as variable | Steps | Binds the step output to a named context variable. Available to all subsequent steps. |
| Return var1, var2 | Steps | Declares workflow output(s). Comma-separated. Must be the last statement. |
| If {var} equals "val" then | Steps | Conditional block. Condition uses {curly braces} and keyword operators. |
| End If | Steps | Closes an If block. Required — scope is keyword-defined, not indentation-defined. |
| Run in parallel | Steps | Opens a parallel execution block. All inner steps run concurrently. |
| Run in parallel for Ns | Steps | Parallel block with timeout. N is a number, s/m/h/d is the time unit. |
| Run in parallel with escalation: | Steps | Opens an escalation block. Levels execute sequentially with timeouts. |
| Level N: description | Escalation | Declares an escalation level with optional timeout parsed from description. |
| End | Steps | Closes a parallel or escalation block. |
| Use ToolName | Steps | Invokes a tool without a prompt string. Distinct from Ask. |
| Debrief Agent with "msg" | Steps | Sends a debrief message to a named agent. Emits a 'debrief' event. |
| Evolve @resolver using feedback: "..." | Steps | Requests resolver evolution with feedback. Emits evolution metadata. |
| Prompt user to "question" | Steps | Declares a user prompt step. Output not automatically saved. |
| Persist var to "path" | Steps | Writes a context variable to a file. JSON files serialized automatically. |
| Emit "event" with payload | Steps | Emits a named event with an interpolated payload string. |
| Constraint: key = value | Step | Per-step constraint (e.g. max_time_sec, cost_limit, allowed_resolvers). |
The kernel maintains a context object that grows throughout execution. Variables declared with with are seeded at start. Each Save as appends to context — nothing is ever overwritten. Dot-path notation accesses nested fields inside saved objects.
{input_name}Runtime input
Declared in the with clause. Available from Step 1.
{saved_var}Saved output
The full output object of a previous Save as step.
{saved_var.field}Dot-path access
A specific field inside a saved output. Supports multiple levels: {a.b.c}.
Object interpolation is blocked by the kernel. You cannot use {account} directly in a prompt if account is an object — the kernel throws a safety error to prevent data corruption into LLM prompts. Always use dot-path: {account.balance}.
Workflow "Invoice Check" with invoice_id, question Allow resolvers: InvoiceLookup VendorLookup GroqLLM Step 1: Ask InvoiceLookup "{invoice_id}" Save as invoice # {invoice.vendor_id} — dot-path into the Step 1 output object Step 2: Ask VendorLookup "{invoice.vendor_id}" Save as vendor # {question} — runtime input {invoice.amount} — dot-path {vendor.name} — dot-path Step 3: Ask GroqLLM "{question}. Amount: {invoice.amount}. Vendor: {vendor.name}" Save as answer Return answer
The Allow resolvers: block is an explicit allowlist. The kernel loads it before Step 1 and enforces it on every resolver call. Any resolver not in this list is blocked, logged to logs/disallowed_resolvers.json, and written to the cryptographic audit trail as a security_violation event.
Both formats are accepted by the parser
Allow resolvers: - GroqLLM - Extractor
With leading dash (recommended)
Bare names also valid
Allow resolvers: GroqLLM Extractor
Without leading dash
The built-in math resolver (builtInMathResolver) is added to the allowlist automatically when the kernel detects math keywords (Add, Sum, Avg, etc.) in step actions. You do not need to list it manually.
Steps execute in ascending numeric order. Step numbers must be monotonically increasing (gaps are allowed but discouraged). Each step makes one resolver call — either via Ask (with a prompt string) or Use (tool invocation without prompt).
Step N:N is a positive integer. Steps execute in order. Numbers must only increase.
AskCalls the named resolver with the prompt. The kernel prepends 'Action' internally before dispatch.
ResolverNameMust appear in the Allow resolvers: block. Blocked and audit-logged if not listed.
"prompt"Prompt string. {variable} references are interpolated from context before the call is made.
Save as nameSaves the resolver's unwrapped output to context as 'name'. Available to all subsequent steps.
Use — invoke a tool without a prompt string:
Step 3: Use NotifySystem Save as notification_result
O-Lang supports explicit conditional branching via If / End If. Scope is defined by keywords, never by indentation. The condition must reference a context variable in curly braces and use one of three comparison operators.
equals "value"String equality check. Value must be quoted.
greater than NNumeric greater-than. N is an unquoted number.
less than NNumeric less-than. N is an unquoted number.
End IfCloses the block. Required. No implicit closing by indentation.
There is no Else branch. The parser supports If / End If only. For two-path branching, use two sequential If blocks with inverse conditions.
Workflow "Risk Triage" with account_id Allow resolvers: RiskClassifier ComplianceAlert GroqLLM Step 1: Ask RiskClassifier "{account_id}" Save as risk # Condition: {curly braces} + operator keyword + value If {risk.score} greater than 80 then Step 2: Ask ComplianceAlert "Flag {account_id} — score {risk.score}" Save as alert_result End If If {risk.level} equals "low" then Step 3: Ask GroqLLM "Summarise low-risk profile for {account_id}" Save as summary End If Return risk
Three parallel execution forms are supported. All are closed with End. Time units for timeouts: s seconds, m minutes, h hours, d days.
1 — Basic parallel (no timeout)
Run in parallel Step 1: Ask GroqLLM "Analyse {text}" Save as analysis Step 2: Ask Extractor "{text}" Save as entities End
2 — Timed parallel (with timeout)
# All steps must complete within 30 seconds or timed_out = true in context Run in parallel for 30s Step 1: Ask GroqLLM "Analyse {text}" Save as analysis Step 2: Ask Extractor "{text}" Save as entities End
After a timed parallel block, { timed_out } is set in context: true if the deadline was exceeded.
3 — Escalation (sequential levels with timeouts)
# Levels execute sequentially. First level to complete stops escalation. Run in parallel with escalation: Level 1: immediately Step 1: Ask AutoResponder "Handle {incident_id}" Save as response Level 2: within 5m Step 2: Ask OnCallEngineer "Escalation for {incident_id}" Save as response Level 3: within 30m Step 3: Ask IncidentCommander "Critical: {incident_id}" Save as response End Return response
After escalation, context contains escalation_completed, escalation_level, and timed_out.
These statements cover side effects, persistence, agent communication, and event emission. All are parsed and executed by the kernel.
Debrief — send a message to an agent
Emits a debrief event. Variable references in the message are validated before emission.
Debrief System with "High risk detected for {account_id} — score: {risk.score}"
Evolve — request resolver improvement
Records an evolution request with feedback. When OLANG_EVOLUTION_API_KEY is set, advanced evolution is enabled.
Evolve Extractor using feedback: "Entity extraction missed subsidiary company names" Save as evolution_result
Prompt user — declare a user input step
Declares that user input is needed at this point. The kernel logs it; actual input collection depends on the runtime environment.
Prompt user to "Please confirm the account ID before proceeding"
Persist — write context variable to file
Writes a saved variable to a file path. JSON files are serialised automatically. The variable must exist in context or the step is skipped with a warning.
# Writes aggregated_risk as formatted JSON Persist aggregated_risk to "./logs/aggregated_risk.json"
Emit — fire a named event
Emits a runtime event with an interpolated payload. All {variable} references in the payload are validated before emission.
Emit "risk_flagged" with {account_id}
Constraints can be declared at the workflow level or per-step. The parser recognises and stores them; enforcement varies by constraint type.
Workflow-level constraint
Workflow "Agentic Loop" with task # Prevents runaway agentic loops — enforced by the kernel at runtime Constraint: max_generations = 5
Per-step constraints
Step 1: Ask SlowLookup "{task}" Save as result Constraint: max_time_sec = 10 Constraint: cost_limit = 0.05
Per-step constraints are parsed and stored. The kernel currently warns when they are present — enforcement is in active development.
Complete production-style workflows using verified syntax only.
Finance — AML screening with branching
Workflow "AML Screening" with account_id, query Allow resolvers: BankAccountLookup RiskClassifier ComplianceAlert GroqLLM Step 1: Ask BankAccountLookup "{account_id}" Save as account Step 2: Ask RiskClassifier "{account.transactions}" Save as risk If {risk.score} greater than 80 then Step 3: Ask ComplianceAlert "Flag {account_id} — risk score {risk.score}" Save as alert End If Step 4: Ask GroqLLM "{query}. Risk: {risk.level}. Score: {risk.score}. Account: {account.id}" Save as analysis Persist risk to "./logs/aml_risk.json" Return analysis, risk
Healthcare — parallel ICU assessment
Workflow "ICU Assessment" with patient_id, question Allow resolvers: ICUAdmission LabResults GroqLLM # Fetch admission record and lab results in parallel — 20s timeout Run in parallel for 20s Step 1: Ask ICUAdmission "{patient_id}" Save as admission Step 2: Ask LabResults "{patient_id}" Save as labs End Step 3: Ask GroqLLM "{question}. Ward: {admission.ward}. Hb: {labs.haemoglobin}" Save as summary Return summary
Multi-agent — risk assessment with debrief and persist
Workflow "Risk Assessment Demo" with text, user_id Allow resolvers: GroqLLM Extractor AggregateConfidence NotifySystem Persist Step 1: Ask GroqLLM "Analyse the incident: {text}" Save as analysis Step 2: Ask Extractor "{analysis}" Save as risks Step 3: Ask AggregateConfidence "{risks}" Save as aggregated_risk If {aggregated_risk.score} greater than 80 then Debrief System with "High risk detected for user {user_id} — score {aggregated_risk.score}" End If Persist aggregated_risk to "./logs/aggregated_risk.json" Return risks, aggregated_risk
Every verified keyword in one annotated file.
# ── DECLARATION ──────────────────────────────────────────────── Workflow "Name" with input_one, input_two Constraint: max_generations = 5 # optional workflow constraint # ── RESOLVER ALLOWLIST ───────────────────────────────────────── Allow resolvers: ToolA ToolB # ── STEPS ────────────────────────────────────────────────────── Step 1: Ask ToolA "{input_one}" Save as result_a Constraint: max_time_sec = 10 # optional per-step constraint Step 2: Ask ToolB "{input_two}. Field: {result_a.field}" Save as result_b Step 3: Use ToolA Save as tool_result # ── CONTROL FLOW ─────────────────────────────────────────────── If {result_a.score} greater than 80 then Step 4: Ask ToolA "{result_a.id} flagged" Save as flag_result End If If {result_a.status} equals "low" then Step 5: Ask ToolB "Summarise {result_b}" Save as summary End If # ── PARALLEL ──────────────────────────────────────────────────── Run in parallel for 30s Step 6: Ask ToolA "{input_one}" Save as p_result_a Step 7: Ask ToolB "{input_two}" Save as p_result_b End # ── ADVANCED STATEMENTS ──────────────────────────────────────── Debrief System with "Completed for {input_one}" Evolve ToolA using feedback: "Improve accuracy on edge cases" Prompt user to "Confirm before proceeding" Persist result_b to "./logs/output.json" Emit "workflow_done" with {result_b} # ── RETURN ────────────────────────────────────────────────────── Return result_a, result_b # comma-separated, always last line