Turns the design doc into an ordered, dependency-correct build plan (phases 0-9) with an exit criterion per phase, and records the twelve architectural decisions it rests on as ADRs. Decisions: single Rust binary with routed compose services; Streamable HTTP only; per-agent bearer tokens with clientInfo as a display hint only; SQLite; get_rules delivered via session gating; full-document rule delivery; project_id from day one; enforcement tier scoped to Bridle-mediated actions; stable tool list on upstream failure; OpenAI-compatible embeddings with model/dim guarding; pattern RAG gated behind a spike; CLAUDE.md + AGENTS.md as v1 renderer targets. Deviates from the design doc's original 1-8 ordering by moving the audit log and multi-project schema into phase 1, building the admin API incrementally rather than all at the Web Panel phase, downgrading vendor guardrail sourcing to manual-first, and gating the pattern-example RAG behind a validation spike. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RZerbsHGF9Ka3bKhCjJ9m
205 lines
9.2 KiB
Markdown
205 lines
9.2 KiB
Markdown
# Agentic Bridle — Phased Implementation Plan
|
||
|
||
**Status:** approved 2026-08-08 by Colby Whitlock (org lead)
|
||
**Source design:** [`Agentic Bridle MCP Plan.md`](../Agentic%20Bridle%20MCP%20Plan.md)
|
||
**Decision records:** [`docs/adr/`](adr/)
|
||
|
||
This document turns the design doc into an ordered, dependency-correct build plan. Every
|
||
architectural decision it depends on is recorded as an ADR; this plan is the schedule, the ADRs are
|
||
the reasoning.
|
||
|
||
---
|
||
|
||
## Locked decisions
|
||
|
||
| Area | Decision | ADR |
|
||
|---|---|---|
|
||
| Deployment | One Rust binary (gateway, rules, memory, admin API, panel); Qdrant / Kanboard / Gitea MCP / embeddings as separate compose services, all traffic routed through the binary | [0001](adr/0001-single-binary-with-routed-services.md) |
|
||
| Transport | Streamable HTTP only (`/mcp`) | [0002](adr/0002-streamable-http-only.md) |
|
||
| Auth | Bridle-issued per-agent bearer tokens; `clientInfo` is a display hint, never a permission source | [0003](adr/0003-per-agent-bearer-tokens.md) |
|
||
| Storage | SQLite (`bridle.db`) | [0004](adr/0004-sqlite-datastore.md) |
|
||
| Rule delivery | `get_rules` tool, with hard gating — every other tool errors until `get_rules` succeeds in that session | [0005](adr/0005-get-rules-tool-with-session-gating.md) |
|
||
| Rule scope | Full rendered document, never a dynamic subset | [0006](adr/0006-full-document-rule-delivery.md) |
|
||
| Multi-project | `project_id` in schema and tool signatures from day one; single-project UX at 1.0 | [0007](adr/0007-project-id-from-day-one.md) |
|
||
| `enforcement` tier | Scoped to Bridle-mediated actions; bypass documented as a known limit | [0008](adr/0008-enforcement-scoped-to-mediated-actions.md) |
|
||
| Degradation | Stable tool list always; downed upstreams return structured errors | [0009](adr/0009-stable-tool-list-on-upstream-failure.md) |
|
||
| Embeddings | Generic OpenAI-compatible endpoint; model + dimension recorded in collection metadata, mismatched writes refused | [0010](adr/0010-openai-compatible-embeddings.md) |
|
||
| Pattern RAG | Gated behind a throwaway validation spike | [0011](adr/0011-pattern-rag-behind-spike.md) |
|
||
| Renderers v1 | `CLAUDE.md` + `AGENTS.md` (Codex); Kimi/DeepSeek dialects post-1.0 | [0012](adr/0012-v1-renderer-targets.md) |
|
||
|
||
---
|
||
|
||
## Phase 0 — Foundations
|
||
|
||
**Size:** small
|
||
|
||
Cargo workspace, `docs/adr/` populated, compose skeleton (bridle plus profiles for `with-qdrant`,
|
||
`with-kanboard`, `with-embeddings`), Gitea CI on pull request, `.env.example`, Docker secrets
|
||
wiring.
|
||
|
||
**Exit criterion:** `docker compose up` yields a health-checked binary that completes an MCP
|
||
`initialize` over Streamable HTTP and advertises zero tools.
|
||
|
||
## Phase 1 — Gateway core
|
||
|
||
**Size:** large
|
||
|
||
The security and accountability spine. Everything later assumes it exists.
|
||
|
||
- Streamable HTTP MCP server; session lifecycle and per-session state, including the
|
||
*rules-fetched* flag that phase 2's gate depends on.
|
||
- Token auth → agent identity → permissions lookup. Token issuance, rotation, and revocation
|
||
through the admin API.
|
||
- SQLite schema carrying `project_id` throughout: `projects`, `agents`, `tokens`, `permissions`,
|
||
`audit_log`.
|
||
- Append-only audit log written from the first request, with source attribution
|
||
(user / agent / vendor).
|
||
- Docker-secrets loading, structured logging, per-request timeouts.
|
||
- Admin API skeleton — the Web Panel's contract starts here so it is not written twice.
|
||
|
||
**Exit criterion:** two agents connect under distinct authenticated identities with different
|
||
permission sets; a forged `clientInfo` changes nothing about what either may do; every call appears
|
||
in the audit log correctly attributed.
|
||
|
||
## Phase 2 — Rules server, renderers, gating
|
||
|
||
**Size:** large
|
||
|
||
The core thesis of the project.
|
||
|
||
- Canonical schema v1 with `schema_version` and JSON Schema validation: guardrails, patterns,
|
||
provenance, agent profiles, permissions.
|
||
- Storage and full CRUD admin API.
|
||
- `get_rules(project_id, agent_type)` plus the gate: any other tool call before it returns a
|
||
structured error naming the required call.
|
||
- `CLAUDE.md` and `AGENTS.md` renderers under golden-file tests. Renderer correctness is the
|
||
deliverable of this phase, so it carries real test coverage.
|
||
- Agent-proposal tools (`propose_guardrail`, `add_pattern`) writing to an approval queue. Nothing
|
||
auto-applies.
|
||
|
||
**Exit criterion:** one edit in Bridle demonstrably changes behavior in a live Claude Code session
|
||
*and* a live Codex session; a session that skips `get_rules` cannot use any tool.
|
||
|
||
## Phase 3 — Upstream proxy and Gitea
|
||
|
||
**Size:** medium
|
||
|
||
- Generic upstream-MCP client with connection pooling and health tracking.
|
||
- Tool namespacing, stable across restarts (see open item 1).
|
||
- Per-agent tool filtering driven by the phase-1 permissions table.
|
||
- Credential injection per request; upstreams never hold their own tokens.
|
||
- Failure isolation per [ADR-0009](adr/0009-stable-tool-list-on-upstream-failure.md).
|
||
|
||
**Exit criterion:** an agent completes a full Gitea PR workflow through Bridle while holding no
|
||
Gitea token itself; killing the Gitea MCP container degrades only Gitea tools.
|
||
|
||
## Phase 4 — Kanboard wrapper
|
||
|
||
**Size:** medium
|
||
|
||
Thin REST-to-MCP translation inside the binary. Validates both the author-our-own-upstream path and
|
||
the bundled-versus-external compose story.
|
||
|
||
**Exit criterion:** task CRUD from an agent against a bundled Kanboard and against an external one,
|
||
switched by environment variable alone.
|
||
|
||
## Phase 5 — Session memory
|
||
|
||
**Size:** medium
|
||
|
||
Qdrant integration, pluggable embeddings with model/dimension guarding, `<project>_sessions`
|
||
collections, `remember` and `recall` tools, retention and purge.
|
||
|
||
**Exit criterion:** a decision recorded by Claude Code is recalled by Codex in a later session;
|
||
swapping embedding models is detected and refused rather than silently corrupting the collection.
|
||
|
||
## Phase 5.5 — Pattern RAG spike
|
||
|
||
**Size:** small, throwaway
|
||
|
||
Index one real example repo with naive chunking, run roughly ten realistic queries, and judge
|
||
whether the retrieved chunks would actually improve an agent's output. This is an explicit go/no-go
|
||
gate on phase 6 — the spike code is discarded either way.
|
||
|
||
**Exit criterion:** a written go/no-go recommendation with the queries and retrieved chunks as
|
||
evidence.
|
||
|
||
## Phase 6 — Pattern-example RAG
|
||
|
||
**Size:** large — **conditional on phase 5.5 passing**
|
||
|
||
Repo fetch → tree-sitter AST-aware chunking → tagged upsert; `search_pattern_examples(query,
|
||
arch_tag?)`; `refresh_pattern_example(reference_tag)` with clean replacement of prior vectors.
|
||
|
||
**Exit criterion:** a named pattern's example repo is indexed and returns relevant code for a
|
||
natural-language query; a refresh fully replaces prior vectors for that tag.
|
||
|
||
## Phase 7 — Web Panel
|
||
|
||
**Size:** large
|
||
|
||
Served by the same binary. The admin API already exists from phases 1–6, so this phase is a client
|
||
against it: approval queue, rules editor, secrets (write, rotate, revoke — never display), audit
|
||
browser, memory browser, integration status.
|
||
|
||
**Exit criterion:** every phase 1–6 operation is achievable through the UI with no CLI or database
|
||
access.
|
||
|
||
## Phase 8 — Guardrail sourcing
|
||
|
||
**Size:** small
|
||
|
||
Manual paste-a-document flow producing proposed guardrails into the existing approval queue.
|
||
Automated vendor-doc fetching only if the manual workflow proves its worth.
|
||
|
||
**Exit criterion:** a vendor guidance document becomes a reviewable proposal that requires explicit
|
||
approval before entering the canonical ruleset.
|
||
|
||
## Phase 9 — Multi-project, hardening, 1.0
|
||
|
||
**Size:** medium
|
||
|
||
Project switcher on the day-one schema, backup and restore, install documentation, packaging,
|
||
release.
|
||
|
||
**Exit criterion:** a second person can stand up Bridle from the README against their own Gitea and
|
||
reach a working agent session.
|
||
|
||
---
|
||
|
||
## Cross-cutting
|
||
|
||
- Security review on every phase that touches authentication or secrets.
|
||
- Documentation written as features land, not retrofitted.
|
||
- One ADR per architectural decision.
|
||
- Golden-file and integration tests from phase 2 onward.
|
||
|
||
---
|
||
|
||
## Deviations from the design doc's original 1–8 ordering
|
||
|
||
- **Audit log and multi-project schema moved to phase 1.** Both are cheap to add upfront and
|
||
expensive to retrofit across five subsystems.
|
||
- **Web Panel moved after memory,** but its admin API is built incrementally from phase 1 rather
|
||
than all at the end.
|
||
- **Vendor guardrail sourcing downgraded to manual-first.** Scraping vendor documentation is
|
||
fragile; the manual path captures most of the value at a fraction of the cost.
|
||
- **Pattern-example RAG gated behind a spike** rather than committed to outright.
|
||
|
||
---
|
||
|
||
## Open items
|
||
|
||
Not blocking phase 0. Sensible defaults noted where they exist.
|
||
|
||
1. **Tool namespacing convention.** Proposed default: `gitea__create_pr` (double underscore).
|
||
Effectively permanent once agents start depending on it.
|
||
2. **Audit log integrity.** Plain append-only table, or hash-chained and tamper-evident?
|
||
3. **Approval queue notifications.** Panel-only, or out-of-band webhook/email when an agent
|
||
proposes a change?
|
||
4. **Web Panel stack.** Server-rendered Rust templates versus an embedded SPA. Not blocking until
|
||
phase 7.
|
||
5. **Kanboard instance and auth model.** API user versus application token.
|
||
6. **CI.** Gitea runner availability, and whether PR-gated CI starts at phase 0.
|
||
7. **Repository visibility** and whether GPLv3 remains the license.
|