Engineering

Fork the conversation, or go in cold.

“Send the report to Sarah” is clear only if the worker knows which report and which Sarah. Every delegated task therefore makes an explicit choice: inherit the conversation, or start with the request alone.

Unify has several layers of workers. The conversation layer dispatches a task, that task may call a manager, and the manager may delegate again. At every boundary, the parent knows details that the request itself does not contain.

The caller chooses between two behaviours. With parent context, the child forks the outer conversation. It receives the history up to the moment of dispatch, then continues on its own timeline. Without that context, it starts cold with only the request text.

What the fork actually is

When act(...) includes context, the child's prompt gains a section called ## Parent Chat Context. It contains the outer messages, notifications, and recent results. Their roles are renamed to outer_user and outer_assistant, so the child cannot mistake them for turns in its own task. The framing explains that this history supplies the broader goal while the child handles its narrower assignment.

As with a Git fork, the child receives history at the branch point but not a live copy of everything that happens later. A relevant change in the outer conversation arrives as an explicit interjection carrying only the new parent state. We also remove the conversation layer's steering tools from the snapshot, because the child cannot call them.

Where the request string falls short

The parent model usually writes a reasonable request. It can still omit a detail that feels obvious in the current conversation. The child then sees ambiguity that the parent could not see from inside that context.

One outer conversation dispatching the same request two ways. With context=True, the task inherits the full history as Parent Chat Context and already knows which report and which Sarah. With context=False, the task knows only the request string and must ask a clarification or guess.
The same dispatch, forked and cold.

Say the thread has touched two documents this week, the Q2 board deck and the weekly metrics report, and also two Sarahs, one in finance mentioned three messages up and one in the contact book who does design. The user says "send it to Sarah once it's done", and the conversation layer dispatches act("email the report to Sarah when it's finalized"). Which report, and which Sarah? The forked task doesn't even register that there's a question, because the answers are sitting in its inherited history. The cold task can only ask or guess.

There's a quieter version of this too. Twenty messages ago, about something else entirely, the user mentioned they're flying on Monday. Later a task gets dispatched to schedule a vendor follow-up, and nothing in that request says "not next week", because no reasonable query-writer would think to include it. The forked task sees the flight and books Friday. The cold task books Tuesday and is wrong in a way nobody catches until the calendar invite goes out.

Asking isn't a disaster, since clarifications bubble up through the layers cleanly. But every clarification is a round-trip that interrupts someone for something the system already knew. Guessing is worse than asking, because a wrong assumption carried out confidently is roughly how you get people to stop delegating things. If you pass the context eagerly then the ambiguity mostly never comes up.

The same choice at every layer

Manager calls such as contacts.ask and transcripts.ask accept the same parent-context argument. The actor adds its local messages to the context it inherited, so the manager can see the full chain. Generated Python does not have to pass this bookkeeping itself. A proxy around the primitives injects the context behind the scenes.

Who decides

Whether to fork is itself a judgment call, so we handed it to the model doing the dispatching. The conversation layer's act tool takes include_conversation_context, which defaults to true, with docstring guidance to switch it off when the task is self-contained, like a web search or a simple lookup where the query really is all there is to say. The choice sticks too, so if you opt out at dispatch then later interjections into that task skip context forwarding as well. Nested loops make the same per-call decision on their own boundaries.

A self-contained lookup can still run cheaply without context. A task whose meaning depends on the conversation can inherit it with one argument. We default to including the context because a request rarely captures every detail that may matter later. I'm less certain that true should remain the default at every internal boundary, but the explicit choice has held up.

Where to look

All open at github.com/unifyai/unify:

Read next

The rest of the notes