458 lines
24 KiB
Markdown
458 lines
24 KiB
Markdown
# 03e — Evolution & Governance API
|
|
|
|
**Purpose.** This subsystem groups the Waggle OS sidecar's "machine-improves-itself + you-stay-in-control" surfaces: self-evolution runs (propose/accept/reject/run prompt mutations), user feedback capture, local-only telemetry, EU AI Act compliance reporting, agent cost tracking, capability/plugin status, and the approvals inbox (pending tool approvals + persistent grants). Every endpoint is served by the local Fastify sidecar under `/api/...` and returns JSON unless noted (one route returns a PDF binary, one streams SSE). For a frontend rebuild, treat the tables below as the literal contract — paths, methods, request bodies, and response shapes are quoted from the route source.
|
|
|
|
> Source files: `packages/server/src/local/routes/{evolution,feedback,telemetry,compliance,cost,capabilities,approval,validate}.ts` and `packages/server/src/local/services/{evolution-service,optimizer-service}.ts`. Data shapes are grounded in `packages/hive-mind-core/src/mind/evolution-runs.ts`, `packages/core/src/compliance/types.ts`, and `packages/core/src/telemetry.ts`.
|
|
|
|
---
|
|
|
|
## 1. Endpoint Index (every route in this subsystem)
|
|
|
|
| Method | Path | Purpose |
|
|
|---|---|---|
|
|
| GET | `/api/evolution/runs` | List evolution proposals/runs (newest first; filterable) |
|
|
| GET | `/api/evolution/runs/:uuid` | Single run detail with parsed JSON blobs |
|
|
| POST | `/api/evolution/runs/:uuid/accept` | Accept + deploy a proposed run |
|
|
| POST | `/api/evolution/runs/:uuid/reject` | Reject a proposed run |
|
|
| GET | `/api/evolution/targets` | Enumerate evolvable targets (personas + spec sections) |
|
|
| GET | `/api/evolution/baseline` | Fetch current baseline text for a target |
|
|
| POST | `/api/evolution/run` | Trigger a real evolution run (JSON or SSE stream) |
|
|
| GET | `/api/evolution/status` | Aggregate status counts for the dashboard |
|
|
| POST | `/api/feedback` | Record thumbs up/down feedback on an agent message |
|
|
| GET | `/api/feedback/stats` | Improvement stats + trend |
|
|
| GET | `/api/telemetry/summary` | Local telemetry summary object |
|
|
| GET | `/api/telemetry/events` | Query telemetry events (filterable) |
|
|
| DELETE | `/api/telemetry/events` | Clear all telemetry events (right to delete) |
|
|
| GET | `/api/telemetry/status` | Telemetry enabled flag + total event count |
|
|
| POST | `/api/telemetry/toggle` | Enable/disable telemetry |
|
|
| POST | `/api/telemetry/track` | Record a single telemetry event (frontend) |
|
|
| GET | `/api/compliance/status` | EU AI Act compliance status (per-article) |
|
|
| POST | `/api/compliance/export` | Generate audit report (JSON) |
|
|
| POST | `/api/compliance/export-pdf` | Generate audit report as a PDF binary |
|
|
| GET | `/api/compliance/interactions` | List recorded AI interactions |
|
|
| POST | `/api/compliance/interactions` | Record an AI interaction |
|
|
| GET | `/api/compliance/models` | Model inventory for a date range |
|
|
| GET | `/api/compliance/templates` | List saved compliance report templates |
|
|
| GET | `/api/compliance/templates/:id` | Get one template by numeric id |
|
|
| POST | `/api/compliance/templates` | Create a compliance template |
|
|
| PATCH | `/api/compliance/templates/:id` | Update a compliance template |
|
|
| DELETE | `/api/compliance/templates/:id` | Delete a compliance template |
|
|
| GET | `/api/cost/summary` | Cost dashboard: today/week/all-time + daily breakdown + budget |
|
|
| GET | `/api/cost/by-workspace` | Per-workspace cost breakdown (TEAMS tier gated) |
|
|
| GET | `/api/costs` | Alias → `/api/cost/summary` (internal re-route, 200) |
|
|
| GET | `/api/capabilities/status` | Plugins/MCP/skills/tools/commands/hooks/workflows status |
|
|
| POST | `/api/capabilities/plugins/:name/enable` | Enable a plugin |
|
|
| POST | `/api/capabilities/plugins/:name/disable` | Disable a plugin |
|
|
| POST | `/api/approval/:requestId` | Approve/deny a pending tool execution |
|
|
| GET | `/api/approval/pending` | List pending approvals (for reconnection) |
|
|
| GET | `/api/approval/grants` | List all persistent approval grants |
|
|
| DELETE | `/api/approval/grants/:id` | Revoke a single grant |
|
|
| POST | `/api/approval/grants/clear` | Wipe all grants |
|
|
|
|
> `validate.ts` exposes **no routes** — it is a helper module (`isSafeSegment`, `assertSafeSegment`) for rejecting path-traversal in route params. `optimizer-service.ts` is an internal service (prompt classify/expand via Haiku) consumed by the chat loop, **not** an HTTP route.
|
|
|
|
---
|
|
|
|
## 2. Evolution (self-improvement loop)
|
|
|
|
The evolution loop proposes mutations to either a **persona system prompt** or a **behavioral-spec section**, gates them, and stores `proposed` runs. The user reviews and accepts/rejects from the Memory → Evolution UI. Accepting deploys the new text to disk and fires a cache-invalidation event; the loop **never auto-deploys**.
|
|
|
|
### 2.1 The `EvolutionRun` entity (full shape)
|
|
|
|
Returned by list/detail/accept/reject. From `packages/hive-mind-core/src/mind/evolution-runs.ts`:
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `id` | number | Autoincrement row id |
|
|
| `run_uuid` | string | Stable id used in all `:uuid` routes |
|
|
| `target_kind` | `'persona-system-prompt' \| 'behavioral-spec-section' \| 'tool-description' \| 'skill-body' \| 'generic'` | Only the first two can currently be **deployed** |
|
|
| `target_name` | string \| null | Persona id (e.g. `coder`) or spec section id |
|
|
| `baseline_text` | string | The pre-evolution instruction text |
|
|
| `winner_text` | string | The evolved/winning instruction text |
|
|
| `winner_schema_json` | string \| null | JSON-encoded `Schema` (DSPy signature) when structure evolved |
|
|
| `delta_accuracy` | number | Score improvement over baseline |
|
|
| `gate_verdict` | `'pass' \| 'fail'` | Constraint-gate verdict |
|
|
| `gate_reasons_json` | string | JSON array of `{gate, verdict, reason}` |
|
|
| `status` | `'proposed' \| 'accepted' \| 'rejected' \| 'deployed' \| 'failed'` | Lifecycle state |
|
|
| `artifacts_json` | string \| null | Per-generation history / scores / Pareto front |
|
|
| `user_note` | string \| null | Note attached on accept |
|
|
| `failure_reason` | string \| null | Set when status is `failed` |
|
|
| `created_at` | string | ISO timestamp |
|
|
| `decided_at` | string \| null | Set on accept/reject |
|
|
| `deployed_at` | string \| null | Set on successful deploy |
|
|
|
|
### 2.2 `GET /api/evolution/runs`
|
|
|
|
List runs, newest first. All query params optional.
|
|
|
|
| Query param | Type | Meaning |
|
|
|---|---|---|
|
|
| `status` | string or string[] (repeatable) | Filter by one or more statuses |
|
|
| `targetKind` | string | Filter by target kind |
|
|
| `targetName` | string | Filter by target name (e.g. `coder`) |
|
|
| `since` | ISO string | Only runs after this time |
|
|
| `limit` | string→int | Default 50, clamped to `1..500` |
|
|
|
|
**Response:** `{ runs: EvolutionRun[], count: number }`.
|
|
|
|
### 2.3 `GET /api/evolution/runs/:uuid`
|
|
|
|
Single run. **404** `{ error: 'Run not found' }` if missing. On success returns the full `EvolutionRun` **plus** parsed convenience fields:
|
|
|
|
```jsonc
|
|
{
|
|
...EvolutionRun,
|
|
"winnerSchema": object | null, // parsed from winner_schema_json
|
|
"artifacts": object | null, // parsed from artifacts_json
|
|
"gateReasons": Array<{gate, verdict, reason}> // parsed from gate_reasons_json, defaults []
|
|
}
|
|
```
|
|
|
|
### 2.4 `POST /api/evolution/runs/:uuid/accept`
|
|
|
|
- **Body:** `{ note?: string }`
|
|
- **Guards:** 404 if not found; **409** if `status !== 'proposed'` (`error: 'Run is in status "<x>" — only proposed runs can be accepted'`).
|
|
- **Effect:** marks accepted → runs the deploy dispatcher → moves to `deployed` (success) or `failed` (throw). Deploy is only implemented for `persona-system-prompt` (writes a persona override) and `behavioral-spec-section` (writes a spec-section override); `tool-description`/`skill-body`/`generic` throw "not yet implemented" and end as `failed`.
|
|
- **Side effect:** emits `persona:reloaded` or `behavioral-spec:reloaded` on the server event bus so the chat route drops its cached system prompt.
|
|
- **Response:** the updated `EvolutionRun` (200). 500 if accept returned no record.
|
|
|
|
### 2.5 `POST /api/evolution/runs/:uuid/reject`
|
|
|
|
- **Body:** `{ reason?: string }`
|
|
- **Guards:** 404 if not found; **409** if not `proposed`.
|
|
- **Response:** the updated `EvolutionRun` (200).
|
|
|
|
### 2.6 `GET /api/evolution/targets`
|
|
|
|
Populates the "Run" form dropdowns. **Response:**
|
|
|
|
```jsonc
|
|
{
|
|
"personas": Array<{ id, name, description, icon }>, // from listPersonas()
|
|
"sections": string[], // BEHAVIORAL_SPEC_SECTIONS
|
|
"defaultSchema": Schema // generic default DSPy signature
|
|
}
|
|
```
|
|
|
|
### 2.7 `GET /api/evolution/baseline?kind=X&name=Y`
|
|
|
|
Returns the current live baseline for one target so the Run form can pre-fill it.
|
|
|
|
- **400** if `kind` or `name` missing, or `kind` not one of the two supported.
|
|
- **404** for unknown persona / unknown section.
|
|
- `kind=persona-system-prompt` → returns the persona's live `systemPrompt`.
|
|
- `kind=behavioral-spec-section` → returns the **active** section text (deployed overrides applied, falling back to compile-time `BEHAVIORAL_SPEC`).
|
|
- **Response:** `{ baseline: string, schemaBaseline: Schema }`.
|
|
|
|
### 2.8 `POST /api/evolution/run` (trigger a real run — JSON or SSE)
|
|
|
|
Synchronously runs a GEPA + EvolveSchema composition using the vault's Anthropic key (Haiku-backed judge/mutate/execute). Persists a `proposed` run if one wins.
|
|
|
|
**Body:**
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `targetKind` | EvolutionTarget | Required; must be in the 5-value union |
|
|
| `targetName` | string | Required; non-empty |
|
|
| `baseline` | string | Required; non-empty current instruction text |
|
|
| `schemaBaseline` | `Schema` | Required object `{ name: string, fields: array, version }` |
|
|
| `minDelta` | number | Optional; default 0.02 |
|
|
| `gepa` | object | `{ populationSize?, generations?, miniEvalSize?, anchorEvalSize?, seed?, concurrency? }` (concurrency default 4) |
|
|
| `schema` | object | `{ populationSize?, generations?, evalSize?, anchorEvalSize?, seed? }` |
|
|
| `gateOptions` | `GateOptions` | Optional constraint-gate config |
|
|
|
|
**Status codes:** `200` ran (see `body.outcome`), `400` validation error, `422` no Anthropic key in vault (`'No Anthropic API key configured. Add one in Settings → Vault.'`), `503` `@ax-llm/ax` unavailable, `500` run threw.
|
|
|
|
**JSON response payload:**
|
|
|
|
```jsonc
|
|
{
|
|
"outcome": string, // e.g. "proposed" / "skipped-*"
|
|
"reason": string,
|
|
"run": EvolutionRun | null,
|
|
"gateResults": ...,
|
|
"composeSummary": {
|
|
"combinedDelta", "fullyImproved",
|
|
"schemaImproved", "schemaDelta",
|
|
"instructionImproved", "instructionDelta",
|
|
"winnerId"
|
|
} | null
|
|
}
|
|
```
|
|
|
|
**SSE mode (frontend should prefer this):** send header `Accept: text/event-stream`. The route streams `event:` frames — `open` (`{targetKind, targetName}`), repeated `progress` (the `GEPAProgress` object: `{phase, generation, populationSize, best, message?}`), then either `done` (the JSON payload above) or `error` (`{error}`). The run completes server-side even if the client disconnects (it does not cancel in-flight LLM spend).
|
|
|
|
### 2.9 `GET /api/evolution/status`
|
|
|
|
- **Query:** `targetKind?`, `targetName?`, `since?`
|
|
- **Response:** `{ counts: Record<EvolutionRunStatus, number>, pendingCount: number }` where `pendingCount === counts.proposed`.
|
|
|
|
### 2.10 Background autonomy (no HTTP surface)
|
|
|
|
`EvolutionService` (in `services/evolution-service.ts`) is an **opt-in** `setInterval` daemon (env `WAGGLE_EVOLUTION_AUTO_ENABLED=1`, default off; tick interval 6h, min 60s). Each tick picks one target whose new-trace count clears `minTracesPerTarget` (default 20; eligible outcomes `success`/`corrected`/`verified`) and produces a `proposed` run via the same orchestrator as `/api/evolution/run`. It **never auto-accepts** — proposals still flow through the manual accept/reject routes above. The frontend does not call this directly; it just sees new `proposed` runs appear.
|
|
|
|
---
|
|
|
|
## 3. Feedback
|
|
|
|
Feedback writes to a `feedback_entries` table in the personal `.mind` DB (auto-created). Negative feedback with a reason is cross-recorded as a `correction` improvement signal feeding the self-improvement loop.
|
|
|
|
### 3.1 `POST /api/feedback`
|
|
|
|
**Body (`FeedbackBody`):**
|
|
|
|
| Field | Type | Required | Notes |
|
|
|---|---|---|---|
|
|
| `sessionId` | string | yes | 400 if missing/non-string |
|
|
| `messageIndex` | number | yes | Must be ≥ 0 |
|
|
| `rating` | `'up' \| 'down'` | yes | 400 if not in set |
|
|
| `reason` | `'wrong_answer' \| 'too_verbose' \| 'wrong_tool' \| 'too_slow' \| 'other'` | no | Validated if present |
|
|
| `detail` | string | no | Free text, defaults `''` |
|
|
|
|
**Response:** `{ ok: true }`. 500 on DB failure.
|
|
|
|
### 3.2 `GET /api/feedback/stats`
|
|
|
|
**Response:**
|
|
|
|
```jsonc
|
|
{
|
|
"totalFeedback": number,
|
|
"positiveRate": number, // 0..1, 2 decimals
|
|
"topIssues": string[], // up to 5 negative-feedback reasons by frequency
|
|
"correctionsThisWeek": number, // from improvement_signals (last 7d)
|
|
"improvementTrend": string // e.g. "+12%" / "-5%" / "0%"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Telemetry (local-only — no cloud reporting)
|
|
|
|
`TelemetryEvent = { id, event, properties: Record<string, unknown>, created_at }`. `TelemetrySummary = { enabled, totalEvents, firstEvent, lastEvent, onboardingCompleted, totalSessions, embeddingProvider, templatesUsed, ... }`.
|
|
|
|
| Endpoint | Request | Response |
|
|
|---|---|---|
|
|
| `GET /api/telemetry/summary` | — | `TelemetrySummary` |
|
|
| `GET /api/telemetry/events` | query `event?, since?, until?, limit?` (limit default 100) | `TelemetryEvent[]` |
|
|
| `DELETE /api/telemetry/events` | — | result of `telemetry.clear()` (right-to-delete) |
|
|
| `GET /api/telemetry/status` | — | `{ enabled: boolean, totalEvents: number }` |
|
|
| `POST /api/telemetry/toggle` | `{ enabled: boolean }` | `{ enabled }` (also persists to `WaggleConfig`) |
|
|
| `POST /api/telemetry/track` | `{ event: string, properties?: object }` | `{ ok: true }`; 400 if `event` missing |
|
|
|
|
---
|
|
|
|
## 5. Compliance (EU AI Act)
|
|
|
|
All compliance routes require the personal mind; they return **503** `{ error: 'Personal mind not available' }` if it isn't ready. Interactions live in the `ai_interactions` table; templates in their own table on the same personal DB.
|
|
|
|
### 5.1 `GET /api/compliance/status?workspaceId=`
|
|
|
|
Returns the per-article `ComplianceStatus`:
|
|
|
|
| Field | Shape |
|
|
|---|---|
|
|
| `overall` | `'compliant' \| 'warning' \| 'non-compliant'` |
|
|
| `art12Logging` | `ArticleStatus & { totalInteractions }` |
|
|
| `art14Oversight` | `ArticleStatus & { humanActions, approvalRate }` |
|
|
| `art19Retention` | `ArticleStatus & { oldestLogDate, retentionDays }` |
|
|
| `art26Monitoring` | `ArticleStatus & { activeMonitors: string[] }` |
|
|
| `art50Transparency` | `ArticleStatus & { modelsDisclosed: boolean }` |
|
|
|
|
`ArticleStatus = { status: 'compliant'|'warning'|'non-compliant', detail: string }`.
|
|
|
|
### 5.2 `POST /api/compliance/export` and `POST /api/compliance/export-pdf`
|
|
|
|
Both take the same `AuditReportRequest` body:
|
|
|
|
```jsonc
|
|
{
|
|
"workspaceId": string?,
|
|
"from": string, // required ISO date — 400 if missing
|
|
"to": string, // required ISO date
|
|
"format": "json" | "pdf" | "both",
|
|
"include": {
|
|
"interactions": boolean, "oversight": boolean, "models": boolean,
|
|
"provenance": boolean, "riskAssessment": boolean, "fria": boolean
|
|
}
|
|
}
|
|
```
|
|
|
|
- `/export` returns the `AuditReport` JSON object: `{ report:{version,generatedAt,period,generatedBy}, workspace:{id,name,riskLevel,riskClassifiedAt}|null, complianceStatus, modelInventory[], humanOversightLog[], harvestProvenance[], interactionCount }`.
|
|
- `/export-pdf` returns **`application/pdf`** binary with `Content-Disposition: attachment; filename="ai-act-compliance-<from>-to-<to>.pdf"`. It additionally accepts three optional template-override fields on the body: `templateOrgName`, `templateFooterText`, `templateRiskClassification` (an `AIActRiskLevel`). 500 on PDF render failure.
|
|
|
|
### 5.3 `GET /api/compliance/interactions` and `POST`
|
|
|
|
- **GET** query `limit?` (default 20, max 100), `workspaceId?`. Response `{ interactions: AIInteraction[] }` (by workspace if `workspaceId` given, else recent N).
|
|
- **POST** body `RecordInteractionInput` (requires `model` + `provider`, else 400). Returns the stored `AIInteraction`.
|
|
|
|
`AIInteraction` fields: `id, timestamp, workspaceId, sessionId, model, provider, inputTokens, outputTokens, costUsd, toolsCalled[], humanAction('approved'|'denied'|'modified'|'none'), riskContext, importedFrom, persona, inputText, outputText`.
|
|
|
|
### 5.4 `GET /api/compliance/models?from=&to=&workspaceId=`
|
|
|
|
Returns `{ models: ModelInventoryEntry[] }`, each `{ model, provider, calls, inputTokens, outputTokens, costUsd }`.
|
|
|
|
### 5.5 Compliance templates (M-03 CRUD)
|
|
|
|
`ComplianceTemplate = { id, name, description, sections: ComplianceTemplateSections, riskClassification: AIActRiskLevel|null, orgName, footerText, createdAt, updatedAt }`, where `sections` mirrors the six `include` booleans.
|
|
|
|
| Endpoint | Body | Notes |
|
|
|---|---|---|
|
|
| `GET /api/compliance/templates` | — | `{ templates: ComplianceTemplate[] }` |
|
|
| `GET /api/compliance/templates/:id` | — | 400 invalid id, 404 not found, else `{ template }` |
|
|
| `POST /api/compliance/templates` | Zod-validated `CreateComplianceTemplateInput` | 201 `{ template }`; 400 on invalid body (`detail` = Zod issues) |
|
|
| `PATCH /api/compliance/templates/:id` | Zod-validated `UpdateComplianceTemplateInput` | 404 if not found |
|
|
| `DELETE /api/compliance/templates/:id` | — | `{ deleted: true }`; 404 if not found |
|
|
|
|
Zod schemas: `sections` is all six booleans required; `riskClassification` ∈ `{minimal, limited, high-risk, unacceptable}`. Sections **merge (union)** with the runtime `include` flags in the UI before POSTing to `/export` — the export routes stay template-agnostic.
|
|
|
|
---
|
|
|
|
## 6. Cost dashboard
|
|
|
|
Data source is the **in-memory** `CostTracker` (populated by the chat route per agent turn). All costs are estimates from published model pricing; fallback pricing is Sonnet (`$0.003`/1K in, `$0.015`/1K out).
|
|
|
|
### 6.1 `GET /api/cost/summary?days=`
|
|
|
|
`days` default 7, max 90. **Response:**
|
|
|
|
```jsonc
|
|
{
|
|
"today": { inputTokens, outputTokens, estimatedCost, turns },
|
|
"allTime": { inputTokens, outputTokens, estimatedCost, turns, byModel },
|
|
"week": { inputTokens, outputTokens, estimatedCost, turns },
|
|
"daily": [ { date, inputTokens, outputTokens, cost, turns } ], // one per day in range
|
|
"budget": { dailyBudget: number|null, todayCost, budgetStatus: 'ok'|'warning'|'exceeded', budgetPercent }
|
|
}
|
|
```
|
|
|
|
`budget.dailyBudget` is read from `/api/settings`; `budgetStatus` is `warning` at ≥80% and `exceeded` at ≥100% of `dailyBudget`.
|
|
|
|
### 6.2 `GET /api/cost/by-workspace` (TEAMS-gated)
|
|
|
|
Guarded by `requireTier('TEAMS')`. Returns `{ workspaces: Array<{ workspaceId, workspaceName, inputTokens, outputTokens, estimatedCost, turns, percentOfTotal }>, totalCost }`, sorted by cost descending.
|
|
|
|
### 6.3 `GET /api/costs`
|
|
|
|
Discoverability alias. Internally re-routes to `/api/cost/summary` (passing `days` through) and returns the same 200 body. Free for all tiers (usage info is not gated).
|
|
|
|
---
|
|
|
|
## 7. Capabilities (read-only status + plugin toggles)
|
|
|
|
### 7.1 `GET /api/capabilities/status`
|
|
|
|
One aggregated snapshot (500 with `{error}` on failure):
|
|
|
|
```jsonc
|
|
{
|
|
"plugins": [ { name, state, tools, skills } ],
|
|
"mcpServers": [ { name, state, healthy, tools } ],
|
|
"skills": [ { name, length } ],
|
|
"tools": { count, native, plugin, mcp },
|
|
"commands": [ { name, description, usage } ],
|
|
"hooks": { registered: 10, recentActivity: [ { event, timestamp, cancelled, reason } ] },
|
|
"workflows": [ { name, description, steps } ]
|
|
}
|
|
```
|
|
|
|
### 7.2 Plugin toggles
|
|
|
|
| Endpoint | Effect | Response |
|
|
|---|---|---|
|
|
| `POST /api/capabilities/plugins/:name/enable` | `pluginRuntimeManager.enable(name)` | `{ ok: true, name, state: 'active' }`; 503 if no runtime; 400 on error |
|
|
| `POST /api/capabilities/plugins/:name/disable` | `pluginRuntimeManager.disable(name)` | `{ ok: true, name, state: 'disabled' }`; 503/400 as above |
|
|
|
|
---
|
|
|
|
## 8. Approvals inbox + grants
|
|
|
|
The agent loop registers a **pending approval** when a tool needs human sign-off; the request hangs on a promise until the user resolves it via the API. "Always allow" persists a **grant** so future identical `(toolName, input, sourceWorkspaceId)` requests resolve silently.
|
|
|
|
| Endpoint | Body / Params | Behavior |
|
|
|---|---|---|
|
|
| `POST /api/approval/:requestId` | `{ approved: boolean, always?: boolean, reason?: string, sourceWorkspaceId?: string\|null }` | 404 if no pending request; if `approved && always` persists a grant first; resolves the pending promise and removes it. Returns `{ ok, requestId, approved, always }` |
|
|
| `GET /api/approval/pending` | — | `{ pending: Array<{ requestId, toolName, input, timestamp }>, count }` |
|
|
| `GET /api/approval/grants` | — | `{ grants: [...], count }` |
|
|
| `DELETE /api/approval/grants/:id` | param `id` | `{ ok: true, id }`; 404 if grant not found |
|
|
| `POST /api/approval/grants/clear` | — | `{ ok: true }` — wipes every grant |
|
|
|
|
For the frontend: poll `GET /api/approval/pending` on reconnect to rebuild the inbox; the live push of new approval requests arrives via the chat/SSE stream (out of scope here).
|
|
|
|
---
|
|
|
|
## 9. Cross-cutting notes for the rebuild
|
|
|
|
- **No auth headers documented here** — these are local sidecar routes. Only `/api/cost/by-workspace` is tier-gated (`requireTier('TEAMS')` preHandler).
|
|
- **Compliance routes degrade with 503** when the personal mind isn't loaded — handle that as an empty/loading state, not an error toast.
|
|
- **Two non-JSON responses:** `/api/compliance/export-pdf` (PDF binary, trigger a download) and `/api/evolution/run` with `Accept: text/event-stream` (SSE; render a progress bar from `GEPAProgress`).
|
|
- **State machine:** evolution runs only leave `proposed` via accept/reject; the UI must disable accept/reject buttons for any non-`proposed` run (the server returns 409 otherwise).
|
|
- **Numeric template ids:** compliance template routes use a numeric `:id` and 400 on non-finite values; everything else keyed by string `uuid`/`requestId`/grant `id`/plugin `name`.
|
|
|
|
---
|
|
|
|
## 10. Subsystem map
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
subgraph UI[Frontend - Lovable rebuild]
|
|
EvoUI[Memory: Evolution tab]
|
|
FbUI[Feedback thumbs]
|
|
CompUI[Compliance app]
|
|
CostUI[Cost dashboard]
|
|
CapUI[Capabilities status]
|
|
AprUI[Approvals inbox]
|
|
end
|
|
|
|
subgraph API[Fastify sidecar /api]
|
|
Evo[/evolution/*/]
|
|
Fb[/feedback/*/]
|
|
Tel[/telemetry/*/]
|
|
Comp[/compliance/*/]
|
|
Cost[/cost/* and /costs/]
|
|
Cap[/capabilities/*/]
|
|
Apr[/approval/*/]
|
|
end
|
|
|
|
subgraph Stores[Persistence and runtime state]
|
|
RunStore[(EvolutionRunStore<br/>evolution_runs)]
|
|
TraceStore[(ExecutionTraceStore)]
|
|
FeedTbl[(feedback_entries +<br/>improvement_signals)]
|
|
TelTbl[(telemetry_events)]
|
|
Inter[(ai_interactions +<br/>compliance_templates)]
|
|
CostMem[CostTracker in-memory]
|
|
Pending[pendingApprovals map]
|
|
Grants[(approvalGrantStore)]
|
|
end
|
|
|
|
EvoUI --> Evo --> RunStore
|
|
Evo -->|runOnce| TraceStore
|
|
Evo -->|accept deploy| Disk[persona / spec override files]
|
|
Evo -.persona:reloaded / behavioral-spec:reloaded.-> Bus[server eventBus]
|
|
EvoSvc[EvolutionService daemon<br/>opt-in setInterval] --> RunStore
|
|
|
|
FbUI --> Fb --> FeedTbl
|
|
Tel --> TelTbl
|
|
CompUI --> Comp --> Inter
|
|
Comp -->|export-pdf| PDF[renderComplianceReportPdf]
|
|
CostUI --> Cost --> CostMem
|
|
CapUI --> Cap --> Runtime[plugin / mcp / hook registries]
|
|
AprUI --> Apr
|
|
Apr --> Pending
|
|
Apr --> Grants
|
|
```
|
|
|
|
---
|
|
|
|
## Workflow Templates CRUD (`workflows.ts`) — `/api/workflows`
|
|
|
|
> Added to close audit gap #2. These manage **multi-step agent workflow templates** (sequences of agent steps with an aggregation strategy). Built-in templates from `WORKFLOW_TEMPLATES` are read-only; custom ones are persisted as JSON under `~/.waggle/workflows/` (the sidecar `dataDir`). Execution of a workflow happens through the agent-run surface (see 03a / 05e); these endpoints only manage the template definitions.
|
|
|
|
| Method | Path | Request body | Response | Notes |
|
|
|---|---|---|---|---|
|
|
| `GET` | `/api/workflows` | — | `{ workflows: (WorkflowTemplate & { builtIn: boolean })[], builtInCount: number, customCount: number }` | Returns built-ins first, then custom (`builtIn:false`) |
|
|
| `POST` | `/api/workflows` | `Partial<WorkflowTemplate>` — **requires** `name` + non-empty `steps[]` | `201 WorkflowTemplate` | `description` defaults `""`, `aggregation` defaults `"concatenate"`; `400 { error }` if `name`/`steps` missing |
|
|
| `DELETE` | `/api/workflows/:name` | — | `{ deleted: true, name }` | `404 { error: "Workflow not found" }` if the custom workflow doesn't exist (built-ins can't be deleted) |
|
|
|
|
**`WorkflowTemplate`** = `{ name: string; description: string; steps: WorkflowStep[]; aggregation: 'concatenate' | ... }` (from `@waggle/agent`). The frontend's workflow-composer UI reads `GET` to populate the template list and `POST`/`DELETE` to manage user-authored ones.
|