From 700e33e3b173a52b9e8731a50017fa78481068dd Mon Sep 17 00:00:00 2001 From: leetcrypt Date: Mon, 1 Jun 2026 01:24:48 -0700 Subject: [PATCH] docs: AI agent bridge spec (model-agnostic, /ai command, owner-gated ops) Co-Authored-By: Claude Opus 4.6 --- docs/spec-agent-bridge.md | 165 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/spec-agent-bridge.md diff --git a/docs/spec-agent-bridge.md b/docs/spec-agent-bridge.md new file mode 100644 index 0000000..9440403 --- /dev/null +++ b/docs/spec-agent-bridge.md @@ -0,0 +1,165 @@ +# hack-house → AI Agent Bridge — Spec + +> **Status:** Draft v1 · **Date:** 2026-06-01 +> **Scope:** Let model-agnostic AI agents participate in a hack-house room as +> first-class encrypted clients — addressed on demand to answer questions, do +> research, check work, and give hints — without weakening the zero-knowledge +> server, E2E Fernet, or SRP guarantees. +> **Baseline reviewed:** `cmd_chat/` server + `hh/` client @ `dev`. + +--- + +## 0. Decisions locked (from product owner) + +| # | Decision | Choice | +|---|----------|--------| +| A | Runtime language | **Python** agent runtime (`cmd_chat/agent/`), reusing the existing client's SRP/Fernet/WS plumbing. Not in the Rust TUI. | +| B | Default model | **Ollama, local, recommended.** Privacy-preserving default. | +| C | Model-agnostic | A **provider plugin interface** any backend can implement (ollama, anthropic, openai-compatible, or a custom `module:Class`). | +| D | Addressing | **`/ai` command.** `/ai ` if exactly one agent present; `/ai ` to target one by name. | +| E | Sandbox access | Per-agent, owner-gated, mirroring `/drive`: `/ai enable-sbx ` / `/ai disable-sbx `. Default **off**. | +| F | Operator permission | Operating an agent is privileged. The **room admin** (host) may `/ai grant ` / `/ai revoke ` to let non-hosts bring/operate an agent. | +| G | Capacity | Each agent consumes one room seat against `max_users`. MVP documents raising `CMD_CHAT_MAX_USERS`; a server-side "agent pool" is a possible later change. | + +--- + +## 1. Core model + +**An agent is just another client.** It runs SRP with the room password + a +display name, derives the room key `HKDF(password, room_salt)`, opens the WS, +decrypts every broadcast, and — only when explicitly addressed — encrypts a +reply and sends it like any human. The server (`chat_ws`, a blind relay that +stamps the authenticated `username` on each frame) needs **no changes** for the +MVP. + +``` + human TUI ─┐ ┌─ agent runtime (headless python client) + ├── encrypted WS ─ SERVER ─┤ decrypt → addressed? → Provider.complete() + human TUI ─┘ (blind relay) └─ → encrypt reply → send as chat +``` + +This preserves all existing guarantees: the server still sees only ciphertext; +the agent only reads a room it already has the password for. + +### Privacy posture (non-negotiable defaults) +1. **Addressed-only.** The agent reads everything (it's a client) but forwards to + the model *only* what triggers it. No passive surveillance; lower cost/noise. +2. **Local-first.** Ollama/llama.cpp are the recommended default; cloud providers + are opt-in. +3. **Announce-on-join.** On joining, the agent posts a visible chat line so humans + know an agent is present and where their words go, e.g. + `🤖 oracle joined — ollama/llama3 (local), operated by alice`. + +--- + +## 2. Command grammar (`/ai`) + +Reserved first-tokens (an agent name may **not** collide with these): +`list`, `grant`, `revoke`, `enable-sbx`, `disable-sbx`. + +| Command | Who | Effect | +|---|---|---| +| `/ai ` | any member | Address the sole agent (error if 0 or >1 present). | +| `/ai ` | any member | Address a named agent. | +| `/ai list` | any member | List agents present + provider/model, operator, sbx state. | +| `/ai grant ` | **admin** | Allow `` to operate agents. | +| `/ai revoke ` | **admin** | Withdraw operator permission. | +| `/ai enable-sbx ` | **sandbox owner** | Allow agent to run sandbox commands. | +| `/ai disable-sbx ` | **sandbox owner** | Withdraw sandbox access. | + +Resolution order for the first token: reserved verb → known agent name → +otherwise treat the whole remainder as a question to the sole agent. + +--- + +## 3. Permission tiers (mirrors the existing drive/sudo ACL) + +| Tier | Who | Granted by | What it unlocks | +|---|---|---|---| +| **admin** | room/server host | seeded at launch | grant/revoke operators | +| **operator** | admin-granted (or admin) | `/ai grant` | summon & operate an agent the room will honor | +| **sandbox-owner** | whoever hosts `/sbx` | implicit | `/ai enable-sbx` per agent | +| **member** | anyone in the room | — | `/ai ` to a present agent | + +### Identifying the admin +The agent and operator clients learn the admin's username at agent launch +(`--admin `), defaulting to the room/server host. Because the server +stamps the authenticated sender on every frame, an agent can trust that an +`/ai grant bob` line genuinely came from the admin before honoring it. + +### Enforcement honesty (matches the drive-ACL trust model) +- **Sandbox-exec gating is hard-enforced:** the sandbox owner's broker is the + sole writer to the PTY (it already filters drive input), so an agent's + `sandbox_exec` only runs if the owner has `enable-sbx`'d it. Real. +- **Operator ACL is app-layer:** a well-behaved agent obeys the admin's + broadcast ACL and refuses to act if its operator isn't authorized. A hostile + process that already has the room password could ignore this — the same class + of trust as the app-level drive ACL (minus the OS backstop that `sudo` adds). + Documented, not hidden. + +--- + +## 4. Wire conventions + +All agent control rides the existing "encrypted JSON with a prefix" convention +(`{"_sbx":…}`, `{"_ft":…}`, `{"_perm":…}` → add `{"_ai":…}`), so it's E2E like +everything else. + +- **Announce / meta** (agent → room, on join): + `{"_ai":"meta","agent":"oracle","operator":"alice","provider":"ollama","model":"llama3","sbx":false}` +- **Operator ACL** (admin → room): `{"_ai":"acl","operators":["alice"],"sbx":["oracle"]}` + +**MVP simplification:** to avoid Rust TUI work up front, the announce can be a +plain visible chat line and `/ai` queries can be sent as ordinary chat that the +agent scans for. Structured `_ai` frames + a 🤖 roster glyph are Phase 3. + +--- + +## 5. Provider interface (the "API to plug in") + +```python +class Provider(Protocol): + name: str + supports_tools: bool + def complete(self, system: str, messages: list[Msg], + tools: list[Tool] | None = None) -> Reply: ... +``` + +- Bundled adapters: `ollama` (default), `anthropic`, `openai-compatible` + (OpenAI / Groq / Together / local vLLM …). +- Third-party: `--provider mypkg.module:MyProvider`. +- Per-agent config (TOML): `name, provider, model, admin, context_window, + system_prompt, tool_allowlist, rate_limit`. + +When triggered, the agent assembles `system` + a bounded window of recent room +transcript + the trigger, calls `complete()`, buffers the reply, and sends it as +one encrypted chat message. (Streaming = Phase 3; the protocol is append-only.) + +--- + +## 6. Phasing + +| Phase | Scope | +|---|---| +| **0** | Design + `dev` branch ✓ | +| **1 (MVP)** | Python runtime; `/ai` query (addressed-only); providers ollama + anthropic + openai-compatible; bounded context; buffered reply; plain-text announce-on-join; operator ACL (`/ai grant`/`revoke`) honored by the agent. Minimal Rust: route `/ai …` to chat send. | +| **2** | Tool layer: `web_fetch` + `sandbox_exec` (owner-gated via `/ai enable-sbx`); `/research` `/check` `/hint` verbs; provider tool-use where supported, text-only fallback elsewhere. | +| **3** | Rust TUI polish: structured `{"_ai":…}` frames, 🤖 roster glyph, "typing…" line, `/ai list` rendering, response streaming/coalescing. | + +--- + +## 7. Security notes +- **Prompt injection:** room text is untrusted LLM input. Harden the system + prompt; the agent never exposes the password/key; `sandbox_exec` is capped + + owner-gated. +- **Secrets:** provider API keys live in the agent's env, never in the room. +- **Abuse/cost:** respond only when addressed + per-agent rate limit. +- **Capacity:** agent = one seat; document raising `CMD_CHAT_MAX_USERS`. + +--- + +## 8. Open question +- **Designating the room admin in-protocol.** MVP uses `--admin ` at + agent launch (defaults to host). A sturdier option (server marks an admin via + `CMD_CHAT_ADMIN` env or an admin token) would make the operator ACL + tamper-resistant but requires a small server change — deferred unless wanted.