diff --git a/Agentic Bridle MCP Plan.md b/Agentic Bridle MCP Plan.md new file mode 100644 index 0000000..aa00153 --- /dev/null +++ b/Agentic Bridle MCP Plan.md @@ -0,0 +1,199 @@ +# Agentic Bridle MCP + +## Purpose + +A self-hosted MCP server that acts as the single point of contact between coding agents (Claude Code, Codex, Kimi, DeepSeek, and others) and a project's tools, memory, and rules. The goal: switching which coding agent you're using should not change the behavior, context, or constraints that agent operates under. The intelligence and consistency live in this server, not in each agent's local config file. + +**Design principle:** Bridle is project-agnostic infrastructure — a control plane other projects plug into, not something built around or dependent on any single project. RunicGateway (or any other project) is an early adopter/consumer of Bridle, not a dependency of it. + +Deployed self-hosted via Docker Compose. Designed to be usable by others, not just a single homelab: Gitea is assumed to already exist (it's the one thing wired in as a pure external integration), but every other supporting service — Kanboard, Qdrant, LM Studio-equivalent embedding, etc. — ships as an *optional installable* component in the compose file. Someone who already runs their own Kanboard or vector DB points the gateway at their existing instance via env vars; someone who doesn't can spin the bundled version up alongside everything else with one `docker compose up`. + +--- + +## Architecture + +``` + ┌─────────────────────┐ + Agents ───────► │ Gateway (Rust) │ ← single MCP entrypoint + (Claude Code, │ - auth/session │ + Codex, Kimi, │ - routing │ + DeepSeek, ...) │ - token vault │ + └──────────┬───────────┘ + │ + ┌──────────────┬───────┴────────┬───────────────┬────────────────┐ + ▼ ▼ ▼ ▼ ▼ + ┌───────────┐ ┌────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ + │ Rules │ │ Memory │ │ Gitea MCP │ │ Kanboard MCP │ │ Web Panel │ + │ Server │ │ Service │ │ (existing) │ │ (custom) │ │ (admin UI) │ + │ (custom) │ │ (custom) │ │ │ │ │ │ │ + └─────┬──────┘ └──────┬──────┘ └──────────────┘ └──────────────┘ └──────┬───────┘ + │ │ │ + └────────► Qdrant (vector store) ◄───────────────────────────────────┘ +``` + +All admin actions — from any surface — go through the Gateway's admin API. The Web Panel is a thin client against that API, not a separate integration point per service. + +--- + +## Components + +### 1. Gateway (custom, Rust) + +- Single MCP endpoint all agents connect to. +- Identifies connecting agent (via MCP handshake client info or explicit config) and tags requests with agent type. +- Routes requests to backend services, aggregates responses into a consistent shape. +- **Owns all secrets/tokens centrally.** Backend services never hold their own credentials — the gateway injects them per-request. Agents and sub-services never see raw tokens. +- Token storage: Docker secrets for v1 (file-mounted, not env vars); consider SOPS if secrets need to be version-controlled in Gitea; Vault only if this scales beyond single-host/single-user. +- Exposes an admin API that the Web Panel consumes for full control of every subsystem. +- Maintains an audit log distinguishing the source of every change: user-edited (via panel), agent-proposed (via MCP tool, requires approval), or vendor-seeded (via guardrail suggestion feed, requires approval). + +### 2. Rules Server (custom) + +Stores one canonical ruleset per project — the source of truth that gets rendered into each agent's native format. + +**Canonical document structure:** +```yaml +project: + overview: ... + stack: ... + conventions: ... + +guardrails: + - id: no-unrequested-refactors + statement: "Do not perform unrelated refactoring outside the requested task." + severity: guidance | hard-instruction | enforcement + applies_to: [all_agents] + provenance: + source: user | agent | vendor + agent_session: ... # if agent-proposed + approved_by: ... + approved_at: ... + +patterns: + standard: [MVC, MVVM, hexagonal, ...] + custom: + - name: "modified-mvc" + description: ... + example_repos: + - url: ... + reference_tag: "modified-mvc-example-1" + arch_tag: "modified-mvc" + last_indexed: +``` + +Guardrail severity tiers: +- **guidance** — model can deviate, shouldn't +- **hard-instruction** — agent is required to follow it. For scope-expansion rules specifically, this means the agent evaluates its own next action against the rule and calls a `request_scope_approval` MCP tool to check in before proceeding — not the gateway intercepting tool calls in the live path. +- **enforcement** — the gateway itself gates the action (e.g. rules-approval, secret access) rather than relying on the agent to self-report. + +**Guardrails vs. agent profiles — kept separate:** project guardrails (above) are project policy, with their own provenance and lifecycle. This is deliberately distinct from vendor-published best-practice guidance, tracked separately per agent: + +```yaml +agent_profiles: + claude: + capabilities: [...] + instruction_format: "CLAUDE.md conventions" + vendor_guardrail_suggestions: [...] # sourced from Anthropic docs, pending review + codex: + capabilities: [...] + instruction_format: "AGENTS.md" + vendor_guardrail_suggestions: [...] + kimi: {...} + deepseek: {...} +``` + +A vendor suggestion only becomes project policy once explicitly approved via the Web Panel — at which point it's promoted into the canonical `guardrails:` list with its own provenance record. This keeps vendor documentation from silently becoming project policy. + +**Permissions:** each agent's authority over Bridle itself is explicit, not implied — e.g. an agent may be permitted to *propose* rule changes without being permitted to *approve* them: +```yaml +permissions: + claude: + gitea: {read: true, write: true} + kanboard: {read: true, write: true} + rules: {propose: true, approve: false} + secrets: {read: false} +``` + +**Per-model rendering:** `get_rules(project_id, agent_type)` renders the canonical doc into the target format: +- Claude → `CLAUDE.md`, native heading conventions +- Codex → `AGENTS.md` +- Kimi / DeepSeek → `AGENTS.md`-style, but formatted with more explicit, step-by-step phrasing to compensate for weaker instruction-following at the guardrail level + +**Full document delivered, not a dynamic subset.** The rendered rules doc is always sent in full — no path-based or task-based filtering of what an agent receives. Some models (e.g. Opus-tier) need a fuller, more complete leash to reliably stay on task than others; how much a given model needs is itself a per-model concern handled by the renderer, not a universal token-saving optimization applied to everyone. Dynamic subsetting was considered and rejected for this reason. + +**Guardrail sourcing:** a periodic or manually-triggered job pulls each vendor's published agent-guidance docs and proposes guardrail entries into a review queue. Nothing auto-merges — proposals require approval via the Web Panel before becoming part of the canonical doc. + +**Editing paths:** +- Web Panel — primary surface for reviewing vendor suggestions, editing guardrails/patterns directly, managing example repos. +- Agent-initiated — MCP tools (`propose_guardrail`, `add_pattern`, `update_rules`) let an agent suggest changes mid-session. These are gated behind a confirmation step (surfaced in the Web Panel's approval queue) rather than auto-applied. + +### 3. Memory Service (custom) + +- Wraps Qdrant (self-hosted) for vector storage. +- Embeddings generated locally via LM Studio (RTX 3060). +- Two primary use cases sharing the same Qdrant instance in separate collections/namespaces: + - **Session memory** — decisions, context, and history from agent sessions. + - **Pattern example RAG** — indexed code from example repos linked to architecture patterns. + +**Pattern example indexing:** +- Triggered once when a user adds an example repo to a pattern entry (clone → chunk → embed → upsert). +- Payload metadata per chunk: `reference_tag`, `arch_tag`, `source_repo`, `file_path`, `chunk_type`, `last_indexed`. +- **No automatic re-fetching.** Re-indexing only happens on explicit trigger — via the Web Panel or by telling an agent to refresh (`refresh_pattern_example(reference_tag)`), which re-runs the fetch/chunk/embed/upsert flow and replaces prior vectors for that tag. +- Exposed as an MCP tool: `search_pattern_examples(query, arch_tag?)` — semantic search optionally filtered to a specific named pattern. + +### 4. Kanboard MCP (custom wrapper) + +- Wraps Kanboard's existing REST API as MCP tools for task/plan management. +- Kanboard itself is not replaced — this is a thin translation layer. + +### 5. Gitea MCP (existing) + +- Uses an existing Gitea MCP server implementation rather than a custom-built wrapper. +- Exposes issues, PRs, commits, diffs as tools, authenticated via a gateway-issued token. + +### 6. Web Panel (custom) + +- Full admin surface — this project is user/developer-first, and the panel is not an add-on but the primary control interface. +- Talks only to the Gateway's admin API (not directly to individual backend services), mirroring the centralized-token principle. +- Controls: + - Gateway: connected agents/sessions, routing, agent-identity mapping + - Secrets: add/rotate/revoke (raw values never displayed after storage) + - Rules: edit canonical doc, approve/reject agent-proposed and vendor-seeded guardrails, manage patterns and example repos, trigger re-indexing + - Memory: browse/search indexed content, manual purge/re-index + - Integrations: Gitea/Kanboard connection status and project mappings + - Audit log: full change history with source attribution + +--- + +## Deployment + +- Docker Compose, self-hosted (dev instance on Darrow/Proxmox or Lucifer/Unraid). +- WireGuard for secure agent access when off local network. +- Pangolin if external reachability is needed. + +**Optional/bundled services:** everything except Gitea is toggleable in the compose file — bundled via Compose profiles (e.g. `--profile with-kanboard`, `--profile with-qdrant`) so each service is either: +- **Bundled**: spun up as part of the stack for someone with no existing instance, or +- **External**: pointed at an existing self-hosted instance via env vars (`KANBOARD_URL`, `QDRANT_URL`, etc.), skipping the bundled container entirely. + +Gitea is the exception — treated as a required external integration (via the existing Gitea MCP server) rather than something bundled, since it's assumed the user already runs one. + +--- + +## Build Phases + +1. Gateway skeleton + Rules Server (prove single-source-of-truth rendering across at least two agent formats) +2. Qdrant integration for session memory +3. Kanboard MCP wrapper +4. Gitea MCP integration (existing server, wired through gateway auth) +5. Pattern-example RAG pipeline (fetch/chunk/embed/tag/search) +6. Web Panel — full admin surface across all of the above +7. Guardrail vendor-sourcing feed + approval queue +8. Audit log and multi-project support + +--- + +## Open Questions + +- Exact schema/versioning strategy for the canonical rules document as it evolves (e.g. schema migrations when new sections are added). +- Whether Web Panel auth is single-user or needs multi-user roles (currently designed single-user/developer-first). +- Long-term: whether to integrate with agentglass for session observability once the gateway is routing all agent traffic. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ae0f6d4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Subagents + +Use subagents (the Agent tool) when a task genuinely benefits from one — and match the model to the +work: + +- **Reach for a subagent** for broad, read-heavy fan-out that would otherwise flood the main context: + sweeping many files across services, tracing a change through multiple components, or researching + where/how something is wired. Prefer `Explore` (read-only search) or `general-purpose` for these. + Use `Plan` to design a multi-step implementation before editing. Keep narrow, single-file edits and + quick lookups on the main thread — spawning a cold agent for those costs more than it saves. +- **Assign the model to the task.** Route heavy reasoning — architecture decisions, contract/protocol + changes, security-sensitive work (auth, secrets, credential handling) — to a stronger model (Opus). + Route routine, well-scoped mechanical work — bulk search, boilerplate, simple refactors, + running/reporting tests — to a faster, cheaper model (Sonnet or Haiku). State the model explicitly + when spawning rather than defaulting. + +## Git & Gitea access + +- **All commits and pushes use the `wtclaude` bot identity, authenticated with the token at + `C:\Users\colby\.gitea_token_claude`.** Do not use the human user's git credentials. Configure the + remote (or a one-off push) to carry the token and set the author/committer to `wtclaude`, e.g.: + + ```bash + TOKEN=$(cat /c/Users/colby/.gitea_token_claude) + git -c http.extraHeader="Authorization: token $TOKEN" \ + -c user.name=wtclaude -c user.email=claude@whitlocktech.net \ + push origin + ``` + + Keep the token out of committed files, remote URLs, and command output/logs (pass it via + `http.extraHeader` or a credential helper, never inline in the `origin` URL). +- **Use the Gitea MCP tools (`mcp__gitea__*`) for all server-side Gitea operations** — opening and + reviewing pull requests, issues, releases, branches, labels, and reading repo contents. Prefer the + MCP over shelling out to `git`/`gh`/`tea` or the raw REST API for these. Reserve local `git` for + working-tree operations (commit, push, branch checkout) using the token above. +- **Sync `main` before you start coding.** Before creating any new branch, switch to `main` and pull + so the local copy matches the remote (branches are always cut from an up-to-date `main`, never a + stale one): + + ```bash + TOKEN=$(cat /c/Users/colby/.gitea_token_claude) + git checkout main + git -c http.extraHeader="Authorization: token $TOKEN" pull --ff-only origin main + git checkout -b / + ``` + + If the working tree has uncommitted changes that block the checkout/pull, stop and surface it + rather than discarding them. + +## SonarQube (code quality / security scanning) + +- **SonarQube is at `https://sonar.whitlocktech.com`.** The API token lives in a local file in + `SONAR_TOKEN=squ_…` (KEY=value) format — extract the value after `=`. Read it at call time, and + keep it out of committed files, remote URLs, and command output/logs. **Authenticate with HTTP + Basic auth (token as username, empty password) — `-u "$TOKEN:"`; the `Authorization: Bearer` header + returns 401 on this instance.** Example: + + ```bash + TOKEN=$(grep -oP '(?<=SONAR_TOKEN=).*' /path/to/sonar_token.txt) + curl -s -u "$TOKEN:" \ + "https://sonar.whitlocktech.com/api/issues/search?componentKeys=&types=VULNERABILITY" + ``` + +- **Project key(s):** _fill in once the Sonar project(s) for this repo are created._ + +## Conventions + +- **Conventional Commits** (`type(scope): summary`, e.g. `feat(gateway): add token vault`). +- **AI-assisted contributions must be disclosed** (org policy): tick the PR-template box naming the + tool, and mark AI-authored commits with a trailer such as `Co-Authored-By: Claude `. + Undisclosed AI-generated contributions may be closed. See `CONTRIBUTING.md`. +- Branch from `main` (`feature/…`, `fix/…`, `docs/…`, `chore/…`). +- **All architectural and design decisions must be asked and approved by the org lead (Colby + Whitlock) before implementation. You must ask before writing code.**