Skip to content

Symbiont DSL Specification

This specification reflects the actual grammar in crates/dsl/tree-sitter-symbiont/grammar.js, which is the source of truth. For a practical, example-driven walkthrough see the DSL Guide. Agent files use the .symbi extension (legacy .dsl is still recognized). Validate any file with symbi dsl -f agents/<name>.symbi.

Overview

The Symbiont DSL is a declarative language for defining governed agents, their policies, supporting types, and runtime integrations (schedules, channels, memory, webhooks). A program is a sequence of top-level items.

Top-level items

A program is any number of:

  • metadata { ... } — document metadata
  • agent name(params) -> Type { ... } — an agent definition
  • policy name { ... } — a named policy
  • type Name = ... — a type alias or struct
  • function name(params) -> Type { ... } — a free function
  • schedule name { ... } — a cron schedule
  • channel name { ... } — an inter-agent channel
  • memory name { ... } — a persistent memory store
  • webhook name { ... } — webhook ingestion
  • comments

Metadata

metadata {
    version = "1.0.0"
    author = "ThirdKey"
    description = "..."
    tags = ["category", "use-case"]
}

Each pair is identifier = (or :) value, where a value is a string, number, boolean, identifier, array, or record. Managed-CLI (Mode B) agents also recognize executor, model, allowed_tools, and system_prompt — see the DSL Guide.

Agent definition

agent name(param: Type, ...) -> ReturnType {
    capabilities = ["read", "analyze"]

    policy policy_name {
        allow: read(input) if true
        deny: write(any)
    }

    with memory = "ephemeral", sandbox = "tier1" {
        result = process(input);
        return result;
    }
}

The parameter list and -> ReturnType are optional. The only items allowed inside an agent body are: capabilities, policy, function, with blocks, and comments.

Capabilities

capabilities = ["read", "write", "analyze"]

An array of strings (the separator may be = or :).

Policies

policy name {
    allow:   <expr> [if <expr>]
    deny:    <expr> [if <expr>]
    require: <expr> [if <expr>]
    audit:   <expr> [if <expr>]
}

Each rule is one of allow / deny / require / audit, then :, then an expression, then an optional if <expr> guard. Policies may also appear at the top level and inside channel blocks.

with blocks

with memory = "persistent", sandbox = "tier1", timeout = "30m" {
    // statements
}

Zero or more identifier = value (or array) attributes, comma-separated, followed by a block. Attributes the runtime understands include sandbox (docker/tier1, gvisor/tier2, firecracker/tier3, e2b), timeout, memory, security, and others; unrecognized attributes parse but are ignored.

Functions

function name(param: Type) -> Type {
    // statements
}

Types

Built-in types: String, int, float, bool. Generics use angle brackets and may take multiple arguments: Map<K, V>, Result<T, E>. User-defined types:

type FileInfo = {
    path: String,
    size: int,
}

type AgentId = String   // alias

Statements

Statements appear inside blocks. Most statements must end with ;:

  • let x = <expr>;
  • assignment: x = <expr>; (also +=, -=, *=, /=, %=; the left side may be a member/index expression)
  • return <expr>; (the expression is optional)
  • expression statement: <expr>;
  • if <expr> { ... } else { ... } — also if let pattern = <expr> { ... }; else may chain another if or a block
  • for x in <expr> { ... }
  • match <expr> { pattern => result, _ => result }
  • try { ... } catch (e) { ... } (one or more catch clauses; a catch binds an identifier)

A block may end with a trailing expression (no ;), which becomes the block's value. There is no while loop.

Expressions

Precedence, lowest to highest: ||, &&, equality (==, !=, in), comparison (<, >, <=, >=), additive (+, -), multiplicative (*, /, %), unary (!, not, -), postfix, primary.

  • Boolean operators are && and ||and/or are not keywords. Negation is ! or not.
  • Postfix: member access a.b, function call f(x, y), indexing a[i].
  • Calls accept positional and named arguments: f(a, name = b) (named-arg separator may be = or :).
  • Record / struct literals: TypeName { field: expr, ... } or { field: expr }.
  • Lambda: x => expr.
  • if <expr> { ... } else { ... } is also usable as an expression.
  • Grouping ( <expr> ), arrays [a, b], and vault references vault://path/to/secret.

Literals

  • String: "..." with \ escapes. (No string interpolation.)
  • Number: 42, 1_000, 3.14.
  • Boolean: true, false.
  • Duration: 30s, 5m, 2h, 7d, 1w, 6months, 1y, and the N.seconds / N.minutes / N.hours forms.
  • Identifier: [a-zA-Z_][a-zA-Z0-9_]*.

Comments

Both // and # introduce line comments. (There is no block-comment syntax.)

Notes

  • The grammar file is the authoritative reference; this document summarizes it.
  • For real, parse-tested examples see the agents under agents/ and the DSL Guide.