Dayward AI
Week 3 · D17About 4 hours

Subagents and Parallelism: Independent Context, a Tool Allowlist, Worktree Isolation, and Result Aggregation

Split the work when one agent can't finish it alone: implement a subagent mechanism where each subagent has its own context and tool allowlist, edits files in its own git worktree without stepping on others, and aggregates results back into the main session in structured form — and work out which tasks shouldn't be parallelized.

Today's goals 0/3

Sign in to tick these off and save your progress.

Today's Goals

  1. Implement subagent dispatch and result collection, and explain its context relationship with the main session
  2. Use worktrees for file isolation, and handle conflicts when parallel work touches the same file
  3. Judge which tasks suit parallelism and which are only slower and more expensive in parallel

Yesterday put a piece of writing in front of the model at the right moment; today sends a whole model out to work. When you finish, scroll back up and tick the three goals off.

Plain-Language Walkthrough

Taking on apprentices: split the work, one desk each

When the work piles up, the old hand takes on two apprentices. And three of the arrangements they make are so obvious they never think about them.

First, they explain this one job and nothing else — they do not replay every phone call of the morning. Second, the toolbox is prepared: someone sanding a part gets sandpaper and calipers, not the cutting machine. That is not distrust; fewer tools mean fewer ways for the hand to slip. Third, one desk each — two people must never crowd the same vise fighting over the same part.

And then there is the one a new supervisor forgets: at the end of the day, check whether the two pieces of work actually fit together. If both apprentices sanded the same part, each in their own way, you cannot shut your eyes and fit whichever arrived last. That is not merging. That is quietly throwing the first one away.

Those four things are exactly what mca gets today: its own context, its own tool allowlist, its own desk, and one honest merge.

What a subagent is: a one-shot loop, not another user

Start by correcting the impression that sends people off course. A subagent is not "another user talking to your Agent," and it is not a new architectural layer. It is the same run() loop from day five, run once more, with four things swapped out: a different message array, a smaller tool table, a tighter budget, and a different working directory.

That claim has a hard test attached. In today's lab, not one line under kernel/ changes — no field is added to the message, tool or event protocols, and the loop is untouched. A subagent wraps around the loop; it is not a branch inside it.

So what does "independent" actually mean? Three things, each blocking a different kind of failure.

An independent message array blocks contamination. A subagent doing one small job may need seven or eight round trips: read the file, edit it, notice the edit was wrong, edit again. Those round trips are worth nothing to the main session and occupy its window for real. Worse, they drag it off course — the main session was thinking about how to break the requirement down, and now its context is full of "the indentation on line 37 is off."

An independent allowlist blocks wandering. Fewer tools, fewer chances to pick the wrong one. A subagent that only has to edit documentation gains nothing from a tool that runs commands, and carries all the downside of it suddenly deciding to run a build.

An independent budget blocks collateral damage. One subagent spinning until it hits a ceiling should not burn through the allowance for a different job. Day six's LimitTracker is reused here verbatim, just with tighter numbers — do not build a second ledger for subagents, or you will shortly own two sets of books that disagree.

There is one more rule, and it runs the other way: a subagent must never inherit the main session's system prompt. That prompt is full of things that are only true of the main session — the conversation may have been compacted, the user pastes material with @ references, skills may appear on their own, and when something is unclear you can ask. None of that holds for a subagent: it has no history, nobody to ask, and no next turn. Inheriting the prompt does not leave it "knowing a bit more." It leaves it waiting for something that will never happen.

The allowlist: authorization happens at the moment of dispatch

The implementation is simple: pick a few ToolDef objects out of the parent registry by name and register them again. What matters is that it reuses the same tool objects. Truncation (day three), approval (day five) and repeat detection (day six) all hang off the ToolDef interface, so reusing the objects carries every one of those mechanisms into the subagent with nothing rewritten.

src/agents/run.ts
export function buildSubRegistry(parent: ToolRegistry, allow: string[]): SubRegistry {
  const registry = new ToolRegistry()
  const unknown: string[] = []
  const blocked: string[] = []
  for (const name of [...new Set(allow)]) {
    // The dispatch tool never enters a sub-table, even if the allowlist names it
    if (name === DISPATCH_TOOL) {
      blocked.push(name)
      continue
    }
    const tool = parent.get(name)
    // A misspelled tool name must be reported: silently ignoring it looks like
    // "this subagent got nothing done"
    if (!tool) {
      unknown.push(name)
      continue
    }
    registry.register(tool)
  }
  return { registry, unknown, blocked }
}

