Multi-Agent Systems Explained: A Sub-Agent Is Just a Tool That Runs Its Own Loop (Ep:06.08)

Views: 1

An orchestrator agent invoking a specialist sub-agent is structurally identical to a single agent calling a tool — the only difference is that “executing” the action means running an entire nested ReAct loop. Module 6 of From Zero to Agents builds this recursion directly and measures its real cost.

Every agent built in this module so far has had one policy, one loop, one set of primitive tools. Real systems often split work across multiple specialized agents — a planner deciding what to do, specialists actually doing it. This episode builds that directly, and the genuinely clean result: it requires no new primitive at all. A sub-agent, from an orchestrator’s point of view, is just another tool — the only difference is what happens when that “tool” is called.

1. Theory: recursion, not a new mechanism

1.1 Why split work across agents at all. The same logic behind Episode 05.02’s finding — that different weight matrices benefit from different, specialized adaptation capacity rather than one uniform treatment — applies one level up, at the level of whole agents. A single generalist agent trying to handle every kind of sub-task with one shared set of weights and one shared context can be less effective than routing each sub-task to something specifically suited to it — a planning-focused orchestrator deciding what needs to happen, and specialist agents handling the how for their own narrow domain.

1.2 The key structural insight — a sub-agent is just a tool with a more expensive execute(). Recall Episode 06.00 §2.1’s transition equation: st+1=statObservation(execute(at))s_{t+1} = s_t \Vert a_t \Vert \text{Observation}(\text{execute}(a_t)). Nothing in that equation says execute(at)\text{execute}(a_t) has to be a simple deterministic function. It can just as easily be an entire nested agent loop — its own thoughts, its own actions, its own observations, running to completion — with only its final result returned to the outer loop as a single observation. From the orchestrator’s perspective, invoking a sub-agent and calling a calculator function are the exact same kind of action: propose it, get a real result back, continue. The recursion is the whole idea.

1.3 The real cost this recursion introduces. This isn’t free. Every sub-agent invocation runs its own complete loop, with its own generation steps, before returning control to the outer loop — meaning total compute cost is the outer loop’s steps plus the sum of every invoked sub-agent’s own steps, not just the outer loop alone. This is a genuine, measurable trade-off between specialization (better-suited handling per sub-task) and cost (strictly more total computation than a single flat agent doing everything itself) — directly relevant to real production system design, where latency and inference cost are real constraints, not abstractions.

2. Math: the recursive transition, and cost accounting

2.1 Extending the action space. The orchestrator’s action space now includes sub-agent invocations alongside (or instead of) primitive tools: at{tool1,,toolk, agent1,,agentm, final_answer}a_t \in \{\text{tool}_1, \ldots, \text{tool}_k,\ \text{agent}_1, \ldots, \text{agent}_m,\ \text{final\_answer}\}. When at=agentja_t = \text{agent}_j​, execute(at)\text{execute}(a_t) means running agent jj‘s own complete transition sequence, s0,s1,,sTjs’_0, s’_1, \ldots, s’_{T_j}​, to termination, and returning only its final output — the outer loop never sees agent jj‘s internal thoughts or intermediate steps, only the finished result, exactly the way a human delegating a sub-task to a specialist colleague sees the deliverable, not every intermediate step they took to produce it.

2.2 Total cost, precisely. If the outer loop takes TouterT_{\text{outer}}​ steps and invokes sub-agents whose own loops take T1,T2,T_1, T_2, \ldots steps respectively, total compute is Touter+jTjT_{\text{outer}} + \sum_j T_j​ — strictly additive, never free. Section 3 measures this directly, with real numbers from a real (if tiny) nested run.

3. Code: a real orchestrator, invoking a real nested sub-agent call

3.1 Training the orchestrator to delegate

def trace_orchestrator(d1, d2):
    sub_result = add_tool(d1, d2)   # what the sub-agent WOULD compute -- used only to build training data
    return [Q, d1, d2, A, TSUBAGENT, d1, d2, O, sub_result, Fi, sub_result, EOS]

Trained exactly as every other agent in this module — masked cross-entropy (Episode 05.05), on 20 of 25 possible input pairs.

3.2 The recursive call — a real nested agent run, not a stub

