A hundred workers. One project. Nothing collides.
Lacunari reads an entire project — source code, documents, scans, research — and turns it into a living roadmap: what exists, what is missing, and what to do next. Engineers, contractors and AI agents work it at the same time, on the same repository, without duplicating each other or overwriting each other's files.
The coordination is a Postgres database. There is no daemon, no broker, no
scheduler to run and no vendor in the path. Anything that can reach the
database is a peer: a Claude Code session, a GPT worker, a local Llama, a cron
job, a contractor with psql.
lac doc index . --source my-repo # read the whole project
lac doc extract && lac web build # map it
lac web gaps # what is missing
files 92 · 6 languages · 13,582 lines
no docs mention 29 nothing calls 122
nothing names it 2 no test mentions 86
lac web suggest --dispatch # → 47 claimable tasks
lac work & lac work & lac work & # and go to bedGit is superb at what it was built for: recording that bytes changed, and reconciling two people who changed them, at the speed people work. It has never read a file, has no idea what your project is, and detects a collision only once both parties have finished.
That was a reasonable set of assumptions when the whole team was human. It is not the team you have now.
| Question | Git | Lacunari |
|---|---|---|
| What changed? | line diffs between commits | every version content-addressed, who contributed it, when, in an immutable audit trail |
| What is this codebase? | stores your files, never reads them | every symbol, import and language, and what calls what |
| What is unfinished? | absence is not a diff | undocumented, untested, uncalled, orphaned — already filed as claimable work |
| Who is on it right now? | a merge conflict, after both did the work | an atomic claim, before a keystroke is spent |
| What is this goal made of? | — | decomposed into a tree, one level at a time, against the real files |
| Merging two parties' work | conflicts when you touched the same lines | cannot conflict — same bytes, same hash, so a merge is a union |
| What did it cost? | — | dollars and human minutes, per task, with hard ceilings |
Most teams run both, pointed at the same tree. See docs/VERSIONING.md for the full overlap.
A roadmap is normally a document somebody keeps up to date until they stop. Here it is derived from the project itself, expanded one level at a time against what is actually in the tree, and it updates itself as work lands.
lac goal add "Ship multi-tenant support" \
--criteria "every query scoped to a tenant, with tests" \
--depth 3 --budget 40.00
lac goal plan 1 # expand ONE level, grounded in the code
○ Ship multi-tenant support
○ Add tenant_id to the 9 tables that lack it work
○ Scope the 41 queries in core/ that ignore it work
○ Decide the isolation boundary: RLS or app-level needs a human
○ Backfill fixtures for the 12 untested paths work
lac work & lac work & lac work &Planned one level at a time. The decomposition at depth 4 depends on what depth 2 learned, so leaves are expanded when they are reached, with their siblings' findings in hand. Work starts before planning ends, and a wrong branch is pruned before its subtree exists.
It grooms itself. Every derived item carries a stable identity, so when the work is genuinely done the item withdraws itself. No backlog triage, and no tickets describing a codebase that moved on six weeks ago.
Status without a status meeting. Rollups by subtree — open, claimed, blocked and shipped per epic or department, with the dollars and human minutes each one cost. The report is the database.
Judgement calls come back to you. A planner marks what it cannot decide and escalates rather than guessing confidently. Humans stay on the decisions, not the dispatching.
Every expansion records what the planner was shown and what it returned, so "why is this on the roadmap?" has an answer. Depth is capped, budget is checked before expanding, and malformed output is dropped rather than filed. Two of the three shipped planners need no model at all.
Every search engine, vector database and RAG pipeline answers one question: what in here matches? Ask any of them what you are missing and they return nothing — which is indistinguishable from a question with no answer.
year | documents | bar
------+-----------+------------
1941 | 1 | █
1942 | 1 | █
1943 | 0 | ◻ MISSING
1944 | 1 | █
Nobody typed that year in. Lacunari read the dates out of the documents, saw the span was otherwise covered, and reported the hole. Pointed at a repository the same query shape reads "29 files no documentation mentions, 122 symbols nothing calls."
Four shapes of absence, each a lead rather than a verdict:
| a hole in a covered span | you hold 1942 and 1944, so where is 1943 |
| resting on one source | nothing corroborates this — the reason to federate |
| connected to nothing | named once and never placed: a dead end, or the loose thread |
| never read | held but never extracted, so invisible to every question above |
Each becomes a task with a title and a priority — and withdraws itself when someone closes it, so the board reflects the project rather than a backlog nobody trusts.
All of it offline: set-based SQL over data you already hold. No model, no API key, no network call. That makes results reproducible, and means they do not shift because a vendor shipped a new checkpoint.
lac work & lac work & lac work & lac work &Each worker asks the board for the top task it is capable of, claims it, declares the files it will touch, does the work, records what it cost, and asks again. They do not step on each other, for structural reasons:
- A claim is one atomic
UPDATE. Two workers cannot take the same task. - A claim locks the files that task declared. The second worker is told "dana holds core/auth.py on task #12" rather than discovering it at merge time. Readers coexist; writers are exclusive.
- A worker that dies loses its lease. Its files unlock and the task returns to the board with its history intact.
- Capabilities route the work. The agent with production credentials gets the deploy task; the one without never sees it.
- Judgement calls escalate to a human on a deduplicated backoff, because an agent that cries wolf trains people to ignore it.
For teams, identity is the connected Postgres role, lane permissions are row-level security, and the audit log is append-only in the database — so a contractor and a model are governed identically. See docs/TEAM.md.
A product line is the other thing people mean by "branch". Windows, Windows Server, Windows Embedded — variants that live for years, share most of their ancestry, diverge on purpose and are never merged back. Forking one costs pointers, not bytes, because both lines point at the same content-addressed rows. And the question that quietly rots a product family gets a straight answer:
lac line fork base enterprise-tier # 0 bytes copied
lac line drift # we fixed that — did we fix it there?line | path | parent_hash | child_hash | parent_moved
-----------------+---------------+--------------+--------------+-------------
enterprise-tier | core/auth.py | 4f2a1c8e9b03 | 8d71e0a5c2f4 | 07-29
Git cannot answer that. A cherry-pick leaves no durable record that two lines now agree, so it decays into archaeology across branches nobody has opened in a year. Here it is a lookup: same path, different hash. There is no merge, so there is nothing to resolve — you are shown the fact and you decide.
Changesets, merge, and the rest of it. A lac change is the useful half of
a branch: N files that land together or not at all, with review that a later edit
invalidates, and a compare-and-swap so it cannot clobber someone. lac merge
is a real three-way merge for the case locks cannot prevent — two federated
peers, or work done offline — using git's own merge algorithm, because that
algorithm was never the problem.
What git keeps. Short-lived topic branches you intend to merge back, and the plumbing around them. If your workflow is "everyone edits main freely and we sort it out at review time", git is the better tool for that part and this sits on top. See docs/VERSIONING.md for the full map of what overlaps.
./quickstart.sh # finds or starts a Postgres, installs everything
lac doc index . --source my-repo # read the codebase
lac doc extract && lac web build # map it
lac web gaps # what is unfinished
lac goal add "Ship multi-tenant support" # state the objective once
lac work # agents drain the board — no "keep going"No daemon, no broker, no framework to adopt. A peer is anything that can reach
the database: a shell script, an MCP client, a psql session. Your Claude
agents, someone's local Llama, a GPT worker, the contractor in Lisbon, and you —
same board, different vendors, no sandbox.
19 migrations · 20 commands · 23 test suites · CI on Postgres 14/16/17 · macOS, Ubuntu, Linux Mint. Runs unattended: a keeper reclaims dead agents' work, budgets stop runaway spend, and Postgres is the only moving part.
Six years of accretion, two people who wrote most of it gone, and a mandate to ship something on top of it. Normally you point an agent at it and hope.
1 · Let it read the whole thing.
lac doc index . --source my-repo
lac doc extract
lac web buildNot a vector blob — a structured map. Language, line count, every symbol defined, every import, which file names which. Content-addressed, so the same vendored file in three places is one document with three contributions.
2 · Ask what you actually have.
lac web gapsfiles 92 · 6 languages · 13582 lines
files_no_doc_mentions 29 symbols_nothing_uses 122
files_nothing_names 2 files_no_test_mentions 86
Real output from this repository indexing itself. 29 files that no documentation mentions. 122 symbols nothing else calls. 2 files nothing in the tree names at all — entry points, or dead.
That last number is the one nobody ever has. It is not in git, not in your issue tracker, and not in your agent's context window.
3 · Ask about one thing.
lac doc symbol lac_claimable DEFINED IN
short_hash | file | language
--------------+---------------+----------
6b77b9eedc6f | 002_fleet.sql | sql
bb4ba56a5245 | 003_scale.sql | sql
Defined in two places. An agent about to "fix" one of them now knows about the other — which is exactly the class of thing it would otherwise get wrong confidently.
4 · State the objective and let it decompose.
lac goal add "Document and harden the codebase" \
--criteria "every command has a doc mention" \
--criteria "no orphaned files" \
--depth 3 --budget 25.00
lac goal plan 1 tree | id | kind
----------------------------------------------+----+------
○ Document and harden the Lacunari codebase | 1 |
○ Document lac-peer | 2 | work
○ Document lac-hook | 3 | work
○ Document lac-web | 4 | work
…
Nothing invented. Every subtask names a real file, and carries the evidence that
produced it — lac-web (bash, 341 lines) is mentioned in no .md file — because
the planner is shown the map rather than asked to imagine one.
And it expands one frontier at a time — not a 400-item plan written by something that has learned nothing yet. Five subtasks now, work starts on them immediately, and their children get planned once those five report back. Depth capped, budget checked before it spends, a wrong branch pruned before its subtree exists. Tested to 50,000 tasks in a tree, four workers draining it concurrently.
5 · Everyone works the same board.
lac work # an agent claims and drains
lac board # a human watcheslac work does not need to be told what to do. It asks the board, takes the
top thing it is capable of, does it, records what it cost, and asks again. Run
five of them and you have five agents working in parallel on one plan — this is
the sentence that replaces a hundred "keep going"s.
One task, one owner, enforced by the database, so two agents cannot pick up the same file. An agent dies mid-task at 3am, its lease expires, the work returns to the board and someone else takes it. Something needs a judgement call and it escalates to a human instead of guessing.
Route work by what a worker is: requires: gpu, requires: prod-access. The
agent with production credentials gets the deploy task; the one without never
sees it.
6 · In the morning.
overnight 14 done · 3 blocked on you · $2.18 · 40m human
goal #1 ████████░░ 8 of 11
Costed in dollars and human hours, because your people are the expensive part. Every result content-addressed and attested, so you can check the work instead of trusting it — a second agent can re-derive it and the two are compared by hash.
7 · And the roadmap maintains itself.
Somebody writes the docs for lac-doc. On the next nightly pass:
withdrew 1 task(s) that are no longer suggested
filed 2 task(s) — already-open suggestions were skipped
The task deletes itself, because the condition that created it is gone. The board reflects the repository, not a pile of stale tickets nobody trusts.
That is the loop: the code describes itself, the description becomes the roadmap, the roadmap gets worked in parallel, and finished work quietly leaves the board. Nobody is narrating it one prompt at a time.
The map does not care that it is code. Point it at scanned documents and the same machinery reads EXIF, dates and place names instead of symbols and imports — and "which years does this record skip?" comes out the same way "which files has nobody documented?" does. See docs/RESEARCH.md for the archive case, and docs/CODEBASE.md for the code one.
Agent frameworks usually assume one process, one language, one vendor. That breaks the moment you have Claude Code in one window, Cursor in another, and a scheduled job on a server — three agents that could help each other and instead each rediscover the same context and redo the same work.
Lacunari makes the coordination layer something all of them can already talk to. The integration surface is "can you run a command, or speak MCP" rather than "can you import this SDK", which is why a bash cron job and Claude Desktop can be peers on the same queue.
It solves three specific problems:
- Work outlives the agent doing it. A task is a database row, not something held in a session's context. If a session crashes, its claim goes stale, the keeper releases it, and someone else picks it up. Nothing is silently lost.
- Two agents don't do the same job. Claiming is a single atomic
UPDATE. The loser is told to pick something else. - Stuck work reaches a human. Blocked and high-priority-unclaimed tasks escalate on a 30m/2h/6h backoff to whatever channel you configure. This is the part most setups get wrong, and the reason the keeper looks the way it does — see the keeper's header comment.
- Nobody has to write the roadmap by hand. The board is only useful if something fills it. Indexing what you already hold produces the gaps, and the gaps are the work — including in a codebase, where they are undocumented modules and symbols nothing calls. When a gap gets filled, the task withdraws itself.
- Enormous goals stop being unusable. An objective is expanded one frontier at a time rather than planned up front, because the decomposition at depth 4 depends on what was learned at depth 2. Work starts before planning ends, and a runaway costs one expansion instead of a budget.
Being clear about this up front, because the category invites assumptions:
- Not an agent runtime. It doesn't run models or manage context. You bring the agents; this coordinates them.
- Not a security boundary — until you turn one on. By default
cpermandcruleare cooperative gates: they constrain agents that ask, not agents that don't. For a team, team mode makes the database enforce it — identity comes from the connected Postgres role and cannot be spoofed, and lane permissions are row-level security rather than convention. - Not a workflow engine. Tasks can depend on each other and nest into departments, but there is no fan-out/fan-in, no retries, no conditional branching. If your problem is a graph, use a graph engine.
| Command | What it does |
|---|---|
csend cread cwho cwatch |
the bus — post, read, see who's active, live tail |
ctask |
the durable queue — add, claim, beat, done, block |
cagent cperm |
identity, and the cooperative permission gate |
crule |
policy — allow / require-approval / deny, by exit code |
crole |
role briefs (system prompt + lane + definition of done) |
cbuild |
decompose a goal into role-matched queued tasks |
cask cresolve cstatus |
put a question to a human; report what you're doing |
lac work |
a worker — claims, runs, heartbeats, survives being killed |
cboard |
the whole fleet on one screen, live off LISTEN/NOTIFY |
lac spend |
what it cost, by lane/agent/task/model — and hard budgets |
lac reconcile |
keeps reality matching what you declared — maintenance, not cron |
lac doc |
the library — catalogue, search, dedup, inventory |
lac peer |
federate with another group's catalogue |
lac attest / lac verify |
results others can re-derive, not just believe |
lac ingest |
pull work in from GitHub, Jira, Linear, anything with JSON |
lac hook |
push task events out to Slack, CI, anything with a URL |
lac digest |
what happened while you were away |
lac replay <id> |
one task's whole life, from the record |
lac |
one entry point for all of the above, plus lac doctor |
lac-admin |
add, list and revoke members (team mode) |
cpod |
sub-teams, once one queue gets crowded |
tasks/lac-keeper.sh |
releases stale claims, escalates stuck work — run this on cron |
tasks/lac-drain.sh |
runs queued tasks through your executor, unattended |
Everything gates by exit code, so it composes with anything:
crule check deploy-fix && ./deploy.sh # 0 = allow
crule check money-transfer || cask "approve?" # 2 = ask a human
cperm "$LAC_NAME" deploy || exit 1 # default-denyStart a worker on any machine. It claims what it is permitted and able to do, runs it through your executor, heartbeats while working, and puts the claim back if it dies:
export LAC_EXECUTOR=$PWD/executors/claude-code # or executors/shell, or your own
lac work --lanes backend,review --concurrency 3Run one on your laptop, one on a server, one on a Pi — they all pull from the
same queue. An executor is any program that takes a task on stdin and exits 0
(done), 2 (refused), or anything else (blocked with the reason). There is no
default executor, no default model and no default vendor: nothing spends your
money until you wire it up. See executors/.
Work survives the worker. Kill one mid-task and the claim goes stale, the keeper releases it, and another worker finishes the job:
lac work --lanes auto & # claims #1
kill -9 %1 # hard kill, mid-task
tasks/lac-keeper.sh # releases the orphaned claim
lac work --lanes auto --once # a different worker completes itctask dep 3 needs 2 # 3 cannot start until 2 is done
ctask ready # what you could actually claim now
cagent register box-pi --owner me --caps arm64,camera
lac spend budget lane:research 5.00 # a hard daily cap; work stops, never overspends
lac spend --by task # where the money wentcbuild chains its recipe steps automatically, so a reviewer cannot start
before there is anything to review. See docs/COSTS.md.
The default install trusts everyone who holds the connection string, and takes your word for who you are. That is right for one person and wrong for a team.
Team mode gives every person and agent their own Postgres role, and moves identity and permissions into the database:
psql "$LAC_ADMIN_DSN" -f schema/team-mode.sql
bin/lac-admin adduser alice --perms research,docs --role researcher
bin/lac-admin revoke alice # cuts live sessions, not just new ones
bin/lac-admin audit alice # append-only; no member can rewrite itAfter that, LAC_NAME=someone-else does nothing — the server stamps the
sender from the connected role — and an agent scoped to research cannot claim
a deploy task even from raw psql. Verify it yourself rather than trusting
the claim:
tests/run-all.sh team-mode # the boundary, asserted from a hostile clientSetup, TLS, and the honest list of what it still doesn't protect: docs/TEAM.md.
Any MCP client can join a fleet without installing the CLIs — it talks to the
database directly, so the client can be on a laptop and the fleet on a server.
See mcp/README.md.
{"mcpServers": {"lacunari": {
"command": "uvx",
"args": ["--from", "/path/to/lacunari/mcp", "lacunari-mcp"],
"env": {"LAC_DSN": "postgres://…", "LAC_NAME": "desktop-claude"}}}}| Variable | Meaning |
|---|---|
LAC_DSN |
the database — the only required setting |
LAC_NAME |
this node's stable identity; required, never invented for you |
LAC_ESCALATE_CMD |
how the keeper reaches a human (unset = bus only, which is the weak path) |
LAC_EXECUTOR |
the program the drainer runs tasks through |
LAC_DRAIN_LANE |
which lane the drainer works (default auto) |
LAC_LEASE_MIN |
how long before a silent claim is released (default 20) |
LAC_COORDINATOR |
who gets named in the keeper's nag |
CLAUDE_NAME is accepted as an alias for LAC_NAME, for existing deployments.
There is no bundled web UI, deliberately — a board is the part every team wants differently. What ships instead is a surface to build one against:
ctask list --json # --json on every read command
cboard --json # lanes, tasks, presence and inbox in one object
psql "$LAC_DSN" -f schema/views.sql # stable views: the integration contract
LISTEN lac_update; # push, not polldocs/INTEGRATING.md has the details, including the rules for writing your own peer without these CLIs at all.
Most teams adopting this already have GitHub Issues, Jira, PagerDuty and a Slack channel. A coordination layer that ignores those is a second silo, and the second silo loses. So work flows both ways on day one:
# in — idempotent, so it is safe on a webhook AND on a timer
gh issue list --repo acme/api --json number,title,body,url,state \
| lac ingest --source github --map gh --lane backend
# out — an outbox with retry, so Slack being down delays but never loses
lac hook add slack "$SLACK_URL" --template slack --lanes ops,deploy
lac hook add ci "https://ci.internal/lacunari" --events task.done,task.blocked --secret "$KEY"Ingest is keyed on (source, external_id): re-syncing 500 issues creates zero
duplicates. Closing an issue upstream closes the task here — but an upstream
sync can never reopen work someone here already finished. Built-in mappings
for github, jira, linear, plus generic for everything else.
See connectors/README.md.
The queue tracks what you are doing. The library tracks what you have, and for a group pooling material that is the half that turns into a mess first.
lac doc add scans/*.pdf --source alice --subject "Operation Market Garden"
lac doc index . --source my-repo # a repo is just a corpus that compiles
lac doc extract # facets: EXIF, symbols, imports, PDF metadata
lac doc symbol lac_claimable # defined where, imported by whom
lac doc near 51.9797 5.9110 5 # photographs within 5 km of the bridgeDocuments are keyed by content hash, so three people uploading the same scan under three filenames produce one document with three contributions — nobody's attribution is lost, and independent copies count as corroboration. Where two sources disagree, both are kept and the contradiction is recorded rather than quietly resolved. LIBRARY.md · INDEXING.md · CODEBASE.md
Indexing gives you facts; the web gives you relationships and the holes in them.
lac web build # subjects, mentions, edges
lac web path "Jacques Goudstikker" "Altaussee"
lac web gaps
lac web suggest --dispatch # → the task queueSimilar names are proposed for merging, never merged silently. An archive that quietly decides two spellings are one person has destroyed evidence. RESEARCH.md
Goals run the other direction — top-down, an objective decomposed into a tree agents can claim. Not planned up front: the decomposition at depth 4 depends on what was learned at depth 2, so leaves are expanded when they are reached, with their siblings' findings in hand. Work starts before planning ends, and a wrong branch is pruned before its subtree exists.
lac goal add "Establish what happened to Portrait of a Young Man" \
--criteria "A dated chain of custody from 1939 to 1945" --depth 3 --budget 25.00
lac goal plan 1 # expand ONE level, grounded in what you actually holdDepth is capped, budget is checked before expanding, malformed planner output
is dropped rather than filed, and every expansion records what the planner was
shown and what it returned — so "why is Lacunari doing this?" has an answer.
planners/offline needs no model at all.
GOALS.md · planners/README.md
And two groups can connect without either surrendering their catalogue:
lac peer add kew https://kew.example/catalogue.json
lac peer pull kew
# 2 they hold that we do not · 1 overlap · 1 titled differentlySearch their catalogue without holding a byte of it, then fetch only what is worth having, hash-verified on arrival. A peer's catalogue is evidence, not authority: pulling never overwrites your metadata. FEDERATION.md
A result is prose — it says an agent claims something happened. For research,
and for anywhere a stranger has to build on your answer, that is not enough:
lac attest 41 --output scores.csv --claim "ranking of the 3 windows" \
--method "./score.py" --inputs candidates.csv --exact
lac verify --all # on someone else's machine
# ✓ #1 alice reproduced ranking of the 3 windowsA hash proves the bytes are unchanged; it does not prove they are right. What
counts is how many independent peers re-derived them — so --exact means
"deterministic, go ahead and re-run it", and the default recorded means
"pinned and unalterable, but I am not claiming a re-run reproduces this."
Marking a sampled model output --exact would fail verification for an honest
reason, so the modest default is the right one.
Disagreements are recorded, not hidden. Attestations cannot be edited or deleted. See docs/VERIFIABLE.md.
A build queue terminates. Maintenance does not — the certificate expires again, the docs drift again. Cron is the wrong shape for it: it creates work whether or not anything is wrong, and the queue fills with no-ops.
Declare the state you want instead. An observer reports what is. A task is raised only for the gap — and withdraws itself when the gap closes:
lac reconcile add cert-expiry \
--component "api.example.com" \
--intent "the TLS certificate has more than 30 days left" \
--observer "$PWD/observers/tls-expiry api.example.com 30" \
--every 3600However often it runs, drift produces exactly one task; repair closes it with no one tidying up. See docs/MAINTAINING.md.
Model spend is the cheap half. Human time is the other half, and a budget that counts only API calls tells a project manager nothing:
lac spend time 42 90 150 # 90 minutes at $150/hour
lac spend --by true # model + labour, the real number
lac spend budget tree:7 6h # cap a department's HUMAN time, not just $ lane | tasks | human_min | model_cost | labour_cost | total
--------+-------+-----------+------------+-------------+--------
design | 1 | 135.0 | 0.18 | 337.50 | 337.68
Either ceiling stops work — whichever runs out first.
The point of all of it:
lac work --lanes backend,docs --concurrency 3 & # on each machine
lac spend budget global 10.00 # a ceiling it cannot cross
# go to bed
lac digest # in the morningLACUNARI DIGEST — last 12 hours
2 shipped · 0 still open · 1 blocked · 1 waiting on you · $0.04 spent
SHIPPED
#2 [auto] Refresh the search index (night)
#1 [auto] Rotate the log archives (night)
NEEDS YOU (nothing moves on these until a human acts)
blocked-task #3 0m Renew the TLS certificate
→ executor error rc=4: cannot reach the DNS provider
- docs/ARCHITECTURE.md — how it works, what was originally intended, and where that changed
- docs/STATUS.md — where the project stands and what it grew out of
- docs/ALTERNATIVES.md — what to use instead of this, and the three things that are actually different here
- docs/TEAM.md — running it with other people
- docs/MAINTAINING.md — reconciliation: maintenance that isn't cron
- docs/COSTS.md — money, human time, and hard budgets
- docs/VERIFIABLE.md — attestations, re-derivation, lineage
- docs/LIBRARY.md — cataloguing, search, dedup, disagreement
- docs/INDEXING.md — facets, code symbols, geography, gaps
- docs/RESEARCH.md — the web: entities, connections, what is missing
- docs/GOALS.md — objectives decomposed into claimable work
- docs/CODEBASE.md — pointing it at your own repository
- docs/VERSIONING.md — versions, lines, changesets, merge: the git overlap
- planners/README.md — the planner contract
- docs/FEDERATION.md — connecting two groups' catalogues
- docs/INTEGRATING.md — JSON, views, and the event stream
- rules/AUTONOMY-CHARTER.md — the default answer to "should the agent ask me first?"
- roles/README.md — writing a role brief
Postgres 12+, psql, bash 4+. The MCP server needs Python 3.10+.
Copyright © 2026 Evan King. Licensed under the GNU Affero General Public License v3.0 or later — see LICENSE.
In short: use it, run it, modify it, self-host it, freely. If you distribute it or offer a modified version to others over a network, you have to release your source under the same terms.
Commercial licensing. If that does not work for you — you want to build Lacunari into a proprietary product, or offer it as a hosted service without publishing your changes — a commercial license is available. Contact hello@lacunari.com. Every contribution is covered by a CLA, so the terms can actually be granted.