8.0 KiB
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 <question> if exactly one agent present; /ai <agent> <question> to target one by name. |
| E | Sandbox access | Per-agent, owner-gated, mirroring /drive: /ai enable-sbx <agent> / /ai disable-sbx <agent>. Default off. |
| F | Operator permission | Operating an agent is privileged. The room admin (host) may /ai grant <user> / /ai revoke <user> 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)
- 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.
- Local-first. Ollama/llama.cpp are the recommended default; cloud providers are opt-in.
- 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 <question> |
any member | Address the sole agent (error if 0 or >1 present). |
/ai <agent> <question> |
any member | Address a named agent. |
/ai list |
any member | List agents present + provider/model, operator, sbx state. |
/ai grant <user> |
admin | Allow <user> to operate agents. |
/ai revoke <user> |
admin | Withdraw operator permission. |
/ai enable-sbx <agent> |
sandbox owner | Allow agent to run sandbox commands. |
/ai disable-sbx <agent> |
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 <question> to a present agent |
Identifying the admin
The agent and operator clients learn the admin's username at agent launch
(--admin <username>), 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_execonly runs if the owner hasenable-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
sudoadds). 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")
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_execis 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 <username>at agent launch (defaults to host). A sturdier option (server marks an admin viaCMD_CHAT_ADMINenv or an admin token) would make the operator ACL tamper-resistant but requires a small server change — deferred unless wanted.