Syntax basics
File structure
A Corvid file is a sequence of declarations. There is no main()
function — the entry point is whichever agent you run.
effect ... # named effect with dimensionstool ... # side-effecting functionprompt ... # LLM-backed functionagent ... # composable program entryfn ... # pure functionstruct ... # record typeimport ... # bring in another moduleComments
# single-line comment
#: doc comment for the next declaration; renders in --help and LSP hoverIdentifiers and types
Identifiers are snake_case. Type names are PascalCase. Effect names are snake_case (they look like values, not types — they ARE values).
Built-in types: Int, Float, Bool, String, List<T>, Map<K,V>,
Option<T>, Result<T,E>, Grounded<T>.
Declarations
fn — pure function
fn double(x: Int) -> Int: return x * 2Pure functions have no effect row. They cannot call prompts, tools, or agents.
prompt — LLM-backed function
prompt summarize(text: String) -> String uses llm_effect: "Summarize: " + textThe body is the prompt template. Variables interpolate. The return type tells the compiler what to decode the model output as. Decoding failure is a typed error, not a panic.
tool — side-effecting function
tool send_email(to: String, body: String) -> Unit uses email_effect: @host.email.send(to, body)The @host.x.y syntax is the FFI call to the runtime’s host
interface. The runtime resolves this to the configured connector at
deploy time.
agent — composable program entry
@budget($0.50)agent main(input: String) -> String: ...Agents compose prompts and tools. They have effect rows that the compiler infers from their bodies (you can also write them explicitly). Agents can call other agents.
effect — named effect declaration
effect refund_effect: cost: $50.00 trust: supervisor_required reversible: false data: external_actionEffects are values you compose by name into prompts, tools, and agents
via uses. See Effects for the full dimension
catalog.
Control flow
if condition: # ...else: # ...
while predicate: # ...
for x in xs: # ...
match value: Some(x) -> x None -> 0Expressions
Strings concatenate with +. Numeric ops are conventional. Boolean ops
are and, or, not.
Method-call syntax (x.method()) works on any type. Postfix ? on a
Result<T,E> propagates the error.
Effect rows in signatures
prompt foo() -> String uses llm_effect, retrieval_effect: ...Effect rows are sets, not tuples. Order doesn’t matter. You can union effects across calls; the compiler computes the closure.
Types in depth
The full formal grammar (EBNF derived from the parser) is on the
Grammar page.
docs/reference/lexer-rules.md
documents the lexer’s continuation rules (backslash, brackets, triple-
quoted strings).
The typing rules are in
docs/internals/effect-spec/03-typing-rules.md.