How Lovable’s agents work together
Three things that are easy to conflate: what happened, what an agent needs to know, and when it should run. Lovable keeps them apart — trajectories, context projection, and inboxes + activations. (On a typical weekday, Lovable’s Trajectory System appends roughly half a billion events, covering about 2.6 million user turns.) Every animation below is one of those ideas slowed down enough to watch.
Based on lovable.dev/blog/how-lovable-agents-work-together. Every figure runs itself when scrolled into view. Prefer your own pace? Switch a figure to Step by step in its controls: it then waits for you, and Next / Back — or the ← → keys — move one beat at a time.
The same system, as a factory floor: messages ride the Agent Control Plane like parcels on a conveyor.
01Trajectories: an event log modeled after Git
Every agent conversation is a log of immutable events: UserMessage, AgentStart/AgentDone around a response, IterationStart/IterationEnd around each LLM call, and the full tool lifecycle in between. Nothing is deleted — even a revert is an appended Revert.
Each event has exactly one parent pointer. Named heads point at the tip of each line of history, like Git branches. That makes forking free: from any IterationEnd or AgentDone, a new head appears whose first event, ThreadForkConfig, points back at the fork point. No events are copied. And unlike Git, trajectories never merge — they only branch.
02Context is a projection of the trajectory
The trajectory is the source of truth, but it isn’t what gets sent to the model. Right after IterationStart is written, the prompt builder walks the head backwards and renders a prompt. How much the model sees, and in what shape, is decided per call.
Compaction shows why this matters. The agent writes PromptCompactionStart and keeps working while a summarizer runs on a side trajectory. At a later boundary the agent admits the result as PromptCompactionEnd. From then on, when the backward walk meets that End paired with its Start, it adopts the summary in place of the older events and renders everything after the compaction point as ordinary history.
03Streaming: partial events and field deltas
Events are whole and immutable, but LLM output arrives in chunks. So there’s a side channel for events that don’t exist yet:
a PartialOpened, then a run of PartialDeltas (“append this text”), pushed live to the browser.
When the block finishes, the real event is appended to the trajectory with the same id, which closes the partial.
Partials have no parent and are never persisted. A browser that connects halfway gets the persisted events plus each open partial materialized once — then follows the live stream. Refreshes and extra tabs all converge on the same trajectory.
04Two logs per agent: the inbox and the trajectory
Each agent has two logs. The inbox is where everything sent to it lands — user messages and ExternalAgentNotifications from other agents or the control plane — a subagent finishing, a message from another agent, a scheduled wake-up. The trajectory is what the agent actually did.
Other actors only ever write to the inbox. The agent copies unhandled messages onto its trajectory at run start and at every iteration boundary — so a message that arrives mid-response becomes an interjection instead of waiting for the turn to end.
05The Agent Control Plane
Spawn, result, progress report, hand-off, scheduled wake-up — ACP collapses every interaction into one primitive: append a message to the recipient’s inbox, then send an activation. Agents never call each other or know which machine the other is on.
The inbox write is the durable part. The activation is just a wake-up signal: if the recipient is asleep, any node can pick it up and start a run. If it’s already running, the next iteration boundary picks the message up and the duplicate activation is simply acknowledged and dropped — each trajectory has a single exclusive writer.
06Suspend and resume: agents that survive deploys
Right after an IterationEnd, the trajectory holds everything the next iteration needs. So the loop can just stop: the run exits suspended, no AgentDone is written, and the still-open agent block is the signal that there’s more to do. ACP sends a resume activation; any node can pick it up, check the inbox, and continue the same trajectory.
During a deploy, nodes drain: most turns finish, the rest suspend at their next boundary and resume on fresh nodes. The sandbox lives separately; the work lives in the trajectory and in git. The process is disposable.
07Putting it together: Chats
The chat agent runs at the workspace level, on its own trajectory, with access to all your projects. You talk an idea through, and
once you confirm a change it calls send_message_to_project — which is just SendMessage: a notification appended to that project’s
inbox plus an activation that wakes its builder (or, if the builder is busy, is folded in at its next iteration boundary).
The chat agent writes each message to be self-contained. It can read a project’s chat and state to decide what to say, but the builder only ever sees the message it was sent.
Progress flows back over the same primitive. Mid-turn updates and the final result (credits used, files changed, build status) land in the chat agent’s inbox as ExternalAgentNotifications, and the chat agent admits them like any other inbox message — at its next iteration boundary if it’s running, or at run start once an activation wakes it. Several calls in one turn run projects in parallel.