Harness Academy
Tài liệu gốc 13 11 phútTiếng Việt + English
docs/adr/0003-milestone-2-permission-system-and-agents-catalog.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.

0003. Cột mốc 2 — danh mục hệ thống cấp phép và agent

Trạng thái: Đã chấp nhận Ngày: 2026-06-25 Được thay thế một phần bởi ADR-0013 — "chỉ agent chính - không" của §5 chỉ điều khoản sinh sản của subagent"; các chế độ cấp phép, kiểu công cụ, công cụ quy tắc, prepare= hạn chế và móc hướng dẫn được ADR-0013 giữ lại và tái sử dụng.

Bối cảnh

Cột mốc 1 đã vận chuyển một nhân viên hỗ trợ trên thiết bị yêu cầu con người trong mọi cuộc gọi công cụ (ADR-0002 §3). Cánh cổng (permissions/gate.py) là một đối tượng chính sách thuần túy có check() luôn luôn trả lại ask; PermissionMode có một giá trị duy nhất ASK; vòng lặp (agent/loop.py _decide) cuộc gọi gate.check() nhưng phớt lờ phán quyết và hỏi con người một cách vô điều kiện. M1 cũng chạy một Lời nhắc hệ thống cố định duy nhất với bộ công cụ phẳng đầy đủ luôn có sẵn.

ADR-0002 rõ ràng đã để lại cột mốc này dưới dạng một phần mở rộng theo kế hoạch ("Có thể mở rộng sang chế độ M3 default/plan/edit/bypass, tự động cho phép chỉ đọc và các quy tắc được duy trì mà không cần viết lại"; "modes + danh mục agent" dưới các đường nối có chủ ý). (Các bình luận mã M1 gọi tác phẩm này là "M3" và gọi Nhà cung cấp LLM "M2"; đánh số kế hoạch khóa học ở bước 2 này - các nhãn đề cập đến cùng một cột mốc.)

Cột mốc 2 biến cánh cổng thành chính sách cho phép/yêu cầu/từ chối thực sự được điều khiển bởi chế độ + kiểu công cụ + quy tắc dự án/agent và thêm danh mục agent (Xây dựng / Lập kế hoạch / Khám phá / Người đánh giá mã) mỗi phạm vi sẽ có lời nhắc hệ thống, bộ công cụ được phép và chế độ mặc định. Các quyết định đã được nướng và bị con người khóa qua hai vòng; chúng có liên quan với nhau (một chế độ là vô nghĩa nếu không có phân loại kiểu công cụ; việc chọn một agent sẽ đặt lại chế độ và đóng góp các quy tắc; sự kiểm soát các bề mặt biến đổi cùng một trạng thái chung), vì vậy chúng được ghi lại ở đây dưới dạng một cột mốc ADR — phản chiếu ADR-0002. Phân tích nhiệm vụ theo thứ tự nằm trong tasks/ (đặc điểm permission-system-agents-catalog); ADR này ghi lại tại sao. Xem ADR-0001, ADR-0002.

Hai dữ kiện khung đã được xác minh dựa trên pydantic-ai 1.107.0 đã cài đặt (không giả định), bởi vì họ định hình thiết kế:

  • Agent.__init__ có không prepare_tools kwarg, nhưng Agent.tool(...) có một công cụ prepare: ToolPrepareFunc = Callable[[RunContext[Deps], ToolDefinition], Awaitable[ToolDefinition | None]]. Trở về None ẩn công cụ đó cho lần chạy hiện tại và cuộc gọi lại nhận được ctx.deps. A sự tăng vọt có thể chạy được đã xác nhận điều đó (a TestModel gọi mọi công cụ hiển thị chỉ được gọi là một trong danh sách cho phép). FunctionToolset.filtered(...) là một sự thay thế nhưng cần đăng ký được cơ cấu lại thành một bộ công cụ rõ ràng trước tiên.
  • Shift+Tab xuất hiện trong bộ công cụ nhắc nhở dưới dạng s-tab chìa khóa (Keys.BackTab).

