Lucifer Architecture at a Glance: Engine, Office and the Protocol Between Them

Cover Image for Lucifer Architecture at a Glance: Engine, Office and the Protocol Between Them
AI & Machine Learning5 min read

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         โ”‚
                         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
PackageRoleStack
lucifer-engineThe company runtime: org chart, task queue, delegation, agent tools, REST and WebSocket APIPython 3.13, CrewAI, FastAPI
lucifer-officeThe office you watch and steer: a 3D yacht and beach where each agent is an angelReact 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

MethodPath
GET/api/snapshotFull state: company, engine, agents, tasks, activities
POST/api/tasksAssign work: {title, description?, assigneeId?, priority?, language?}
POST/api/tasks/{id}/cancelCancel a task and its open subtasks
POST/api/engine/start ยท /stopStart 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 pure applyEvent(state, event) reducer, plus pure derived views such as task trees, progress and phases in workflow.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

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.