The line about the dispatch tool never entering a sub-table is a hard rule, not a precaution. Let subagents dispatch subagents and the dispatch tree has no boundary: the budget stops adding up (whose account does the third level spend from?), and when something goes wrong you cannot say which level edited the file. If you want more layers, build them explicitly. Do not let recursion happen by default.

The allowlist also answers a permission question on the way past: where exactly does approval happen?

It happens once, at dispatch. dispatch_agents is itself a tool that changes things, so it passes through day five's approval gate — and inside the subagent there is no further gate. That is not laziness. Approving every step is theater here: a user can understand "send two workers to do these two jobs, and they may only read and edit files," and cannot understand a subagent's seventh edit_file, because they cannot see its context and have nothing to judge it on. The boundary of authorization moves to the allowlist instead: if no command-running tool is on the list, no command can ever run. One authorization a person can understand beats ten confirmations they cannot.

Isolation by worktree: a shared working tree guarantees collisions

Now for the most concrete part of the day. What happens when two subagents edit files in one repository at the same time?

Not "sometimes they conflict." You cannot know what happened. A and B both read calc.js. A writes its change back; B, still holding the copy it read, performs an exact replacement — and day four's rule that the old text must match verbatim saves you, once: B's replacement fails. Next time B edits a different passage, the match succeeds, and the file now holds A's change and B's change together, two changes never designed with each other in mind. A green test run does not mean it is right, only that those two happened not to collide.

So isolation is not an optimization, it is the precondition for parallelism. That is exactly what git's worktree is for: one repository can be checked out into several directories, each with its own HEAD and index, while the object store is shared — opening a desk is a constant-cost operation, and a two-gigabyte repository does not get copied twice.

A real implementation runs these four commands, none of them mysterious:

BashBash
git worktree add --detach ../.mca/agents/calc-guard HEAD   # open a desk
# ...the subagent works in this directory, its cwd pointing here...
git -C ../.mca/agents/calc-guard diff HEAD                 # collect its output
git worktree remove --force ../.mca/agents/calc-guard      # pack up

One thing has to be said plainly: what this course's lab uses is not a real git worktree. Our sandbox repo work/repo has not been under git since day one (the seed.ts that generates it is a frozen file and does not change all course), so there is no object store to lean on, and the lab simulates a desk by copying the directory and recording its contents at the moment of dispatch. The two share the same semantics — take a baseline at dispatch, work only in your own room, collect the difference between baseline and now — so the conflict rules below hold just as well with real git underneath. But the simplified version does not recognize renames, does not handle binary files, and does not preserve permission bits, and real git does all three. Do not lift the simplified version into production as an implementation reference.

One piece of timing is easy to get wrong: the baseline must be taken at the moment of dispatch, not by comparing against the main working tree at collection time. During the seconds a subagent works, the main working tree may already have moved under someone else's hand — another subagent, or the user's editor. Treat an already-changed scene as the baseline and the computed difference will include things that subagent never touched.

Different files Same place changed Main session: the model splits the job into two work orders Approval gate: the user approves once Desk calc-guardown messages, allowlist, budget Desk doc-writerown messages, allowlist, budget Difference from baseline to now Difference from baseline to now Three-way comparison Write to disk and feed back a structured receipt Report a conflict, write nothing
Mermaid source
mermaidmermaid
flowchart TD
  A[Main session: the model splits the job into two work orders] --> B[Approval gate: the user approves once]
  B --> C1[Desk calc-guard<br/>own messages, allowlist, budget]
  B --> C2[Desk doc-writer<br/>own messages, allowlist, budget]
  C1 --> D1[Difference from baseline to now]
  C2 --> D2[Difference from baseline to now]
  D1 --> E[Three-way comparison]
  D2 --> E
  E -->|Different files| F[Write to disk and feed back a structured receipt]
  E -->|Same place changed| G[Report a conflict, write nothing]