Quyết định

  1. Bốn chế độ cấp phép thay thế một chế độ ASK. PermissionMode trở thành {DEFAULT, PLAN, EDIT, BYPASS} (cũ ASK giá trị mode bị xóa; cổng mặc định là DEFAULT). các PermissionOutcome liệt kê allow/ask/deny không thay đổi. Ngữ nghĩa theo kiểu công cụ:

    • mặc định — các công cụ chỉ đọc tự động-ALLOW; công cụ đột biến HỎI con người.
    • gói — tự động chỉ đọc-ALLOW; bất kỳ công cụ đột biến nào TỪ CHỐI, kèm theo lý do cho biết mô hình để trình bày kế hoạch của mình và gọi exit_plan_mode.
    • chỉnh sửa — tự động chỉ đọc-ALLOW; công cụ chỉnh sửa tập tin (write/edit) tự động-** CHO PHÉP**; khác công cụ đột biến (bash) HỎI.
    • bỏ qua — mọi thứ PHÉP (không cần nhắc).
  2. A ToolKind phân loại (READ_ONLY | FILE_EDIT | OTHER) thay thế đơn read_only bool trên sổ đăng ký. Chế độ chỉnh sửa phải phân biệt chỉnh sửa tệp với trình thực thi shell, bool nào không thể diễn đạt được. ToolSpec mang theo kind: ToolKind; read_only có nguồn gốc (kind is READ_ONLY) tồn tại như vậy is_read_only() người gọi tiếp tục làm việc. Lập bản đồ: read/glob/grep/web_fetch → READ_ONLY; todo_write → READ_ONLY (danh sách kiểm tra trong bộ nhớ không có bên đĩa/thực thi hiệu ứng - nó phải luôn sử dụng được ở chế độ kế hoạch, trong đó nhân viên kế hoạch xây dựng danh sách kiểm tra của mình và cần không nhắc ở đâu cả); write/edit → FILE_EDIT; bash → OTHER. (ask_user và các công cụ điều phối không được chỉnh sửa và không bao giờ chạm tới cổng.)

  3. Cổng trở thành quyết định thực sự và chế độ có thể thay đổi. gate.check(request) trả lại CHO PHÉP/HỎI/TỪ CHỐI bằng cách đánh giá, theo thứ tự ưu tiên: quy tắc từ chối → quy tắc cho phép → quyết định chế độ → (HỎI con người). PermissionGate lợi ích set_mode(mode) vì vậy TUI/công cụ có thể chuyển đổi chế độ giữa phiên. Vòng lặp của _decide được nối lại thành tôn trọng phán quyết: CHO PHÉP → chạy công cụ mà không cần hỏi; TỪ CHỐI → trả lại lý do (ánh xạ tới ToolDenied); HỎI → hỏi con người (con đường hôm nay). Đây là sự thay đổi hành vi duy nhất thực hiện các chế độ.

  4. Hai nguồn quy tắc, một động cơ; quyền ưu tiên hợp nhất chúng. Quy tắc cấp phép là Tool(pattern) hoặc trần trụi Tool, khớp (toàn cầu thông qua fnmatch) đối với từng loại chủ đề: bash → cái lệnh; công cụ tập tin → đường dẫn; web_fetch → địa chỉ; trần-Tool khớp với bất kỳ lệnh gọi nào của công cụ đó. Các quy tắc đến từ hai nguồn:

    • Cài đặt người dùng — cấp độ dự án .decode/settings.json ({"permissions": {"allow": [...], "deny": [...]}}) đó là tùy chọn của người dùng cá nhân hóa (mục đích duy nhất của nó là quy tắc cấp phép; không có cấp độ người dùng/toàn cầu/tổ chức - YAGNI). Tệp bị thiếu/không đúng định dạng không gây nguy hiểm.
    • Quy tắc agent hoạt động — tùy chọn allow/deny liệt kê trong danh mục đầu tiên của agent, vì vậy các giá trị mặc định của agent tích hợp sẵn trong danh mục, không được đưa vào settings.json của người dùng (ví dụ: người đánh giá mã mang allow: ["bash(git *)"]). Cổng đánh giá công đoàn: deny(user ∪ agent) → allow(user ∪ agent) → mode → ask (từ chối quy tắc từ một trong hai nguồn đánh bại mọi thứ; người dùng từ chối luôn có thể thắt chặt sự cho phép của agent). các câu trả lời luôn cho phép tương tác (a/always) vẫn tồn tại một quy tắc cho phép phù hợp với người dùng .decode/settings.json (tự động cho phép cuộc gọi giống hệt tiếp theo); y/yes ở lại cho phép một lần. Lỗi ghi liên tục không gây tử vong (ghi nhật ký, quay lại cho phép một lần).
  5. Danh mục agent = Tệp đánh dấu có bộ xử lý YAML + trình tải/trình xác thực (không phải mã hóa cứng) dict). Mỗi agent tích hợp là một gói *.md: YAML tiền đề (name, description, tools danh sách cho phép, mode mặc định, tùy chọn allow/deny quy tắc) + phần thân nhắc hệ thống. Không model field yet — các tổng đài viên chạy trên một mô hình Gemini đã được định cấu hình cho đến khi đạt được cột mốc của nhà cung cấp (bước 3); trình tải bỏ qua các khóa không xác định, vì vậy việc thêm model sau này tương thích về phía trước. Một AgentDef thực thể (được xác thực) là kết quả được phân tích cú pháp; trình tải bị lỗi với một lỗi rõ ràng trên một tên công cụ không xác định/chế độ xấu/trường bị thiếu. Bốn phần tích hợp (chỉ agent chính - ** không có subagent sinh sản** cột mốc này):

    • xây dựng — tất cả công cụ (bao gồm. todo_write, enter_plan_mode/exit_plan_mode/sleep), mode: default.
    • kế hoạch — bộ chỉ đọc + todo_write + enter_plan_mode/exit_plan_mode + ask_user, mode: plan.
    • khám phá — bộ chỉ đọc + ask_user, mode: default.
    • người đánh giá mã — bộ chỉ đọc + bash + ask_user, mode: default, với allow: ["bash(git *)"] vậy git diff/log/show tự động cho phép trong khi bash khác vẫn hỏi.
  6. Hạn chế công cụ cho mỗi agent thông qua mỗi công cụ prepare= gọi lại (pydantic-ai 1.107). Mỗi công cụ đã đăng ký nhận được một prepare gọi lại có nội dung ctx.deps.active_agent.tools và trả về None để ẩn công cụ khi nó không có trong danh sách cho phép của agent đang hoạt động (được xác minh bởi Spike). một agent, không xây dựng lại, không handler._agent trao đổi.

  7. Vị trí trạng thái hoạt động. Chế độ hoạt động tồn tại trên PermissionGate (có thể thay đổi thông qua set_mode). agent đang hoạt động vẫn tiếp tục tồn tại AgentDeps.active_agent: AgentDef (có thể thay đổi). Đang chọn một bộ agent deps.active_agent, đặt lại cổng về chế độ mặc định của agent đó, và tải quy định của agent vào cổng. Lời nhắc hệ thống cho mỗi agent dựa trên động hiện có @agent.instructions hook (cơ chế sử dụng bộ nhớ): nó đọc ctx.deps.active_agent.prompt, vậy agent chuyển mạch có hiệu lực ở lượt tiếp theo mà không cần xây dựng lại.

  8. Dụng cụ hòa âm + ngủ. enter_plan_mode chuyển cổng sang PLAN và trả về một sự thừa nhận. exit_plan_mode trình bày kế hoạch và yêu cầu con người phê duyệt kế hoạch rời đi chế độ (thông qua Kênh Quyết định hiện có - không có tính năng như ask_user, vì vậy không có dấu nhắc kép): bật phê duyệt → chuyển sang chế độ EDIT (để agent có thể triển khai kế hoạch vừa được phê duyệt) và quay lại "đã được phê duyệt"; từ chối → ở lại PLAN và trả về thông báo "tinh chỉnh và gọi lại exit_plan_mode". sleep(seconds) là một dòng await asyncio.sleep(...) giới hạn ở settings.sleep_max_s (một người lành mạnh max) và từ chối đầu vào âm với ModelRetry. Cả ba đều là các nút điều khiển không có cổng (chúng chạm vào không có hệ thống tập tin) và không bao giờ đạt được cổng cấp phép.

  9. Bề mặt điều khiển. Khởi động --agent NAME / --mode NAME Nhấp vào tùy chọn trên cli.py (mặc định agent build; không xác định → một dòng stderr thân thiện + lối ra khác 0). Lệnh gạch chéo giữa phiên /agent <name> và /mode <name> được phân tích cú pháp trong vòng đầu vào TUI cùng với /quit. Một Shift+Tab (s-tab) chế độ chu kỳ liên kết phím default → edit → plan → bypass → default. Thanh công cụ phía dưới hiển thị chế độ + agent hoạt động; một công tắc hiển thị một dòng xác nhận. Tất cả điều này dẫn đến một đơn bề mặt đầu vào (bất biến Kênh Quyết định từ M1 giữ - không có giây prompt_async()).

  10. Kỷ luật (không thay đổi từ ADR-0002 / AGENTS.md). filterwarnings=["error"], nhận biết UTC ngày giờ, bao gồm cả chú thích kiểu đầy đủ. -> None, nhật ký mã thư viện (không bao giờ print()), cơ sở hạ tầng 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 (TestModel/FunctionModel). PyYAML trở thành phần phụ thuộc trực tiếp được khai báo (uv add pyyaml) cho trình tải danh mục.

