pace-gene/bee

A specification language for prompts, agents and pipelines

★ 0Forks 0PythonGitHub ↗Compare

README

Bee

A specification language for LLM agents.

Structured enough to lint. Loose enough to think in.

License: MIT Spec: draft Files: .bee

Specification · Documentation · Getting Started · Examples


A Bee agent showing boundary, preconditions and actions with syntax highlighting

The problem

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.

The same language, scaled up

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.

Why it looks like this

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.

Three document types, one grammar

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

The one rule to internalise

Line classification. First match wins, single pass, no lookahead:

  1. starts with - → list item
  2. starts with name = → binding (actions: only)
  3. starts with a hard verb and ends with : → guard or error block
  4. matches name: → section or entry
  5. otherwise → prose

That is the whole parser, and it is why a colon inside a bullet needs no escaping.

What is in this repository

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.

Quick start

$ uv run --project linter bee-lint examples/showcase.bee
$ echo $?
0

A 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.

Running a spec

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-cli

PREAMBLE.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.

Use it in pre-commit

repos:
  - repo: https://github.com/<you>/bee
    rev: v0.1.0
    hooks:
      - id: bee-lint

Use bee-lint-strict instead to fail on warnings too.

Editor support

bee-mode in Emacs showing a complete prompt and agent

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.

An honest caveat

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.

Status

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.

License

MIT. See LICENSE.

Contributors

pace-gene

Issues