How results come back: a receipt, not a transcript

The subagent is done. What should go back to the main session?

The intuitive answer is "paste back what it said," which hands straight back the window you just saved. What should come back is a structured receipt: who it was, what the work order said, whether it finished, which files it changed, which tools it used, what it cost, and its closing sentence. Everything it said along the way stays on its side and is discarded with it.

The lab's receipt looks like this — seventeen lines, and the shape is reproducible offline:

TextText
Dispatched 2 subagents. Receipts:
 
[calc-guard] done  order: add a divide-by-zero guard to divide in src/calc.js,
                   throwing "divide by zero" when the divisor is 0
  changed: src/calc.js
  tools used: edit_file
  usage: 1 tool call / about 398 tokens / 458ms
  receipt: src/calc.js updated: divide throws "divide by zero" when the
           divisor is 0; all other behavior unchanged.
 
Merge result: 2 files written, 0 conflicts.
  README.md -> changed by doc-writer, written
  src/calc.js -> changed by calc-guard, written

Three details deserve their own mention. Keep only what was said on the last turn: the "let me take a look first" lines are process, not conclusion, and cutting at each tool call is a good enough boundary. Truncate the receipt: one chatty subagent should not blow out the main session's window. Put conflicts first: a conflict is the thing in a dispatch that most needs a human decision next, and the model weights the first lines of a long text most heavily.

Conflicts on aggregation: two people changed the same place

This step is most often assumed to be easy. The intuitive implementation is "write each subagent's changes back in order," which means last writer wins: both receipts say "done," the main working tree holds one of the changes, and nothing on screen says so. This is the classic silent data loss of parallel Agents, and today's biggest trap.

The right approach is a three-way comparison: what the baseline was, what this side changed it to, and what the other side changed it to. Three rules, blunt but honest.

src/agents/merge.ts
for (const [file, edits] of [...byPath].sort((a, b) => a[0].localeCompare(b[0]))) {
  const by = edits.map((e) => e.by)
  const versions = new Set(edits.map((e) => e.after))
  // Rule three: several people produced different versions — write none of them,
  // because you have no way to judge which one is right
  if (versions.size > 1) {
    entries.push({
      path: file,
      by,
      action: 'conflict',
      why: `${by.join(' and ')} produced ${versions.size} versions`,
    })
    continue
  }
  // The main working tree drifts too: if the baseline no longer matches, do not
  // write, or you will silently overwrite what someone just saved
  if ((current.get(file) ?? null) !== (edits[0]?.before ?? null)) {
    entries.push({ path: file, by, action: 'conflict', why: 'this file changed in the main working tree after dispatch' })
    continue
  }
  // Rules one and two: one person changed it, or several produced identical
  // content — write it once
  applied.set(file, edits[0]?.after as string)
  entries.push({ path: file, by, action: 'apply', why: by.length > 1 ? 'identical content' : 'written' })
}

Two of those judgments are worth explaining.

Why write nothing on a conflict instead of picking one? Picking automatically requires a way to tell which is right, and you do not have one. Real git's line-level three-way merge can combine changes that do not overlap, which needs baseline line information plus a merge algorithm; this course leaves that to real implementations and guarantees one thing: nothing is lost silently. Reporting a readable conflict always beats quietly producing a version nobody designed.

Why is identical content not a conflict? That is one job done twice; write it once and move on. Report it as a conflict and the conflict signal becomes cheap, people stop reading it — and a warning nobody reads is no warning at all.

The cost of parallelism: when it is not worth it

Everything so far has been about doing parallelism correctly. This section is about when not to do it.

Look at the bill first. In the lab's dispatch, the main session's turn used 1405 input and 51 output tokens, and all that scrolled past on screen was one line saying two subagents had been dispatched. Then /agents unfolds the hidden part: calc-guard about 398, doc-writer about 363, 761 tokens the main session never saw and you pay for all the same. (Those figures come from the offline estimator and are identical across two consecutive runs on the same machine; the timing columns only support relative comparison.)

That is parallelism's first cost: every subagent pays for the system prompt and the tool table all over again. Context cannot be shared; that is a consequence of isolation, not a gap in the implementation. The smaller the task, the larger that fixed cost looms — dispatch a subagent to change one line of copy and explaining who it is and what it may use costs more than the work.

