docs(giellie): add agent design spec and implementation plan
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7555196166
commit
662610ca28
2 changed files with 3637 additions and 0 deletions
3447
docs/superpowers/plans/2026-09-30-giellie-agent.md
Normal file
3447
docs/superpowers/plans/2026-09-30-giellie-agent.md
Normal file
File diff suppressed because it is too large
Load diff
190
docs/superpowers/specs/2026-09-30-giellie-agent-design.md
Normal file
190
docs/superpowers/specs/2026-09-30-giellie-agent-design.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# Giellie Agent — design
|
||||
|
||||
**Date:** 2026-09-30 · **Status:** draft, awaiting review
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the sidebar "Giellie" web view (`/webview?src=hermes`) with a chat
|
||||
agent that can see all WFDASH data, answer questions, do its own
|
||||
calculations, and perform any action the app supports — except delete —
|
||||
with the user's approval for changes. Powered by MiniMax (M3, multimodal)
|
||||
using the key already stored in `/settings/ai`.
|
||||
|
||||
## Decisions (agreed with Dave)
|
||||
|
||||
| Topic | Decision |
|
||||
|---|---|
|
||||
| Name | **Giellie** (two l's). Sidebar label "Giellie Agent", route `/giellie`. |
|
||||
| Tool access | In-process call to the existing MCP handler (approach A). No tool logic is duplicated. |
|
||||
| Writes | Approve/Reject card per pending write batch. Per-chat "trust this chat" toggle auto-approves ordinary writes. |
|
||||
| Deletes | Never. `*.delete` tools are removed from Giellie's catalogue. |
|
||||
| Who can use it | Any logged-in user (Dave's call; reps/drivers can therefore ask for costs and margins). |
|
||||
| Persistence | Chats saved per user in **one** PB collection, `giellie_chats`. |
|
||||
| Input | Typed/pasted text, file upload, camera/photo button. |
|
||||
| Calculations | Done by the model itself; no calculator tool. |
|
||||
| Python | Not used. Production is `node:20-alpine`; the TS extractor already covers the formats. |
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Browser (/giellie)
|
||||
│ POST /api/v1/giellie/chat (SSE stream)
|
||||
│ POST /api/v1/giellie/chat/[id]/decision
|
||||
▼
|
||||
giellie-service (src/lib/server/giellie-service.ts)
|
||||
├─ auth: verify PB token → user ── reject 401 if invalid
|
||||
├─ load/save chat in PB (giellie_chats)
|
||||
├─ agent loop: MiniMax ⇄ tools
|
||||
└─ tool bridge → POST handler of src/app/api/mcp/route.ts
|
||||
(synthetic JSON-RPC request, Bearer AGENT_API_KEY, actor = user)
|
||||
```
|
||||
|
||||
### Auth (important)
|
||||
|
||||
The app has **no server-side route auth** — the PB token is forwarded as
|
||||
`x-wfdash-auth` for audit attribution only. Giellie's routes hold
|
||||
`AGENT_API_KEY` and can write, so they MUST verify the token themselves:
|
||||
`authRefresh()` against PB with the `x-wfdash-auth` token; 401 on failure.
|
||||
The verified user id/name becomes the `actor` passed to every MCP call, so
|
||||
the `audit_log` hook credits the real person.
|
||||
|
||||
### Tool bridge
|
||||
|
||||
- Catalogue: `tools/list` via the MCP handler, minus the denylist below,
|
||||
mapped to Anthropic `tools` (`name` → sanitised, `.` → `__`).
|
||||
- Calls: `tools/call` through the same handler; the MCP text envelope is
|
||||
unwrapped to a string for the model. Large results are truncated for
|
||||
the model (with a "truncated, narrow your query" note), never silently.
|
||||
- Denylist (never offered, never executable): `companies.delete`,
|
||||
`customers.delete`, `suppliers.delete`, `ai_settings.get`,
|
||||
`ai_settings.update` (the latter two would expose/alter the API key).
|
||||
|
||||
### Read vs write classification
|
||||
|
||||
Default-deny: a tool is **read** only if it is in an explicit `READ_TOOLS`
|
||||
allowlist (`*.list`, `*.get`, `*.find_*`, `*.lookup`, `*.stats`,
|
||||
`*.daily_summary`, `sprint.*` reads, `audit_log.get`, `files.fetch`,
|
||||
`tools.discovery`, `users.lookup`, `stock_quick_draft.search|suppliers`,
|
||||
…). The allowlist is reviewed against the handlers during implementation —
|
||||
notably `*.next_number` may reserve a counter and is treated as write.
|
||||
Everything else is a **write**.
|
||||
|
||||
**Always-confirm** (ignore the trust toggle): `quotes.send_email`,
|
||||
`quotes.retry_email`, `purchase_orders.send_email`,
|
||||
`purchase_orders.retry_email`, `stock_purchases.send`,
|
||||
`stock_purchases.send_email`, `stock_purchases.retry_email`. Anything that
|
||||
emails a customer or supplier always needs a click.
|
||||
|
||||
### Agent loop and approvals
|
||||
|
||||
1. Client posts the user message (+ attachments, `trust` flag). Server
|
||||
appends it to the transcript and calls MiniMax (streaming).
|
||||
2. Text and tool activity stream to the client as SSE events
|
||||
(`text`, `tool_start`, `tool_result`, `approval_required`, `done`, `error`).
|
||||
3. Read tool calls run immediately and loop back to the model. Loop cap:
|
||||
25 model turns per request.
|
||||
4. If the model emits write calls, the server stores them in
|
||||
`pending_action`, ends the stream with `approval_required`, and waits.
|
||||
Reads in the same turn still run first.
|
||||
5. `POST …/decision { approve }` executes the batch (approve) or returns
|
||||
"rejected by user" as the tool results (reject), then resumes the loop
|
||||
and streams again. A page reload mid-approval re-renders the card from
|
||||
`pending_action`.
|
||||
6. Trust toggle is sent per request, never persisted; it covers non-
|
||||
always-confirm writes only.
|
||||
|
||||
A batch is approved or rejected as a whole. The card shows tool name and
|
||||
readable arguments.
|
||||
|
||||
### Prompt injection
|
||||
|
||||
Email bodies, attachments and supplier text are untrusted data. The system
|
||||
prompt says so explicitly, and writes still pass through the approval card
|
||||
(the trust toggle is the residual risk and never covers sends).
|
||||
|
||||
### Model / config
|
||||
|
||||
Reuses `getActiveAiConfig()` from `ai-settings-service` (provider, key,
|
||||
base URL, model, `enabled`). Giellie gets its own system prompt constant in
|
||||
`src/lib/server/giellie-system-prompt.ts` (role, SA business context,
|
||||
"verify by reading before answering", "do your own arithmetic, show
|
||||
working for money", "never delete; cancel/archive instead", today's date).
|
||||
If AI is disabled or no key is set, the page shows a link to `/settings/ai`.
|
||||
|
||||
**Open verification (first implementation step):** MiniMax's
|
||||
Anthropic-compatible endpoint has historically been text-only. A probe
|
||||
sends one image block through the configured endpoint. If rejected, image
|
||||
turns use MiniMax's native vision endpoint; nothing else changes.
|
||||
|
||||
### Files and photos
|
||||
|
||||
- Upload button (xlsx, csv, docx, txt, pdf, images) and a camera button
|
||||
(`<input accept="image/*" capture="environment">`).
|
||||
- Server extraction via the existing `attachment-extractor`: text formats
|
||||
and text-layer PDFs become text; images and scanned PDFs (rendered
|
||||
pages) go to the model as image blocks.
|
||||
- Originals are stored in the chat's `attachments` file field so the
|
||||
transcript only holds filenames. Limits: 10 files/message, 10 MB each
|
||||
(matches `maxSelect: 10` on existing attachment fields).
|
||||
|
||||
### Persistence — `giellie_chats` (single collection)
|
||||
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `user` | relation → users, required | Owner. List/view/create/update rules: `user = @request.auth.id`. |
|
||||
| `title` | text | From the first message. |
|
||||
| `messages` | json (maxSize ≈ 5 MB) | Full transcript; tool results truncated when stored. |
|
||||
| `attachments` | file, multi (`maxSelect` 50) | Uploads and photos. |
|
||||
| `pending_action` | json | Write batch awaiting approval, else null. |
|
||||
|
||||
Created in PB admin **by hand first** (per AGENTS.md: the live instance is
|
||||
the source of truth); a migration script
|
||||
`scripts/migrations/2026-09-30-create-giellie-chats.ts` and a
|
||||
`pb:migrate:giellie-chats` npm script accompany it. The server writes via
|
||||
`getAdminPocketBase()` and filters by the verified user id itself.
|
||||
|
||||
## UI
|
||||
|
||||
- `src/app/(app)/giellie/page.tsx`: two panes — chat list (left), chat
|
||||
(right). Message bubbles, tool-activity lines (collapsed by default,
|
||||
expandable to arguments/result), approval card, composer with
|
||||
attach/camera/send, "Trust this chat" toggle in the header, new-chat button.
|
||||
- Sidebar: `Giellie` → `Giellie Agent`, href `/giellie`, icon `Bot`
|
||||
([sidebar.tsx:141](../../../src/components/sidebar.tsx), plus the
|
||||
`/webview` breadcrumb entry at ~482 → `/giellie`).
|
||||
- `/webview` route is left in place (unlinked) unless Dave wants it removed.
|
||||
- Next.js here is 16.2.x with breaking changes: read
|
||||
`node_modules/next/dist/docs/` for route handlers/streaming before coding.
|
||||
|
||||
## Data-layer rule
|
||||
|
||||
Per AGENTS.md: business logic in `src/lib/server/giellie-service.ts`,
|
||||
exposed through thin `src/app/api/v1/giellie/**` route handlers. No
|
||||
`pb-helpers.ts`.
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit (vitest): tool classification (read/write/always-confirm/denied,
|
||||
default-deny for unknown names); denylist filtering; MCP envelope
|
||||
unwrapping; result truncation; auth rejection; decision flow
|
||||
(approve/reject/reload); loop cap.
|
||||
- Agent loop tested with a stubbed model client (scripted tool calls).
|
||||
- Manual/browser check on `:4001` (per memory, dev port): text Q&A, a
|
||||
write with approval, a rejection, photo ingest.
|
||||
- **No test runs MCP write tools against live PB** — memory records three
|
||||
prior incidents of test fixtures polluting the real quotes board.
|
||||
Write-path tests stub the bridge.
|
||||
|
||||
## Out of scope
|
||||
|
||||
Voice input; per-role tool restrictions; editing tool arguments inside the
|
||||
approval card; multi-user shared chats; token/cost accounting.
|
||||
|
||||
## Risks
|
||||
|
||||
- Everyone-logged-in access exposes costs and margins to reps/drivers
|
||||
(accepted; revisit with per-role restrictions).
|
||||
- Trust toggle plus prompt injection could cause unwanted non-email writes;
|
||||
mitigated by audit trail and always-confirm sends.
|
||||
- MiniMax vision over the Anthropic-compatible endpoint is unverified (probe first).
|
||||
- Manual deploy takes 5–10 min; the PB collection must exist before deploy.
|
||||
Loading…
Reference in a new issue