Sơ đồ

Quy trình cấp quyền-quyết định — mỗi cuộc gọi được kiểm soát sẽ thực hiện quy tắc từ chối → quy tắc cho phép → chế độ → hỏi.

flowchart TD
    call["Gated tool call<br/>(name, subject, kind)"]:::call
    deny{"deny rule matches?<br/>(user ∪ agent)"}:::rule
    allow{"allow rule matches?<br/>(user ∪ agent)"}:::rule
    mode{"active mode"}:::mode

    DENY[["DENY<br/>(reason → model)"]]:::deny
    ALLOW[["ALLOW<br/>(run, no prompt)"]]:::allow
    ASK[["ASK<br/>(human via Decision Channel)"]]:::ask

    call --> deny
    deny -- yes --> DENY
    deny -- no --> allow
    allow -- yes --> ALLOW
    allow -- no --> mode

    mode -- bypass --> ALLOW
    mode -- plan --> kP{"kind"}:::kind
    kP -- read_only --> ALLOW
    kP -- file_edit / other --> DENY
    mode -- default --> kD{"kind"}:::kind
    kD -- read_only --> ALLOW
    kD -- file_edit / other --> ASK
    mode -- edit --> kE{"kind"}:::kind
    kE -- read_only / file_edit --> ALLOW
    kE -- other --> ASK

    classDef call fill:#1e293b,stroke:#0ea5e9,color:#e2e8f0
    classDef rule fill:#334155,stroke:#f59e0b,color:#fde68a
    classDef mode fill:#334155,stroke:#a855f7,color:#e9d5ff
    classDef kind fill:#334155,stroke:#38bdf8,color:#bae6fd
    classDef deny fill:#7f1d1d,stroke:#ef4444,color:#fee2e2
    classDef allow fill:#14532d,stroke:#22c55e,color:#dcfce7
    classDef ask fill:#713f12,stroke:#eab308,color:#fef9c3