The second cost is debugging. When a serial run goes wrong you read back along one event log. In parallel there are N interleaved timelines, and the log is written in arrival order — two subagents' output ends up threaded together, and telling at a glance which sentence belongs to whom is hard. This is why a receipt must carry the desk name: it is your only attribution clue.

The third cost is merging. The rules in the last section are already the minimum version; real situations add renames, deletions and binary files. You committed to those costs the moment you chose to parallelize; the invoice just arrives later.

So the criteria can be stated bluntly. Three kinds of work should not be sent out:

This kind of workWhy it should not be parallel
Ordered dependencies (change the interface, then every caller)The second step needs the first step's result, so a dispatched worker only waits, and it ends up serial anyway
Edits to the same set of files (refactoring one module)It will hit the conflict rules for certain, and after the merge fails the work has to be redone
One-step jobs (change a line of copy, find where a function lives)The fixed cost of dispatch exceeds doing it yourself

Turned around, there is a very serviceable self-check for work that does suit parallelism: if these two jobs were given to two real people at the same time, would they need to talk to each other? If yes, do not dispatch. If no, that is where parallelism belongs. The lab's two jobs — add a divide-by-zero guard to divide, and add a paragraph to the README — are typical: different files, and whoever finishes first does not affect the other.

One boundary to close on, so today does not blur into the days ahead: today is about working at the same time and not stepping on each other. Tomorrow's background tasks are a different thing — how a long-running job reports back without interrupting the conversation, where the difficulty is lifecycle and notification, not isolation.

Source Reading

Hands-On Lab

🧪 D17 lab: subagent dispatch, worktree isolation and result aggregation

Code location: labs/my-coding-agent-21days/day-17-subagents

Today leaves five exercises, all of the "runs fine without them, and fails silently" kind: a shared working tree, an allowlist that does nothing, subagents spending the main session's budget, last-writer-wins on merge, and prose fed back instead of a structured receipt. The starter passes six of thirteen unmodified.

  1. Make opening a desk really copy an independent worktree, and take the baseline at the moment of dispatch, then watch the isolation check go from red to green — the main working tree should not change by one byte before the merge.
  2. Get the allowlist right: register only the named tools, report misspelled names, and hard-block the dispatch tool itself, then watch the parent's 9 tools narrow to 2.
  3. Give each subagent its own budget, then squeeze one of them to a single round and watch it hit its own ceiling while the other is untouched.
  4. Switch the merge to a three-way comparison and run the conflict command from the README: two subagents change one file to different versions, and it should report a conflict and write nothing.
  5. Turn the feedback into a structured receipt, run MOCK=1 SELFTEST=1 pnpm start to see all thirteen checks pass, then use /agents to see how many tokens that dispatch quietly spent.

Acceptance is five ticks: all thirteen self-test checks pass; the two subagents' time ranges genuinely overlap; the main working tree is untouched before the merge; nothing is written when one file is changed to two different versions; and no desks are left behind under work/agents.

Interview Questions

Today's three questions test engineering judgment about parallel Agents, not "what is multi-agent":

  1. What should a subagent inherit from the main session, and what must it never inherit?
  2. Editing one repository in parallel, what do you isolate with? How are conflicts found and handled?
  3. Which tasks suit delegation to a subagent, and which get worse in parallel?

Full prompts, analyses and key points are in this course's day-seventeen question bank. Question three discriminates most — most people answer "parallelize anything separable," and few can state both the fixed cost of dispatch and the "do they need to talk to each other" test.

Checklist and Tomorrow

  • I can say that a subagent is the same loop run again, and why no protocol field changed today
  • I can name what each of the three kinds of independence blocks
  • I can explain why a subagent must not inherit the main session's system prompt
  • I can say why the dispatch tool may never enter a subagent's allowlist
  • I can say why approval happens only at dispatch, and what carries the authorization boundary instead
  • I can explain why a shared working tree is not "sometimes conflicts" but "you cannot know what happened"
  • I can name the four real git worktree commands, and why it is cheaper than copying a directory
  • I can say why the baseline must be taken at the moment of dispatch
  • I can name the fields a receipt carries, and why conflicts go first
  • I can explain why merging needs a three-way comparison, and why a conflict writes nothing
  • I can name parallelism's three costs, and the three kinds of work not to dispatch

