docs/adr/0003-milestone-2-permission-system-and-agents-catalog.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.
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ôngprepare_toolskwarg, nhưngAgent.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 đượcctx.deps. A sự tăng vọt có thể chạy được đã xác nhận điều đó (aTestModelgọ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-tabchìa khóa (Keys.BackTab).
Quyết định
-
Bốn chế độ cấp phép thay thế một chế độ
ASK.PermissionModetrở thành{DEFAULT, PLAN, EDIT, BYPASS}(cũASKgiá trị mode bị xóa; cổng mặc định làDEFAULT). cácPermissionOutcomeliệt kêallow/ask/denykhô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).
-
A
ToolKindphân loại (READ_ONLY | FILE_EDIT | OTHER) thay thế đơnread_onlybool 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.ToolSpecmang theokind: ToolKind;read_onlycó nguồn gốc (kind is READ_ONLY) tồn tại như vậyis_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_uservà 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.) -
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).PermissionGatelợi íchset_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ớiToolDenied); 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ế độ. -
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ụiTool, khớp (toàn cầu thông quafnmatch) đố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-Toolkhớ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/denyliệ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ã mangallow: ["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).
- Cài đặt người dùng — cấp độ dự án
-
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,toolsdanh sách cho phép,modemặc định, tùy chọnallow/denyquy tắc) + phần thân nhắc hệ thống. Khôngmodelfield 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êmmodelsau này tương thích về phía trước. MộtAgentDefthự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ớiallow: ["bash(git *)"]vậygit diff/log/showtự động cho phép trong khi bash khác vẫn hỏi.
- xây dựng — tất cả công cụ (bao gồm.
-
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ộtpreparegọi lại có nội dungctx.deps.active_agent.toolsvà 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ônghandler._agenttrao đổi. -
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 quaset_mode). agent đang hoạt động vẫn tiếp tục tồn tạiAgentDeps.active_agent: AgentDef(có thể thay đổi). Đang chọn một bộ agentdeps.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.instructionshook (cơ chế sử dụng bộ nhớ): nó đọcctx.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. -
Dụng cụ hòa âm + ngủ.
enter_plan_modechuyển cổng sangPLANvà trả về một sự thừa nhận.exit_plan_modetrì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ạiPLANvà trả về thông báo "tinh chỉnh và gọi lại exit_plan_mode".sleep(seconds)là một dòngawait 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ớiModelRetry. 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. -
Bề mặt điều khiển. Khởi động
--agent NAME/--mode NAMENhấp vào tùy chọn trêncli.py(mặc định agentbuild; 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ímdefault → 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âyprompt_async()). -
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ươngsrc/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
defaultchế độ, các công cụ chỉ đọc sẽ tự động cho phép, do đó, capstoneread,todo_writevàweb_fetchcá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ó haiwritecá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ụ 017tests/integration/test_milestone1_capstone.pyvà bất kỳ bài kiểm tra đơn vị "đọc lời nhắc" nào, giữmake cimà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.ASKbị loại bỏ. Bốn chế độ thực thay thế nó;PermissionDecisionmặc định củamode=di chuyển đếnDEFAULT. 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.depsmỗi lượt (móc hướng dẫn +prepare=), vậy/agentbiến đổideps.active_agentvớ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
modeltrườ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=Nonegiấu một công cụ dưới tên Song Tử thật đường dẫn như nó làm dướiTestModel; đós-tabkích hoạt trong terminal thực/$TERM của người dùng; đófnmatchcá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ằngexit_plan_modePhê 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).