docs/adr/0013-explore-subagents.mdXem bản gốc Bản dịch đầy đủ theo đúng cấu trúc tài liệu; code, liên kết và định danh kỹ thuật được giữ nguyên.
0013. Khám phá các subagent — công cụ agent và phân xuất song song trong quá trình
Trạng thái: Đã chấp nhận
Ngày: 2026-07-04
Đã sửa đổi: 2026-07-13 — cơ chế phân xuất của §7 (N agent(prompt) cuộc gọi cho mỗi phản hồi) và §8
hình dạng kết quả báo cáo đơn được ** thay thế bởi
ADR-0017**: công cụ này hiện đã có
agent(prompts: list[str]) — một cuộc gọi, phân xuất tập hợp khai thác, tập hợp được gắn nhãn, đầu vào và
xác nhận đầu ra. §1–6, semaphore §7 / UsageLimits / không sử dụng luồng, §8 truncate()
thành ngữ/sự phù du/sự im lặng, §9 và §10 đứng. Phần thân bên dưới không được chỉnh sửa.
Đã sửa đổi: 2026-07-14 — "im lặng cho đến khi hoàn thành" của §8 hiện là có điều kiện: im lặng vẫn là
MẶC ĐỊNH, nhưng nút chuyển đổi Chế độ chi tiết của TUI (Ctrl+O, trực tiếp giữa lượt) cho phép các lệnh gọi công cụ của trẻ phát ra
vào phần chìm của PARENT, được hiển thị bằng trình kết xuất của chính bộ decode dưới dạng thụt lề [child N] dòng (N = của trẻ
chỉ mục nhắc nhở dựa trên 1). Không có gì thay đổi: cổng của trẻ vẫn BỎ QUA, cách sử dụng vẫn giữ nguyên
không được phân luồng, bản ghi của nó vẫn tồn tại phù du và nếp gấp không bị ảnh hưởng.
Thay thế: Thay thế một phần ADR-0003
§5 — cụ thể là "Bốn phần dựng sẵn (chỉ agent chính - không sinh ra subagent cột mốc này)"
mệnh đề và kết quả phù hợp "Các đường nối còn lại cho các mốc quan trọng sau: sinh sản của subagent". Mọi thứ
khác trong ADR-0003 (các chế độ cấp phép, phân loại kiểu công cụ, công cụ quy tắc, mỗi công cụ
prepare= hạn chế, móc hướng dẫn động) được giữ lại và tái sử dụng - ADR này được xây dựng
trên hết, không chống lại nó.
Bối cảnh
ADR-0003 đã gửi một Danh mục agent (Xây dựng / Lập kế hoạch / Khám phá / Đánh giá mã) có các tính cách
được chọn làm một agent chính và trì hoãn sinh sản subagent như một đường nối được đặt tên trong tương lai. các
bảng thuật ngữ đã hứa hẹn về khái niệm — "subagent — Một agent con được sinh ra bởi agent chính thông qua
công cụ agent; chạy một tác vụ có phạm vi và trả về kết quả nén" (docs/glossary.md) — và
Danh sách cấu trúc dự án của AGENTS.md agents/ là “danh mục agent … + agent phụ”. Cột mốc 9 trả tiền
hứa hẹn đó: agent chính có thể sinh ra Khám phá các subagent đọc một cơ sở mã lớn trong
song song và gửi lại một báo cáo nén, thông qua ** mớiagent công cụ**.
Cơ chế ADR-0003 §6-7 được xây dựng là công cụ hỗ trợ chính và không được phát minh lại: công cụ cho mỗi agent
hạn chế đi theo từng công cụ prepare= đọc gọi lại ctx.deps.active_agent.tools
(tools/registry.py) và lời nhắc của hệ thống trên mỗi agent sẽ điều khiển động @agent.instructions cái móc
đọc sách ctx.deps.active_agent.prompt (agent/factory.py). "Một agent, trạng thái hoán đổi - không hoán đổi
agent" (Hậu quả ADR-0003). Do đó, subagent là agent tương tự được nhập lại với thông tin mới,
deps bị thu hẹp — không có máy móc mới.
Ba thông tin về khung đã được xác minh dựa trên pydantic-ai-slim đã cài đặt (pyproject.toml)
và các tài liệu Pydantic AI hiện tại, vì chúng định hình thiết kế:
- Ủy quyền agent là một tổ chức lồng nhau
agent.run()từ bên trong một công cụ. Một công cụ có thểawait other.run(prompt, Cách sử dụng=ctx.usage, Usage_limits=Giới hạn sử dụng(request_limit=N)); passingCách sử dụng=ctx.usagegấp mức sử dụng của con vào tổng số của cha mẹ. Đại biểu không cần phải là một đối tượng agent khác — một agent là một nhà máy hoạt động không quốc tịch, vàagent.override(model=…)nằm trong phạm vi bối cảnh, vì vậy hãy nhập lại cùng một đối tượng (thậm chí đồng thời) được hỗ trợ. decode đã được ủy quyền theo cách này để nén trình tóm tắt và trích xuất bộ nhớ (context/compaction.py,memory/extract.py). - Các lệnh gọi công cụ song song là gốc. "Khi một mô hình trả về nhiều lệnh gọi công cụ trong một phản hồi,
Pydantic AI lên lịch cho chúng đồng thời bằng cách sử dụng
asyncio.create_task, thực hiện chúng theo thứ tự mô hình đã phát ra chúng." Vậy Nagent(...)gọi trong một mô hình phản hồi đồng thời với không tùy chỉnhasyncio.gather. UsageLimits(request_limit=…, total_tokens_limit=…, tool_calls_limit=…)giới hạn một cuộc chạy độc lập với kế toán sử dụng - mức giới hạn dành cho một đứa trẻ.
Quyết định
-
Ranh giới = vòng lặp agent lồng nhau trong quá trình, được đóng khung để phân xuất song song (mô hình của mã claude). A đứa trẻ cũng vậy
agent.run()được nhập lại với số deps được thu hẹp — không phiên con, không a subprocess. Ba mô hình tham chiếu, trên các trục quan trọng ở đây:Mô hình tham khảo Ranh giới Cách ly Chi phí cho mỗi đứa trẻ Kiên trì Kết quả gấp claude-code (đã chọn) vòng lặp agent lồng nhau trong quá trình - giống nhau Agent,agent.run()được nhập lại với số dep mới được thu hẹplogic: cổng mới/task_store/lịch sử/sự kiện chìm; chia sẻ quy trình + thông dịch viên rẻ nhất - tái sử dụng những gì đã xây dựng Agent, mô hình và ứng dụng khách HTTP; không sinh sản, không khởi tạo lạiphù du — bảng điểm con chỉ tồn tại cho cuộc gọi, sau đó bị loại bỏ tầm thường — văn bản cuối cùng của trẻ là kết quả của công cụ, được gấp nội tuyến mã mở Đối tượng session con liên tục, được duy trì qua các cuộc gọi trạng thái cấp phiên, vẫn đang trong quá trình trung bình — một phiên trực tiếp cho mỗi đứa trẻ được tổ chức bền bỉ trong suốt thời gian sử dụng Medium — đọc lại kết quả của phiên con pi ngoài quy trình quy trình con (agent con là quy trình hệ điều hành) mạnh — tiến trình/bộ nhớ/fs riêng biệt đắt nhất — quá trình sinh sản + IPC + khởi tạo lại mô hình vòng đời quá trình độc lập nặng nhất — tuần tự hóa + IPC kết quả trang chủ Tại sao việc decode lại thắng lợi trong quá trình decode. decode là một cách khai thác giảng dạy theo một quy trình và việc thu hẹp cơ chế (
deps.active_agent+prepare=+ móc hướng dẫn) đã tồn tại từ ADR-0003 §6-7 — một đứa trẻ theo nghĩa đen là agent chính được nhập lại với các phần phụ mới chỉ đọc, vì vậy toàn bộ tính năng là xây dựng deps + một công cụ mỏng, không phải kiến trúc mới. Phân xuất song song có nguồn gốc (asyncio.create_task), việc gấp kết quả là chuyện nhỏ (văn bản cuối cùng của trẻ là kết quả của công cụ) và chi phí tối thiểu (tái sử dụng mô hình + máy khách HTTP) — điều này quan trọng vì mô hình phân tán ra nhân lên kêu gọi chống lại giới hạn bậc miễn phí của Gemini. Cách ly quy trình con (pi) không mua được gì ở đây: trẻ em là chỉ đọc theo cấu trúc (không chứa ghi/bash). Phiên con liên tục (opencode) mua độ bền mà khám phá chỉ đọc trong phạm vi không cần và làm phức tạp nhật ký phiên. các cái giá phải trả của sự lựa chọn là trung thực và được chấp nhận: con cái có cùng lỗi lầm với cha mẹ (con cái bề mặt ngoại lệ đang được xử lý) và không có sự cô lập bộ nhớ/fs - có thể chấp nhận chính xác vì bộ công cụ ở chế độ chỉ đọc. -
Bộ công cụ con =
read/glob/grep/lspCHỈ — chỉ đọc theo cấu trúc. Bị loại trừ, mỗi vì một lý do:bash(không đảm bảo chỉ đọc),web_fetch/write/edit(tác dụng phụ),todo_write(sổ sách con là tiếng ồn),skill(không cần thiết để đọc trong phạm vi),ask_user(**nó chặn trên một Kênh Quyết định duy nhất — một phần tử con sẽ khóa kênh phân xuất ra **) vàagentchính nó (từ chối mặc định đệ quy - bài học về mã mở). Bởi vì một đứa trẻ chạy trên đồng bằngagent.run()(không phải harnessRunner/AgentTurnHandler) và các công cụ của nó không bao giờ tăngApprovalRequired, đứa trẻ không bao giờ chạm vào cổng hoặc Kênh Quyết định - đó là tại saoask_userbị cấm. -
Khám phá được hạ cấp xuống chỉ dành cho subagent thông qua trục chính/subagent tối thiểu.
AgentDefđạt được một booleansubagenttrường (mặc địnhFalse; một cái tên cố tình không va chạm -AgentDef.modelà chế độ cho phép).explore.md'stools:danh sách được chỉnh sửa trong tập tin một cách chính xácread/glob/grep/lsp, nó tuyên bốsubagent: true, và phần thân của nó nêu rõ thông điệp cuối cùng là báo cáo.--agent explore(CLI) và/agent explore(giữa phiên) bị từ chối bằng đường dây thân thiện chỉ liệt kê các agent chính (bản dựng/người đánh giá mã/kế hoạch). Không có logic thu hẹp thời gian sinh sản - khám phá không bao giờ là chính nữa. -
Cái
agentcông cụ được cấp để xây dựng + lập kế hoạch + người đánh giá mã, không bao giờ khám phá và được đăng ký làToolKind.READ_ONLY. -
Các quyền được cấp miễn phí — đã được xác minh, chưa được xây dựng.
agentlà một công cụ READ_ONLY không có cổng (nó không bao giờ tăng lênApprovalRequired, giống như các công cụ tệp chỉ đọc), vì vậy nó chạy nội tuyến và không bao giờ nhắc; và mỗipermissions/gate.pyREAD_ONLY tự động cho phép ở mọi chế độ. Tất cả các công cụ của trẻ đều CHỈ ĐỌC, nên trẻ con cũng không bao giờ nhắc nhở. Trẻ em vẫn nhận được tươiPermissionGate+ tươiAgentDeps(không bao giờ có cổng/task_store có thể thay đổi của cha mẹ). -
Tái nhập cùng một agent thông qua đường nối mô-đun.
agentcông cụ tiếp cận agent đang chạy thông qua một đường nối mô-đun đặt một lần (set_main_agent(agent)được gọi bởibuild_agent, phản chiếu bash_EXECUTOR). Sử dụng lại một agent được xây dựng (so vớibuild_agent()-ing một đứa trẻ mỗi lần sinh sản) có nghĩa là không có mô hình/máy khách HTTP xây dựng lại cho mỗi đứa trẻ, đứa trẻ sẽ kế thừa mô hình của cha mẹ + bất kỳ Ghi đè mô hình nào + chế độ luồng khách hàng không cần duy trì sự sống miễn phí và một khách hàngagent.override(model=…)trong buổi lái thử cha mẹ và trẻ em (một chiến thắng lớn về khả năng kiểm tra kín). Đệ quy là không thể về mặt cấu trúc: của đứa trẻactive_agent=explorebỏ quaagent, vậyprepare=giấu nó - không cần bộ đếm độ sâu. -
Xuất phát gốc + chạy con có giới hạn, có giới hạn. N
agent(...)cuộc gọi trong một lần chạy phản hồi đồng thời (asyncio.create_task, không thu thập tùy chỉnh). Một mỗi lần chạyasyncio.Semaphorecó kích thước đểsubagent_max_parallel(mặc định 4) giới hạn trẻ em đồng thời (thân thiện với bậc miễn phí của Gemini); mỗi đứa trẻ chạy dướiUsageLimits(request_limit=subagent_max_requests)(mặc định 25). Đứa trẻ chạy không chủ đềusage=ctx.usage, vậy cha mẹ_last_input_tokensthước đo chân trang và trình kích hoạt nén chỉ tiếp tục phản ánh bối cảnh của cha mẹ (agent/loop.py). -
Kết quả = chỉ văn bản cuối cùng của trẻ, ngắn gọn và phù du. Thông điệp cuối cùng của trẻ là cắt ngắn thành
subagent_result_max_bytes(mặc định 16 KB) thông qua chia sẻtruncate()thành ngữ (tools/truncate.py) và trả về dưới dạng kết quả của công cụ — không bao giờ là bản ghi. Bảng điểm của trẻ là phù du (không tồn tại): lịch sử gốc chỉ mang lệnh gọi sinh sản + tóm tắt, vì vậy--resumechỉ hoạt động. Quá trình sinh sản hiển thị thông qua đường ống hiện có (ToolCallStarted→ công bố,ToolResult→ bảng điều khiển); trẻ em nhận được một sự kiện chìm không hoạt động, vì vậy các sự kiện nội bộ của chúng im lặng (im lặng-cho đến khi hoàn thành). -
headless không cần vỏ đặc biệt. Toàn bộ quá trình chạy con là một lệnh gọi công cụ không rõ ràng → một điểm kiểm tra dưới
checkpoint_strategy="calls". Bản tóm tắt được lưu trong bộ nhớ đệm của trẻ chỉ đọc là an toàn khi phát lại (ngược lại sandbox-bash{"cache": False}trongruntime/flow.py), do đó bộ nhớ đệm mặc định sẽ giữ nguyên vàagentkhông bao giờ được thêm vào bộ vô hiệu hóa bộ đệm. Mức trần được ghi lại: các lệnh gọi mô hình con lồng nhau không neo phát lại riêng lẻ, mộtdecode replay --modeltrao đổi không đến được bên trong một đứa trẻ và đứa trẻ chi tiêu token là vô hình cho đến khi Opik hạ cánh (M10). Mô hình con = mô hình cha mẹ (AgentDefkhông cómodellĩnh vực, theo thiết kế). Đóng trước M10 (ADR-0014): chi tiêu token con hiện hiển thị trong Opik dấu vết - đứa trẻagent.run()làm tổ bên trong lượt cha mẹchat_turndấu vết (cùng một nhiệm vụ asyncio / contextvars), vì vậy việc sử dụng token của mỗi đứa trẻ sẽ có phạm vi riêng. -
Kỷ luật (không thay đổi).
filterwarnings=["error"], thời gian nhận biết UTC, bao gồm chú thích đầy đủ.-> None, nhật ký mã thư viện (không bao giờprint()), nhập khẩu-không-trừu tượng,tests/gươngsrc/1:1, TDD-first, không có mạng trong CI (FunctionModel; một làn khói sốngskipif-được bảo vệ).
Sơ đồ
Sinh ra và gấp — một phản hồi mô hình hướng tới N phần tử con chỉ đọc, mỗi phần tử được gấp lại như một kết quả của công cụ.
flowchart TD
subgraph parent["Parent agent loop — build / plan / code-reviewer"]
model["model response:<br/>N × agent(prompt) in one turn"]:::call
atool["agent tool<br/>(READ_ONLY → runs inline, never prompts)"]:::tool
sem{{"per-loop asyncio.Semaphore<br/>subagent_max_parallel"}}:::gate
end
model --> atool --> sem
sem --> c1
sem --> c2
sem --> c3
subgraph children["Explore subagents — in-process agent.run(), FRESH read-only deps"]
c1["child 1 · active_agent=explore<br/>read · glob · grep · lsp"]:::child
c2["child 2"]:::child
c3["child N"]:::child
end
c1 -- "final text = compressed report" --> fold["truncate() → ToolResult"]:::result
c2 --> fold
c3 --> fold
fold --> model
note["fresh gate + fresh task_store + no-op emit (silent TUI)<br/>no agent tool in child → no recursion<br/>no usage threading → parent gauge + compaction unchanged"]:::note
children -.-> note
classDef call fill:#1e293b,stroke:#0ea5e9,color:#e2e8f0
classDef tool fill:#334155,stroke:#a855f7,color:#e9d5ff
classDef gate fill:#713f12,stroke:#eab308,color:#fef9c3
classDef child fill:#14532d,stroke:#22c55e,color:#dcfce7
classDef result fill:#334155,stroke:#38bdf8,color:#bae6fd
classDef note fill:#0f172a,stroke:#64748b,color:#cbd5e1
hậu quả
- Danh mục phát triển trục chính/phụ.
AgentDef.subagentphân biệt các cá tính có thể lựa chọn với tư cách là chính (--agent//agent) từ những thứ chỉ có thể sinh sản thông quaagentcông cụ. Thêm một giây agent phụ sau này là một agent mớisubagent: truetập tin + mộtsubagent_typeparam trên công cụ - một param Ngoài ra, không phải là viết lại. - Khám phá không còn có thể được chọn làm chính.
--agent explore//agent explore/select_agent("explore")bây giờ có lỗi với danh sách chỉ dành cho bầu cử sơ bộ; các bài kiểm tra lựa chọn khám phá hiện có được cập nhật (test_loader.py,test_select.py,test_app_e2e.py). - Một agent, đã được nhập lại — không được xây dựng lại. Trẻ sử dụng lại mô hình + máy khách đã xây dựng thông qua mô-đun
đường may, vậy
agent.override(model=…)bao gồm cha mẹ + con cái trong các bài kiểm tra. - Các đường nối còn lại dành cho phần sau: subagent thứ hai + agent đã được xác thực
subagent_typethông số; cầu nối trẻ em các sự kiện tới TUI (tiến trình phụ trực tiếp); phân luồng sử dụng + chi phí cho mỗi đứa trẻ khi Opik hạ cánh (M10); một bằng chứng ngăn xếp được triển khai cho thấy subagent không có đầu sẽ phát lại. - Chi phí cho mỗi trẻ hiện đã được phân phối (M10, ADR-0014). Đường nối "chi phí cho mỗi trẻ khi Opik hạ cánh" ở trên là
đã đóng: một đứa trẻ Khám phá
agent.run()lồng bên trong dấu vết Opik của lượt cha mẹ, vì vậy token cho mỗi lượt con chi tiêu hiển thị trên các nhịp riêng của nó. Các đường nối còn lại vẫn mở. - Rủi ro cần xác nhận trong quá trình triển khai: (a) mối quan hệ vòng lặp sự kiện của mô-đun semaphore — đã tạo
trên mỗi vòng lặp đang chạy nên nó an toàn trong cả vòng lặp REPL đơn và vòng lặp mỗi cuộc gọi của Kitaru; (b) cái đó
nhập lại tương tự
Agentđối tượng concurrently (con song song) không can thiệp theo con đường Song Tử thực sự (agent là một nhà máy điều hành không quốc tịch;overridenằm trong phạm vi bối cảnh); (c) cái đóprepare=giấuagenttừ một đứa trẻ dưới sự chỉ đạo của Song Tử thực sự như nó làm dướiFunctionModel; (d) phân phát theo giới hạn tỷ lệ bậc miễn phí của Gemini — semaphore +request_limitlà những người bảo vệ.