Tomorrow is D18, "Hooks and Background Tasks: Lifecycle Hooks, Deterministic Checks, and Notifications That Don't Interrupt the Conversation." Today split the work so it happens at the same time; tomorrow makes certain checks happen automatically at fixed moments — one governs division in space, the other governs points in time.

Interview questions

  • What should a subagent inherit from the main session, and what must it never inherit?子 Agent 该继承主会话的什么、绝不该继承什么?
    Common in ChinaCommon overseasBasic#subagent#context-boundary

    How to reason about it · think before answering

    1. This tests whether you have actually dispatched one. People who only read docs answer "a subagent has its own context"; people who built one first separate what is reused from what must be rebuilt, because the two categories have different criteria.
    2. How to break it down - give one criterion, sort things into two piles by it, then show what breaks when the wrong thing is inherited.
    3. The criterion in one line - capability can be inherited, situation cannot. Tool implementations, truncation rules, the approval gate and loop detection are capabilities; they hang off the tool interface, so reusing the same tool objects carries them over with no rewriting.
    4. The first thing never to inherit is the message array. Seven or eight round trips for one small task is normal, they are worthless to the main session, they consume its window, and they drag it off course - it was reasoning about how to split the work and its context is now full of indentation on line 37.
    5. The second is the system prompt. The main one is full of things true only of the main session - history may have been compacted, the user pastes files with @, skills appear automatically, ask the user when unsure. None of that applies to a subagent, and inheriting it does not add knowledge - it makes the subagent wait for something that will never happen, such as an answer to a question nobody will read.
    6. The third is the budget. A shared allowance means one looping subagent burns the whole task's quota while some unrelated piece of work quietly fails, and the session that dispatched it never learns why. Reuse the same accounting class with tighter numbers; do not write a second set of books.
    7. One more thing must be cut off explicitly - the dispatch capability itself. The dispatch tool must never appear in a subagent's allow-list, or the dispatch tree is unbounded, the budget cannot be attributed, and after an incident you cannot say which level edited the file.
    8. Likely follow-ups - whether a subagent should see the main todo list or plan; whether its events belong in the main event log; when multi-level dispatch is genuinely needed.

    分析过程 · 先想清楚再作答

    1. 这题在考「你有没有真的派过」。只看过文档的人会答「子 Agent 有自己的上下文」,实现过的人会先说清哪几样东西是复用的、哪几样是必须重造的,因为这两类的判据不同。
    2. 怎么拆:先给一条判据,再按这条判据把东西分成两堆,最后举一个继承错了会怎样的例子。
    3. 判据一句话:**能力可以继承,情境不能继承**。工具实现、截断规则、审批门、打转检测这些是能力,它们挂在 ToolDef 这个接口上,子 Agent 复用同一批工具对象就等于原样带走,一行都不用重写。
    4. 绝不该继承的第一样是**消息数组**。子 Agent 一趟往返七八轮很正常,那些往返对主会话毫无价值,却会实打实占掉它的窗口,还会把主会话带偏——它本来在想需求怎么拆,上下文里却堆满了第 37 行的缩进。
    5. 绝不该继承的第二样是**系统提示**。主会话那份里写满了只对主会话成立的事:对话可能被压缩过、用户会用 @ 贴资料、有技能会自动出现、不清楚可以提问。子 Agent 一条都不适用,继承过去的后果不是多知道一点,而是它会去等一件永远不会发生的事——比如问一个没人会答的问题。
    6. 绝不该继承的第三样是**预算**。共用一份额度意味着一个打转的子 Agent 能把整次任务的钱烧光,而另一件事莫名其妙没做完,派它出去的那个会话毫不知情。复用同一个记账器的类、但给一组更紧的数字,不要另造一套账。
    7. 还有一样必须显式截断的是**派发能力本身**:派发工具绝不能出现在子 Agent 的白名单里,否则派发树没有边界,预算算不出来,出了事也说不清是第几层在改文件。
    8. 可预期的追问:子 Agent 要不要拿到主会话的任务清单或计划;它的事件要不要写进主会话的日志;多层派发什么时候真的需要。

    Key points

    • The criterion - capability is inheritable (tool objects, truncation, approval, loop detection); situation is not
    • Never inherit the message array - those round trips are worthless to the main session, consume its window and derail it
    • Never inherit the system prompt - it asserts things true only of the main session, and the subagent ends up waiting on what will never happen
    • Never inherit the budget - a shared quota lets one looping subagent starve unrelated work; reuse the tracker with tighter numbers
    • The dispatch tool itself must never be in a subagent's allow-list, or the dispatch tree becomes unbounded

    答题要点

    • 判据:能力可以继承(工具对象、截断、审批、打转检测),情境不能继承
    • 不继承消息数组:子 Agent 的七八轮往返对主会话没有价值,还会占窗口并带偏它
    • 不继承系统提示:主会话那份写满只对主会话成立的事,继承过去它会去等一件不会发生的事
    • 不继承预算:共用额度会让一个打转的子 Agent 连坐掉另一件事,复用记账器但给更紧的数字
    • 派发工具本身绝不进子 Agent 的白名单,否则派发树没有边界
  • How do you isolate parallel agents editing one repository, and how are conflicts detected and handled?并行改同一个仓库,你用什么手段隔离?冲突怎么发现和处理?
    Common in ChinaCommon overseasIntermediate#worktree#merge-conflict

    How to reason about it · think before answering

    1. The crux is the second half. Naming worktrees is a pass; what separates candidates is how conflicts are detected, because most people assume merging is trivial and that is exactly the step that fails silently.
    2. How to break it down - say why a shared working directory is unacceptable, then the isolation mechanism, then the merge rules one by one.
    3. The problem with a shared directory is not that conflicts sometimes happen but that you cannot know what happened. Two agents read the same file; one writes back, the other does an exact replacement against stale content. The rule that old content must match uniquely saves you once, but next time it edits a different span, matches, and the file now carries two edits that were never designed together. Green tests only mean those two edits happened not to collide.
    4. The isolation mechanism is git worktree - one repository checked out into several directories, each with its own HEAD and index while the object store is shared, so opening a workspace is constant cost rather than copying a two-gigabyte tree. The four commands are worktree add --detach, work in that directory, diff HEAD to collect, worktree remove --force to clean up.
    5. One timing detail is easy to get wrong - the baseline must be taken at dispatch time, not by comparing against the main workspace at collection time. The main workspace may have moved during those seconds, and a stale baseline mixes other people's edits into this agent's diff.
    6. Detection is a three-way comparison - the baseline, this side's version, the other side's version. The instinctive implementation writes each agent's changes back in order, which means last writer wins - both receipts say done, only one change survives, and nothing on screen says so. That is the classic silent data loss of parallel agents.
    7. Three rules - one editor, apply; several editors producing identical content, apply once and do not call it a conflict (it is the same work done twice, and crying conflict devalues the signal); different content, report a conflict and apply none. Plus one that is easy to miss - the main workspace drifts too, and when the baseline no longer matches, writing would silently overwrite what the user just saved.
    8. Why not auto-pick on conflict - auto-picking presupposes you can tell which version is right, and you cannot. Real git merges line-level when edits do not overlap, which needs line information and an algorithm; a degraded implementation must at least never lose work silently. A conflict a human can read beats a quietly produced version nobody designed.
    9. Likely follow-ups - how deletes and renames merge; whether the model should resolve conflicts; what happens when workspaces are left uncollected.

    分析过程 · 先想清楚再作答

    1. 这题的题眼在后半句。隔离说得出 worktree 就算及格,真正分出高下的是「冲突怎么发现」——多数人默认合并是件顺手的事,而那恰恰是并行里最容易静默出错的一步。
    2. 怎么拆:先说共享工作区为什么不可接受,再说隔离手段,最后把合并规则一条条摆出来。
    3. 共享工作区的问题不是「有时候会冲突」,而是**你无法知道发生了什么**。两个 Agent 各自读了同一个文件,一个改完写回,另一个拿旧内容做替换:精确替换那条「旧内容必须唯一匹配」的规则会救你一次,但下一次它改的是另一段、匹配成功,于是文件里同时有了两处**从未被一起设计过**的改动。测试绿了只说明这两处恰好没打架。
    4. 隔离手段是 git worktree:同一个仓库签出到多个目录,每个目录有自己的 HEAD 与索引,而对象库共享——所以开一间工位是常数级开销,不会因为仓库两个 G 就复制两个 G。四条命令是 worktree add --detach、在那个目录里干活、diff HEAD 收产物、worktree remove --force 收摊。
    5. 一个时机很容易错:**基线要在派单那一刻取**,不能等回收时拿主工作区去比。子 Agent 干活那几秒里主工作区可能已经被别人动过,拿一个变了的现场当基线,差异里会混进不是它改的东西。
    6. 冲突发现靠**三方比较**:基线、这一边改成什么、另一边改成什么。直觉写法是按顺序把每个人的改动写回去,那等于后写的赢——两份回执都写着已完成,主工作区里却只剩一份改动,而且屏幕上没有任何提示。这是并行 Agent 最典型的静默数据丢失。
    7. 规则三条:只有一个人改就落盘;多个人改成相同内容落一次且不算冲突(那只是同一件事做了两遍,报成冲突会让告警变廉价);改成不同内容就报冲突、一份都不落。再加一条容易漏的:主工作区自己也会漂移(用户在编辑器里存了盘),基线对不上时落盘会无声覆盖他刚写的东西。
    8. 冲突时为什么不自动挑一份:能自动挑的前提是你有办法判断哪份对,而你没有。真 git 的行级三方合并能在改动不重叠时自动合,那需要行信息与一套算法;退化实现至少要保证不静默丢东西——报一个人看得懂的冲突,好过悄悄产出一个没人设计过的版本。
    9. 可预期的追问:删除与重命名怎么合;要不要让模型自己解冲突;工位残留没收会怎样。

    Key points

    • The real problem with a shared directory is not knowing what happened - two edits never designed together end up in one file
    • Isolate with git worktree - several directories each with their own HEAD and index over a shared object store, so each one costs a constant
    • Take the baseline at dispatch time, or other people's edits leak into this agent's diff
    • Detect with a three-way comparison; writing changes back in order means last writer wins, the classic silent data loss
    • Rules - single editor applies, identical content applies once, differing content is a conflict with nothing applied; a drifted main workspace is also refused

    答题要点

    • 共享工作区的真问题是「无法知道发生了什么」:两处从未被一起设计过的改动会同时留在文件里
    • 隔离用 git worktree:多个目录各有 HEAD 与索引,对象库共享,开一间是常数级开销
    • 基线必须在派单那一刻取,否则差异里会混进别人的改动
    • 冲突靠三方比较;直觉的顺序写回等于后写的赢,是最典型的静默数据丢失
    • 规则:单人改落盘、多人改成相同内容落一次、改成不同内容报冲突且一份都不落;主工作区漂移同样拒绝落盘
  • Which tasks are worth dispatching to subagents, and which get worse when parallelized?什么任务适合派给子 Agent,什么任务并行反而更差?
    Common in ChinaCommon overseasDeep dive#parallelism-cost#agent-design

    How to reason about it · think before answering

    1. This tests cost awareness. Answering "parallelize anything separable" just restates the definition; naming the three costs and offering an actionable self-check shows you have done the arithmetic.
    2. How to break it down - lay out the three costs, derive the three kinds of work not to dispatch, then give a one-sentence test.
    3. The first cost is tokens, and it genuinely multiplies - every subagent pays again for the system prompt and the tool table. Context cannot be shared; that is a consequence of isolation, not a shortcoming. Worse, the main session cannot see that spend - the screen shows one line saying two subagents were dispatched - so the tool needs a command that opens the books, or nobody knows what they spent.
    4. The second cost is debugging. Serially you follow one timeline; in parallel there are N interleaved ones and the log is written in arrival order, so the two outputs are braided together. That is why the receipt must carry the workspace id - it is the only attribution you get.
    5. The third cost is merging - conflict rules, renames, deletes, binary files. You pay all of it the moment you decide to parallelize; the invoice just arrives later.
    6. From those, three kinds of work not to dispatch - anything with ordering dependencies (the second step needs the first, so it waits and you end up serial having paid twice); anything touching the same files (guaranteed to hit the conflict rule, and a failed merge means redoing the work); anything done in one step (the fixed cost of dispatching exceeds the work, since explaining who you are and what you may use costs more than the edit).
    7. For the tasks that do fit, one test works well - if these two pieces of work went to two people at the same time, would they need to talk to each other? If yes, do not dispatch. It beats "can it be split" because it asks about coupling rather than form.
    8. One positive case gets overlooked - read-only exploration. Dispatching three subagents into three corners of a large repository, each returning a summary, has no write conflicts and blocks context pollution most effectively; it is the cleanest win parallelism offers.
    9. Likely follow-ups - how to pick the degree of parallelism and what caps it; whether a failed subagent should be redispatched; whether the user should see per-subagent progress.

    分析过程 · 先想清楚再作答

    1. 这题在考成本感。答「能拆开的就并行」是在复述定义;能把三笔成本报出来、并给一条可操作的自查问题的人,明显算过这笔账。
    2. 怎么拆:先把三笔成本摆出来,再由成本反推出不该派的三类活儿,最后给一条一句话的自查判据。
    3. 第一笔成本是 token,而且是实打实翻倍的:每个子 Agent 都要重新付一遍系统提示与工具表的钱。上下文不能共享是隔离的必然结果,不是实现没做好。更糟的是这笔钱**主会话看不见**——屏幕上只滚过一行「已派出 2 个子 Agent」,所以工具里必须有一条命令把这笔账摊开,否则没人知道自己花了多少。
    4. 第二笔成本是调试。串行时顺着一条时间线看就行,并行之后有 N 条线交错发生,而日志是按到达顺序写的,两边的输出彼此穿插。这也是回执里必须带工位号的理由——那是唯一的归属线索。
    5. 第三笔成本是合并:冲突规则、重命名、删除、二进制文件,这些代价在你决定并行的那一刻就付了,只是账单晚一点到。
    6. 由此反推三类不该派的活儿:有先后依赖的(后一步要前一步的结果,派出去只能干等,最后还是串行还多花一份钱);要改同一批文件的(必然撞冲突规则,合并失败还得重做);一步就能做完的(派发的固定成本比自己顺手做完还高——给它讲清楚「你是谁、能用什么」就比活儿本身贵)。
    7. 适合的那一类有一条很好用的自查问题:**这两件事如果交给两个真人同时做,他们需要互相说话吗?** 需要就别派,不需要才是并行该出场的地方。它比「能不能拆开」准得多,因为它问的是耦合而不是形式。
    8. 还有一个容易被忽略的正面场景:**只读的探查**。派三个子 Agent 分头去大仓库的三个角落找线索,各自只回一段摘要——没有写冲突、上下文污染也最严重地被挡在外面,这是并行收益最干净的一类。
    9. 可预期的追问:并行度该设多少、上限该按什么定;失败的那一个要不要重派;要不要让用户看得到每个子 Agent 的进度。

    Key points

    • Three costs - tokens genuinely multiply and stay invisible to the main session, debugging means attributing across N interleaved timelines, and merging carries its own complexity
    • Three kinds not to dispatch - ordered dependencies, work touching the same files, and anything done in a single step
    • The fixed cost is the crux - telling a subagent who it is and what it may use can cost more than the edit itself
    • The self-check - if two people did these at the same time, would they need to talk? If yes, do not dispatch
    • The cleanest win is read-only exploration - no write conflicts, and the strongest block on context pollution

    答题要点

    • 三笔成本:token 实打实翻倍且主会话看不见、调试要在 N 条交错时间线里找归属、合并规则的复杂度
    • 不该派的三类:有先后依赖的、要改同一批文件的、一步就能做完的
    • 固定成本是关键:给子 Agent 讲清「你是谁、能用什么」可能比活儿本身还贵
    • 自查问题:这两件事交给两个真人同时做,他们需要互相说话吗?需要就别派
    • 最干净的正面场景是只读探查:没有写冲突,而且最有效地挡住了上下文污染

Comments