Building Lucifer: an open-source AI company you can watch ยท โญ GitHub
๐ Start here: 1. Meet Lucifer ยท 2. Why I built it ยท 3. Architecture
๐ Use it: 4. Getting started ยท 5. Connect an LLM ยท 6. Office tour
๐ Build the engine: 7. Org chart in YAML ยท 8. Real delegation ยท 9. Sandboxed tools ยท 10. Simulation brain
๐จ Build the office: 11. 3D office with R3F ยท 12. WebSocket + zustand ยท 13. Visualizing workflow
๐งญ What's next: 14. Roadmap ยท 15. Contributing
Lucifer is a monorepo with two packages and one contract between them. This post maps the whole system, so the deeper posts in the series have somewhere to hang.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ lucifer-office โโโโ
you โโ assign task โโโถ โ 3D yacht office ยท kanban ยท activity feed โ
โโโโโโโโโโโโโโโโโฒโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ REST + WebSocket
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโ lucifer-engine โโโโ
โ org chart โ heartbeat โ CrewAI agents โ LLM โ
โ tasks ยท delegation ยท workspace files โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
| Package | Role | Stack |
|---|---|---|
lucifer-engine | The company runtime: org chart, task queue, delegation, agent tools, REST and WebSocket API | Python 3.13, CrewAI, FastAPI |
lucifer-office | The office you watch and steer: a 3D yacht and beach where each agent is an angel | React 19, React Three Fiber, drei, zustand, Vite |
The engine
The engine is small, about 1,900 lines of Python, and each file has one job:
src/lucifer_engine/
protocol.py data types shared with lucifer-office
config.py environment variables and the org chart
store.py in-memory state saved to JSON, thread-safe, emits events
engine.py heartbeat, dispatch, waking parent tasks
session.py what an agent may do on a task
prompts.py backstory and task brief
language.py task language detection
brains/crew.py CrewAI with Claude or Cloudflare AI Gateway
brains/sim.py simulation brain
server.py FastAPI REST + WebSocket
cli.py lucifer-engine serve | schema
Store: the single source of truth
CompanyStore holds agents, tasks and the activity log in memory, saves them to data/state.json on a short debounce, and emits an event on every change. It is thread-safe, because each agent's brain runs in its own thread. Listeners run synchronously, under the lock, so events go out in the same order as the changes.
Engine: the heartbeat
Every LUCIFER_HEARTBEAT_MS (2 seconds by default), the engine runs a tick(). Each idle employee picks up its highest-priority todo task, up to LUCIFER_MAX_CONCURRENT agents at once (4 by default), and a work session starts in a thread pool. When something changes, for example a new task or a finished session, the engine calls nudge() so it doesn't have to wait for the next tick.
Session: the rules of the company
A TaskSession is the only way a brain can touch the company. It knows who the agent may delegate to (only people below it in the org chart), where files may go (inside the workspace) and whether the task is still open. Keeping those rules in one class means the CrewAI brain and the simulation brain can't drift apart.
Brains: who does the thinking
A brain is anything with a work(session) method:
class Brain(Protocol):
kind: Literal["crew", "sim"]
def work(self, session: TaskSession) -> WorkUsage: ...
CrewBrain builds a one-agent CrewAI crew for each session and talks to Claude or to Cloudflare AI Gateway. SimBrain follows a script with no API calls. With LUCIFER_BRAIN=auto (the default) the engine picks crew when a key is configured and sim otherwise.
The task lifecycle
todo โโโถ in_progress โโโถ done
โโโโถ failed
โโโโถ waiting โโ(all subtasks finished)โโโถ todo โโโถ in_progress โโโถ done
โโโโถ cancelled (from any open state, together with its open subtasks)
The interesting arrow is waiting โ todo. A manager who delegated ends its session. When the last subtask finishes, the engine moves the parent back to todo. On its next turn the manager receives a brief that lists every subtask with its status, result and files, and merges them. The same mechanism works at every level of the org chart, so a three-level tree needs no special handling.
Agents have their own status too, and it drives the animations in the office: idle, thinking, working, waiting and offline.
The protocol
JSON is camelCase, generated from Pydantic models with an alias generator. The engine's protocol.py is the source, and the office mirrors it in protocol.ts. You can export a JSON Schema to check them against each other:
uv run lucifer-engine schema > protocol.schema.json
REST: giving orders
| Method | Path | |
|---|---|---|
| GET | /api/snapshot | Full state: company, engine, agents, tasks, activities |
| POST | /api/tasks | Assign work: {title, description?, assigneeId?, priority?, language?} |
| POST | /api/tasks/{id}/cancel | Cancel a task and its open subtasks |
| POST | /api/engine/start ยท /stop | Start or pause the company |
| GET | /api/files, /api/files/{path} | Browse the workspace |
WebSocket: watching
On connect, /ws sends one snapshot event, then pushes every change as it happens:
{ "type": "task.updated", "data": { "id": "T-3f9a1c2e", "status": "waiting", "assigneeId": "michael" } }
The event types are agent.updated, task.created, task.updated, activity and engine.updated. Activities are the narrative of the company: task_assigned, task_started, task_delegated, update, thought, file_written, task_completed, task_failed and error. A task_delegated activity carries a targetAgentId, and that is all the office needs to fly a comet from manager to report.
The office
The office is a Vite app with three layers:
state/: a zustand store with a pureapplyEvent(state, event)reducer, plus pure derived views such as task trees, progress and phases inworkflow.ts.scene/: the React Three Fiber world, including the yacht, ocean, beach, angels, work links and the camera director.ui/: DOM panels on top, including the top bar, team roster, board, missions, drawers and the assign bar.
Everything visible is derived from the engine's state. The office never asks "what is Michael doing?". It reads Michael's status and the latest activities, then animates them. That keeps the office a pure view of the company, and it is why several browser tabs, or several people, can watch the same company at once.
Where to go next
- Want to run it? Getting started.
- Curious about delegation? Real delegation with CrewAI.
- Interested in the event stream? WebSocket + zustand.
All of the code is on GitHub: siri2moon/lucifer.
Lucifer is open source under the MIT license. Clone it, run it for free with the simulation brain, and โญ star it if you'd like to follow along: github.com/siri2moon/lucifer.
Next in the series: Getting Started with Lucifer: Run an AI Company Locally in 5 Minutes.

