(~ solve (x) '(-> ,x "Verify the answer"))
(["Ship one result"] (solve "Find the bug") (solve "Design the fix"))
In two forms this defines a reusable agent graph, fires two instances, routes each real answer through verification, and mediates both verified results into the program's returned value.
Rebis is a pure S-expression language for programming LLM-agent systems.
Quoted strings are raw prompts, bare atoms are Lisp-like symbols, ~ defines
structural macro abstractions, arrows route actual answers, and squares contain
executable mediator code.
program := expr+ EOF
expr := prompt | symbol | '\'' expr | ',' expr | '(' form ')'
form := '~' symbol '(' symbol* ')' expr
| '#' module | '<' expr '>' expr+
| '$' expr+
| '&' symbol expr+
| '^' expr
| '%' expr expr expr
| '->' expr expr+ | '<-' expr expr+
| symbol expr* | expr+
As in Lisp source files, a program may contain multiple top-level forms without an extra pair of parentheses. They share one lexical definition scope and execute in source order; the parser retains that boundary as an implicit program node.
The central form is ([M] A B ...). Branches run first; their actual answers
become RESULT 1, RESULT 2, and so on in source order, then enter mediator
program M. A host may evaluate mutually isolated branches concurrently while
preserving that source-ordered join.
orchestrate_parallel evaluates every eligible [] child concurrently, bounded
by RuntimeLimits::with_max_concurrency. orchestrate_parallel_with_inlet adds
a thread-shareable input seam. Groups, arrows, gates, and the mediator remain
sequential; branch answers, events, and diagnostics rejoin in source order.
An Oracle may override begin_parallel, try_fire_scoped, and
finish_parallel. Those lifecycle callbacks give hosts a stable
ExecutionScope for branch-local resources and put reconciliation immediately
before the mediator. Returning Ok(false) from begin_parallel safely falls
that square back to ordinary sequential execution. Rebis itself remains
zero-dependency and filesystem-free; a host can supply containers, copies, Git
worktrees, or no external isolation at all.
Lazy control is explicit: (% condition when-one when-zero) evaluates the
condition first, requires exactly 1 or 0, and evaluates only the chosen
continuation. A square is never conditional; [] always mediates all of its
branches. The embedded std-binary protocol uses $ to ask for one exact
decision token before the gate validates it.
([(->
"Compare all reports"
"Resolve disagreements"
"Write one verified fix")]
"Inspect the code and reproduce the failure"
"Trace the execution and find the root cause")
Macro abstractions use (~ name (parameter ...) body) and ordinary Lisp-style
calls:
(
(~ inspect (target)
(-> target "Write a detailed report"))
(inspect "Inspect the parser"))
Application substitutes argument expressions structurally. It never changes text inside quoted prompts. Only quoted prompts fire agents.
The string is the language's fundamental value. ($ A B ...) is the one
operator that transforms it: it interpolates its operands into one string
and yields that string — pure text construction, nothing inside $ fires or
runs. An operand contributes its text: a prompt its characters, a symbol its
bound value, a macro its expanded text (not fired), a nested $ its assembled
text. The assembled string is a prompt in the position the $ sits, so it fires
there, once. Because it is an operator, not in-string interpolation, it never
peeks inside a quoted string — "it cost $100" stays literal.
Variables are macro parameters. A macro binds names; a call supplies the
values, which $ weaves in as text — reused freely, with no extra model call
per use:
(~ case (self rival)
($ "Make the strongest case that " self " beats " rival " in hip-hop."))
(["Deliver the verdict: who has the greater hip-hop legacy, and why?"]
(case "Jay-Z" "Kanye West")
(case "Kanye West" "Jay-Z"))
This fires exactly two advocates and one mediator: self and rival are
text, so reusing them does not multiply model calls.
A text constant is just a macro whose body is a prompt — $ interpolates
its text without firing it, so no quoting is needed:
(~ topic () "the fall of Rome")
($ "Write a short explainer on " (topic) ".")
; one model call — (topic) is woven in as text, not fired on its own.
To carry a model-computed value into a prompt, use -> (it flows the answer
in as INPUT:); $ builds text, -> carries results.
(& port body) receives an external input under the name port, then runs
body with that name bound to whatever the host supplies:
(& input (-> input "Summarize this"))
; `input` yields the host value and flows into the prompt as its INPUT:
The host decides what fills the port — a prior run's answer, another running
agent's output, a line typed at a terminal. Using the port name where a value
flows (an arrow producer, a $ operand) yields that value; no model fires for
the port itself. The core stays pure: the value is fixed for the run. A host may
block inside its input seam until the value arrives, so & is the
language's "stop until it receives input" — the building block for one agent
feeding another and for long-lived, supervised agents.
(^ E) takes the orientation dual of Rebis syntax without calling a model. It
recursively exchanges -> and <-, preserves written operand order, and
leaves prompts, symbols, imports, and the complete contents of $ fixed:
(^ (-> "gather evidence" (<- "write report" "challenge evidence")))
; becomes (<- "gather evidence" (-> "write report" "challenge evidence"))
It is an involution: (^ (^ E)) is exactly E. Groups, squares, and quotes
retain their shape while their children are dualized. Macro definitions stay
fixed; a call expands first and its resulting graph is inverted, so a local
definition and its call are dualized exactly once.
Macros are higher-order: a named macro symbol can be passed as an argument and substituted in call-head position.
(
(~ apply (worker target) (worker target))
(~ inspect (target) (-> target "Write a report"))
(apply inspect "Inspect the parser"))
Repeated parameters may duplicate model work, so hosts should enforce expanded call, size, token, and time budgets.
For explicit Scheme/Common-Lisp-style construction, quote holds output syntax and comma splices caller syntax:
(~ twice (work) '(-> ,work ,work))
Macros may call themselves. The % binary gate evaluates its condition as an
exact 1/0 decision and runs only the selected branch. This provides loops
without a separate recursion operator;
the reference runtime caps expansion at 256 macro calls.
(# module) imports the top-level macro definitions of a host-resolved Rebis
module. Kaos resolves modules from saved hypersigils in ~/.kaos/sigils; names
may be qualified, such as std/loops. The crate embeds sixteen modules under
that reserved namespace: import one leaf with (# std/loops), or the complete
folder with (# std). Hosts may apply the same definition-only expansion to
their own folders; Kaos recursively imports a saved folder such as (# team).
Module bodies are definition-only and may re-export other modules with #.
For example, save this as the hypersigil engineering.rebis:
(
(~ investigate (issue)
'(->
,issue
"Reproduce the failure"
(["Choose the strongest causal explanation"]
(<- "Challenge the trace" "Trace state backward from the symptom")
(-> "Inspect the relevant code" "Propose the earliest wrong state"))))
(~ repair (issue)
'(->
(investigate ,issue)
"Implement the smallest root-cause fix"
(<- "Return the reviewed patch" "Run tests and search for regressions"))))
Then a program can import and expand it twice:
(
(# engineering)
([(->
"Compare both repairs"
"Resolve contradictory evidence"
"Write the final design and patch plan")]
(repair "Tower middleware lacks ad-hoc span metadata")
(repair "Future cancellation can lose the closing event")))
The compact call:
(repair "Future cancellation can lose the closing event")
structurally expands into the full nested ->, <-, mediator, and
investigate graph from the module. Arguments remain syntax throughout the
expansion; no prompt text is interpolated or reparsed.
kaos rebis run --allow-tools examples/incident.rebis
kaos rebis run --dry '(["Combine reports"] "Inspect code" "Trace failure")'
kaos rebis tree '(["synthesize"] "Inspect code" "Trace failure")'Kaos provides a direct editor plus an optional Vim-like mode with
normal/insert/visual modes and % bracket matching. ctrl / opens Kaos commands
(/run, /tree, /mandala, /format,
/panel, /graph); : remains reserved for Vim file commands. See the specification,
guide, symbol reference,
standard library, and host notes.