Harness Academy
Tài liệu gốc 23 12 phútTiếng Việt + English
docs/adr/0013-explore-subagents.mdXem bản gốc
Ngôn ngữ bài đọc
Bản dịch tiếng Việt

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)); passing Cách sử dụng=ctx.usage gấ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 N agent(...) gọi trong một mô hình phản hồi đồng thời với không tùy chỉnh asyncio.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

  1. 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ảoRanh giớiCách lyChi 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ênrẻ 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ọitrạng thái cấp phiên, vẫn đang trong quá trìnhtrung bình — một phiên trực tiếp cho mỗi đứa trẻ được tổ chứcbền bỉ trong suốt thời gian sử dụngMedium — đọc lại kết quả của phiên con
    pingoà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ìnhvòng đời quá trình độc lậpnặ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.

  2. Bộ công cụ con = read / glob / grep / lsp CHỈ — 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à agent chí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ằng agent.run() (không phải harness Runner/AgentTurnHandler) và các công cụ của nó không bao giờ tăng ApprovalRequired, đứa trẻ không bao giờ chạm vào cổng hoặc Kênh Quyết định - đó là tại sao ask_user bị cấm.

  3. 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 boolean subagent trường (mặc định False; một cái tên cố tình không va chạm - AgentDef.mode là chế độ cho phép). explore.md's tools: danh sách được chỉnh sửa trong tập tin một cách chính xác read/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.

  4. Cái agent cô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.

  5. Các quyền được cấp miễn phí — đã được xác minh, chưa được xây dựng. agent là một công cụ READ_ONLY không có cổng (nó không bao giờ tăng lên ApprovalRequired, 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ỗi permissions/gate.py READ_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ươi PermissionGate + tươi AgentDeps (không bao giờ có cổng/task_store có thể thay đổi của cha mẹ).

  6. Tái nhập cùng một agent thông qua đường nối mô-đun. agent cô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ởi build_agent, phản chiếu bash _EXECUTOR). Sử dụng lại một agent được xây dựng (so với build_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àng agent.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=explore bỏ qua agent, vậy prepare= giấu nó - không cần bộ đếm độ sâu.

  7. 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ạy asyncio.Semaphore có 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ưới UsageLimits(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_tokens thướ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).

  8. 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 --resume chỉ 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).

  9. 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} trong runtime/flow.py), do đó bộ nhớ đệm mặc định sẽ giữ nguyên và agent khô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ột decode replay --model trao đổ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ẹ (AgentDef không có model lĩ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_turn dấ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.

  10. 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ương src/ 1:1, TDD-first, không có mạng trong CI (FunctionModel; một làn khói sống skipif-đượ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.subagent phâ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 qua agent công cụ. Thêm một giây agent phụ sau này là một agent mới subagent: true tập tin + một subagent_type param 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_type thô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; override nằm trong phạm vi bối cảnh); (c) cái đó prepare= giấu agent từ một đứa trẻ dưới sự chỉ đạo của Song Tử thực sự như nó làm dưới FunctionModel; (d) phân phát theo giới hạn tỷ lệ bậc miễn phí của Gemini — semaphore + request_limit là những người bảo vệ.
Mục lục nguồn