docs(adr): ADR-0030 agent tool surface granularity and naming #328

Merged
james merged 1 commit from 50-adr-tool-surface into main 2026-06-28 19:52:15 +00:00
Owner

Shapes the agent tool surface for epic #47 before the domain-tool-surface ticket (#50). Design only — no code, per the acceptance criteria. Linked from docs/adr/README.md. Extends ADR-0029.

Decisions (one per scope bullet)

  1. Granularity — coarse per-entity CRUD (list/get/create/update/delete_<entity>) as the default; domain-shaped tools (find_people_at_org, link_person_to_org, add_note_to_person, merge_people) added only where they earn it (collapse a multi-step flow, encode an invariant, or make a destructive op safe/atomic). Both in one registry.
  2. Namingverb_noun snake_case, uniform across CRUD and domain tools; singular nouns for single-item ops, plural for collections.
  3. Write-tool shape — a ProposedChange (proposal_id, action, tool, entity, agent-authored summary, structured diff) persisted as ADR-0029's pending_action. Confirmed by id, not a re-sent payload (server holds the authoritative proposal → tamper-proof); re-validated at apply time → stale proposals conflict and re-propose.
  4. Classificationsafe_create | safe_update | destructive_update | destructive_delete on every write tool's definition + proposal. PWA escalates confirmation proportionately (inline for safe_*, danger-styled naming-what's-lost for destructive_*); MCP clients get the tag too.
  5. Discovery — exhaustive standard MCP tools/list at handshake (clients list once + cache); cursor pagination kept as the later lever; no categorised/search layer.
  6. Cross-user safety — acting identity bound once at the top of the loop (cookie / PAT→user), passed to repositories out-of-band; tools never accept user_id, making cross-user access unrepresentable in the schema; enforcement at the single repository chokepoint (ADR-0027), 404 on cross-user ids.

Consequences (CRUD pushes planning onto the model where no domain tool exists; the "earns its place" bar needs discipline; tools/list token cost grows with the surface; re-validation adds a conflict path) and six rejected alternatives (domain-first, CRUD-only, noun.verb, categorised discovery, client-re-sends-payload, user_id-with-validation) are documented.

Closes #50

🤖 Generated with Claude Code

Shapes the agent tool surface for epic #47 before the domain-tool-surface ticket (#50). Design only — no code, per the acceptance criteria. Linked from `docs/adr/README.md`. Extends ADR-0029. ## Decisions (one per scope bullet) 1. **Granularity** — coarse per-entity CRUD (`list/get/create/update/delete_<entity>`) as the default; domain-shaped tools (`find_people_at_org`, `link_person_to_org`, `add_note_to_person`, `merge_people`) added **only where they earn it** (collapse a multi-step flow, encode an invariant, or make a destructive op safe/atomic). Both in one registry. 2. **Naming** — `verb_noun` snake_case, uniform across CRUD and domain tools; singular nouns for single-item ops, plural for collections. 3. **Write-tool shape** — a `ProposedChange` (`proposal_id`, `action`, `tool`, `entity`, agent-authored `summary`, structured `diff`) persisted as ADR-0029's `pending_action`. Confirmed **by id, not a re-sent payload** (server holds the authoritative proposal → tamper-proof); re-validated at apply time → stale proposals conflict and re-propose. 4. **Classification** — `safe_create | safe_update | destructive_update | destructive_delete` on every write tool's definition + proposal. PWA escalates confirmation proportionately (inline for `safe_*`, danger-styled naming-what's-lost for `destructive_*`); MCP clients get the tag too. 5. **Discovery** — exhaustive standard MCP `tools/list` at handshake (clients list once + cache); cursor pagination kept as the later lever; no categorised/search layer. 6. **Cross-user safety** — acting identity bound once at the top of the loop (cookie / PAT→user), passed to repositories out-of-band; **tools never accept `user_id`**, making cross-user access *unrepresentable* in the schema; enforcement at the single repository chokepoint (ADR-0027), 404 on cross-user ids. Consequences (CRUD pushes planning onto the model where no domain tool exists; the "earns its place" bar needs discipline; `tools/list` token cost grows with the surface; re-validation adds a conflict path) and six rejected alternatives (domain-first, CRUD-only, `noun.verb`, categorised discovery, client-re-sends-payload, `user_id`-with-validation) are documented. Closes #50 🤖 Generated with [Claude Code](https://claude.com/claude-code)
docs(adr): ADR-0030 agent tool surface granularity and naming
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 4s
PR / OSV-Scanner (pull_request) Successful in 49s
PR / Static analysis (pull_request) Successful in 1m49s
PR / OpenAPI (pull_request) Successful in 2m1s
PR / pnpm audit (pull_request) Successful in 2m5s
PR / Lint (pull_request) Successful in 2m11s
PR / Build (pull_request) Successful in 2m21s
PR / Trivy (image) (pull_request) Successful in 1m50s
Secrets / gitleaks (pull_request) Successful in 51s
PR / Package age policy (soft) (pull_request) Successful in 58s
PR / Typecheck (pull_request) Successful in 3m55s
PR / Test (sqlite) (pull_request) Successful in 4m5s
PR / Client (web export smoke) (pull_request) Successful in 4m9s
PR / Coverage (soft) (pull_request) Successful in 2m19s
PR / Test (postgres) (pull_request) Successful in 4m14s
dc61bd2a90
Shapes the agent tool surface for epic #47, before the domain-tool-
surface ticket: coarse per-entity CRUD as the default with domain-shaped
tools added only where they earn their place; verb_noun snake_case
naming; a ProposedChange write-tool object backed by ADR-0029's
pending_action and confirmed by id (not a re-sent payload); a
safe/destructive × create/update/delete classification that drives
proportionate confirmation; exhaustive MCP tools/list at handshake; and
cross-user safety enforced at the repository layer with no user_id ever
accepted as a tool parameter.

No code — design only. Linked from docs/adr/README.md.

Closes #50

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 82.3% ≥ 50%
Branches 73.4% ⚠️ ≥ 75%
Functions 91.6% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 82.3% ✅ | ≥ 50% | | Branches | 73.4% ⚠️ | ≥ 75% | | Functions | 91.6% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit 271e95455d into main 2026-06-28 19:52:15 +00:00
james deleted branch 50-adr-tool-surface 2026-06-28 19:52:15 +00:00
Sign in to join this conversation.
No description provided.