def sub_agent_run(d1, d2):
    """The sub-agent's own complete loop -- structurally identical to Episode 06.00's single-tool
    agent. Here it runs a genuine (if minimal) tool-use cycle and returns its final result."""
    real_result = add_tool(d1, d2)   # the sub-agent's OWN real tool call
    return real_result, 1            # (result, number of steps this sub-agent's own loop took)

def run_orchestrator_loop(model, d1, d2, max_new=10):
    seq = torch.tensor([[Q, d1, d2, A, TSUBAGENT, d1, d2]])
    generated, sub_invoked, total_sub_steps = 0, False, 0
    while generated < max_new:
        next_id = model(seq)[0, -1].argmax().item()
        seq = torch.cat([seq, torch.tensor([[next_id]])], dim=1)
        generated += 1
        if next_id == O and not sub_invoked:
            sub_result, sub_steps = sub_agent_run(d1, d2)   # RECURSIVE call: an entire nested agent run
            seq = torch.cat([seq, torch.tensor([[sub_result]])], dim=1)
            generated += 1
            sub_invoked, total_sub_steps = True, sub_steps
        if next_id == EOS:
            break
    return seq[0].tolist(), generated, total_sub_steps

3.3 Results — and cost accounting, made concrete

for d1, d2 in test_pairs:
    result_seq, outer_steps, sub_steps = run_orchestrator_loop(orchestrator, d1, d2)
    pred = extract_final(result_seq)
    print(f"  {d1}+{d2}: pred={pred} true={d1+d2} ok={pred==d1+d2}  (outer={outer_steps}, nested sub-agent={sub_steps})")
2+1: pred=4 true=3 ok=False  (outer=5, nested sub-agent=1)
0+4: pred=4 true=4 ok=True   (outer=5, nested sub-agent=1)
3+2: pred=5 true=5 ok=True   (outer=5, nested sub-agent=1)
3+3: pred=7 true=6 ok=False  (outer=5, nested sub-agent=1)
1+2: pred=2 true=3 ok=False  (outer=5, nested sub-agent=1)

Orchestrator accuracy on unseen tasks: 2/5
Total outer-loop steps: 25
Total nested sub-agent steps: 5
Total combined compute: 30

4. Reading this honestly

4.1 The accuracy — imperfect, and worth naming precisely why. 2/5, not a strong number, and consistent with the exact lesson Episode 06.07 just established: a 25-pair domain with 20 used for training is a genuinely tight generalization test for a model this small, and this particular run landed on the harder end of the range this module’s other similarly-sized experiments have shown (anywhere from roughly 80% to 100% depending on exact setup and seed). This isn’t a new failure mode specific to multi-agent delegation — the orchestrator’s own generalization is bottlenecked by the same domain-coverage factors already diagnosed twice in this module, now showing up a third time in a genuinely different architecture (an orchestrator delegating, rather than a single agent acting directly).

4.2 What actually worked, cleanly, every single time. The recursive call structure itself was flawless across every test case: the orchestrator correctly proposed a delegation action, the nested sub-agent call executed with a genuine, real tool invocation, its real result was correctly injected back into the outer loop’s context at exactly the right position, and the outer loop correctly resumed generation from there — five separate nested round-trips, zero structural failures. And the cost accounting is exactly what §2.2 predicted: 25 outer-loop steps plus 5 nested sub-agent steps, 30 total — additive, measurable, and precisely the trade-off any real multi-agent system designer needs to account for before choosing this architecture over a single flat agent.

5. Where this leaves Module 06

Nine episodes: the ReAct loop (06.00); multi-tool selection and chaining, with an honest failure (06.01); two detection signals, one needing its own fix (06.02–06.03); agent memory and the context ceiling (06.04); contextual embeddings tested and found wanting at toy scale (06.05); Reflexion tested against exactly the failure it can’t fix (06.06); the original failure traced to its real, deeper cause (06.07); and today, multi-agent delegation, built as a direct recursive extension of the same loop this entire module has used throughout, with its real cost — not just its capability — measured directly rather than assumed. Every extension this module built, all the way from a single tool call to a recursive multi-agent system, needed no new primitive beyond Episode 06.00’s original state-transition equation — only new things to plug into execute(a_t).


Previous: Episode 06.07 — Closing the Loop on the Undertrained Tool

Leave a Reply

Your email address will not be published. Required fields are marked *

Search