Trạng thái agent + chế độ — nơi trạng thái tồn tại và người thay đổi/đọc nó.

flowchart LR
    subgraph surfaces["Control surfaces"]
        cli["CLI flags<br/>--agent / --mode"]:::ui
        slash["Slash cmds<br/>/agent /mode"]:::ui
        keyb["Shift+Tab<br/>cycle modes"]:::ui
        etools["enter/exit_plan_mode<br/>(model, HITL exit)"]:::tool
    end

    catalog[("Agents Catalog<br/>built-in *.md<br/>(YAML frontmatter)")]:::store
    deps["AgentDeps.active_agent<br/>: AgentDef"]:::state
    gate["PermissionGate<br/>_mode (mutable)<br/>+ user ∪ agent rules"]:::state
    rules[(".decode/settings.json<br/>USER allow/deny rules")]:::store

    instr["@agent.instructions<br/>→ active_agent.prompt"]:::read
    prep["per-tool prepare=<br/>→ active_agent.tools"]:::read
    decide["loop._decide<br/>→ gate.check verdict"]:::read

    catalog --> deps
    cli --> deps
    cli --> gate
    slash --> deps
    slash --> gate
    keyb --> gate
    etools --> gate
    deps -. "select: reset mode + load agent rules" .-> gate
    rules --> gate

    deps --> instr
    deps --> prep
    gate --> decide

    classDef ui fill:#334155,stroke:#a855f7,color:#e9d5ff
    classDef tool fill:#334155,stroke:#0ea5e9,color:#bae6fd
    classDef state fill:#1e293b,stroke:#22c55e,color:#dcfce7
    classDef store fill:#1e293b,stroke:#f59e0b,color:#fde68a
    classDef read fill:#0f172a,stroke:#64748b,color:#cbd5e1

