A specification language for LLM agents.
Structured enough to lint. Loose enough to think in.
Agent definitions today are YAML with a prompt stuffed in a string, or a Python file where the prompt is a triple-quoted blob. Both fight you. YAML makes you escape the very characters prompts are made of. Code makes the interesting part — the natural language — invisible to every tool you own.
Bee inverts it. The structure is machine-checked. The meaning is prose, because the thing reading it is probabilistic and prose is the format it reads best.
prompt:
context:
Why does this test flake only in CI and never locally?
That is a complete, valid Bee document. Four lines.
Nothing above gets rewritten as the work grows. You add sections.
agent ReleaseNotes:
context:
Runs on tag push. Turns the pull requests merged since the previous tag
into release notes, then attaches them to the GitHub release.
inputs:
tag:
The git tag that triggered this run, e.g. v1.4.0.
tools:
GitHub:
Access using $GITHUB_TOKEN, scoped to this repository.
list_merged:
List pull requests merged between two tags.
boundary:
allow:
- read pull requests and tags in this repository
- update the release body for [tag]
deny:
- never push commits, tags, or branches
- never call any host other than api.github.com
actions:
merged = list everything merged since the previous tag through [GitHub.list_merged]
if nothing was merged:
- abort, leaving the draft release untouched
summary = call [Summarize] with [merged]
- publish [summary.notes] to the release for [tag]
error the publish is rejected or times out:
- retry once with backoff, then abort
outputs:
returns:
status:
SUCCESS when the release body was published.
ABORTED when a precondition failed or the publish was rejected.
Emit the word verbatim, uppercase, alone on its line.
effects:
release_published:
The GitHub release for [tag] carries the generated notes. No other
release, tag, or branch was touched.
No quotes to escape. No types to satisfy. Every [reference] is checked to
resolve, and nothing else pretends to be checked.
Every byte is executor input. There is no privileged channel to the human, which is why Bee has no comments. A comment would be read by the model anyway — it would be prose lying about being invisible. Anything worth saying gets said in the open.
Structure is macro-only; meaning is prose. No type system, no value grammar,
no expression language. A type annotation on an LLM's output is a comment
pretending to be a constraint. Prose can say when ABORTED applies, which a
type never could.
Required structure scales with blast radius. A prompt cannot touch
anything, so it owes you nothing but a context:. An agent has effects, so it
owes you a boundary:. Nothing is mandatory until it can cause harm.
The contract is the document; the steps are optional. actions: is an
escape hatch — write it to pin the sequence, omit it and let the executor plan.
inputs: and outputs: already say what the thing does.
| Tools | Effects | Composes | Required sections | |
|---|---|---|---|---|
prompt |
no | no | no | context |
agent |
yes | yes | no | context, boundary, outputs |
pipeline |
no | via agents | yes | context, boundary, actions, outputs |
Line classification. First match wins, single pass, no lookahead:
- starts with
-→ list item - starts with
name =→ binding (actions:only) - starts with a hard verb and ends with
:→ guard or error block - matches
name:→ section or entry - otherwise → prose
That is the whole parser, and it is why a colon inside a bullet needs no escaping.
SPEC.md |
The normative specification. Terse, complete, checkable. |
docs/ |
Beginner-friendly guides, a tutorial, a cookbook, and a FAQ. |
docs/prior-art.md |
Why not YAML, Gherkin, BAML or a policy engine — and when to use those instead. |
linter/ |
bee-lint — a Python linter implementing every rule in SPEC.md §9. |
editors/emacs/ |
bee-mode for Emacs: font-lock, indentation, imenu, folding. |
editors/vscode/ |
A VS Code extension with a TextMate grammar. |
examples/ |
Worked documents covering all three document types. |
$ uv run --project linter bee-lint examples/showcase.bee
$ echo $?
0A file with a problem tells you exactly where:
$ uv run --project linter bee-lint broken.bee
broken.bee:11: error: BEE012: guard not allowed in deny: or safety:That one is not a style nit. A prohibition with an escape clause is precisely what a probabilistic executor talks itself past — it only has to convince itself the condition does not hold. Bee makes it a hard error.
There is no runtime to install. The deployment model is concatenation: join the
documents a run needs, put PREAMBLE.md in front, and hand the
result to a model.
$ tools/bundle.sh examples/showcase.bee | your-llm-cliPREAMBLE.md is the executor-facing companion to SPEC.md — roughly 1,100 tokens
against the specification's 6,600, because everything only a linter needs (the
grammar, the diagnostic codes, the highlighting rules) is dead weight to a model
that is reading rather than validating.
repos:
- repo: https://github.com/<you>/bee
rev: v0.1.0
hooks:
- id: bee-lintUse bee-lint-strict instead to fail on warnings too.
Emacs — see editors/emacs/. Font-lock covering every
construct, 4-space indentation, imenu navigation across multi-document files,
and folding via outline-minor-mode.
VS Code — see editors/vscode/. TextMate grammar and
language configuration. A language server wrapping bee-lint is the natural
next step.
In both, [name annotation] renders the first token as a resolvable reference
and the remainder as annotation — deliberately not styled as a comment,
because the executor reads annotations and a greyed-out theme would teach you to
ignore live text.
boundary: is declared intent, not enforcement. What actually holds is the
sandbox, container, or separate machine the document runs in. deny: never run git is a statement to the executor, backed by whatever your runtime does
independently. Bee makes the intent legible and checkable; it does not make it
true.
The same two-tier honesty runs through the language: structure, names, and reference resolution are machine-checked, and everything else is interpreted by a model. The specification says which is which, in §9.
Draft. The language is settled enough to write real documents in. The decisions behind the shape it has — including the ones deliberately left to prose — are recorded at the end of SPEC.md.
MIT. See LICENSE.

