Glorbo is a multi-agent AI orchestration platform with a
sandbox-as-trust-boundary architecture. Changes that touch that boundary
(permissions, bwrap, audit log, company isolation) need extra care —
see docs/DESIGN.md for the load-bearing invariants. This
document describes how to get set up, the workflow we use, and what we
look for in code review.
Project status: pre-1.0 (v0.0.x). APIs, CLI flags, on-disk layout, and SQLite schema may change between minor versions. Pin to exact versions in downstream usage until v1.0.
- Read
docs/DESIGN.md— it is authoritative when it conflicts withREADME.md. - Read
CLAUDE.mdfor the architectural guardrails. - For security-sensitive reports, use
SECURITY.md— do not open a public issue for vulnerabilities.
Glorbo targets Linux (x86_64 and aarch64) and uses kernel-level isolation primitives. Other hosts will not work for running agents.
Required toolchain (see .tool-versions):
- Elixir 1.20.1 / Erlang OTP 29.0.2
bubblewrap(bwrap) — available as an OS package on every major distro. On Ubuntu 24.04 you also need an unconfined AppArmor profile for bwrap (see.github/workflows/ci.ymlfor the canonical fix).inotify-tools(Linux-only).- Zig 0.15.2 (for building single-file releases via Burrito).
Recommended:
mix formatandmix credo --strictmust be clean before you push.
mix deps.get
mix compile --warnings-as-errors
mix testFor anything larger than a one-line fix, open a GitHub issue first to align on approach. This is especially important for:
- Anything touching the kernel / Router / permission-mapper layer.
- Changes to the on-disk layout under
~/.glorbo/. - Changes to the SQLite schema (remember: the SQLite DB is derived
data and must be reconstructable from the filesystem — see
CLAUDE.md). - New external dependencies.
Non-trivial design changes are captured as Glorbo Enhancement
Proposals (GEPs) in docs/geps/. A GEP is a
numbered, append-only design record with a required decision log —
the "why" that outlives any one PR. See
docs/geps/0001-gep-purpose-and-guidelines.md
for the full process, and
docs/geps/README.md for the index.
Write a GEP if your change:
- Introduces a new public contract — CLI flag, config schema, on-disk
layout, API surface,
agent.mdfield. - Touches a load-bearing invariant documented in
docs/DESIGN.mdor an existing GEP. - Reverses, supersedes, or materially extends a prior decision.
- Spans multiple modules or milestones and benefits from a shared reference.
Skip the GEP for:
- Bug fixes, dependency bumps, doc tweaks.
- Refactors contained to one module with no behaviour change.
- Performance work that doesn't change APIs.
Outside contributors are not required to write GEPs. A good PR with a clear description is welcome regardless. Maintainers will retrofit a GEP post-merge if the change warrants one. If you'd like to collaborate on a GEP, open a Draft PR with the GEP file — we'll iterate on it together before accepting.
Working on a change and realising it needs a GEP? Mention it in your PR; the maintainer can either help you add one or retrofit it after merge as an Informational GEP capturing what shipped.
Proposing a GEP without a code change. You're welcome to propose a design, challenge an accepted GEP, or retrofit historical context without any implementation. To avoid wasted effort on both sides:
- Open a GitHub issue first. One or two paragraphs describing
the idea. Label it
gep-proposal. - Wait for a maintainer triage response:
- "Go ahead as Draft" — open a GEP PR at
status: Draft. You've worked through the design space enough to defend concrete choices. A maintainer flips it toAccepted(orRejected) after review. - "Go ahead as Placeholder" — open a GEP PR at
status: Placeholder. The idea is worth capturing and numbering, but the design space is still open. A Placeholder has a problem statement, goals, open questions, and a few settled decisions — the rest is explicitly flagged as "to be worked out." Lower review bar to merge; you (or someone else) promotes it to Draft later when the open questions are resolved. See GEP-1 §"Placeholders." - "Not needed" — the change is too small for a GEP or already covered elsewhere. The issue stays open for discussion and may evolve into something else.
- "Out of scope" — the proposal doesn't fit Glorbo's direction. Closed with context so others can see the reasoning.
- "Go ahead as Draft" — open a GEP PR at
- Rejected GEPs stay in the repo with
status: Rejectedand a brief history entry. This is deliberate — it documents "we considered this, here's why we passed" and saves everyone from re-litigating the same idea.
This triage step prevents two failure modes: contributors writing full GEPs that get rejected on scope (wasted time), and maintainers drowning in speculative proposals (wasted attention). A two-paragraph issue is cheap for both sides.
- Fork the repo (or branch directly if you have write access).
- Branch naming:
feat/<short>,fix/<short>,docs/<short>, etc. - Keep PRs focused — one concern per PR. If you find an unrelated issue while working, file it separately rather than folding it in.
- Rebase on
mainbefore opening the PR; avoid merge commits.
We use Conventional Commits:
<type>(<scope>): <short summary>
<body — what and why, not how>
Types: feat, fix, docs, chore, refactor, test, ci,
perf, build, style.
Examples from git log:
feat(01-01): generate Phoenix skeleton, pin toolchain, enable SQLite WALfix(sandbox/bwrap): replace bash-only ${@:3} with POSIX shiftdocs(03): mark human UAT self-verified (2/3 passed, 1 deferred)
Every PR must pass the following on CI before it can merge. You should run them locally first:
mix compile --warnings-as-errors # no warnings
mix test # all tests green
mix credo --strict # lint clean
mix format --check-formatted # formatter cleanCI also builds a Burrito release binary and smoke-tests it (glorbo doctor --json). Breaking the doctor's JSON contract is a blocker.
- New features need tests. Bug fixes should include a regression test that fails before the fix and passes after.
- Sandbox/bwrap integration tests gate on
bwrapavailability at runtime — keep it that way so contributors on non-Linux hosts can still run the rest of the suite. - Prefer small, focused test files. The test filename convention
mirrors the module under test (enforced by Credo's
Warning.WrongTestFilename).
These are load-bearing invariants. A PR that weakens any of them needs explicit justification and sign-off in the PR description.
- Kernel is the policy engine. Permissions enforced at Elixir Router and POSIX ACLs. Application-only checks are a design bug.
- Filesystem is source of truth. SQLite is derived;
glorbo reindexmust rebuild it fully from markdown/JSONL. - One-way inbox/outbox flow. Elixir writes inbox, reads outbox. Agents read inbox, write outbox. No shortcut.
- Audit log is append-only.
audit/YYYY-MM.jsonlis never modified or deleted. - No Python anywhere. Agents are CLI-tool subprocesses under bwrap. The pre-pivot plan to run Python inside Podman has been dropped (GEP-5 D6).
- Company isolation is absolute. Each agent's bwrap sandbox bind-mounts only the active company's directory; sibling companies are simply not in the mount list.
- Crash isolation follows the OTP supervision tree. Agent crash → only that agent restarts, never the whole dashboard.
- Follow
mix format(it runs on CI with--check-formatted). - Don't add comments that restate WHAT the code does. Comments are for the non-obvious WHY: a kernel gotcha, a subtle invariant, a workaround citing a specific upstream bug.
- Avoid defensive error handling for scenarios that can't happen. Trust internal boundaries. Validate only at the edge (user input, external APIs).
- Keep functions small and named after what they do. Prefer pattern
matching at the function head over
caseinside the body.
Significant design changes go through the GEP process described in §2 above. For smaller contributions, skip it — open an issue, open a PR, be clear about scope. Maintainers retrofit a GEP post-merge if the change warrants one.
Before requesting review, self-check:
- Scope is clear from the PR title + description.
-
mix compile --warnings-as-errors && mix test && mix credo --strict && mix format --check-formattedall pass. - Touched
docs/DESIGN.mdinvariants? Explained why in the PR body. - Added/updated tests for the behaviour change.
- No new top-level deps without discussion.
- No secrets, API keys, or personal paths in diffs, logs, or test fixtures.
- Commits are clean and Conventional.
By contributing, you agree that your contribution will be licensed
under the Apache License 2.0 (see LICENSE).
Open a non-sensitive issue or email security@example.invalid. For
security-sensitive reports, see SECURITY.md.