hậu quả

  • Thay đổi đỉnh M1. Dưới default chế độ, các công cụ chỉ đọc sẽ tự động cho phép, do đó, capstone read, todo_writevà web_fetch các bước không còn nhắc nhở và sử dụng phán quyết không được phép — chỉ có hai write các bước vẫn nhắc (một được phê duyệt, một bị từ chối). Cập nhật nhiệm vụ 017 tests/integration/test_milestone1_capstone.py và bất kỳ bài kiểm tra đơn vị "đọc lời nhắc" nào, giữ make ci màu xanh lá cây. Capstone vẫn thực hiện cả ba kết quả (tự động cho phép + con người cho phép + con người từ chối). A thay đổi hành vi có chủ ý, không phải là sự thoái lui.
  • PermissionMode.ASK bị loại bỏ. Bốn chế độ thực thay thế nó; PermissionDecisionmặc định của mode= di chuyển đến DEFAULT. kết quả PermissionOutcome.ASK (cho phép/yêu cầu/từ chối) ở lại.
  • Một agent, trạng thái đã hoán đổi — không phải agent đã hoán đổi. Lời nhắc cho mỗi agent + giới hạn công cụ ctx.deps mỗi lượt (móc hướng dẫn + prepare=), vậy /agent biến đổi deps.active_agent với không có agent xây dựng lại.
  • Quy tắc là người dùng .decode/settings.json + chỉ danh mục agent. Không có cấp độ người dùng/toàn cầu/tổ chức (YAGNI); việc thêm các cấp độ sau là phần mở rộng được ưu tiên chứ không phải là viết lại. settings.json của người dùng không bao giờ được gieo sẵn với các giá trị mặc định của agent tích hợp sẵn — những giá trị này có trong danh mục.
  • always-persist ghi vào đĩa giữa lượt (vào settings.json của người dùng); một lỗi ghi là không gây tử vong nên sự kiên trì không bao giờ phá vỡ một lượt.
  • Các đường nối còn lại cho các mốc quan trọng sau: sinh sản subagent (danh mục chỉ dành cho agent chính ở đây); cấp độ quy tắc cho mỗi người dùng/toàn cầu; UX chế độ kế hoạch phong phú hơn; quyết định cấp phép dựa trên hook; cái model trường frontmatter (nhà cung cấp, bước 3).
  • Rủi ro cần xác nhận trong quá trình triển khai: đó prepare=None giấu một công cụ dưới tên Song Tử thật đường dẫn như nó làm dưới TestModel; đó s-tab kích hoạt trong terminal thực/$TERM của người dùng; đó fnmatch các chủ đề (đặc biệt là chuỗi lệnh bash) khớp một cách trực quan; chuyển đổi chế độ ở giữa lượt hạ cánh ở ranh giới rẽ (cổng được đọc theo _decide, đúng là như vậy); rằng exit_plan_mode Phê duyệt HITL sẽ vận hành Kênh Quyết định một cách rõ ràng cùng với quyền/ask_user (tạm dừng một chuyến bay).
Mục lục nguồn