clawdie-ai/doc/DASHBOARD-API-MAPPING.md
Clawdie AI 99affee94c Pivot control plane docs to agentic harness
---

Build: not run | Tests: not run

---
Build: pass | Tests: pass — Tests  861 passed (861)
2026-04-11 09:21:55 +00:00

20 KiB
Raw Blame History

Dashboard API Mapping — Paperclip UI ↔ Clawdie Controlplane

ARCHIVED: Paperclip dashboard track is halted. Kept for historical reference. See doc/AGENTIC-HARNESS-PIVOT.md for the current direction.

Date: 2026-04-09 Status: Design document — proxy layer reference for Phase D Scope: MVP 4-panel dashboard only. Dashboard summary, heartbeat runs, and write mutations deferred.


1. Purpose

The Paperclip UI expects its own API contract. We serve it unchanged via a read-only git submodule (or standalone checkout) and bridge the gap with a proxy layer in src/dashboard-proxy.ts. This document defines that translation contract.

The proxy mounts Paperclip-compatible routes under /api/paperclip/* (or rewrites the UI's API base path at build time). It translates inbound Paperclip requests into Clawdie controlplane API calls or direct DB queries, then reshapes responses into what the Paperclip UI expects.


2. Concept Mapping

Clawdie is single-tenant. One instance = one "company" in Paperclip terms.

Paperclip Concept Clawdie Equivalent Notes
Company Entire Clawdie instance Hardcoded company ID "clawdie" in proxy.
Company.id "clawdie" Static. All GET /api/companies/clawdie/... routes resolve to this.
Agent agents table row 4 agents: orchestrator, sysadmin, db_admin, git_admin.
Agent.role (ceo, engineer, ...) agents.role (orchestrator, sysadmin_agent, ...) Role enum differs — see mapping table below.
Agent.status (active, paused, ...) agents.heartbeat_enabled + budget state Synthesized — see field table.
Issue tasks table row Status enum differs — see mapping table below.
Issue.identifier (PAP-42) tasks.id (API-{base36}) Pass through as-is.
Approval approvals table row Fields differ — see field table.
ActivityEvent agent_activity table row Field names differ — see field table.
HeartbeatRun No equivalent Stub only for MVP.
OrgNode No equivalent Flat agent list for MVP.
Project / Goal / Label No equivalent Stub only.

Agent Role Mapping

Paperclip AgentRole Clawdie AgentRole Direction
ceo orchestrator Paperclip → Clawdie: map ceoorchestrator
devops sysadmin_agent Paperclip → Clawdie: map devopssysadmin_agent
db_admin_agent Clawdie has no Paperclip role equivalent; map to general
git_admin_agent Clawdie has no Paperclip role equivalent; map to general

For the reverse (Clawdie → Paperclip response):

Clawdie AgentRole Paperclip AgentRole in response
orchestrator ceo
sysadmin_agent devops
db_admin_agent engineer
git_admin_agent engineer

Task Status Mapping

Clawdie status Paperclip IssueStatus
pending todo
in_progress in_progress
(any other) backlog

Paperclip also has in_review, done, blocked, cancelled. For now, any Clawdie status not in the table above maps to backlog.

Priority Mapping

Clawdie priority Paperclip IssuePriority
critical critical
high high
medium medium
low low
(any other) medium

3. Endpoint Translation

The proxy intercepts 6 Paperclip API calls that the 4 dashboard panels make. All other Paperclip endpoints return 501 (Not Implemented) — the UI should degrade gracefully.

3.1 Agent List Panel

Paperclip calls:

GET /api/companies/{companyId}/agents
GET /api/companies/{companyId}/dashboard  (counts for badges)

Proxy maps to:

GET /api/controlplane/state

Response translation:

Paperclip expects:  Agent[]  (array of full agent objects)
Clawdie returns:    { agents: [{id, role, heartbeat_enabled}], budget: {...} }

Field-by-field mapping for each agent:

Paperclip Agent field Source Strategy
id agents.id Direct.
companyId Hardcode "clawdie".
name agents.id Use agent ID as display name.
urlKey agents.id Same as name — slugified agent ID.
role agents.role Map via role table above.
title agents.role Human-readable label: "Orchestrator", "Sysadmin Agent", etc.
icon Stub null.
status agents.heartbeat_enabled + budget Synthesized: heartbeat_enabled && budget.remaining > 0"active", heartbeat_enabled && budget.hard_limit_exceeded"paused", !heartbeat_enabled"idle".
reportsTo All agents report to orchestrator: return orchestrator ID. Orchestrator returns null.
capabilities Stub null.
adapterType agents.adapter Map "pi-local""process", "codex""codex_local".
adapterConfig Stub {}.
runtimeConfig Stub {}.
budgetMonthlyCents budget allocation % × daily_tokens × 30 Synthesized from budget data. Approximate — Paperclip tracks monthly cents, we track daily tokens. Use (allocation_pct / 100) * daily_tokens * 30 as a rough proxy.
spentMonthlyCents budget.spent_today × 30 Synthesized — rough monthly projection.
pauseReason null when active, "budget" when hard_limit_exceeded, "manual" when heartbeat disabled.
pausedAt Stub null.
permissions Stub { canCreateAgents: false }.
lastHeartbeatAt Latest agent_activity.created_at for this agent Requires new query. See gap analysis. Fallback: null.
metadata Stub null.
createdAt agents.created_at Direct.
updatedAt Stub same as createdAt.

3.2 Task Queue Panel

Paperclip calls:

GET /api/companies/{companyId}/issues?status=...
GET /api/issues/{id}  (detail view)

Proxy maps to:

GET /api/controlplane/tasks
GET /api/controlplane/tasks?role={agentId}  (filtered)

Response translation:

Paperclip expects:  Issue[]  (array of full issue objects)
Clawdie returns:    { tasks: [{task_id, title, description, assigned_to, priority, status, created_at}] }

Field-by-field mapping:

Paperclip Issue field Source Strategy
id tasks.id Direct.
companyId Hardcode "clawdie".
projectId Stub null.
projectWorkspaceId Stub null.
goalId Stub null.
parentId Stub null (no subtask support).
title tasks.title Direct.
description tasks.description Direct.
status tasks.status Map via status table above.
priority tasks.priority Map via priority table above.
assigneeAgentId tasks.assigned_to Direct.
assigneeUserId Stub null.
checkoutRunId Stub null.
executionRunId Stub null.
issueNumber Stub null.
identifier tasks.id Pass through — our API-xxx format serves as identifier.
originKind Stub "manual".
requestDepth Stub 0.
billingCode Stub null.
startedAt tasks.created_at Use created_at as approximation.
completedAt Stub null (no completion tracking).
cancelledAt Stub null.
hiddenAt Stub null.
labelIds Stub [].
blockedBy Stub [].
blocks Stub [].
createdAt tasks.created_at Direct.
updatedAt Stub same as createdAt (no updated_at column).

Query parameter translation:

Paperclip query param Proxy behavior
status=backlog,todo Map to WHERE status IN ('pending') — we don't distinguish backlog vs todo.
status=in_progress Map to WHERE status = 'in_progress'.
assigneeAgentId=X Map to role=X on our GET /tasks?role=X endpoint.
limit=N Forward as-is (add LIMIT to query).
q=search No support — ignore for MVP.

3.3 Approvals Pane

Paperclip calls:

GET /api/companies/{companyId}/approvals?status=pending
GET /api/approvals/{id}

Proxy maps to:

GET /api/controlplane/approvals
GET /api/controlplane/approvals?agent_id={agentId}

Response translation:

Paperclip expects a flat Approval[] with a status field. Clawdie returns { pending: [...], approved: [...] } split by operator_approved boolean.

Paperclip Approval field Source Strategy
id approvals.id Direct.
companyId Hardcode "clawdie".
type Synthesized from context: if estimated_tokens is set → "budget_override_required", otherwise "request_board_approval".
requestedByAgentId approvals.agent_id Direct.
requestedByUserId Stub null.
status approvals.operator_approved operator_approved = true"approved", operator_approved = false"pending".
payload approvals.operation + approvals.estimated_tokens Construct { operation, estimated_tokens }.
decisionNote Stub null.
decidedByUserId Stub null.
decidedAt Stub null. No approved_at column exists.
createdAt approvals.created_at Direct.
updatedAt Stub same as createdAt.

The proxy flattens the two Clawdie arrays into one Paperclip Approval[], setting status per row. When the Paperclip UI filters by ?status=pending, the proxy returns only the pending array.


3.4 Activity Log Panel

Paperclip calls:

GET /api/companies/{companyId}/activity?entityType=X&entityId=Y&agentId=Z

Proxy maps to:

Direct DB query on agent_activity  (no API endpoint exists yet)

GAP: Clawdie has no GET /activity endpoint. The proxy must query the DB directly. See gap analysis in section 5.

Field-by-field mapping:

Paperclip ActivityEvent field Source Strategy
id agent_activity.id Direct.
companyId Hardcode "clawdie".
actorType agent_activity.agent_id Always "agent" (we have no user-initiated activity).
actorId agent_activity.agent_id Direct.
action agent_activity.event_type Map: "task_completed""issue.completed", "approval_request""approval.requested", "error""agent.error".
entityType agent_activity.event_type Map: "task_completed""issue", "approval_request""approval", "error""agent".
entityId agent_activity.payload->>'task_id' Extract from JSONB payload. Fallback: agent_id.
agentId agent_activity.agent_id Direct.
runId Stub null (no heartbeat run tracking).
details agent_activity.payload Direct — pass the full JSONB payload.
createdAt agent_activity.created_at Direct.

Query parameter translation:

Paperclip query param Proxy behavior
entityType=issue Filter WHERE event_type = 'task_completed'.
entityType=approval Filter WHERE event_type = 'approval_request'.
entityType=agent Filter WHERE event_type = 'error'.
agentId=X Filter WHERE agent_id = 'X'.
entityId=Y Filter WHERE payload @> '{"task_id": "Y"}' (JSONB containment).

4. Stub Strategy

Fields the proxy returns as fixed values because Clawdie has no equivalent concept.

Stub value Applied to Rationale
null projectId, goalId, parentId, labelIds, blockedBy, blocks, issueNumber, checkoutRunId, executionRunId, billingCode, icon, metadata, capabilities Paperclip concepts that don't exist in Clawdie's simpler model. UI should show empty/hidden sections.
"manual" originKind All tasks in Clawdie are operator-created.
0 requestDepth No nesting.
false permissions.canCreateAgents Agent creation is a setup-time operation, not runtime.
{} adapterConfig, runtimeConfig Empty but valid objects so the UI doesn't crash on property access.
Same as createdAt updatedAt (on all entities) No updated_at column exists. UI shows identical create/update times.
agent.created_at lastHeartbeatAt (fallback) If no activity found, use agent creation time so the UI shows something rather than "never".

501 Endpoints

These Paperclip endpoints are called by the UI but return 501 Not Implemented for MVP. The UI should degrade gracefully (hide the section or show "not available").

  • POST /api/companies/{id}/agents — agent creation
  • PATCH /api/agents/{id} — agent editing
  • POST /api/agents/{id}/pause, POST /api/agents/{id}/resume — agent control
  • POST /api/companies/{id}/issues — issue creation
  • PATCH /api/issues/{id} — issue editing
  • POST /api/approvals/{id}/approve, POST /api/approvals/{id}/reject — approval decisions
  • GET /api/companies/{id}/heartbeat-runs — heartbeat run history
  • GET /api/companies/{id}/org — org chart
  • GET /api/companies/{id}/dashboard — aggregate summary
  • All /api/projects/*, /api/goals/*, /api/routines/*, /api/secrets/* endpoints

5. Gap Analysis

API endpoints and DB changes needed before the proxy layer can serve all 4 panels.

Critical (blocking)

Gap Impact Fix
No GET /activity endpoint Activity panel cannot function. Proxy must query DB directly — breaks if proxy is separated from DB. Add GET /api/controlplane/activity to controlplane-api.ts with query params: ?agent_id=&event_type=&limit=&offset=. Query: SELECT * FROM agent_activity WHERE ... ORDER BY created_at DESC LIMIT $n.
No updated_at on any table All entities return stale updatedAt values. Add updated_at TIMESTAMPTZ DEFAULT NOW() to agents, tasks, approvals tables. Update on every mutation.
No approved_at on approvals Cannot show when an approval was decided. Add approved_at TIMESTAMPTZ to approvals table. Set when operator_approved flips to true.

Important (degrades experience)

Gap Impact Fix
No lastHeartbeatAt query Agent panel shows "never" for last heartbeat. Add query: SELECT agent_id, MAX(created_at) as last_heartbeat FROM agent_activity GROUP BY agent_id. Expose via GET /api/controlplane/state or a new endpoint.
No task status transitions Tasks stay pending forever in the UI — no way to mark in_progress or done. Add PATCH /api/controlplane/tasks/:id with `{ status: "in_progress"
No approval mutations Approvals can only be viewed, never approved/rejected. Add POST /api/controlplane/approvals/:id/approve and POST /api/controlplane/approvals/:id/reject.
GET /tasks?role=X is misnamed Proxy must know that role param actually filters by assigned_to (agent ID, not role). No code change needed — just document this in the proxy. Long-term: rename param to assigned_to.
context excluded from TaskResponse Task detail view loses rich context stored in JSONB. Add context field to formatTask() in controlplane-api.ts.
deadline excluded from TaskResponse Deadline stored but never returned. Add deadline field to formatTask().

Nice-to-have (deferred)

Gap Impact Fix
No heartbeat run tracking Cannot show execution history per agent. New heartbeat_runs table + write path in heartbeat loop. Phase 2.
No org chart data Flat agent list only. Synthesize from reportsTo pattern (all → orchestrator). No DB change needed.
No project/goal/label taxonomy Tasks have no categorization. New tables if needed. Phase 2.
Agent auth is a no-op Any non-op: Bearer token passes. Implement proper API key verification against api_key_hash.
No rate limiting Proxy and API are unprotected from abuse. Add rate limiting middleware. Phase 2.
CONTROLPLANE_SHARED_SECRET unused Dead config var. Remove from config.ts or repurpose.

6. Proxy Architecture

Route Mounting

The proxy lives in src/dashboard-proxy.ts and mounts in controlplane-api.ts alongside existing routes:

/api/auth/*              → Better Auth handler
/api/controlplane/*      → Existing controlplane API
/api/paperclip/*         → Dashboard proxy (NEW)
/dashboard/*             → Static SPA files

The Paperclip UI's API client uses base path /api. At build time, either:

  1. Rewrite the base path to /api/paperclip via Vite env variable (VITE_API_BASE_URL), or
  2. Mount the proxy at /api and move existing controlplane routes to a different prefix (disruptive).

Recommendation: Option 1. Set VITE_API_BASE_URL=/api/paperclip in the Paperclip UI's .env before building.

Auth Flow

The proxy reuses the same auth middleware as the controlplane API:

  • local_trusted mode: no auth required on any proxy route.
  • authenticated mode: Better Auth session cookie required.
  • Agent bearer tokens (op: scheme) also accepted for machine access.

The proxy injects companyId: "clawdie" automatically — the UI's path parameter is accepted but ignored.

Data Flow

Paperclip UI
    │
    │  GET /api/paperclip/companies/clawdie/issues
    ▼
dashboard-proxy.ts
    │
    │  Translate params, call internal DB/API
    ▼
controlplane-db.ts  (direct query, bypasses HTTP)
    │
    │  Raw rows
    ▼
dashboard-proxy.ts
    │
    │  Reshape rows → Paperclip Issue[] format
    ▼
Paperclip UI

The proxy queries the DB directly (same Pool instance) rather than making HTTP calls to its own API. This avoids double-serialization and keeps latency minimal.


7. Implementation Order

Build order for src/dashboard-proxy.ts:

  1. Skeleton — mount /api/paperclip/* routes, 501 catch-all, auth middleware reuse.
  2. Agent listGET /companies/:id/agents → query agents + agent_budgets → reshape to Paperclip Agent[]. Most complex due to field synthesis.
  3. Task queueGET /companies/:id/issues → query tasks → reshape to Paperclip Issue[]. Simple field mapping.
  4. ApprovalsGET /companies/:id/approvals → query approvals → reshape to flat Paperclip Approval[]. Trivial.
  5. Activity logGET /companies/:id/activity → query agent_activity → reshape. Requires the new GET /activity endpoint or direct DB query.
  6. Error handling — graceful 501 for unimplemented endpoints, proper 404 for missing entities, 401/403 for auth failures.

Each step can be tested independently with curl against the proxy routes.


8. Suggested DB Migration (Pre-Proxy)

Run before building the proxy, ideally in the next FreeBSD agent session:

-- Add updated_at to tasks
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW();

-- Add updated_at to approvals
ALTER TABLE approvals ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW();
ALTER TABLE approvals ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;

-- Add updated_at to agents
ALTER TABLE agents ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW();

-- Index for activity queries (used by proxy)
CREATE INDEX IF NOT EXISTS idx_agent_activity_agent_created
  ON agent_activity (agent_id, created_at DESC);

-- Index for task status filtering
CREATE INDEX IF NOT EXISTS idx_tasks_assigned_status
  ON tasks (assigned_to, status);

9. References

  • doc/DASHBOARD-PHASE-D-HANDOFF.md — Phase D integration plan
  • doc/DASHBOARD-FEASIBILITY.md — Phase A/B/C results (all green)
  • doc/CONTROLPLANE-MESSAGE-CONTRACT.md — Clawdie API shapes
  • doc/CONTROLPLANE-ARCHITECTURE.md — Service architecture
  • src/controlplane-api.ts — Existing API routes
  • src/controlplane-db.ts — DB schema and queries
  • Paperclip upstream: ui/src/api/*.ts (UI client), server/src/routes/*.ts (server routes), packages/shared/src/types/*.ts (type definitions)