An interactive companion to “Inside Chats”

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.

UserMessage AgentStart / AgentDone IterationStart / End thinking content tool lifecycle fork / head compaction ExternalAgentNotification activation Revert

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.

One builder turn, one parent pointer per event. Click Fork here to branch from the latest boundary — watch how nothing gets copied.

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.

Compaction is two events on the agent’s trajectory. The summary is produced elsewhere and admitted later; the prompt builder swaps it in during its backward walk.

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.

Streaming happens beside the trajectory, not in it. Only the final event is persisted. Try Open a late tab mid-stream.

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.

Other actors append to the inbox whenever they like. The agent admits messages on its own schedule, at iteration boundaries. Send a few messages while it’s working.

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.

A subagent reporting back through ACP. The durable step is the append to the parent’s inbox; the activation is only the wake-up.

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.

At an iteration boundary the process is disposable. Hit Deploy and watch a run hop nodes without losing a single event.

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.

Instructions down, progress and results up — all as notifications landing in inboxes.