MEGAINFRA-WEBAPP/docs/ask-architecture.md
DEls ed69b517e8 Integrate agent work and add dashboard attention indicators
Remove repeated Plant, Financial and Workforce shortcut cards. Add site-scoped Plant and Financial sidebar attention status with automatic refresh.

Include accumulated delivery processing, purchase order recovery, supplier reconciliation, plant data and user management changes and audit records.
2026-10-04 21:53:32 +02:00

5.4 KiB

How "Ask" works (MEGA) — brief for building the same thing in Healy

Correction first

The in-app chat does NOT use MCP. It calls tool functions directly inside the Next.js server. MCP (src/mcp/server.ts) is a separate, optional, read-only wrapper over the same tools, run locally over stdio for Claude Desktop/Code. Build the tool layer first; treat MCP as a thin extra.

Stack

  • Next.js 16 (App Router), React 19, TypeScript, Tailwind 4, vitest
  • PocketBase via pocketbase SDK, superuser client on the server only (pbAdmin())
  • Model: MiniMax M3 via OpenAI-compatible endpoint (https://api.minimax.io/v1); Anthropic claude-opus-5 behind the same interface. CHAT_PROVIDER selects.
  • Streaming: SSE from POST /api/chat (plain ReadableStream; browser reads with fetch)
  • Tool schemas: zod -> zod-to-json-schema
  • UI markdown: react-markdown + remark-gfm mapped onto the app's own table styles

Architecture

  1. src/lib/chat/ — provider-neutral core: types.ts (no SDK imports), loop.ts (model -> tools -> model, with max-iterations valve), provider-anthropic.ts, provider-openai.ts (one SDK each), system-prompt.ts (built per request: date + user memories), think-filter.ts.
  2. src/lib/agent/ — tools: tools.ts (read tools), tools-actions.ts (write tools), load.ts (fetching), format.ts (shaping + display strings), resolve.ts (name -> id), query.ts (generic query_records), gateway.ts (the write seam), schema-cache.ts.
  3. src/lib/metrics/ — ALL business calculations as pure tested functions, shared by dashboard and agent.
  4. /api/chat — env gate, identify user, persist thread, run loop, stream events.
  5. /dashboard/chat — client SSE reader, threads, attachments, confirm cards, charts, PDF.

Rules that make it trustworthy

  • The model never does arithmetic. Tools return finished numbers + preformatted display. Prompt bans sum/average/percent/"combined" and says what to do instead (list rows, say aggregate not available). The first wording failed a live probe — keep "combined value of top three" as a regression probe.
  • Name resolution returns ambiguity lists instead of guessing.
  • query_records: typed generic read, filters bound with pb.filter(), grouping/aggregation in JS.
  • Tool failures go back to the model as error results and are logged server-side.

Writes

Model calls propose_changes -> pending row in ask_log -> confirm card -> human presses Confirm -> applyAction writes -> each op keeps a before snapshot -> Undo. Ops validated against live schema. authorize() is the single permission seam with a NEVER_WRITABLE set (_superusers, users, chat tables). Ops run sequentially so $ref: can reference records created earlier in the batch. For Healy: remove the delete op kind entirely (union type, planner, apply). Decide deliberately whether Undo may delete records the agent itself just created (recommended: yes, and nothing else).

Gotchas

MiniMax

  • M2/M2.7 are text-only. M3 is the first accepting BOTH tools and images. Test both together.
  • Use the OpenAI-compatible route; the /anthropic shim mangles tool-call args.
  • Reasoning streams inline in <think>…</think>; strip it with a stateful filter (tags split across chunks).
  • Tool-call args arrive as string fragments per index; concatenate before JSON.parse.
  • Key must be an Open Platform API key (not Token Plan subscription key, not Hailuo/MiniMax Agent).
  • Image endpoint: {base minus /v1}/v1/image_generation (appending /v1 to base gave /v1/v1). Anthropic
  • budget_tokens and assistant prefill are removed (400). Use thinking: {type: "adaptive"}. PocketBase
  • sort=-created / -updated can 400 with an empty body. Sort a real field or use autodate fields.
  • getList(1, 500) silently truncates; use getFullList.
  • fields: that omits a date field makes later date logic silently skip rows.
  • A detail-less 400 is usually a bad filter/sort; reproduce with a direct getFullList. Chat plumbing
  • History crosses JSON; types guarantee nothing. Normalise in the route (assistant turns arrive without toolCalls).
  • Compare Origin to the Host header, not request.url.
  • Agent loop needs more than 8 iterations for "read file -> match -> propose".

Brief to give the Healy agent

Build a chat agent for Healy shaped like MEGA's "Ask". Next.js + PocketBase, SSE from POST /api/chat. Provider-neutral loop with two adapters (MiniMax M3 via OpenAI-compatible endpoint; Anthropic fallback). Tools are zod-schema'd functions; all business maths lives in pure tested functions shared with the UI. Reads: typed tools per domain + generic query_records; model never does arithmetic; tools return display. Writes: create and update only, NO delete. Propose -> pending log row -> human confirm -> apply, with before snapshot and undo; validate against live schema; keep a never-writable list. API access: expose the same handlers as REST and as an MCP server, both authenticated. Also: strip <think>, reassemble fragmented tool args, normalise history at the route, log tool failures.

Decide deliberately (API + MCP + writes)

  • Auth: Ask used an x-ask-user header + shared PIN during testing. A create/update API or MCP surface needs real per-user authentication.
  • MCP is headless (no human to confirm). Either keep MCP read-only, or let it only create PENDING actions a person confirms in the app (recommended).