veritas-kanban

Veritas Kanban — API Reference

Version: 6.1.0 Last Updated: 2026-07-25 Base URL: http://localhost:3001/api Canonical prefix: /api/v1 (alias: /api)

This is the source-of-truth companion to the Swagger/OpenAPI spec. For workflow-engine-specific endpoints, see API-WORKFLOWS.md.


Table of Contents

  1. Authentication
  2. Base URLs & Environments
  3. Error Model
  4. Tasks
  5. Time Tracking
  6. Observations
  7. Analytics
  8. Configuration
  9. Settings
  10. Lifecycle Hooks
  11. Chat & Squad
  12. Agent Status
  13. Auth & Diagnostics
  14. Identity & Workspaces
  15. Telemetry
  16. Health
  17. WebSocket
  18. Shared Run Sessions
  19. Run Approvals
  20. Run Terminal Control
  21. Task Verification
  22. Task Comments
  23. Task Subtasks
  24. Task Deliverables
  25. Recurring Work Scheduler
  26. Queue Intake Monitors
  27. Task Archive
  28. Attachments
  29. Agent Permissions
  30. Workspace Execution Trust
  31. Agent Routing
  32. Sandbox Policies
  33. Shared Resources
  34. Skill Capability Profiles
  35. Skill Security Scanner
  36. Doc Freshness
  37. Cost Prediction
  38. Error Learning
  39. Reflection-to-Memory Promotion
  40. External Tracker Introspection
  41. Run-scoped Tool Control Plane
  42. Tool Policies
  43. Watcher Continuation Policies
  44. Traces
  45. Ceremony Requirements
  46. Governance Decision Traces
  47. Audit
  48. Maintenance Center
  49. Common Workflows
  50. Versioning & Deprecation
  51. Rate Limits
  52. Additional Endpoint Groups
  53. Execution Admission
  54. Durable Goals
  55. Knowledge Collections

Authentication

VK supports three authentication methods. All are optional when running locally with VERITAS_AUTH_ENABLED=false.

Methods

Method Header / Param Use Case
Session Cookie veritas_session cookie Local-owner browser UI login
API Key X-API-Key: <key> Agent integrations, scripts
Device Session Authorization: Bearer vk_dev_… Paired desktop/mobile/PWA clients
WS Query Param ws://host:port/ws?api_key=<key> WebSocket connections

Roles

Role Permissions
admin Full access — all endpoints, destructive operations, deep health
agent Read/write tasks, time tracking, observations, chat, telemetry
read-only Read-only access to all GET endpoints

v5 Permission Context

Protected REST handlers and WebSocket connections receive a shared auth context: role, userId, workspaceId, actorType, authMethod, tokenName, and role-derived permissions. Device sessions also include deviceSessionId, deviceId, clientId, clientMode, capabilities, and degradedReason when a current workspace role downgrade trimmed the approved scopes. Existing endpoints still accept the compatibility roles above, but new v5 route work should declare the specific permission it requires, such as task:read, task:write, workflow:execute, or admin:manage.

Production keeps unauthenticated localhost bypass disabled. A password session that passes the local-owner loopback Host/Origin/Referer checks receives only the explicit local-agent:run client capability needed by packaged desktop agent controls. Remote password-session cookies remain invalid.

Password sessions are intentionally local-owner only for v5 GA. Remote, server-mode, PWA, CLI, MCP, and multi-user clients must use device sessions or scoped API tokens; those credentials are revalidated against workspace membership and revocation state.

Localhost Bypass

When VERITAS_AUTH_LOCALHOST_BYPASS=true, requests from 127.0.0.1 / ::1 are authenticated automatically with the role set by VERITAS_AUTH_LOCALHOST_ROLE (default: read-only).

API Key Configuration

Set via environment:

# Admin key
VERITAS_ADMIN_KEY=your-admin-key

# Additional keys (format: name:key:role, comma-separated)
VERITAS_API_KEYS=agent1:key123:agent,readonly:key456:read-only

Base URLs & Environments

Environment Base URL Notes
Local dev http://localhost:3001/api Default port
Production Deploy behind reverse proxy with TLS Add rate limiting externally

Both /api/v1/... and /api/... resolve to the same handlers. Use /api for brevity.


Error Model

All errors return a consistent JSON envelope:

{
  "error": "Human-readable message",
  "code": "OPTIONAL_ERROR_CODE",
  "details": {}
}

Status Codes

Code Meaning
200 Success
201 Created
400 Bad request — invalid body, missing fields
401 Not authenticated
403 Forbidden — insufficient role
404 Resource not found
409 Conflict — duplicate, state violation, stale revision
429 Rate limited
503 Service degraded (health checks)

Tasks

All task routes are mounted at /api/tasks.

Task Revisions and Conflict Handling

Task reads and writes include optimistic-concurrency metadata:

When the supplied revision is stale, the API returns 409 CONFLICT and includes the latest resource so the client can reload or reapply the edit:

{
  "code": "CONFLICT",
  "message": "task task_20260531_abcd has changed since it was loaded. Reload and retry with the latest revision.",
  "details": {
    "resourceType": "task",
    "resourceId": "task_20260531_abcd",
    "expectedRevision": 3,
    "currentRevision": 4,
    "current": {
      "id": "task_20260531_abcd",
      "title": "Latest task title",
      "revision": 4
    }
  }
}

Duplicate Task Identity Diagnostics

File-backed boards validate task identity directly from markdown files so stale cache entries cannot hide duplicate cards. The scanner detects:

GET /api/tasks and GET /api/backlog keep their existing response data shape. When conflicts exist, enveloped API responses include meta.taskIdentityDiagnostics, and the response includes X-Veritas-Task-Identity-Conflicts with the number of conflicts.

Mutating or moving a task with a duplicate identity fails with 409 CONFLICT instead of silently selecting one matching file. The error details include the operation, target task ID, duplicate IDs, source paths, and destination path when the operation moves a task:

{
  "code": "CONFLICT",
  "message": "Duplicate task identity detected",
  "details": {
    "operation": "backlog.promote",
    "taskId": "task_20260603_dup",
    "destinationPath": "active",
    "duplicateIds": ["task_20260603_dup"],
    "conflicts": [
      {
        "kind": "task-id",
        "id": "task_20260603_dup",
        "sources": [
          { "location": "active", "path": "active/task_20260603_dup-active.md" },
          { "location": "backlog", "path": "backlog/task_20260603_dup-backlog.md" }
        ]
      }
    ]
  }
}

List Tasks

GET /api/tasks

Returns all active tasks. Supports query filters.

Response 200 (abridged manifest assessments):

{
  "tasks": [
    {
      "id": "TASK-001",
      "title": "Implement login",
      "status": "in-progress",
      "priority": "high",
      "project": "rubicon",
      "assignee": "agent-1",
      "createdAt": "2026-03-01T10:00:00Z"
    }
  ]
}

Get Task Counts

GET /api/tasks/counts

Returns task counts grouped by status.

Create Task

POST /api/tasks

Body:

{
  "title": "Fix auth bug",
  "description": "Session tokens not refreshing",
  "priority": "high",
  "project": "rubicon",
  "type": "bug"
}

Response 201: The created task object.

Get Task

GET /api/tasks/:id

Update Task

PATCH /api/tasks/:id

Body: Partial task fields to update (title, description, status, priority, assignee, etc.).

Headers:

If-Match: "task:task_20260531_abcd:3"

If the task has been updated since revision 3, the API returns 409 CONFLICT with the latest task in details.current.

Delete Task

DELETE /api/tasks/:id

Reorder Tasks

POST /api/tasks/reorder

Body: { "taskIds": ["TASK-003", "TASK-001", "TASK-002"] }

Bulk Update

POST /api/tasks/bulk-update

Body: { "taskIds": ["TASK-001", "TASK-002"], "updates": { "status": "done" } }

Bulk Archive

POST /api/tasks/bulk-archive-by-ids

Body: { "taskIds": ["TASK-001", "TASK-002"] }

Blocking Status

GET /api/tasks/:id/blocking-status

Returns whether a task is blocked by unresolved dependencies.

Dependencies

POST   /api/tasks/:id/dependencies          # Add dependency
DELETE /api/tasks/:id/dependencies/:targetId # Remove dependency
GET    /api/tasks/:id/dependencies           # List dependencies
GET    /api/tasks/:id/dependency-graph       # Full dependency graph

Progress & Checkpointing

GET  /api/tasks/:id/progress         # Get progress
PUT  /api/tasks/:id/progress         # Set progress
POST /api/tasks/:id/progress/append  # Append progress entry

POST   /api/tasks/:id/checkpoint     # Save checkpoint
GET    /api/tasks/:id/checkpoint     # Get checkpoint
DELETE /api/tasks/:id/checkpoint     # Clear checkpoint

Context

GET /api/tasks/:id/context

Returns enriched context for agent consumption (task + dependencies + observations).

Worktree (Git)

GET    /api/tasks/worktrees/cleanup-preview     # Preview expired cleanup candidates
POST   /api/tasks/:id/worktree                  # Resolve base and create worktree
POST   /api/tasks/:id/worktree/adopt            # Admin: validate and adopt a legacy worktree
GET    /api/tasks/:id/worktree                   # Get status and manifest evidence
GET    /api/tasks/:id/worktree/cleanup-preview   # Preview destructive-operation safety
DELETE /api/tasks/:id/worktree                   # Remove worktree when safe
POST   /api/tasks/:id/worktree/rebase            # Fetch and rebase onto an exact commit
POST   /api/tasks/:id/worktree/merge             # Integrate without changing primary checkout
GET    /api/tasks/:id/worktree/open              # Open in editor

Creation fetches the configured base from origin, resolves the exact commit, and persists worktree-manifest/v1 before Git mutates the repository. A fetch failure returns 409. Offline creation requires an explicit acknowledgement:

{
  "allowStaleBase": true,
  "staleBaseAcknowledgement": {
    "reason": "Operator confirmed the repository is intentionally offline."
  }
}

The response includes the manifest ID, ownership lease, exact base commit and source, lifecycle state, remote freshness, ahead/behind counts, and cleanup preview. Agent launch claims the same lease for the exact attempt; the task envelope and run launch manifest retain the manifest, lease, attempt, and base commit references.

Deletion is preview-first. Active attempts cannot be overridden. Dirty, untracked, unpushed, unmerged, or externally held worktrees require force=true plus a reason, which is persisted in the manifest:

DELETE /api/tasks/:id/worktree?force=true&reason=Operator%20accepted%20the%20risk

Safe cleanup remains available with task:write, but a forced cleanup requires admin:manage. An unavailable external-process inspection is incomplete evidence and therefore also requires an admin override. An unexpired attempt lease, active run, branch mismatch, or manifest mismatch cannot be overridden.

Pre-6.0 tasks that have a git.worktreePath but no manifest are not silently trusted. An admin can call POST /api/tasks/:id/worktree/adopt; Veritas requires an exact registered path and branch, matching common Git directory and remote fingerprint, a unique cross-task allocation, and a freshly resolved remote base that is proven to be an ancestor of the legacy HEAD before creating the manifest. The baseline source is recorded as legacy-adopted, not as original creation provenance. Local tracked and untracked changes are preserved and appear in cleanup preview.

Merge creates a detached integration worktree from the latest exact remote base, merges the task branch there, and pushes HEAD to the base without force. It never checks out, pulls, stages, commits, or otherwise changes the configured primary checkout. Interrupted create, rebase, push, and cleanup states remain recorded with a recoverable error and path. Restart recovery reconciles persisted preparing, merging, pushing, integrated, rebasing, and cleanup states against Git and the fetched remote before resuming.

Apply Template

POST /api/tasks/:id/apply-template

Demote Task

POST /api/tasks/:id/demote

Moves a task back to backlog.


Time Tracking

Mounted at /api/tasks.

Summary

GET /api/tasks/time/summary

Returns aggregate time tracking data across all tasks.

Start Timer

POST /api/tasks/:id/time/start

Stop Timer

POST /api/tasks/:id/time/stop

Add Manual Entry

POST /api/tasks/:id/time/entry

Body:

{
  "durationMs": 3600000,
  "description": "Code review"
}

Delete Entry

DELETE /api/tasks/:id/time/entry/:entryId

Observations

Observational memory for tasks — agents record learnings, blockers, and notes.

Add Observation

POST /api/tasks/:id/observations

Body:

{
  "content": "Rate limiter needs Redis for distributed deployments",
  "type": "insight",
  "agent": "codex-1"
}

List Observations

GET /api/tasks/:id/observations

Delete Observation

DELETE /api/tasks/:id/observations/:obsId

Search Observations (cross-task)

GET /api/observations?q=redis&type=insight

Analytics

GET /api/analytics/timeline   # Task completion timeline
GET /api/analytics/metrics    # Throughput, cycle time, WIP
GET /api/analytics/health     # Board health indicators

Configuration

Mounted at /api/config.

Get Config

GET /api/config

Repository Management

GET    /api/config/repos               # List repos
POST   /api/config/repos               # Add repo
PATCH  /api/config/repos/:name         # Update repo
DELETE /api/config/repos/:name         # Remove repo
POST   /api/config/repos/validate      # Validate repo config
GET    /api/config/repos/:name/branches # List branches

Agent Configuration

GET /api/config/agents        # List configured agents
GET /api/config/agent-support # Redacted live harness support tiers and remediation
PUT /api/config/agents        # Update agent config
PUT /api/config/default-agent # Set default agent

GET /api/config/agent-support is the canonical operator projection used by Settings and vk doctor. Each row identifies the agent type, support profile, explicit adapter and transport, enabled state, tier, redacted reason/failure class, executable/authentication posture, version/build evidence, manifest digest, safe diagnostic commands, and remediation. It never returns credential values.


Settings

GET   /api/settings/features   # Get feature flags
PATCH /api/settings/features   # Toggle feature flags

Body (PATCH):

{
  "darkMode": true,
  "squadChat": true,
  "analyticsEnabled": true,
  "enforcement": {
    "ceremonyDesignReview": "block",
    "ceremonyFailureRetrospective": "warn"
  }
}

Ceremony enforcement modes are off, warn, or block. block prevents task completion until the matching ceremony is completed; warn records a pending ceremony and governance trace without blocking completion.


Lifecycle Hooks

Event-driven hooks that fire on task state transitions.

GET    /api/hooks                # List hooks
GET    /api/hooks/executions     # List recent executions
POST   /api/hooks                # Create hook
PATCH  /api/hooks/:id            # Update hook
DELETE /api/hooks/:id            # Delete hook
POST   /api/hooks/fire           # Manually fire a hook

Create Hook Body:

{
  "name": "notify-on-done",
  "event": "task.status.changed",
  "filter": { "newStatus": "done" },
  "action": {
    "type": "webhook",
    "url": "https://example.com/webhook"
  }
}

Chat & Squad

Squad Chat

Post messages to the squad chat channel (agent coordination).

POST /api/chat/squad

Body:

{
  "agent": "VERITAS",
  "message": "Starting cleanup - 14 steps",
  "model": "claude-opus-4.6",
  "tags": ["cleanup"],
  "replyToId": "msg_parent",
  "mentions": [{ "target": "case", "kind": "agent" }],
  "taskId": "task-123",
  "runId": "run-456",
  "pinned": false,
  "decision": false
}
GET /api/chat/squad

Returns recent squad messages. Supports ?limit=N, ?agent=AGENT, ?since=ISO, and ?includeSystem=false.

GET  /api/chat/squad/search?q=review&limit=20
GET  /api/chat/squad/unread?actor=case
POST /api/chat/squad/read
GET  /api/chat/squad/:messageId/thread
POST /api/chat/squad/:messageId/pin
POST /api/chat/squad/:messageId/react

Read body:

{
  "actor": "case",
  "messageId": "msg_latest"
}

Pin/decision body:

{
  "pinned": true,
  "decision": true
}

Reaction body:

{
  "actor": "case",
  "reaction": "ack"
}

Search returns redacted snippets only. Mention notifications link back to the squad message and do not include raw secrets beyond the existing redaction rules.

Communication Adapters

Communication adapters live under integrations. Microsoft Teams provides the existing human reply/send posture. Buzz adds a native signed-event bridge between one mapped community channel and Squad Chat. External thread mappings, Buzz event coordinates, delivery audits, and replay cursors remain separate from message content.

GET  /api/integrations/communication/adapters
PUT  /api/integrations/communication/adapters/:adapterId
GET  /api/integrations/communication/adapters/:adapterId/health
POST /api/integrations/communication/adapters/:adapterId/test
POST /api/integrations/communication/adapters/:adapterId/send
POST /api/integrations/communication/adapters/:adapterId/replies
POST /api/integrations/communication/adapters/:adapterId/poll
POST /api/integrations/communication/adapters/:adapterId/disconnect
GET  /api/integrations/communication/adapters/:adapterId/buzz/channels
PUT  /api/integrations/communication/adapters/:adapterId/buzz/channels/:channelId
POST /api/integrations/communication/adapters/:adapterId/buzz/channels/:channelId/disable
GET  /api/integrations/communication/adapters/:adapterId/buzz/definitions
GET  /api/integrations/communication/adapters/:adapterId/buzz/definitions/links
POST /api/integrations/communication/adapters/:adapterId/buzz/definitions/preview
POST /api/integrations/communication/adapters/:adapterId/buzz/definitions/import
GET  /api/integrations/communication/mappings
GET  /api/integrations/communication/deliveries

Adapter setup uses settings:write. External reply ingestion uses comment:write; approval-targeted replies also require workflow:execute, task:write, or admin authority. Reply ingestion sanitizes the body, redacts secret-like text, dedupes externalReplyId, creates a Squad Chat reply through the normal chat service, and broadcasts it to connected clients.

Configure body:

{
  "kind": "msteams",
  "displayName": "Microsoft Teams",
  "enabled": true,
  "deliveryMode": "webhook",
  "destinationType": "channel",
  "tenantId": "tenant-id",
  "teamId": "team-id",
  "channelId": "channel-id",
  "webhookUrl": "https://example.com/teams-adapter",
  "credential": "write-only-token"
}

Responses never return credentials or raw webhook query strings. Redacted posture appears as webhookUrlConfigured, webhookUrlRedacted, and hasCredential.

Buzz configure body:

{
  "kind": "buzz",
  "displayName": "Buzz",
  "enabled": true,
  "relayHttpUrl": "https://community.example.com",
  "relayWebSocketUrl": "wss://community.example.com",
  "expectedCommunity": "community.example.com",
  "publicKey": "<64-hex-public-key>",
  "credentialRef": "env:BUZZ_PRIVATE_KEY",
  "authTagRef": "env:BUZZ_AUTH_TAG",
  "allowLocalhost": false,
  "allowPrivateNetwork": false,
  "command": {
    "executable": "buzz",
    "args": []
  }
}

Buzz rejects raw credentials, URL userinfo/query/fragment components, and credential-like command arguments. Health returns an exact status and reasonCode, redacted configured/resolved endpoints, expected/observed community, public-key fingerprint, tested Buzz release/commit, probe revision, relay contract, independent verification checks, optional command diagnostics, and runtime transport/subscription/cursor/send facets.

POST .../buzz-default/test runs the read-only compatibility probe. It does not send. Runtime canSend additionally requires an enabled mapped channel; canReceiveReplies requires an active authenticated subscription.

Send null for relayWebSocketUrl, expectedCommunity, authTagRef, or command to clear that optional setting. Omitting one preserves its current value.

Buzz channel mapping body:

{
  "target": { "kind": "squad" },
  "enabled": true,
  "actor": "operator"
}

The channelId path value is a Buzz channel UUID. One enabled channel may map to Squad Chat. Disabling retains the mapping and cursor.

Buzz send body:

{
  "target": {
    "kind": "squad",
    "squadMessageId": "msg_local_reply"
  },
  "replyToSquadMessageId": "msg_local_root",
  "message": "Reply from Veritas",
  "actor": "VERITAS"
}

Omit replyToSquadMessageId for a root. The response includes the signed Buzz coordinate and a delivery status. delivery_unknown is a durable visible state, not a failure alias. POST .../poll queries the event ID before any safe resubmission. See Buzz Communication Adapter.

Buzz definition preview body:

{
  "coordinate": {
    "authorPubkey": "<64-hex-public-key>",
    "kind": 30175,
    "dTag": "security-reviewer"
  },
  "action": "create"
}

The list returns only signature-verified NIP-33 persona and team heads from the current healthy Buzz connection. Preview returns the exact event/content hash, mapped/source-only/ignored/rejected field report, proposed disabled profile/roster, collisions, unresolved same-author persona slugs, and an optimistic expectedLocalRevision when a local target or linked persona dependency participates in the proposed materialization.

Buzz definition import body:

{
  "coordinate": {
    "authorPubkey": "<64-hex-public-key>",
    "kind": 30175,
    "dTag": "security-reviewer"
  },
  "action": "refresh",
  "targetId": "buzz-security-reviewer",
  "expectedEventId": "<64-hex-event-id>",
  "expectedLocalRevision": "<64-hex-revision>"
}

Actions are create, link, refresh, or skip. Source replacement, concurrent local changes, and unresolved collisions return 409. Created profiles, rosters, and roster members are disabled. The link-status endpoint reports current, changed, missing, or unavailable without deleting the local materialization. Preview requires settings:read; import requires settings:write. No endpoint writes definitions back to Buzz.

Reply ingest body:

{
  "externalThreadId": "teams-thread-1",
  "externalReplyId": "reply-42",
  "actor": "alice@example.com",
  "displayName": "Alice",
  "message": "Looks good. Please ship it.",
  "target": {
    "kind": "squad",
    "squadMessageId": "msg_parent",
    "taskId": "task-123"
  }
}

Chat Sessions

POST   /api/chat/send                 # Send message to a session
GET    /api/chat/sessions              # List sessions
GET    /api/chat/sessions/:id          # Get session
GET    /api/chat/sessions/:id/history  # Get session history
DELETE /api/chat/sessions/:id          # Delete session

Agent Status

Real-time agent activity indicator for the board.

GET  /api/agent/status   # Current status
POST /api/agent/status   # Update status

Update Body:

{
  "status": "working",
  "subAgentCount": 2,
  "activeAgents": [
    { "agent": "TARS", "status": "working", "taskTitle": "Fix auth" },
    { "agent": "CASE", "status": "working", "taskTitle": "Add tests" }
  ]
}

Delegation Violation

POST /api/agent/status/delegation-violation

Reports when an agent violates delegation rules.


Auth & Diagnostics

GET  /api/auth/status           # Check auth status & current role
POST /api/auth/setup            # Initial admin setup
POST /api/auth/login            # Login (returns JWT)
POST /api/auth/logout           # Logout / invalidate token
POST /api/auth/recover          # Account recovery
POST /api/auth/change-password  # Change password
POST /api/auth/rotate-secret    # Rotate JWT secret
GET  /api/auth/rotation-status  # JWT rotation status
POST /api/auth/device-pairing/exchange # Redeem a one-time pairing payload

Login Example

POST /api/auth/login

Body: { "password": "admin-password" }

Response 200:

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "role": "admin",
  "expiresIn": "24h"
}

Identity & Workspaces

v5 adds SQLite-backed identity management for users, workspaces, memberships, roles, and invitations. These endpoints are mounted at /api/identity and /api/v1/identity.

Method Path Description
GET /api/identity/profile Current user profile plus workspace memberships.
GET /api/identity/workspaces Workspaces available to the current user.
POST /api/identity/workspaces/switch Validate/select an active workspace membership.
GET /api/identity/workspaces/:workspaceId/members List active workspace members.
GET /api/identity/workspaces/:workspaceId/invitations List invitations. Requires admin.
POST /api/identity/workspaces/:workspaceId/invitations Create an invitation. Requires admin.
POST /api/identity/invitations/accept Accept an invitation.
POST /api/auth/invitations/accept Accept an invitation before login.
POST /api/identity/invitations/:id/revoke Revoke a pending invitation. Requires admin.
PATCH /api/identity/workspaces/:workspaceId/members/:id Update a member role. Requires admin.
DELETE /api/identity/workspaces/:workspaceId/members/:id Remove a member. Requires admin.
GET /api/identity/workspaces/:workspaceId/device-sessions List trusted device sessions. Requires admin.
POST /api/identity/workspaces/:workspaceId/device-pairing-codes Create a short-lived pairing payload. Requires admin.
POST /api/identity/workspaces/:workspaceId/device-sessions/:id/test Test current device session state. Requires admin.
POST /api/identity/workspaces/:workspaceId/device-sessions/:id/revoke Revoke a trusted device session. Requires admin.

Create Invitation

POST /api/identity/workspaces/local/invitations
{
  "email": "reviewer@example.com",
  "role": "reviewer"
}

The response includes the plaintext invitation token once. SQLite stores only the token hash.

Accept Invitation

POST /api/auth/invitations/accept
{
  "token": "plaintext-token-from-invite",
  "displayName": "Reviewer",
  "email": "reviewer@example.com"
}

Membership mutations are recorded in audit and activity history.

Device Pairing

POST /api/identity/workspaces/local/device-pairing-codes
{
  "deviceName": "Brad phone",
  "clientMode": "mobile-pwa",
  "capabilities": ["workspace:read", "task:read"],
  "scopes": ["workspace:read", "task:read"],
  "role": "read-only"
}

The response returns a plaintext code and veritas://pair?... link once. SQLite stores only hashes for pairing codes and device session secrets. Clients redeem the returned payload through:

POST /api/auth/device-pairing/exchange

Pairing payloads include client id, client mode, capabilities, device id, scopes, role, workspace, nonce, signed timestamp, and signature. Codes expire quickly, cannot be reused, and failed attempts are rate-limited and audited.


Telemetry

Run events, token usage, and metrics — powers the dashboard graphs.

Post Event

POST /api/telemetry/events

Body (run started):

{
  "type": "run.started",
  "taskId": "TASK-001",
  "agent": "veritas"
}

Body (run completed):

{
  "type": "run.completed",
  "taskId": "TASK-001",
  "agent": "veritas",
  "durationMs": 45000,
  "success": true
}

Body (token usage):

{
  "type": "run.tokens",
  "taskId": "TASK-001",
  "agent": "veritas",
  "model": "claude-opus-4.6",
  "inputTokens": 12000,
  "outputTokens": 3500,
  "cacheTokens": 8000,
  "cost": 0.15
}

Bulk Events

POST /api/telemetry/events/bulk

Body: { "events": [ ... ] }

Query Events

GET /api/telemetry/events                  # All events (?type=, ?limit=, ?taskId=)
GET /api/telemetry/events/task/:taskId     # Events for a specific task
GET /api/telemetry/status                  # Telemetry subsystem status
GET /api/telemetry/count                   # Event counts
GET /api/telemetry/export                  # Export events (CSV/JSON)

The run-scoped gateway emits network.egress events internally. These records contain run and gateway keys, policy digest, hashed host key, protocol, port, decision, reason, and optional approval correlation. They never contain full URLs, query strings, headers, bodies, proxy credentials, or raw paths.


Health

Three-tier health check system for container orchestration.

Endpoint Auth Purpose
GET /health None Alias for /health/live
GET /health/live None Liveness probe — process running
GET /health/ready None Readiness probe — storage, disk, memory
GET /health/deep Admin Full diagnostics — version, WS count, circuit breakers
GET /api/health None Lightweight API liveness signal
GET /api/health/deep Admin Same as /health/deep, under /api

Authenticated deep-health responses include dependencyCircuits.summary, redacted durable circuit snapshots, and active expiring overrides for provider/model calls, selected agent hosts, MCP servers and tools, outbound integrations, and storage operations. Dependency IDs are opaque, and secret-bearing request and response data is never returned.

Endpoint Auth Purpose
POST /health/dependency-circuits/:key/reset Admin Reset one known circuit with an audited operator reason
POST /health/dependency-circuits/:key/override Admin Create an audited expiring allow or block override
DELETE /health/dependency-circuits/:key/override Admin Clear the active override for one circuit

Reset accepts { "reason": "at least 8 characters" }. Override accepts { "mode": "allow" | "block", "reason": "at least 8 characters", "durationSeconds": 60..3600 }; allow bypasses admission without erasing unhealthy circuit evidence, while block rejects new work. Unknown reset targets and missing overrides return 404.

Readiness Response:

{
  "status": "ok",
  "checks": { "storage": "ok", "memory": "ok", "disk": "ok" },
  "timestamp": "2026-03-02T07:00:00Z"
}

Authenticated deep-health responses include an optional top-level sqlite object after SQLite initializes successfully. The same redacted sqlite-storage/v1 contract is returned by the Maintenance summary:

Field Meaning
schemaVersion Always sqlite-storage/v1
databaseLocation memory, configured, or runtime-default; never a raw path
platform Runtime platform used for classification
filesystemType Normalized filesystem type or conservative sentinel
filesystemPosture supported-local, known-unsafe, unknown, or not-applicable
detectionSource / reasonCode Redacted evidence and decision reason
journalMode wal, delete, memory, refused, or unknown
decisionSource / overrideSource Automatic, memory, compatibility, or expert-override decision provenance
healthPosture / lockingPosture Healthy/degraded/refused posture and WAL or single-host ownership-lock state
override Optional redacted policy ID, source, status, expiry, host binding, and restart posture
lastIntegrityCheck Optional timestamp, ok/failed status, and bounded result from PRAGMA quick_check

Raw database paths, mount points, and mount sources are intentionally omitted. Unsafe or unknown startup posture cannot be queried through health because the server refuses to bind; use the redacted startup/supervisor log instead.

SQLite Journal Maintenance

Journal-mode changes are staged through the authenticated API and execute only during the next server bootstrap, before route modules or SQLite-backed services load. Requests always target the configured authoritative database; clients cannot submit an arbitrary filesystem path.

Endpoint Permission Purpose
POST /api/maintenance/sqlite/journal/preview backup:write Preview posture, sidecars, ownership, backup class, and risks
POST /api/maintenance/sqlite/journal/apply admin:manage Schedule a confirmed preview for the next restart
GET /api/maintenance/sqlite/journal/status backup:read Read the scheduled operation and active override summary
GET /api/maintenance/sqlite/journal/operations/:id backup:read Read one redacted operation result
POST /api/maintenance/sqlite/journal/override/revoke admin:manage Revoke compatibility/override policy; restart remains required

Preview body:

{
  "targetMode": "delete",
  "singleHost": true,
  "overrideReason": "Temporary single-host compatibility",
  "expiresAt": "2026-07-16T00:00:00.000Z"
}

delete mode and any expert override require an explicit single-host acknowledgement, a reason, a bounded future expiry, VERITAS_SQLITE_TOPOLOGY=single-host, and a stable VERITAS_SQLITE_HOST_ID. Known-unsafe filesystems cannot be overridden.

Apply body:

{
  "previewId": "98af3a58-1b8b-41b3-8162-dfdb1f257740",
  "previewToken": "<one-time token from preview>",
  "confirm": "98af3a58-1b8b-41b3-8162-dfdb1f257740",
  "acknowledgeRisks": true
}

Apply returns 202 with state: "scheduled". Restart the server once; the bootstrap takes an ownership lock, creates and verifies a local backup, checkpoints WAL, closes and reopens the source across the mode change, verifies the effective mode and full integrity, then commits policy. A pre-close failure reverts journal mode in place while exclusivity is still held. After the first close, recovery only completes a verified current mode; it never replaces the database with an older backup. A recovery-required result fails startup closed. Auth-disabled and localhost-bypass requests cannot schedule or revoke maintenance even when their implicit role is admin.


WebSocket

Endpoint: ws://localhost:3001/ws

Connection

const ws = new WebSocket('ws://localhost:3001/ws?api_key=YOUR_API_KEY');

Authentication

Pass API key as api_key query parameter only for WebSocket clients that cannot send headers during the upgrade. HTTP requests must use Authorization: Bearer or X-API-Key. In production, do not rely on localhost bypass.

Client → Server Messages

Subscribe to task output:

{
  "type": "subscribe",
  "taskId": "TASK-001",
  "attemptId": "attempt_001",
  "afterSequence": 41
}

attemptId defaults to the active/latest attempt. afterSequence defaults to 0. The server attaches the live tail before replaying stored events, so events appended during replay are delivered once and in sequence. The subscribed confirmation returns the resolved attemptId and current cursor.

Subscribe to chat session:

{ "type": "chat:subscribe", "sessionId": "session-abc" }

Subscribe to shared run session events:

{ "type": "run-session:subscribe" }

The server confirms with run-session:subscribed after the connection has task:read access in the current workspace.

Server → Client Messages

Task change broadcast:

{ "type": "task:updated", "task": { "id": "TASK-001", "status": "done" } }

Durable agent event:

{
  "type": "agent:event",
  "taskId": "TASK-001",
  "attemptId": "attempt_001",
  "data": {
    "schemaVersion": "run-event/v1",
    "eventId": "runevt_abcdefghijklmnopqr",
    "taskId": "TASK-001",
    "runId": "attempt_001",
    "attemptId": "attempt_001",
    "sessionId": "provider-session-001",
    "sequence": 42,
    "receivedAt": "2026-07-23T20:00:00.000Z",
    "kind": "message.delta",
    "source": { "provider": "codex-cli", "adapter": "codex-cli" },
    "redaction": {
      "status": "none",
      "fields": [],
      "originalBytes": 16,
      "persistedBytes": 30
    },
    "payload": { "content": "Running tests..." },
    "payloadHash": "<64 lowercase hex characters>"
  },
  "timestamp": "2026-07-23T20:00:00.000Z"
}

Unknown future event kinds remain replayable. Persisted payloads are bounded and redacted; redaction.status: "dropped" means the provider payload exceeded the journal limit and was replaced by safe metadata.

Legacy agent output projection:

{
  "type": "agent:output",
  "taskId": "TASK-001",
  "attemptId": "attempt_001",
  "outputType": "stdout",
  "content": "Running tests...",
  "sequence": 42,
  "timestamp": "2026-07-23T20:00:00.000Z"
}

Chat message:

{ "type": "chat:message", "sessionId": "session-abc", "message": { ... } }

Agent status change:

{ "type": "agent:status", "status": "working", "activeAgents": [ ... ] }

Shared run session event:

{
  "type": "run-session:event",
  "event": {
    "id": "run_event_abc",
    "shareId": "run_share_abc",
    "taskId": "TASK-001",
    "attemptId": "attempt_001",
    "type": "message.sent",
    "actor": { "id": "editor-1", "label": "Pair Editor" },
    "message": "Run the focused verification gate",
    "createdAt": "2026-06-18T10:00:00.000Z"
  },
  "workspaceId": "local",
  "sequence": 42,
  "timestamp": "2026-06-18T10:00:00.000Z"
}

Shared Run Sessions

Workspace-scoped live sharing for active task agent runs.

Mounted at /api/run-sessions.

Method Path Description Permissions
GET /api/run-sessions List shares in the current workspace. Supports filters. task:read
POST /api/run-sessions Create a view, edit, or fork share for a task run. task:write
GET /api/run-sessions/:id Read an active share snapshot. task:read
GET /api/run-sessions/:id/events Read share lifecycle, message, approval, and fork events. task:read
PATCH /api/run-sessions/:id Update permission, expiry, label, or mobile-safe classes. task:write
POST /api/run-sessions/:id/revoke Revoke a share. task:write
POST /api/run-sessions/:id/messages Send an attributed co-drive message into the run. task:write
POST /api/run-sessions/:id/approvals Resolve an exact pending provider request. task:write
POST /api/run-sessions/:id/fork Create a linked fork task without mutating the parent. task:write

Create Share

POST /api/run-sessions
{
  "taskId": "TASK-001",
  "permission": "view",
  "expiresAt": "2026-06-19T10:00:00.000Z",
  "actorLabel": "Release reviewer",
  "mobileSafeApprovalClasses": ["elicitation"]
}

permission is one of view, edit, or fork.

The response includes stablePath, snapshot, mobileSafeApprovalClasses, status, and forkedTaskIds. The share path is not anonymous public access; it still requires workspace authentication and task read/write permissions.

Co-drive Message

POST /api/run-sessions/run_share_abc/messages
{
  "message": "Run the focused verification gate before release."
}

The server records the request actor on the resulting message.sent event and forwards the message to the active run when the backing provider exposes an interactive stream. If interactive stdin is unavailable, the message is still recorded and streamed as session history.

Mobile-safe Approval

POST /api/run-sessions/run_share_abc/approvals
{
  "approvalId": "runapproval_cQBUv6oGPvQrBT9y8C14",
  "actionClass": "elicitation",
  "response": "approved",
  "expectedRevision": 1,
  "expectedActionHash": "c8a1f6f26c61b770694b5136f9a9ae9b08fd068cb8919ffebae4f2e88ec791f0",
  "note": "Use the release branch.",
  "responseData": {
    "answers": {
      "branch": { "answers": ["release/v6.0.2"] }
    }
  }
}

The response is delivered to the exact paused provider request only after the broker accepts the revision and action hash. Mobile/PWA clients require both an approval marked mobileSafe by the provider adapter and an action class listed on the share. Unsafe, stale, changed, expired, or already-resolved requests fail closed.

Fork Session

POST /api/run-sessions/run_share_abc/fork
{
  "title": "Investigate forked run",
  "priority": "high",
  "reason": "Continue independently without changing the parent run."
}

Forking creates a new task linked to the parent task and attempt. The fork description includes redacted parent context and a redacted run excerpt when available, but it does not inherit worktrees, thread IDs, credentials, or local-only handles.

Revoked and expired shares fail closed for reads, messages, approvals, and forks. Share lists are scoped to the current workspace.


Run Approvals

Durable provider-native approval and elicitation requests. Mounted at /api/run-approvals.

Method Path Description Permissions
GET /api/run-approvals List workspace requests; filter by status, task, attempt, agent. task:read
GET /api/run-approvals/:approvalId Read one workspace-scoped request. task:read
POST /api/run-approvals/:approvalId/decision Atomically approve or reject the exact pending request. admin

Action classes are tool, shell, filesystem, network, budget, workflow, and elicitation. Status is pending, approved, rejected, expired, or cancelled.

List Pending Requests

GET /api/run-approvals?status=pending&taskId=TASK-001&attemptId=attempt_001
[
  {
    "schemaVersion": "run-approval/v1",
    "id": "runapproval_cQBUv6oGPvQrBT9y8C14",
    "workspaceId": "local",
    "taskId": "TASK-001",
    "attemptId": "attempt_001",
    "provider": "codex-app-server",
    "agentId": "codex-app-server",
    "requestKind": "approval",
    "actionClass": "shell",
    "action": "Execute command: pnpm test",
    "actionHash": "c8a1f6f26c61b770694b5136f9a9ae9b08fd068cb8919ffebae4f2e88ec791f0",
    "resourceScope": ["/workspace/veritas-kanban"],
    "workingDirectory": "/workspace/veritas-kanban",
    "riskClass": "high",
    "evidenceRevision": "sha256:provider-runtime-manifest",
    "providerRequestId": "string:approval-1",
    "mobileSafe": false,
    "status": "pending",
    "revision": 1,
    "createdAt": "2026-07-23T12:00:00.000Z",
    "updatedAt": "2026-07-23T12:00:00.000Z",
    "expiresAt": "2026-07-23T12:05:00.000Z"
  }
]

The exact provider arguments are hashed but are not persisted or sent to remote clients. Display text, bounded resource scope, working directory, risk, policy reason, runtime evidence revision, and expiry provide the review context. Secrets in displayed or persisted fields are redacted. Structured response values are delivered only to the waiting provider process; they are not persisted or broadcast. The durable resolution records only that a response payload was provided.

Decide a Request

POST /api/run-approvals/runapproval_cQBUv6oGPvQrBT9y8C14/decision
{
  "decision": "approved",
  "expectedRevision": 1,
  "expectedActionHash": "c8a1f6f26c61b770694b5136f9a9ae9b08fd068cb8919ffebae4f2e88ec791f0",
  "note": "Focused command approved once."
}

Reviewer identity and authentication time come from the verified request context; caller-supplied identity fields are rejected. Critical-risk decisions require authentication no older than five minutes. The transition is a single compare-and-set from pending. Duplicate, stale, out-of-order, changed-action, expired, and cancelled decisions return a conflict or authorization error and cannot resume the provider action.

Each request and resolution is appended to run-event/v1 with the same approval ID, action hash, and provider correlation ID. Attempt interruption or cancellation resolves every remaining pending request as cancelled.

Subscribers to the run-sessions WebSocket channel receive run-approval:changed messages for requests in their workspace:

{
  "type": "run-approval:changed",
  "approval": {
    "id": "runapproval_cQBUv6oGPvQrBT9y8C14",
    "taskId": "TASK-001",
    "status": "approved",
    "revision": 2
  },
  "workspaceId": "local",
  "sequence": 43,
  "timestamp": "2026-07-23T12:01:00.000Z"
}

Run Terminal Control

Provider-neutral execution and supervision for commands owned by an active run attempt. Mounted at /api/v1/run-terminals with the standard /api/run-terminals compatibility alias.

Method Path Description Permissions
POST /api/v1/run-terminals/runs/:taskId/:attemptId/execute Request approval or start one exact command. agent:write
GET /api/v1/run-terminals/runs/:taskId/:attemptId List handles for the exact active attempt. agent:read
GET /api/v1/run-terminals/runs/:taskId/:attemptId/handles/:handleId Read one scoped handle. agent:read
GET /api/v1/run-terminals/runs/:taskId/:attemptId/handles/:handleId/output Read bounded output after a cursor. agent:read
POST /api/v1/run-terminals/runs/:taskId/:attemptId/handles/:handleId/wait Wait up to 300 seconds for one handle. agent:write
POST /api/v1/run-terminals/runs/:taskId/:attemptId/wait-any Wait for any of up to 64 unique handles. agent:write
POST /api/v1/run-terminals/runs/:taskId/:attemptId/wait-all Wait for all of up to 64 unique handles. agent:write
POST /api/v1/run-terminals/runs/:taskId/:attemptId/handles/:handleId/detach Detach a foreground handle without disowning it. agent:write
POST /api/v1/run-terminals/runs/:taskId/:attemptId/handles/:handleId/terminate Gracefully terminate, then escalate if necessary. agent:write

Output accepts afterCursor and limit query parameters. Single waits accept {"timeoutMs": 25000}. Multi-handle waits accept {"handleIds": ["terminal_1", "terminal_2"], "timeoutMs": 25000}.

Every route resolves the current active attempt before checking the handle’s workspace, task, and attempt identity. Stale attempts and cross-scope handles return 404. Output and wait limits return 400 when invalid.

Execution accepts a stable caller-owned request ID, an executable plus argument array, explicit pipe or PTY posture, start mode, optional worktree-relative cwd, and environment names only:

{
  "requestId": "verify-build-1",
  "command": "pnpm",
  "args": ["build"],
  "mode": "pipe",
  "startMode": "background",
  "cwd": ".",
  "environmentKeys": ["PATH"]
}

The first valid request returns 202 with status: "approval-required" and an exact-action run approval. Resolve that approval through /api/v1/run-approvals/:approvalId/decision, then retry the identical request. An approved retry returns 201 with status: "started" and the run-owned handle. Reusing requestId with a changed command returns 409; retrying an already-started exact request returns the original handle.

The server, not the caller, supplies environment values and applies the active run’s filesystem wrapper and network-egress environment. Commands are launched without a shell. Missing process authority, stale manifest or phase evidence, unenforceable required sandbox posture, cwd escape, credential-shaped arguments, unapproved environment names, and unsupported PTY mode fail closed before spawn.


Task Verification

Verification step checklists for tasks — define acceptance criteria that must be checked off before a task is considered truly complete.

Mounted at /api/tasks.

Add Verification Step

POST /api/tasks/:id/verification

Body:

{
  "description": "All unit tests passing"
}

Response 201: The updated task object with the new verification step added.

Update Verification Step

PATCH /api/tasks/:id/verification/:stepId

Body (partial):

{
  "checked": true
}

When checked changes, checkedAt is automatically set (or cleared).

Response 200: The updated task object.

Delete Verification Step

DELETE /api/tasks/:id/verification/:stepId

Response 200: The updated task object with the step removed.


Task Comments

Comment threads on tasks — supports adding, editing, and deleting comments. Comments auto-sync to linked GitHub issues.

Comment mutations use the parent task revision. Send the latest task ETag in If-Match when adding, editing, or deleting comments. A stale comment edit returns the same 409 CONFLICT shape documented in Tasks, with the latest task and comments in details.current.

Mounted at /api/tasks.

Add Comment

POST /api/tasks/:id/comments

Body:

{
  "author": "veritas",
  "text": "Root cause identified — auth middleware skips token refresh"
}

Response 201: The updated task object with the new comment.

Edit Comment

PATCH /api/tasks/:id/comments/:commentId

Body:

{
  "text": "Updated analysis — the issue is in the session store"
}

Delete Comment

DELETE /api/tasks/:id/comments/:commentId

Task Subtasks

Break tasks into smaller work items with optional acceptance criteria per subtask.

Mounted at /api/tasks.

Add Subtask

POST /api/tasks/:id/subtasks

Body:

{
  "title": "Add input validation",
  "acceptanceCriteria": ["Rejects empty strings", "Returns 400 on invalid input"]
}

Response 201: The updated task object.

Update Subtask

PATCH /api/tasks/:id/subtasks/:subtaskId

Body (partial):

{
  "completed": true
}

Delete Subtask

DELETE /api/tasks/:id/subtasks/:subtaskId

Toggle Acceptance Criterion

PATCH /api/tasks/:id/subtasks/:subtaskId/criteria

Body:

{
  "criteriaIndex": 0
}

Toggles the checked state of a specific acceptance criterion on a subtask.


Task Deliverables

Track deliverable artifacts (files, PRs, docs) produced by agents working on a task.

Mounted at /api/tasks.

List Deliverables

GET /api/tasks/:id/deliverables

Add Deliverable

POST /api/tasks/:id/deliverables

Body:

{
  "title": "API endpoint implementation",
  "type": "code",
  "path": "server/src/routes/new-feature.ts",
  "agent": "codex-1",
  "description": "REST endpoints for the new feature"
}

Response 201: The updated task object.

Update Deliverable

PATCH /api/tasks/:id/deliverables/:deliverableId

Body (partial): Any of title, type, path, status, description.

Delete Deliverable

DELETE /api/tasks/:id/deliverables/:deliverableId

Recurring Work Scheduler

Inspect and control recurring work across scheduled deliverables and scheduled workflow definitions.

Mounted at /api/scheduler.

List Scheduler Items

GET /api/scheduler

Returns scheduler summary counts, scheduler items, retry state, health, and recent events.

Read Item

GET /api/scheduler/items/:id

Item IDs use the source prefix:

Run Item Now

POST /api/scheduler/items/:id/run

Runs one scheduler item immediately. Deliverables execute through the scheduled deliverables runner. Workflows start a normal workflow run and return the started run ID.

Pause Item

POST /api/scheduler/items/:id/pause

Pauses the underlying scheduled deliverable or workflow schedule.

Resume Item

POST /api/scheduler/items/:id/resume

Re-enables the underlying scheduled deliverable or workflow schedule and clears scheduler retry delay.

Validate Item

POST /api/scheduler/items/:id/validate

Returns validation issues. Custom cron schedules are visible and manually runnable, but automatic due-run execution is not enabled until a cron adapter is configured.

Run Due Items

POST /api/scheduler/due/run

Runs all due standard schedules and refuses overlapping due-run passes.


Operations Digest

GET /api/digest/operations returns the deterministic operations source bundle. Use hours, from, to, project, repo, and cwd to scope both JSON and Markdown output. format=markdown returns the briefing generated from the same filtered bundle.

The response distinguishes two time models:

inventory reconciles all board tasks into included tasks or one mutually exclusive exclusion reason: filter mismatch, current status, time window, or missing metadata required by a selected filter. Every count includes task source IDs. dataQuality separately identifies matching tasks grouped under an unassigned project, unknown repository, or missing CWD so operators can drill into incomplete records. Run counts are deduplicated by attempt ID when available, otherwise by event ID.


Queue Intake Monitors

Scan bounded GitHub issue and PR queues, build candidate packets, and gate assign/execute actions through policy, budget, sandbox, auth, and workflow preflight checks.

Mounted at /api/queue-monitors.

Method Path Description
GET /api/queue-monitors List monitors, health, candidate packet state, and events
GET /api/queue-monitors/:id Read one monitor
PUT /api/queue-monitors/:id Update mode, runner, labels, caps, workflow, and guardrail
GET /api/queue-monitors/:id/health Read health and visible action item state
GET /api/queue-monitors/:id/explain Build a fresh packet and planned action without mutation
POST /api/queue-monitors/:id/run Run one monitor now
POST /api/queue-monitors/:id/pause Pause one monitor
POST /api/queue-monitors/:id/resume Resume one monitor

Monitor Modes

runner: "local" is executable in this version. runner: "github-actions" is persisted for monitor definitions but launch is blocked until a workflow-dispatch adapter is configured.

Local queue sandbox preflight probes the configured Codex runtime and evaluates the preset against that exact manifest. If no valid manifest can be resolved, capability-dependent presets fail closed instead of using provider-name assumptions.

Candidate Packet

Each run stores a bounded packet with:

Repeated failed or blocked runs increment failureStreak. When the monitor reaches stopConditions.maxFailureStreak, health becomes blocked and actionItem explains what must be fixed before resuming.

Queue monitor events are included in the operations digest when they fall inside the selected digest window.


Task Archive

Archive completed tasks (by sprint or individually) and restore them. Archived tasks are removed from the active board.

Mounted at /api/tasks.

Method Path Description
GET /api/tasks/archived List all archived tasks
GET /api/tasks/archive/suggestions Get sprints ready for archival
POST /api/tasks/archive/sprint/:sprint Archive all done tasks in a sprint
POST /api/tasks/bulk-archive Archive by sprint name
POST /api/tasks/bulk-archive-by-ids Archive specific task IDs
POST /api/tasks/:id/archive Archive a single task
POST /api/tasks/:id/restore Restore a task from archive

Archive by Sprint

POST /api/tasks/archive/sprint/:sprint

Archives all completed tasks in the given sprint.

Response 200:

{
  "archived": 5
}

Archive Single Task

POST /api/tasks/:id/archive

Auth: Requires admin or agent role. Emits audit log entry.

Restore Task

POST /api/tasks/:id/restore

Restores an archived task back to active status (done).


Attachments

File upload/download for task attachments with automatic text extraction (PDF, DOCX, etc.).

Mounted at /api/tasks.

Method Path Description
POST /api/tasks/:id/attachments Upload files (multipart, max 20)
GET /api/tasks/:id/attachments List all attachments
GET /api/tasks/:id/attachments/:attId Get attachment metadata
GET /api/tasks/:id/attachments/:attId/download Download file
GET /api/tasks/:id/attachments/:attId/text Get extracted text
DELETE /api/tasks/:id/attachments/:attId Delete attachment

Upload Attachments

POST /api/tasks/:id/attachments
Content-Type: multipart/form-data

Field: files — one or more files (max 20 per request).

Files undergo MIME validation via magic bytes. Text is automatically extracted from supported formats.

Response 200:

{
  "attachments": [
    {
      "id": "att_abc123",
      "filename": "design-spec.pdf",
      "originalName": "design-spec.pdf",
      "mimeType": "application/pdf",
      "size": 245000
    }
  ],
  "task": { "..." },
  "rejected": []
}

Get Extracted Text

GET /api/tasks/:id/attachments/:attId/text

Response 200:

{
  "attachmentId": "att_abc123",
  "text": "Extracted document content...",
  "hasText": true
}

Agent Permissions

Role-based permission levels for agents: intern, specialist, lead. Interns require approval for certain actions.

Mounted at /api/agents/permissions.

Method Path Description
GET /api/agents/permissions List all agent permissions
GET /api/agents/permissions/:id Get agent permission config
PUT /api/agents/permissions/:id/level Set permission level
PATCH /api/agents/permissions/:id Update permission fields
POST /api/agents/permissions/check Check if agent can perform action
POST /api/agents/permissions/approvals Request approval (intern)
GET /api/agents/permissions/approvals List pending approvals
POST /api/agents/permissions/approvals/:id Review approval request

Set Permission Level

PUT /api/agents/permissions/:id/level

Body:

{
  "level": "specialist"
}

Valid levels: intern, specialist, lead.

Check Permission

POST /api/agents/permissions/check

Body:

{
  "agentId": "codex-1",
  "action": "deploy"
}

Response 200:

{
  "allowed": true,
  "requiresApproval": false,
  "traceId": "govtrace_1760000000000_ab12cd"
}

Update Permission Fields

PATCH /api/agents/permissions/:id

Body (partial):

{
  "trustedDomains": ["github.com"],
  "canCreateTasks": true,
  "canDelegate": false,
  "canApprove": false,
  "restrictions": ["no-deploy"]
}

Request Approval

POST /api/agents/permissions/approvals

Used by intern-level agents to request approval for restricted actions.

Review Approval

POST /api/agents/permissions/approvals/:id

Approve or reject a pending approval request.


Workspace Execution Trust

Scans repository-controlled execution inputs for a task’s exact registered worktree and manages append-only operator decisions.

Mounted at /api/agents/:taskId/workspace-trust.

Method Path Description Permission
GET /api/agents/:taskId/workspace-trust Scan inventory and show current decision agent:read
POST /api/agents/:taskId/workspace-trust/decisions Record an exact-inventory decision admin:manage
POST /api/agents/:taskId/workspace-trust/revoke Revoke the current decision admin:manage

All routes resolve the worktree from the task record. Callers cannot submit an arbitrary filesystem path.

Scan

GET /api/agents/TASK-001/workspace-trust

The response contains workspace-execution-trust-inventory/v1, stable credential-redacted identity evidence, classified entries and requested capabilities, the project maximum, and the latest decision when one exists. Source contents are never returned.

Record a Decision

POST /api/agents/TASK-001/workspace-trust/decisions
{
  "inventoryDigest": "sha256:...",
  "mode": "restricted",
  "reason": "Reviewed instructions; retain read-only execution",
  "expiresAt": "2026-08-01T00:00:00.000Z"
}

mode accepts trusted, restricted, or denied. expiresAt is optional. If the inventory changed after review, the server returns 409 and does not record the decision.

Revoke

POST /api/agents/TASK-001/workspace-trust/revoke
{
  "inventoryDigest": "sha256:...",
  "reason": "Repository ownership changed"
}

The revocation identifies the superseded decision and remains in the audit history. See Workspace Execution Trust for identity, inventory, restricted-mode, and launch-binding rules.


Agent Routing

Automatic agent resolution — determines the best agent for a task based on configurable routing rules.

Mounted at /api/agents.

Method Path Description
POST /api/agents/route Resolve best agent for a task
GET /api/agents/routing Get routing configuration
PUT /api/agents/routing Update routing configuration

Resolve Agent

POST /api/agents/route

Accepts either a task ID or ad-hoc metadata:

By task ID:

{
  "taskId": "TASK-001",
  "requiredRuntimeCapabilities": ["run.resume", "tool.mcp"]
}

By metadata:

{
  "type": "bug",
  "priority": "high",
  "project": "rubicon",
  "subtaskCount": 3,
  "requiredRuntimeCapabilities": ["run.resume", "tool.mcp"]
}

Response 200:

{
  "agent": "codex-1",
  "model": "gpt-5.5",
  "rule": "high-priority-bugs",
  "reason": "Matched rule: High priority bugs. Selected manifest sha256:... with supported capability evidence.",
  "runtimeSelection": {
    "requiredCapabilities": ["run.resume", "tool.mcp"],
    "compatible": true,
    "selectedManifest": {
      "manifestDigest": "sha256:...",
      "provider": "codex-cli",
      "compatible": true
    },
    "candidates": [
      {
        "manifestDigest": "sha256:...",
        "provider": "codex-cli",
        "compatible": true
      }
    ]
  },
  "runtimeCandidates": [
    {
      "agent": "codex-1",
      "available": true,
      "selected": true,
      "reason": "Agent is healthy",
      "selection": {
        "requiredCapabilities": ["run.resume", "tool.mcp"],
        "compatible": true,
        "selectedManifest": {
          "manifestDigest": "sha256:...",
          "provider": "codex-cli",
          "compatible": true
        },
        "candidates": [
          {
            "manifestDigest": "sha256:...",
            "provider": "codex-cli",
            "compatible": true
          }
        ]
      }
    }
  ],
  "traceId": "govtrace_1760000000000_ab12cd"
}

When an enabled team roster exists, /api/agents/route evaluates the roster before legacy routing rules. Roster-selected responses use a team-roster: rule prefix. When runtime requirements are present, runtimeCandidates preserves every agent manifest evaluation attempted by the rule/fallback chain. Exactly one entry is marked selected on success; terminal 409 Conflict details preserve all rejected entries and their structured manifest assessments.

Get/Update Routing Configuration

GET /api/agents/routing
PUT /api/agents/routing

PUT Body:

{
  "enabled": true,
  "rules": [
    {
      "id": "high-bugs",
      "name": "High priority bugs",
      "match": { "type": "bug", "priority": "high" },
      "agent": "codex-1",
      "model": "claude-sonnet-4.5",
      "enabled": true
    }
  ],
  "defaultAgent": "veritas",
  "fallbackOnFailure": true,
  "maxRetries": 2
}

Start Agent And Require Runtime Capabilities

POST /api/agents/:taskId/launch-preview
POST /api/agents/:taskId/start

The preview and start endpoints accept the same request. Preview compiles the effective run-launch-manifest/v1 without creating an attempt or dispatching a provider. Callers can select an agent or portable profile package, require additional runtime capabilities, and compare against a parent attempt:

{
  "profileId": "docs-reviewer",
  "sandboxPresetId": "codex-repo-contained",
  "phase": "implement",
  "requiredRuntimeCapabilities": ["tool.mcp", "output.structured"],
  "commitPolicy": "allowed",
  "parentAttemptId": "attempt_parent"
}

The launch path resolves the package runtime against configured provider profiles, applies package model/sandbox/budget posture, and renders bounded package instructions in an attributed section of the provider-owned transport. It also records the profile ID and version. Baseline launch capabilities plus caller, profile, sandbox, and budget requirements must be supported or advisory in one valid manifest before attempt state is mutated. Failure returns 409 Conflict with requiredCapabilities, reasons, manifest identity, and remediation.

The preview response contains manifest, current redacted provider and selected-host posture under dependencyCircuits, plus optional parentAttemptId and drift. The manifest contains redacted effective runtime inputs, instruction fingerprints, compiled phase evidence and source references, per-field origins, and an enforcement object. Prompt content, readiness override text, credential values, and raw local output paths are excluded. Preview and start apply the same readiness gate; an accepted operator override is represented by its digest and run-level origin. Start records the same contract in the active attempt, history, run log, and a policy governance trace before provider dispatch. Named tool/MCP/permission restrictions and required profile health checks block launch when the adapter cannot enforce them explicitly.

phase accepts explore, plan, implement, verify, or publish. Compilation intersects the requested profile with the exact parent launch or current transition evidence before attempt mutation. Descendants, retries, fallbacks, and provider changes cannot widen parent authority. An unsupported phase dimension returns 409 Conflict with typed phase-* enforcement blockers. Omitting phase creates explicit legacy evidence only when no profile-authoritative parent exists. ACP stdio can enforce the required pre-execution command and external-action mediation. Other adapters return typed blockers for explicit phases instead of treating prompts or post-execution events as enforcement.

Codex app-server and Claude Code accept a positive MCP catalog through native, run-scoped configuration. Other task adapters reject non-empty MCP selections. Approval-required tools are omitted from native provider configuration and use the mediated tool-call API below. Prompt text is never accepted as equivalent enforcement.

Durable Phase Transitions

GET  /api/agents/:taskId/phase?attemptId=attempt_123&limit=100
POST /api/agents/:taskId/phase/transitions

The GET endpoint returns a server-owned phase snapshot plus compatible current and bounded append-only history fields for one exact run. The snapshot includes the launch phase, effective evidence, manifest digest, transition sequence, and history. A launch with no transition is therefore visible before the first journal record. The POST body is a compare-and-set request:

{
  "attemptId": "attempt_123",
  "operationId": "move-to-implement",
  "expectedSequence": 1,
  "expectedPhaseEvidenceDigest": "sha256:...",
  "expectedManifestDigest": "sha256:...",
  "reason": "The approved plan is ready to implement.",
  "targetEvidence": {
    "schemaVersion": "phase-capability-evidence/v1"
  }
}

targetEvidence must be the complete compiled evidence document with a valid content digest. The first transition also includes fromEvidence; subsequent requests must match the journal’s current sequence and evidence. The active attempt and persisted launch-manifest digest must still match.

Same-authority and narrowing transitions return 201 with status: "applied". Expansion creates an exact-action run approval and returns 202 with status: "approval-required". After approval, retry the identical operation with its approvalId. Emergency expansion accepts emergencyOverride: { justification, expiresAt }, requires admin:manage, and cannot last more than 24 hours. Expiry appends a system-attributed restoration of the prior evidence.

Reads require agent:read; transition requests require task:write. Approval decisions use /api/run-approvals/:approvalId/decision and require an administrator. See Phase Transition Journal for storage, idempotency, and delivery boundaries.

Automatic Run Recovery

GET  /api/agents/:taskId/recovery
POST /api/agents/:taskId/recovery/cancel
POST /api/workflows/runs/:runId/recovery/cancel

The GET endpoint returns the latest durable run-recovery/v1 decision or null. Cancellation requires the exact parent attempt:

{ "attemptId": "attempt_parent" }

Only a scheduled or launching recovery can be cancelled. A stale attempt ID, already launched child, or terminal recovery returns 409 Conflict. Recovery records expose the normalized failure class, action, state, root and parent IDs, sequence, backoff and not-before time, selected route, manifest digests, cumulative budget, and any operator handoff.

Workflow cancellation requires the exact step and causal parent:

{
  "stepId": "implement",
  "parentRunId": "run_123:implement:0"
}

The caller must have execute permission on the workflow.

Progress Watchdog

GET  /api/agents/:taskId/watchdog?attemptId=attempt_123
POST /api/agents/:taskId/watchdog/:findingId/override

The GET endpoint returns typed progress-watchdog-finding/v1, progress-watchdog-action/v1, and progress-watchdog-override/v1 records rehydrated from the exact attempt’s causal journal. Findings expose only redacted event IDs and fingerprint hashes, detector confidence, policy version, suppressed event IDs, chosen action, and remaining recovery budget. Reading requires agent:read.

An agent:write principal with local run-control authority can resolve a paused, failed, or operator-required decision:

{
  "attemptId": "attempt_123",
  "resolution": "acknowledge",
  "reason": "Operator reviewed the evidence and accepts the current terminal state."
}

resolution accepts acknowledge, continue, or cancel. Continue launches a provider-native resume from the exact terminal attempt with a stable idempotency key and an instruction to use materially different evidence. Cancel targets only the exact active attempt. Each request and result is append-only and actor-attributed; a conflicting second resolution returns 409 Conflict.

Conversation Lifecycle

POST /api/agents/:taskId/conversation/resume
POST /api/agents/:taskId/conversation/follow-up
POST /api/agents/:taskId/conversation/fork
POST /api/agents/:taskId/conversation/steer
POST /api/agents/:taskId/conversation/interrupt
POST /api/agents/:taskId/conversation/compact
POST /api/agents/:taskId/conversation/archive
POST /api/agents/:taskId/conversation/close

Resume, follow-up, and fork require a terminal source attempt with durable provider identity:

{
  "sourceAttemptId": "attempt_parent",
  "message": "Continue from the verified history",
  "forkTurnId": "turn_7",
  "phase": "verify",
  "commitPolicy": "allowed",
  "idempotencyKey": "operator-generated-key"
}

forkTurnId is accepted only for fork. The remaining controls require the exact active attemptId; steer also requires message. Every action validates capability and immutable launch evidence before invoking a provider-native operation. Unsupported steering fails with an explicit capability error, and any recorded-only fallback returns delivered: false with a reason. Veritas does not claim that recording a message or writing process stdin reached the provider.

Fresh, resume, follow-up, and fork launches accept idempotencyKey in the JSON body or X-Idempotency-Key in the request headers; the header takes precedence. Veritas hashes the value before persistence. Repeating an admitted or queued request with the same key returns the same durable admission outcome instead of launching duplicate work.

Resume, follow-up, and fork bind the exact source attempt as the phase parent. An optional phase may narrow that authority but cannot widen it. Omitting the field inherits a profile-authoritative parent; a legacy source remains explicitly legacy.

Workspace Checkpoint Rewind

POST /api/agents/:taskId/workspace/checkpoints/rewind

An agent:write principal with local run-control authority can request a preview-first rewind for the exact active attempt:

{
  "attemptId": "attempt_123",
  "targetCheckpointId": "checkpoint_target",
  "descendantCheckpointId": "checkpoint_descendant",
  "requestId": "operator-generated-idempotency-key",
  "resolutions": [
    { "path": "src/agent-change.ts", "decision": "accept" },
    { "path": "src/operator-change.ts", "decision": "reject" },
    { "path": "notes/local.md", "decision": "leave-untouched" }
  ]
}

resolutions is optional. An unresolved attribution conflict returns 409 with its exact path. Re-submit with one decision per conflicted path: accept includes the path in the rewind, while reject and leave-untouched preserve its descendant content. Unknown paths, duplicate decisions, current-state divergence, Git/index conflicts, exclusions, and incomplete inventories still fail closed. The canonical decisions and selected paths are included in the preview digest, approval action, transaction digest, and recovery record.

X-Idempotency-Key may supply requestId and takes precedence over the body. A conflict-free or fully resolved first request returns 202 with a critical, non-mobile approval bound to the checkpoint IDs, stable preview evidence, selected paths, resolutions, runtime state, provider evidence revision, and estimated data loss. Repeating the same request after approval revalidates all evidence, quiesces the provider, commits the recoverable storage transaction, and then recovers provider history from the approved checkpoint cursor.

Production runtime recovery currently supports only an active Codex app-server thread with an earlier exact turn cursor in the same thread. Item-level cursors, the currently interrupted turn, other threads, stale runtime evidence, unresolved workspace conflicts, and providers without native fork return 409 Conflict. A successful response records a new live provider thread plus the checkpoint cursor used as its rewind anchor; it does not pretend the provider reused the old thread ID.

commitPolicy accepts forbidden, allowed, or required. A run value overrides task.executionPolicy.commitPolicy, then the legacy features.agents.autoCommitOnComplete setting. Legacy true maps to required; false or an absent value maps to the compatible allowed default. Unknown policy fields or values return 400 Validation failed.

GET /api/agents/:taskId/status returns the active manifest and its derived controls:

{
  "running": true,
  "attemptId": "attempt_123",
  "provider": "codex-cli",
  "providerRuntimeManifest": { "digest": "sha256:..." },
  "runLaunchManifest": {
    "schemaVersion": "run-launch-manifest/v1",
    "digest": "sha256:...",
    "providerRuntime": {
      "digest": "sha256:...",
      "provider": "codex-cli",
      "probeRevision": 10
    },
    "runtime": {
      "command": "codex",
      "model": "gpt-5.5"
    },
    "enforcement": {
      "enforceable": true,
      "blockers": [],
      "warnings": []
    }
  },
  "taskEnvelope": {
    "schemaVersion": "task-envelope/v1",
    "digest": "sha256:...",
    "commitPolicy": "allowed",
    "workspace": {
      "baseline": {
        "headSha": "0123456789abcdef0123456789abcdef01234567",
        "dirty": false,
        "files": []
      }
    }
  },
  "controls": {
    "manifestDigest": "sha256:...",
    "probeState": "ready",
    "controls": [
      {
        "action": "stop",
        "label": "Stop run",
        "capabilityId": "run.stop",
        "state": "supported",
        "available": true,
        "advisory": false,
        "reason": "The adapter terminates the supervised provider process."
      }
    ]
  }
}

Stop, message, completion, token-reporting, and attempt-log endpoints re-check both persisted snapshots. Invalid or active/persisted provider or run-launch digest mismatches return 409 Conflict; unsupported capability states return the same structured control evidence.

Durable run events can be replayed independently of the Markdown attempt log:

GET /api/agents/TASK-001/attempts/attempt_123/events?afterSequence=41&limit=200

The response contains schemaVersion, taskId, attemptId, ordered events, nextCursor, and hasMore. afterSequence must be non-negative and limit must be between 1 and 500. Access uses the same run.logs capability check as attempt logs. Clients should persist nextCursor and reconnect with it rather than inferring order from timestamps. sessionId, turnId, and itemId are separate optional identities and appear only when the provider reports them.

Run payloads above the inline event limit are redacted before being persisted as run-output-artifact/v1. The event keeps a run-output-preview/v1 under payload.outputArtifact with the opaque artifact ID, media/content class, original and preview byte counts, truncation reason, state, and allowed query operations. No response exposes the host storage path.

Authorized readers query the artifact within the exact workspace, task, run, and attempt scope:

GET /api/agents/TASK-001/output-artifacts/spill_abc123...?runId=attempt_123&attemptId=attempt_123
GET /api/agents/TASK-001/output-artifacts/spill_abc123...?runId=attempt_123&attemptId=attempt_123&operation=byte-range&offset=0&length=4096
GET /api/agents/TASK-001/output-artifacts/spill_abc123...?runId=attempt_123&attemptId=attempt_123&operation=line-range&startLine=100&lineCount=50
GET /api/agents/TASK-001/output-artifacts/spill_abc123...?runId=attempt_123&attemptId=attempt_123&operation=json-path&jsonPath=$.rows[0]
GET /api/agents/TASK-001/output-artifacts/spill_abc123.../download?runId=attempt_123&attemptId=attempt_123&offset=0&length=4194304

Metadata reads remain available after expiry or quarantine so event references do not break silently. Content queries are bounded and rate-limited. Download ranges revalidate the stored hash and secret shapes first; a mismatch quarantines the body. Invalid UTF-8, binary, and compressed content is metadata-only unless an explicit storage policy allows it. Cleanup honors active-run leases, removes eligible bodies deterministically, retains tombstones, and reports reclaimed bytes.

Stop and message requests must carry the attemptId returned by status so a delayed control cannot affect a replacement run:

{ "attemptId": "attempt_123" }

POST /api/agents/:taskId/message adds the attributed message (and optional actor) to that body. POST /api/agents/:taskId/tokens also requires the exact attemptId; the server never falls back to the task’s current attempt.

OpenClaw completion callbacks must identify the exact run that produced them:

{
  "attemptId": "attempt_123",
  "providerRuntimeManifestDigest": "sha256:<64 lowercase hex characters>",
  "success": true,
  "summary": "Implemented and verified the requested change"
}

POST /api/agents/:taskId/complete rejects OpenClaw callbacks whose attempt or manifest digest does not match the active run. The bounded legacy body above remains supported. A callback can instead send an explicit status (success, blocked, failed, interrupted, or partial) plus bounded blockers, provider evidence, artifacts, verification, and continuation. The route fixes the terminal source to callback; clients cannot spoof process, stream, or operator ownership. Only OpenClaw attempts accept callback or remote-session completion; process and SDK providers return 409 Conflict for that transport.

Every terminal path persists one digest-bound completion-result/v1 on the current attempt and attempt history. It includes digest, idempotencyKey, completedAt, terminalSource, envelope/runtime bindings, normalized status, bounded redacted claims, harness evidence, attributable files and artifacts, verification, side effects, continuation, and phase evidence when the launch was phase-bound. Phase evidence identifies the launch digest, final effective authority, transition sequence, and every authority-expanding transition. Exact duplicate callbacks return success without mutating the task again, including after restart. Conflicting terminal claims return 409 Conflict. Startup reconciliation also persists interrupted completion results for harness-owned process or stream attempts that were still running when the server restarted. OpenClaw attempts remain eligible for their authoritative callback after restart.

Codex CLI, Codex SDK, Codex app-server, Claude Code, and Hermes do not call this endpoint; Veritas captures their terminal process or stream output and owns completion normalization. Codex app-server completion is authoritative only after a schema-valid turn/completed notification for the correlated task thread and turn. Claimed success becomes partial when required harness evidence is absent, commit policy is violated, a required output is missing, or an unauthorized side effect is observed. success maps the task to done, blocked maps it to blocked, and failed, interrupted, or partial leaves it in in-progress recovery. Token reports likewise bind telemetry and budget mutation to their required attemptId, so a late event from a prior attempt cannot charge or stop a replacement run.

Provider Runtime Manifest On Attempts

Every executable task adapter is probed before the attempt mutates task state. Task and trace responses can therefore include the immutable providerRuntimeManifest used at launch:

{
  "schemaVersion": "provider-runtime-manifest/v1",
  "probeRevision": 5,
  "provider": "codex-cli",
  "adapter": "codex-cli",
  "protocolVersion": "codex-exec-json/v1",
  "providerVersion": "codex-cli 0.144.0",
  "models": ["gpt-5.5"],
  "capabilities": [
    {
      "id": "run.streaming",
      "state": "supported",
      "source": "contract-test",
      "reason": "Codex JSONL output is streamed into run events."
    }
  ],
  "probe": {
    "state": "ready",
    "probedAt": "2026-07-16T01:30:00.000Z",
    "source": "codex --version",
    "diagnostics": []
  },
  "digest": "sha256:<64 lowercase hex characters>"
}

Task Envelope On Attempts

The start API builds the immutable envelope only after readiness, runtime manifest, and sandbox decisions succeed, but before provider execution. The same taskEnvelope is returned by start/status and persisted on the current attempt and attempt history. Its digest covers the provider-neutral task, workspace baseline, policy, expected outputs, gates, and launch-manifest reference. Dirty baseline entries include indexBlobHash and worktreeSha256, allowing completion attribution to distinguish staged and unstaged pre-launch content. Baseline capture is sequential, retries up to three times when HEAD, status, or fingerprints move, and fails closed if the worktree never stabilizes. Existing attempt records without an envelope remain readable.

New envelopes may also include subject.memory, a maximum of eight ranked, human-accepted reflection lessons. Each item carries reflectionId, source task/run/event IDs, confidence, relevance, observed retrieval count, and the retrieval timestamp. The persisted envelope is the authoritative record of which reviewed memory influenced an attempt. Preview attempts select the same bounded context without incrementing observed use.

Task create/update payloads may set reusable defaults under executionPolicy:

{
  "executionPolicy": {
    "commitPolicy": "forbidden",
    "allowedSideEffects": [{ "kind": "filesystem-write", "scope": "." }]
  }
}

Run-time allowed side effects are intersected with the effective sandbox and manifest posture; a task policy cannot grant a capability that launch policy does not authorize. Path scopes wider than the assigned worktree are clamped to the worktree, while disjoint path scopes are dropped. Generic task PATCH calls cannot set attempt.taskEnvelope or attempt.completionResult; only the launch and finalization services may persist those authoritative contracts.

Before dispatch, the selected adapter renders the envelope through an immutable provider-task-envelope-transport/v1 request. Built-in renderers exist for OpenClaw, Codex CLI, Codex SDK, Codex app-server, Claude Code, and Hermes. Every rendered request includes the envelope and runtime identity, objective and bounded context, workspace baseline, explicit commit policy, allowed side effects, outputs, verification gates, and completion evidence contract. Profile instructions and task checkpoint state are separate attributed sections capped at 20,000 characters each. The exact rendered content is fingerprinted in the run launch manifest as instructions.effective-task-request.

OpenClaw’s transport includes the attempt-bound completion callback. Codex CLI, Codex SDK, Codex app-server, Claude Code, and Hermes transports explicitly forbid calling that callback and return terminal output through harness-owned process or stream capture. Provider and adapter identity must match the envelope before dispatch. Veritas does not infer native structured-output support from prompt rendering and owns completion validation and normalization.

The full manifest contains one entry for every known runtime and sandbox capability. A provider version/build change invalidates cached conformance evidence. Failed probes and unknown versions are not positively cached. Explicitly configured providers without a task execution adapter fail with 409 Conflict instead of falling back to OpenClaw.

Runtime Manifest Registration And Routing

POST /api/agents/register and POST /api/agents/register/:id/heartbeat accept an optional providerRuntimeManifest object using the contract above. The server validates the complete capability inventory and recomputes the canonical digest before storing it. Secret-like evidence and unknown request fields are rejected rather than silently stored or stripped. Invalid persisted manifests are ignored on restart so capability routing fails closed. An agent may write its own manifest when its authenticated key/token identity matches the registry ID; otherwise agent:write is required in addition to registry write access. Every later mutation of that authoritative record remains identity-bound. Changing the registered provider, model, or version without replacement evidence invalidates the prior manifest.

POST /api/agents/route and POST /api/agents/hosts/preview accept:

{
  "requiredRuntimeCapabilities": ["run.resume", "tool.mcp"]
}

Host provider, model, tool.*, and sandbox posture is aggregated from validated manifests. Legacy registry fields are returned as display-only posture and cannot satisfy these requirements. A single manifest must match the requested provider and model and satisfy every required capability. supported evidence qualifies; advisory qualifies with a warning; unsupported, unknown, missing, or failed-probe evidence rejects the candidate with structured reasons. Custom provider identifiers use the same schema and selection path without a central provider branch. Registration enables discovery and routing only; an execution adapter is still required before launch.

Only live registrations with a heartbeat no older than five minutes contribute runtime evidence. requiredTools values using tool.* are evaluated through the same single-manifest path; legacy named tools cannot qualify a host. Launch-time sandbox preset rules and active run controls use the exact manifest persisted on the attempt. Provider events, status, logs, completion, stop, steer, token usage, and artifacts reject missing, invalid, failed-probe, or active/persisted digest-mismatched snapshots.


Team Roster Manifests

Team rosters define the workspace coordinator, enabled agent members, roles, capabilities, routing rules, fallbacks, and reviewers. The manifest is stored in app config as teamRoster.

Mounted at /api/config/team-roster.

Method Path Description Permissions
GET /api/config/team-roster Return the stored roster or null settings:read
PUT /api/config/team-roster Replace the roster manifest settings:write
POST /api/config/team-roster/validate Validate a roster object or YAML/JSON content settings:read
POST /api/config/team-roster/import Import and store YAML/JSON roster content settings:write
GET /api/config/team-roster/export?format=yaml Export the stored roster as YAML or JSON settings:read
POST /api/config/team-roster/preview-route Preview the selected member for task metadata settings:read

Preview Route

{
  "type": "docs",
  "priority": "medium",
  "project": "docs",
  "path": "docs/API-REFERENCE.md",
  "capabilities": ["docs"],
  "subtaskCount": 2
}

Preview responses include the matched member, fallback member when used, reviewer members, selected agent/profile, reason, and validation issues. Invalid rosters never route agent launches.


Workspace Capability Discovery

Workspace capability manifests let a board publish what work it accepts and register trusted peer manifests for delegated intake. Manifest data is stored in app config as workspaceCapability, trustedWorkspaceCapabilities, and workspaceDelegations.

Mounted at /api/workspace-capabilities.

Method Path Description Permissions
GET /api/workspace-capabilities/manifest Return the local published manifest or null workspace:read
PUT /api/workspace-capabilities/manifest Replace the local published manifest settings:write
POST /api/workspace-capabilities/manifest/validate Validate a manifest object or YAML/JSON content workspace:read
POST /api/workspace-capabilities/manifest/import Import and store the local manifest settings:write
GET /api/workspace-capabilities/manifest/export Export the local manifest as YAML or JSON workspace:read
GET /api/workspace-capabilities/trusted List trusted peer manifests workspace:read
POST /api/workspace-capabilities/trusted Register or replace a trusted peer manifest settings:write
DELETE /api/workspace-capabilities/trusted/:workspaceId Remove a trusted peer manifest settings:write
GET /api/workspace-capabilities/discover Return local plus trusted manifests for browsing workspace:read
POST /api/workspace-capabilities/intake Create delegated task intake from a trusted peer task:write
GET /api/workspace-capabilities/delegations List stored delegation records task:read
POST /api/workspace-capabilities/delegations/:id/refresh Refresh latest target state task:write

Manifest Shape

{
  "id": "local-board",
  "schemaVersion": "workspace-capability/v1",
  "workspaceId": "local",
  "name": "Local Board",
  "enabled": true,
  "boardUrl": "https://veritas.example",
  "capabilities": [
    {
      "id": "docs",
      "name": "Documentation",
      "acceptedTaskTypes": ["docs"],
      "defaultLabels": ["docs"],
      "defaultProject": "handbook",
      "defaultPriority": "medium",
      "requiredContextFields": ["acceptance"],
      "intakeTargets": ["task"]
    }
  ]
}

Validation rejects duplicate capability IDs and secret-like manifest fields such as token, password, apiKey, or privateKey. Discovery responses redact manifest import source metadata.

Delegated Intake

{
  "source": {
    "workspaceId": "source",
    "workspaceName": "Source Board",
    "taskId": "task_20260626_source",
    "taskUrl": "https://source.example/tasks/task_20260626_source"
  },
  "capabilityId": "docs",
  "title": "Write operator handoff docs",
  "context": "Document the delegated queue handoff.",
  "contextFields": {
    "acceptance": "Includes handoff and rollback steps"
  },
  "type": "docs",
  "labels": ["handoff"],
  "requestedBy": "user:brad"
}

Intake fails closed unless the source workspace is trusted by the local manifest or registered as a trusted peer manifest. Successful task intake stores a delegation record and, when the source task exists locally, appends a delegatedWork status link to that originating task.

CLI support is available under vk workspaces:

vk workspaces discover
vk workspaces validate ./workspace-capability.yaml
vk workspaces trust ./peer-workspace.yaml
vk workspaces intake --source-workspace source --capability docs --title "Write docs" --context "Document handoff" --context-field acceptance="Includes rollback steps"

Agent Profile Packages

Reusable YAML/JSON packages for agent role, runtime, prompt, tools, permissions, policy posture, workflow entrypoint, and health metadata.

Mounted at /api/config/agent-profiles.

Method Path Description Permissions
GET /api/config/agent-profiles List imported profile package summaries settings:read
POST /api/config/agent-profiles/validate Validate YAML/JSON content with field-path errors settings:read
POST /api/config/agent-profiles/import Import or replace a profile package settings:write
GET /api/config/agent-profiles/:id Get one stored profile package settings:read
GET /api/config/agent-profiles/:id/export?format=yaml Export a package as YAML or JSON settings:read
PATCH /api/config/agent-profiles/:id Edit metadata and enablement settings:write
DELETE /api/config/agent-profiles/:id Remove a package settings:write

Import Package

{
  "format": "yaml",
  "source": "settings",
  "content": "id: docs-reviewer\nschemaVersion: agent-profile-package/v1\nversion: 1.0.0\n..."
}

Invalid packages return valid: false from /validate with issues like $.runtime.agent: Required. Import rejects invalid packages and never installs third-party binaries, tools, credentials, or MCP servers.


Sandbox Policies

Reusable filesystem, network, environment, and credential presets for agent and workflow launch guardrails.

Mounted at /api/sandbox-policies.

Method Path Description Permissions
GET /api/sandbox-policies List built-in and custom presets policy:read
GET /api/sandbox-policies/:id Get one preset policy:read
POST /api/sandbox-policies Create a custom preset policy:write + admin role
PUT /api/sandbox-policies/:id Update a custom preset policy:write + admin role
DELETE /api/sandbox-policies/:id Delete a custom preset policy:write + admin role
POST /api/sandbox-policies/validate Dry-run a preset against provider capabilities policy:read, agent:read

Built-in presets are immutable. Custom presets live in the shared app config. Agent profiles, workflow agents, and one-off agent starts can reference a preset with sandboxPresetId.

Create Preset

POST /api/sandbox-policies

Body:

{
  "id": "repo-contained-no-network",
  "name": "Repo contained, no network",
  "enabled": true,
  "enforcement": "required",
  "requiredCapabilities": ["filesystem.write"],
  "filesystem": {
    "readPaths": ["."],
    "writePaths": ["."],
    "deniedPaths": ["~/.ssh", "~/.aws", "~/.config/gh"],
    "dotfileMasking": true,
    "localOnlyHandles": true
  },
  "network": {
    "defaultEgress": "deny",
    "allowedHosts": [],
    "deniedHosts": ["metadata.example.internal"],
    "allowedMethods": [],
    "allowedPathPrefixes": [],
    "blockPrivateNetwork": true,
    "blockMetadataEndpoints": true,
    "blockLoopback": false,
    "allowApprovals": false,
    "dangerouslyAllowGlobalWildcard": false
  },
  "environment": {
    "passthrough": ["CODEX_HOME", "OPENAI_API_KEY"],
    "redactDisplay": true
  },
  "credentials": {
    "mode": "brokered",
    "brokerRefs": ["openai-api-key"]
  }
}

Response 201: Created preset with createdAt and updatedAt.

Validate Preset

POST /api/sandbox-policies/validate

Body:

{
  "presetId": "repo-contained-no-network",
  "provider": "codex-sdk",
  "providerRuntimeManifestDigest": "sha256:<64 lowercase hex characters>",
  "workspacePath": "/workspace/veritas-kanban",
  "requiredCapabilities": ["filesystem.write"]
}

The digest must belong to a manifest currently registered by a live agent host and must match provider. The API rejects caller-supplied manifest objects, unknown or expired digests, and provider mismatches. Internal launch checks use the immutable manifest already selected and persisted for the attempt.

Response 200:

{
  "decision": "allow",
  "provider": "codex-sdk",
  "effective": {
    "sandboxMode": "workspace-write",
    "networkAccessEnabled": false,
    "envPassthrough": ["CODEX_HOME", "OPENAI_API_KEY"],
    "credentialRefs": ["openai-api-key"],
    "networkPolicy": {
      "schemaVersion": "run-egress-policy/v1",
      "policyHash": "sha256:<64 lowercase hex characters>",
      "defaultEgress": "deny",
      "allowedHosts": [],
      "deniedHosts": [
        {
          "kind": "exact",
          "value": "metadata.example.internal"
        }
      ],
      "allowedMethods": [],
      "allowedPathPrefixes": [],
      "blockPrivateNetwork": true,
      "blockMetadataEndpoints": true,
      "blockLoopback": false,
      "allowApprovals": false,
      "tlsInspection": "disabled"
    }
  },
  "unsupportedRules": [],
  "warnings": [],
  "traceId": "govtrace_1760000000000_ab12cd"
}

decision is allow, warn, or block. Required unsupported controls block launches before execution. Advisory unsupported controls continue with warnings. Credential references and environment-style name=value values are redacted in responses and governance traces.

Network host rules are normalized to exact or scoped *.example.com entries. Explicit deny rules take precedence. A global allow wildcard is rejected unless dangerouslyAllowGlobalWildcard is set on the preset. The compiled policy has a stable digest and is the input to the run-scoped gateway; it does not by itself prove that a provider launch is using that gateway. Until gateway launch evidence is present, required fine-grained egress rules continue to fail closed.


Credential Broker Definitions

Admin-only registry for metadata-only credential definitions. The API never accepts or returns a raw credential value.

Mounted at /api/credential-broker.

Method Path Description Permissions
GET /api/credential-broker List credential definition metadata admin:manage
GET /api/credential-broker/:id Get one credential definition admin:manage
POST /api/credential-broker Create a metadata-only credential definition admin:manage
PUT /api/credential-broker/:id Replace a credential definition admin:manage
DELETE /api/credential-broker/:id Delete a definition with no active leases admin:manage

Create Definition

POST /api/credential-broker
Content-Type: application/json
{
  "id": "github-token",
  "name": "GitHub token",
  "description": "Read-only repository metadata",
  "enabled": true,
  "source": {
    "kind": "environment",
    "reference": "VK_GITHUB_TOKEN"
  },
  "scope": {
    "dispatchTypes": ["http"],
    "hosts": ["api.github.com"],
    "tools": [],
    "destinations": ["https://api.github.com"],
    "methods": ["GET"],
    "actions": ["issues.read"],
    "pathPrefixes": ["/repos/"]
  },
  "lease": {
    "ttlSeconds": 60,
    "maxUses": 1,
    "renewable": false
  },
  "approval": "not-required"
}

Response 201: the definition plus schemaVersion: "credential-definition/v1", a canonical SHA-256 digest, and creation/update timestamps. Environment key names and external manager paths are references, not values. Metadata containing credential-looking values is rejected.

The server exposes no public lease-issue, lease-use, or secret-resolution endpoint. Internal controlled boundaries use credential-lease/v1 records whose persisted form contains a handle hash, definition and scope digests, task/attempt/launch-manifest binding, exact action fingerprint, expiry, use count, SHA-256 fingerprints of unique caller operation IDs, approval reference, and terminal state. Raw operation IDs are not persisted. Duplicate operation IDs fail closed instead of replaying use or refresh. Raw values exist only inside the controlled callback; the server returns a constrained structured clone and rejects results that cannot be safely cloned or that contain credential material. Binary views and buffers are not valid callback results.

The initial local source resolves an environment key at use time. This is a compatibility source, not a replacement for a production secret manager. Definitions using an unavailable external source remain metadata-only and leases fail closed. Required brokered sandbox presets compile only when the selected provider and immutable run catalog prove the system-owned tool bridge or another reviewed non-bypassable boundary. Uncovered references still block.

Run completion retries revocation on duplicate terminal delivery. Startup and one-minute periodic reconciliation expire, block, or revoke invalid leases. Manifest declarations and sandbox brokerRefs must be exact definition IDs; credential-like name=value strings are rejected.

See Credential Broker for lifecycle, limitations, and rollback behavior.


Shared Resources

Registry for shared resources (credentials, config files, API keys, docs) that can be mounted to projects.

Mounted at /api/shared-resources.

Method Path Description
GET /api/shared-resources List all (filters: type, project, tag, name)
GET /api/shared-resources/:id Get one resource
POST /api/shared-resources Create resource
PATCH /api/shared-resources/:id Update resource
DELETE /api/shared-resources/:id Delete resource
POST /api/shared-resources/:id/mount Mount to project(s)
POST /api/shared-resources/:id/unmount Unmount from project(s)

Create Resource

POST /api/shared-resources

Body:

{
  "name": "Production DB Config",
  "type": "config",
  "content": "host=db.example.com\nport=5432",
  "tags": ["database", "production"],
  "projectIds": ["rubicon"]
}

Mount/Unmount

POST /api/shared-resources/:id/mount
POST /api/shared-resources/:id/unmount

Body:

{
  "projectIds": ["rubicon", "brainmeld"]
}

Skill Capability Profiles (/api/skills/capabilities)

Declared-vs-observed capability profiles for shared resources with type: "skill". Reads require policy:read. Creating remediation tasks requires policy:write and task:write.

Method Path Description
GET /api/skills/capabilities/taxonomy List canonical skill capability definitions
GET /api/skills/capabilities List skill profiles with optional filters
GET /api/skills/capabilities/:skillId Get one skill capability profile
POST /api/skills/capabilities/:skillId/remediation-task Create a task for capability mismatches

List filters:

Skill Declaration Syntax

Skills declare capabilities in frontmatter:

---
capabilities:
  - filesystem.read
  - network.egress
---

or in a Markdown section:

## Declared Capabilities

- `filesystem.read`
- `browser.session`

Canonical capability ids:

Capability Meaning
filesystem.read Reads local files or repository content
filesystem.write Writes, edits, moves, or deletes files
shell.execute Runs shell commands or subprocesses
network.egress Calls remote URLs, APIs, or webhooks
credential.access Reads secrets, tokens, env vars, or keychain
external.message Sends messages, comments, issues, or PRs
memory.write Writes durable agent memory
task.mutate Creates or changes tasks, issues, or PRs
schedule.persist Creates recurring or background execution
browser.session Uses browser automation or sessions
mcp.tool Invokes MCP/plugin/tool runtimes

Profile Response

{
  "skillId": "shared_123",
  "name": "Review Helper",
  "declaredCapabilities": ["filesystem.read"],
  "observedCapabilities": [
    {
      "capability": "filesystem.read",
      "confidence": 0.82,
      "evidence": [{ "source": "content-pattern", "label": "File read reference" }]
    },
    {
      "capability": "network.egress",
      "confidence": 0.86,
      "evidence": [{ "source": "content-pattern", "label": "Remote network call reference" }]
    }
  ],
  "undeclaredObservedCapabilities": ["network.egress"],
  "status": "mismatch",
  "severity": "high",
  "findings": [
    {
      "kind": "undeclared-observed",
      "capability": "network.egress",
      "severity": "high",
      "message": "network.egress is observed but not declared."
    }
  ]
}

Mismatch detection writes an audit event once per skill version and finding signature. Evidence snippets are redacted before they are returned.

Create Remediation Task

POST /api/skills/capabilities/shared_123/remediation-task

Body:

{
  "project": "Security",
  "priority": "high"
}

Returns the refreshed profile and created task.


Skill Security Scanner (/api/skills/security)

Static security review for local skill directories or a single SKILL.md. Reads require policy:read; scans require admin:manage because scan requests read local filesystem paths. Scan evidence is redacted before it is returned or persisted.

Method Path Description
GET /api/skills/security/patterns List scanner pattern definitions
GET /api/skills/security/inventory List shared skill risk inventory
POST /api/skills/security/inventory/:skillId/remediation-task Create a task for a risky skill
POST /api/skills/security/inventory/:skillId/exceptions Add a reviewed temporary install exception
POST /api/skills/security/scan Scan a skill path and optionally persist it
GET /api/skills/security/scans List persisted scan summaries
GET /api/skills/security/scans/:id Get one persisted JSON report
POST /api/maintenance/skill-security/scan Maintenance action alias for scan execution

The scanner emits JSON plus a Markdown report when persist is not false. Persisted artifacts are written under .veritas-kanban/skill-security-scans/.

Detector families:

Scan Request

POST /api/skills/security/scan

Body:

{
  "path": "/Users/example/.codex/skills/review-helper",
  "persist": true,
  "includeReferencedFiles": true
}

path can point at a directory containing SKILL.md or at a single SKILL.md. Single-file scans include referenced scripts/ and assets/ files by default.

Scan Response

{
  "id": "skillscan_1770000000000_ab12cd34",
  "targetType": "skill-directory",
  "skillName": "Review Helper",
  "severity": "critical",
  "riskScore": 90,
  "recommendation": "do-not-install",
  "findingCount": 3,
  "files": [{ "path": "SKILL.md", "role": "skill", "bytes": 420, "truncated": false }],
  "findings": [
    {
      "patternId": "credential.env-harvest",
      "severity": "critical",
      "category": "credential-access",
      "evidence": [{ "file": "SKILL.md", "line": 12, "excerpt": "[REDACTED_API_KEY]" }]
    }
  ],
  "persistedJsonPath": ".../skill-security-scans/skillscan_1770000000000_ab12cd34.json",
  "persistedMarkdownPath": ".../skill-security-scans/skillscan_1770000000000_ab12cd34.md"
}

Recommendation values are safe, caution, and do-not-install. A scan also writes an audit event with scan id, severity, risk score, recommendation, and finding count.

Risk Inventory and Install Decisions

GET /api/skills/security/inventory

Returns every shared resource with type: "skill" joined to its capability profile, latest persisted scan, open remediation task, and active exception. Each item includes scanStatus, riskScore, severity, recommendation, installDecision, declaredCapabilities, observedCapabilities, mismatches, findingCount, and highOrCriticalFindingCount.

installDecision values:

Reviewed exceptions are temporary and require an owner, reason, and future expiration:

POST /api/skills/security/inventory/shared_123/exceptions
{
  "owner": "platform",
  "reason": "Reviewed for the current release candidate.",
  "expiresAt": "2026-06-10T18:00:00.000Z"
}

Risk remediation tasks can be created directly from the inventory:

POST /api/skills/security/inventory/shared_123/remediation-task
{
  "project": "Security",
  "sprint": "v5-ga",
  "priority": "high"
}

Both actions write audit events and return the refreshed inventory item.


Doc Freshness

Track documentation freshness — monitor when docs were last reviewed and alert when they go stale.

Mounted at /api/doc-freshness.

Method Path Description
GET /api/doc-freshness List tracked documents (filters: project, type, stale)
GET /api/doc-freshness/:id Get one tracked document
POST /api/doc-freshness Track a new document
PATCH /api/doc-freshness/:id Update document metadata
DELETE /api/doc-freshness/:id Stop tracking
POST /api/doc-freshness/:id/review Mark as freshly reviewed
GET /api/doc-freshness/alerts List freshness alerts (filters: severity, acknowledged)
POST /api/doc-freshness/alerts/:id/acknowledge Acknowledge an alert
GET /api/doc-freshness/summary Freshness health summary

Track a Document

POST /api/doc-freshness

Body:

{
  "path": "docs/API-REFERENCE.md",
  "type": "api-reference",
  "project": "veritas-kanban",
  "maxAgeDays": 30
}

Mark as Reviewed

POST /api/doc-freshness/:id/review

Body (optional):

{
  "reviewer": "brad",
  "reviewedAt": "2026-03-08T10:00:00Z"
}

Get Freshness Summary

GET /api/doc-freshness/summary

Response 200:

{
  "total": 15,
  "fresh": 12,
  "stale": 2,
  "critical": 1,
  "alertCount": 3
}

Cost Prediction

Predict token costs for tasks before execution — uses historical telemetry data to estimate.

Mounted at /api/cost-prediction.

Method Path Description
POST /api/cost-prediction/predict Predict cost for a task
GET /api/cost-prediction/accuracy Prediction accuracy for completed tasks
GET /api/cost-prediction/accuracy/stats Aggregate accuracy statistics

Predict Cost

POST /api/cost-prediction/predict

By task ID:

{
  "taskId": "TASK-001"
}

By metadata:

{
  "type": "feature",
  "priority": "high",
  "project": "rubicon",
  "description": "Implement OAuth2 flow",
  "subtaskCount": 5
}

Response 200:

{
  "estimatedTokens": 45000,
  "estimatedCost": 0.85,
  "estimatedDurationMs": 120000,
  "confidence": 0.78,
  "basedOn": 12
}

Get Accuracy Stats

GET /api/cost-prediction/accuracy/stats

Returns aggregate statistics on prediction accuracy across all completed tasks.


Error Learning

Structured failure analysis — submit errors, record root causes, and search for similar past errors to avoid repeating mistakes.

Mounted at /api/errors.

Method Path Description
POST /api/errors/submit Submit an error for analysis
GET /api/errors List analyses (filters: taskId, errorType, severity, agent, limit)
GET /api/errors/:id Get specific analysis
PATCH /api/errors/:id Update with root cause & fix
GET /api/errors/stats Aggregate error pattern stats
GET /api/errors/search Search similar past errors (?q=<query>)

Submit Error

POST /api/errors/submit

Body:

{
  "taskId": "TASK-001",
  "agent": "codex-1",
  "errorMessage": "ECONNREFUSED 127.0.0.1:5432",
  "errorType": "resource",
  "rawDetails": "Full stack trace...",
  "attemptDescription": "Trying to connect to PostgreSQL"
}

Valid error types: runtime, api, validation, timeout, permission, resource, model, git, build, test, configuration, unknown.

Response 201: The created error analysis object.

Update Analysis

PATCH /api/errors/:id

Body (partial):

{
  "rootCause": "PostgreSQL service not running",
  "severity": "medium",
  "chosenFix": "Add health check before DB operations",
  "preventionSteps": ["Add connection retry logic", "Check service status on startup"],
  "tags": ["database", "connectivity"]
}

Search Similar Errors

GET /api/errors/search?q=ECONNREFUSED&limit=5

Returns past errors similar to the query string — useful for avoiding repeated mistakes.


Reflection-to-Memory Promotion

Reviewed queue for turning corrections, repeated mistakes, and failure lessons into durable Veritas records.

Mounted at /api/reflections. Requires workflow:read for reads and workflow:write for writes.

Method Path Description
GET /api/reflections List candidates with filters for status, category, source kind, task ID
POST /api/reflections Create a reflection candidate from a linked source
GET /api/reflections/consolidation/proposals List durable proposals, optionally for one memory domain
POST /api/reflections/consolidation/proposals Create an idempotent proposal from explicit candidate IDs
GET /api/reflections/consolidation/proposals/:id Inspect one proposal and its reviewer-facing diff
POST /api/reflections/:id/accept Accept a pending candidate and apply its reviewed promotion
POST /api/reflections/:id/reject Reject a pending candidate without affecting future context
POST /api/reflections/:id/merge Soft-merge a duplicate into its representative candidate
DELETE /api/reflections/:id Soft-delete a candidate while preserving audit history

Candidates support session, agent, team, policy, and template categories. Sources can link to task-run, chat-message, error, user-correction, review-feedback, or task-observation.

Create Candidate

POST /api/reflections

Body:

{
  "category": "team",
  "promotionTarget": "task-lesson",
  "confidence": 0.86,
  "source": {
    "kind": "user-correction",
    "taskId": "task_20260626_reflect",
    "messageId": "msg_123",
    "eventIds": ["run_event_456"]
  },
  "summary": "The agent guessed a config field instead of reading the schema.",
  "previousApproach": "Used a remembered field name.",
  "correction": "Read the live schema and nearby route tests first.",
  "nextAttempt": "Inspect the current schema before changing config behavior.",
  "proposedScope": "workspace",
  "rationale": "The correction applies to schema-backed configuration work.",
  "applicability": "Use for future configuration changes in this workspace.",
  "contradictionIds": ["reflection_older_guess"],
  "evidence": [
    {
      "kind": "note",
      "title": "Correction",
      "content": "Reviewer corrected the route field during active work."
    }
  ],
  "tags": ["schema", "workflow"]
}

Response 201: Created pending candidate. idempotencyKey can be supplied by automated extractors to make candidate creation retry-safe. Tokens, credential-looking values, and /Users/... private paths are redacted before storage. Extracted candidates remain pending and cannot affect task lessons or agent context until accepted.

Create a Consolidation Proposal

POST /api/reflections/consolidation/proposals
{
  "domain": {
    "kind": "workspace-memory",
    "id": "primary",
    "workspaceId": "workspace_1"
  },
  "candidateIds": ["reflection_1", "reflection_2"],
  "policy": {
    "staleAfterDays": 180,
    "unusedAfterDays": 90,
    "minimumConfidence": 0.5,
    "maxCandidates": 500
  }
}

The server locks proposal computation per memory domain, rejects unknown or over-limit candidate sets, and persists an idempotent reflection-consolidation-proposal/v1 record. The proposal includes the effective policy, source digest, stable duplicate clusters, and inspectable merge, contradiction, decay, and wider-promotion review operations. Creating a proposal never deletes, accepts, or promotes a candidate.

Accept Candidate

POST /api/reflections/:id/accept

Body:

{
  "reviewedBy": "brad",
  "promotionTarget": "task-lesson",
  "reviewerNote": "Reusable correction for future agent work."
}

For task-lesson, the candidate must have a linked source.taskId; acceptance appends a reviewed reflection lesson to that task and adds reflection lesson tags.

Wider targets fail closed unless the request supplies a matching typed promotion object. The authenticated actor is the reviewer when reviewedBy is omitted. For example, a profile capability promotion is:

{
  "promotionTarget": "profile",
  "promotion": {
    "target": "profile",
    "profileId": "backend-reviewer",
    "capabilitiesToAdd": ["schema-review"]
  },
  "reviewerNote": "Approved for this profile."
}

Typed inputs are available for:

Each adapter writes through the existing target service, records the applied target on the reflection audit event, and is retry-safe for its target. A missing adapter, mismatched target, unknown target record, or incomplete typed payload rejects the acceptance without marking the candidate accepted.

Reject or Merge

POST /api/reflections/:id/reject
POST /api/reflections/:id/merge
DELETE /api/reflections/:id

Rejected, merged, and deleted candidates remain in the audit trail and do not affect future prompts or policies.


External Tracker Introspection

Configurable external work item schema introspection and mapping lives under /api/integrations/trackers. Reads require settings:read; writes require settings:write through the parent integrations permission guard. External creates also require an explicit approvedBy field in the request body.

Method Path Description
GET /api/integrations/trackers/connection Return redacted connection posture
PUT /api/integrations/trackers/connection Save redacted connection metadata; credential values omitted
GET /api/integrations/trackers/schema Return the latest normalized tracker schema
POST /api/integrations/trackers/introspect Run adapter introspection and refresh the schema
GET /api/integrations/trackers/profiles List mapping profiles
PUT /api/integrations/trackers/profiles/:profileId Save a mapping profile after schema validation
POST /api/integrations/trackers/profiles/:profileId/validate Validate a saved profile
POST /api/integrations/trackers/profiles/:profileId/dry-run-create Build and validate a create payload without an external write
POST /api/integrations/trackers/profiles/:profileId/create Create a work item after explicit approval
GET /api/integrations/trackers/audits List metadata-only sync audit events

Introspect Schema

POST /api/integrations/trackers/introspect

Body:

{
  "provider": "mock",
  "project": "Veritas Kanban"
}

Response 200: ExternalTrackerSchema with work item types, fields, planning paths, priorities, states, tags, assignees, capabilities, and redacted connection posture.

Save Mapping Profile

PUT /api/integrations/trackers/profiles/default-mock-profile

Body:

{
  "id": "default-mock-profile",
  "name": "Default Mock Tracker Mapping",
  "provider": "mock",
  "enabled": true,
  "defaultWorkItemType": "Task",
  "defaultProjectPath": "Veritas Kanban",
  "defaultAreaPath": "Veritas Kanban\\Platform",
  "defaultIterationPath": "Veritas Kanban\\Next",
  "fieldMappings": [
    { "trackerFieldId": "System.Title", "source": "title", "required": true },
    { "trackerFieldId": "System.Description", "source": "description" },
    { "trackerFieldId": "Microsoft.VSTS.Common.Priority", "source": "priority" },
    { "trackerFieldId": "System.State", "source": "status" },
    { "trackerFieldId": "System.Tags", "source": "literal", "literalValue": "veritas" }
  ],
  "backlinkFieldId": "Custom.VeritasBacklink"
}

Invalid work item types, planning paths, tracker field ids, and required-field gaps return 400 VALIDATION_ERROR.

Dry-run Create

POST /api/integrations/trackers/profiles/default-mock-profile/dry-run-create

Body:

{
  "taskId": "task_20260626_tracker"
}

Response 200: ExternalTrackerDryRunCreateResult with externalWrite: false, the mapped payload, and validation errors/warnings.

Approved Create

POST /api/integrations/trackers/profiles/default-mock-profile/create

Body:

{
  "taskId": "task_20260626_tracker",
  "approvedBy": "brad"
}

Successful creates return 201, append an externalWorkItems backlink to the task, and write metadata-only audit/activity events. Credential values and private payload content are not logged.


QMD-ready retrieval across task markdown and docs. The endpoint uses the configured backend and gracefully falls back to keyword search when QMD is unavailable.

Mounted at /api/search.

Method Path Description
POST /api/search Search task and docs collections with one query
POST /api/search/index/refresh Refresh QMD collections and embeddings

Search Collections

POST /api/search

Body:

{
  "query": "semantic search duplicate detection",
  "limit": 10,
  "collections": ["tasks-active", "tasks-archive", "docs"],
  "backend": "auto"
}

backend may be keyword, qmd, or auto. QMD is opt-in via VERITAS_SEARCH_BACKEND=qmd or per-request backend: "qmd".

Response 200:

{
  "query": "semantic search duplicate detection",
  "backend": "keyword",
  "degraded": false,
  "elapsedMs": 12,
  "results": [
    {
      "id": "tasks/active/task_20260504_example.md",
      "title": "Add semantic search",
      "path": "tasks/active/task_20260504_example.md",
      "collection": "tasks-active",
      "snippet": "Wire QMD retrieval into Veritas.",
      "score": 4
    }
  ]
}

QMD Setup

npm install -g @tobilu/qmd
pnpm qmd:setup
VERITAS_SEARCH_BACKEND=qmd pnpm dev

QMD Index Refresh

POST /api/search/index/refresh

Body:

{
  "embed": true
}

Set embed to false to run only qmd update.

Response 200:

{
  "backend": "qmd",
  "updated": true,
  "embedded": true,
  "elapsedMs": 982,
  "commands": ["update", "embed"]
}

Run-scoped Tool Control Plane

The canonical endpoints are mounted at /api/v1/tool-servers; /api/tool-servers is the compatibility alias. Definitions and discovery require settings or administrator authority. Run catalogs require agent read authority, and mediated calls require agent write authority.

Method Path Description
GET /api/v1/tool-servers List definitions
POST /api/v1/tool-servers Create a validated definition
GET /api/v1/tool-servers/:id Read a definition
PUT /api/v1/tool-servers/:id Replace a definition
DELETE /api/v1/tool-servers/:id Delete a definition
POST /api/v1/tool-servers/:id/discover Discover tools, optionally bypassing cache
GET /api/v1/tool-servers/runs/:taskId/:attemptId/catalog Read the immutable catalog for an attempt
POST /api/v1/tool-servers/call Invoke one cataloged tool through Veritas policy

Create and update bodies use tool-server-definition/v1 input fields:

{
  "id": "project-tools",
  "version": "1.4.0",
  "displayName": "Project tools",
  "enabled": true,
  "transport": {
    "kind": "stdio",
    "command": "/opt/project-tools",
    "args": ["serve"],
    "environmentKeys": [],
    "credentialReferences": []
  },
  "requirement": "required",
  "startupTimeoutMs": 10000,
  "toolTimeoutMs": 30000,
  "allowedTools": ["read", "search"],
  "deniedTools": [],
  "approvalRequiredTools": ["search"],
  "approvalMode": "never"
}

Discovery is cached by the exact definition digest, which includes server identity and version. A changed definition invalidates that cache. Every launch still checks startup, protocol, and the runtime-reported version before reusing cached schemas. Required discovery failures block launch; optional failures appear as degraded catalog entries. The launch manifest stores the resulting catalog digest before provider dispatch.

POST /api/v1/tool-servers/call requires the exact active task and attempt, the same catalog digest stored in launch evidence, a stable operationId, and arguments that match the discovered JSON Schema. Denied tools fail before dispatch. Approval-required tools create or reuse an exact-action run-approval/v1 request and return its identity until approved. Successful and failed dispatches append bounded, redacted causal events, and an operation ID cannot dispatch twice.

Raw environment and credential values are never stored in a definition, catalog, event, or launch manifest. Credential-bound definitions compile only when each reference resolves to an enabled MCP-scoped broker definition and an exact environment or HTTP-header target. The catalog persists definition and scope digests plus safe target names. Discovery removes source values, and native provider configuration omits credential-bound entries.

New launch manifests classify a task reference as a brokered tool-control-plane boundary only when the exact run catalog covers it. Uncovered references remain blocked. Credential-bound calls derive the exact MCP action and receive the active launch-manifest digest from the server, not the request body. Values are resolved only inside a one-shot downstream dispatch callback; replays, stale evidence, approval mismatch, unavailable sources, and credential-bearing results fail closed.

Credential-bound provider runs receive a system-owned veritas-run stdio MCP bridge. Its opaque handle is accepted only by GET /api/run-tool-bridge/catalog and POST /api/run-tool-bridge/call. Those dedicated routes derive task, attempt, catalog, and manifest identity from the handle; callers cannot supply or override them. The handle grants no general Veritas API authority and is rejected after terminal lifecycle cleanup, expiration, or server restart. See Tool Control Plane v1.


Tool Policies

Role-based tool access restrictions — control which tools each agent role can use.

Mounted at /api/tool-policies.

Method Path Description
GET /api/tool-policies List all policies
GET /api/tool-policies/:role Get policy for a role
POST /api/tool-policies Create a new policy
PUT /api/tool-policies/:role Update an existing policy
DELETE /api/tool-policies/:role Delete a custom policy
POST /api/tool-policies/:role/validate Check if a tool is allowed for a role

Create Policy

POST /api/tool-policies

Body:

{
  "role": "intern",
  "allowed": ["read", "search", "analyze"],
  "denied": ["deploy", "delete", "admin"],
  "description": "Restricted access for intern agents"
}

Validate Tool Access

POST /api/tool-policies/:role/validate

Body:

{
  "tool": "deploy"
}

Response 200:

{
  "role": "intern",
  "tool": "deploy",
  "allowed": false,
  "traceId": "govtrace_1760000000000_ab12cd"
}

Watcher Continuation Policies

Deterministic guardrail endpoint for agent runners that want to continue a run. The server decides before execution whether the continuation is allowed, needs approval, or is blocked by the global kill switch, dispatch filters, risk classes, continuation caps, or spend caps. Decisions are written to the hash-chained audit log without storing prompt or command payloads.

Mounted at /api/watcher-policies.

Method Path Description
GET /api/watcher-policies Return current watcher continuation settings
POST /api/watcher-policies/evaluate Evaluate one proposed continuation before launch

Evaluate Continuation

POST /api/watcher-policies/evaluate

Body:

{
  "runId": "run_123",
  "taskId": "task_456",
  "project": "core",
  "agent": "codex",
  "prompt": "Continue with the next test fix.",
  "continuationCount": 1,
  "monthlySpendUsd": 1.25,
  "hasRecentTestFailures": false,
  "recentProviderErrors": 0
}

Response 200:

{
  "decision": "allow",
  "mode": "auto",
  "riskLevel": "low",
  "riskClasses": [],
  "reasons": ["Continuation is within policy, dispatch, and cap limits."],
  "evidence": [],
  "caps": {
    "maxContinuations": 3,
    "spendCapUsd": 5
  },
  "auditLogged": true,
  "evaluatedAt": "2026-06-04T22:00:00.000Z"
}

Settings live under features.watcherContinuations and are updated through PATCH /api/settings/features. Defaults preserve current behavior: continuations are disabled and the global kill switch is active until explicitly configured.


Execution Admission

Direct starts, scheduled work, watcher continuations, conversation resume and follow-up, retries, fallbacks, provider handoffs, recovery, child-agent starts, workflow roots, and provider-backed workflow steps all pass through the same durable admission and queue contract. Each atomically reserves configured run, process, and estimated-memory capacity before attempt persistence or provider dispatch. Workflow roots use the workflow-control admission provider; child steps use the resolved execution provider and selected host. Inspection requires agent:read.

Method Path Description
GET /api/admission List reservations with optional scope and state filters
GET /api/admission/queue List the bounded, redacted admission queue view
GET /api/admission/queue/:id Inspect one redacted queue entry
POST /api/admission/queue/:id/cancel Cancel one queued launch before provider dispatch
GET /api/admission/tree/:rootObjectiveId Summarize one aggregate execution tree and its control state
POST /api/admission/tree/:rootObjectiveId/cancel Cancel queued and verified running work for one execution tree
POST /api/admission/tree/:rootObjectiveId/resume Safely resume a circuit-breaker-paused execution tree
GET /api/admission/:id Inspect one durable reservation

List filters are workspaceId, taskId, rootTaskId, provider, hostId, workflowRunId, workflowStepId, rootReservationId, rootObjectiveId, nodeId, parentNodeId, repeatable state (active, released, expired), and limit (1-1000).

Queue-list filters are workspaceId, rootObjectiveId, nodeId, repeatable source, repeatable state, raw numeric priority, repeatable limitingScope, minAgeMs, maxAgeMs, page, and limit (1-200).

GET /api/v1/admission/queue?state=queued&source=workflow&minAgeMs=60000&page=1&limit=25

Queue list responses use admission-queue-list/v1; individual entries and get responses use admission-queue-inspection/v1. Every response carries a generation timestamp and conditional: true. Entries report current scheduler position, raw and effective priority, age promotion, readiness, lease posture, redacted limiting policies, conditional start factors, and persisted selection evidence. Global and per-workspace depth are reported against configured queue bounds. Workspace depth includes both the operator workspace identifier and its stable redacted key. Safe navigation fields expose the workspace, task, attempt, workflow run, workflow step, root objective, and execution-tree node identifiers that exist for the entry. Host and root-task identities remain hashed.

Pagination is deterministic for the bounded snapshot. snapshotTruncated signals that older terminal history fell outside the 1,000-entry inspection window. Position and readiness can change after generation because arrivals, capacity, policy, backoff, and lease state can change. Responses never promise an exact start time and never contain raw host or root-task identities, full launch payloads, idempotency material, prompts, messages, tool arguments, or credentials.

GET /api/v1/admission?state=active&provider=codex-cli&limit=25
GET /api/v1/admission?workflowRunId=run_20260725_abc123&state=active
GET /api/v1/admission/tree/objective_0123456789abcdef?limit=100

The response contains admission-reservation/v1 records with the versioned request, requested capacity, evaluated limit policies, attempt binding, lease, revision, and terminal release evidence. It never contains provider credentials or prompts. Workflow records identify the run, optional step, and root reservation. Overloaded starts return a 409 conflict with details.code set to ADMISSION_OVERLOAD, the limiting scope, and bounded retryAfterMs. A request larger than a configured ceiling returns ADMISSION_POLICY_DENIED. A queueable workflow-root or provider-step overload instead persists the run or step with admission state waiting and status pending, without creating a partially active provider attempt. Terminal policy denial fails the run and releases its root.

Execution-tree reservations include execution-tree-identity/v1 and durable requested, remaining, committed, released-unused, and idempotent usage-event state. The tree summary aggregates each node once and returns committed and active reserved usage, per-policy remaining limits, the policy that blocks the next launch, bounded contributors, truncation evidence, and any durable execution-tree-control/v1 cancellation or circuit-breaker state. Committed usage is retained after release; only unused reservation returns to the tree.

Cancellation requests require admin:manage and a strict JSON body:

{
  "idempotencyKey": "operator-request-20260725-001",
  "reason": "Operator stopped runaway child-agent expansion."
}

POST /api/v1/admission/queue/:id/cancel terminalizes a queued or leased launch before dispatch and releases its reservation. A dispatched entry must be controlled through its verified run instead. POST /api/v1/admission/tree/:rootObjectiveId/cancel first records cancellation on the root reservation, then drains queued descendants, releases unbound reservations, and interrupts verified running attempts owned by the local agent supervisor. The response lists any verified running attempts that could not be interrupted so operators can reconcile them explicitly. Reusing the same idempotency key is safe; a different key conflicts with existing terminal ownership. Veritas stores and returns only the key’s SHA-256 identity.

The fan-out circuit breaker is enabled by default under features.admission.fanOutBreaker. It evaluates durable descendant count, depth, active reservations, queued descendants, capacity pressure, and budget pressure before a new tree node is admitted. Defaults are 256 descendants, depth 16, 64 active reservations, 64 queued descendants, and 95 percent capacity or budget pressure after a tree reaches eight descendants. When a threshold is reached, Veritas atomically records a durable paused control on the root and returns retryable EXECUTION_TREE_EXPANSION_PAUSED evidence. Harnesses must stop expanding that root; retrying the same launch cannot bypass the control.

Operators can inspect the bounded evidence through the tree summary or Operations, clear the reported pressure, and resume with the same strict body:

POST /api/v1/admission/tree/:rootObjectiveId/resume

Resume returns 409 Conflict with details.code: "EXECUTION_TREE_RESUME_BLOCKED" while a breaker signal remains unsafe. A successful resume records a durable resumed control and accepts new nodes without discarding the earlier breaker evidence. Cancellation remains terminal: once the root cancellation is recorded, late resume, retry, fallback, workflow-step, and child-agent launches fail closed before provider dispatch. Paused, resumed, and cancelled controls survive restart and are visible through the tree summary, Operations, support bundles, telemetry, and vk admission tree.

Direct POST /api/v1/agents/:taskId/start callers may send an opaque X-Idempotency-Key header (8-240 characters). The header takes precedence over the equivalent JSON idempotencyKey field and lets a retry reuse the same reservation without launching a second attempt. Veritas persists only its SHA-256 identity, never the supplied value.

When capacity is temporarily exhausted, every provider-neutral agent launch returns 201 with status: "queued" instead of creating a provider-specific queue or a partial attempt:

{
  "taskId": "task_0123456789abcdef",
  "attemptId": "attempt_01234567",
  "queueId": "admission_queue_0123456789abcdef",
  "agent": "codex",
  "status": "queued",
  "enqueuedAt": "2026-07-25T12:00:00.000Z",
  "retryAfterMs": 5000,
  "limitingScopes": [{ "scope": "global", "scopeId": "global" }]
}

The response means Veritas durably accepted ownership. Clients and harnesses must not create a second launch. The versioned agent-launch target preserves the bounded provider-neutral options required to reconstruct profiles, readiness overrides, sandbox and budget overrides, runtime capability requirements, commit policy, phase, conversation resume/follow-up/fork, provider handoff, child-agent, retry, and fallback launches. It never stores credentials, process handles, leases, or raw idempotency keys. Queue inspection, telemetry, Operations, and support bundles expose only redacted launch-source and admission evidence; prompts, messages, override text, tool arguments, and the full durable target remain internal. Queue inspection is available through GET /api/v1/admission/queue and GET /api/v1/admission/queue/:id; reservation inspection remains unchanged.

Scheduled workflows retain source: "scheduled" from the root reservation through normal provider steps. Queue-monitor continuations retain source: "watcher". Retry and fallback steps use their specific replacement source while preserving the same root objective and causal parent edge. Queue bounds and retry behavior are configured under features.admission.queue.

Workflow roots, provider-backed steps, and all agent launches use the same durable queue internally. Workflow queue targets preserve the workflow version, run revision, task and step identity, retry or fallback sequence, execution-tree edge, selected provider and host, and immutable runtime and phase digests. They never copy workflow context, prompts, tool arguments, or credentials. Agent-launch targets may retain the bounded operator input needed for exact replay, but public diagnostics always omit it. Before dispatch, Veritas revalidates provider, host, phase authority, budgets, root objective, parent edge, and reservation evidence. Drift terminalizes the queue entry without calling the provider.

A single versioned scheduler orders agent-launch, legacy direct, workflow-root, and workflow-step entries using explicit task priority, bounded age promotion, workspace turns, and stable enqueue identity. Capacity-blocked entries remain queued while the next compatible entry is evaluated. The selected entry persists redacted priority, fairness, readiness, limiting-scope, and conditional-start evidence; provider or host metadata cannot override this ordering. Scheduler bounds live under features.admission.queue.scheduler.

A pre-dispatch failure releases capacity and may requeue with bounded backoff. Once the queue entry is durably dispatched, workflow recovery owns the run. Startup reconciliation resumes a root or step that stopped at that ownership boundary exactly once; already-running provider work continues to use the existing operator-reconciliation rules.

Durable Goals

Durable goals persist an evidence-gated objective independently of any one provider run. Reads require agent:read; creation and mutation require admin:manage. Every endpoint is scoped to the authenticated workspace.

Method Path Description
GET /api/goals List bounded goals with state and root filters
POST /api/goals Create one evidence-gated goal at revision 1
GET /api/goals/:goalId Inspect one goal and its continuation chain
POST /api/goals/:goalId/transition Apply one compare-and-set state transition
POST /api/goals/:goalId/runs Link one task, workflow, or conversation run
POST /api/goals/:goalId/rollover Approve and dispatch one bounded fresh-conversation handoff

List filters are repeatable state, rootTaskId, rootWorkflowId, and limit (1-1000). Goal states are active, paused, blocked, awaiting-approval, usage-limited, budget-limited, complete, cancelled, and failed. Complete, cancelled, and failed goals are terminal.

Creation requires an objective, at least one acceptance criterion, exactly one task or workflow root, a manual or automatic continuation policy, and at least one required completion-evidence contract. Optional constraints and aggregate token, cost, time, tool, retry, and fan-out limits are bounded by the same budget metric names used elsewhere in the runtime.

{
  "objective": "Deliver the provider migration.",
  "constraints": ["Do not broaden provider authority."],
  "acceptanceCriteria": ["Focused provider fixtures pass."],
  "root": { "kind": "task", "taskId": "task_0123456789abcdef" },
  "continuation": {
    "mode": "automatic",
    "maxTurns": 20,
    "maxRollovers": 2,
    "compactAfterTokens": 120000,
    "requireApprovalForRollover": true
  },
  "completionRequirements": [
    {
      "id": "provider-tests",
      "description": "Focused provider fixtures pass.",
      "required": true,
      "verificationKind": "test"
    }
  ]
}

Transitions require expectedRevision, state, and an operator reason. Entering blocked also requires an actionable blocker. Completing a goal requires evidence for every required completion item. The server derives the workspace, actor, verification timestamp, and evidence verifier from authenticated context; these identities cannot be supplied by the caller. Stale revisions return 409 Conflict, terminal states cannot reopen, and cross-workspace lookups return 404.

The durable-goal/v1 record also exposes usageEvents and continuationAttempts. Each completed provider run contributes one idempotency-keyed usage event, verified evidence, and run link. Aggregate goal usage is the sum of those deduplicated events.

For an active automatic goal, the supervisor evaluates required evidence, blockers, turn limits, and aggregate budgets after the provider completion is durable. It persists a planned continuation with a stable admission idempotency key before calling the normal provider follow-up path. That path still enforces runtime capabilities, admission limits, sandbox and phase authority, approvals, immutable launch manifests, and the goal’s remaining budget. The plan becomes dispatched only after an attempt or queue identity exists.

Startup reconciliation closes the crash window in either direction. If the child attempt already exists, the planned continuation is linked without launching another provider. Otherwise the same admission idempotency key is reused. Failed admission blocks the goal with an actionable reason. Provider blockers, missing continuation handles, turn limits, budget limits, and manual continuation mode all fail closed instead of creating a hidden retry loop.

When compactAfterTokens is reached, or a provider has no verified resume handle, the supervisor may plan kind: "rollover" only while maxRollovers has capacity. A rollover starts a fresh provider conversation with a bounded handoff containing the objective, constraints, acceptance criteria, evidence identifiers, unresolved requirements, recent state decisions, prior attempt identities, and cumulative usage. It retains the normal parent-attempt, admission, sandbox, phase, launch-manifest, and remaining-budget controls without copying transient provider authority.

If requireApprovalForRollover is true, the supervisor first transitions the goal to awaiting-approval. POST /api/goals/:goalId/rollover accepts expectedRevision; the authenticated actor becomes the approver and the goal’s exact current run becomes the source. The endpoint rejects stale revisions, missing current-run evidence, non-active states, and exhausted rollover limits. Its planned handoff and admission idempotency key are durable, so startup reconciliation cannot duplicate a fresh conversation.

Knowledge Collections

Knowledge collections establish a workspace-scoped, immutable source catalog for derived project knowledge. Route access requires work_product:read for reads and work_product:write for writes. Collection policies then apply the authenticated actor’s admin, agent, or read-only role and source-classification ceiling.

Method Path Description
GET /api/knowledge/collections List collections readable by actor
POST /api/knowledge/collections Create one versioned collection
GET /api/knowledge/collections/:collectionId Read collection metadata
GET /api/knowledge/collections/:collectionId/sources List immutable source revisions
POST /api/knowledge/collections/:collectionId/sources Register one source revision
GET /api/knowledge/collections/:collectionId/sources/:sourceId Read source metadata
GET /api/knowledge/collections/:collectionId/pages List derived pages
GET /api/knowledge/collections/:collectionId/pages/:pageId Read one cited derived page
POST /api/knowledge/collections/:collectionId/pages/:pageId/claims/:claimId/transitions Transition one material claim
POST /api/knowledge/collections/:collectionId/integrity/lint Run deterministic integrity lint
GET /api/knowledge/collections/:collectionId/integrity/findings List durable integrity findings
POST /api/knowledge/collections/:collectionId/integrity/findings/:findingId/transitions Transition a finding
GET /api/knowledge/collections/:collectionId/integrity/health Read integrity operational health
POST /api/knowledge/collections/:collectionId/search Search raw and derived knowledge
POST /api/knowledge/collections/:collectionId/search/promotions Promote selected search results
POST /api/knowledge/collections/:collectionId/exports Create a cited work product
GET /api/knowledge/collections/:collectionId/ingestion/proposals List ingestion dry runs
POST /api/knowledge/collections/:collectionId/ingestion/proposals Create an ingestion dry run
GET /api/knowledge/collections/:collectionId/ingestion/proposals/:proposalId Read one dry run
POST /api/knowledge/collections/:collectionId/ingestion/proposals/:proposalId/apply Apply atomically
POST /api/knowledge/collections/:collectionId/ingestion/proposals/:proposalId/reverse Reverse atomically
GET /api/knowledge/collections/:collectionId/activity List ingestion activity

Collection, source, page, proposal, and activity lists accept page (default 1) and limit (default 100, maximum 500). Pagination metadata is returned through the standard API response envelope.

Create operations are idempotent. The operation identity is persisted only as a digest and cannot be reused with changed input.

{
  "operationId": "create-product-knowledge",
  "slug": "product-knowledge",
  "name": "Product knowledge",
  "definition": {
    "schemaVersion": "knowledge-collection-definition/v1",
    "version": 1,
    "pageKinds": ["concept", "decision", "overview"],
    "requiredMetadata": ["owner", "reviewState"],
    "naming": "stable-id",
    "links": "bidirectional",
    "ingestion": "review-required",
    "maxPageVersions": 25
  },
  "accessPolicy": {
    "readRoles": ["admin", "agent"],
    "writeRoles": ["admin", "agent"],
    "maxSourceClassification": "confidential",
    "exportPolicy": "redacted-only"
  }
}

Register an inline snapshot by sending storage: "content-addressed-blob" and bounded UTF-8 content. Veritas computes its SHA-256 digest and byte count. Register an externally retained source with storage: "content-addressed-reference", contentHash, and contentBytes; Veritas records the immutable identity without copying content.

{
  "operationId": "readme-revision-1",
  "sourceKey": "product-readme",
  "uri": "repo://README.md",
  "mediaType": "text/markdown",
  "owner": "product",
  "classification": "internal",
  "storage": "content-addressed-blob",
  "content": "# Product"
}

Repeated source keys create a revision chain rather than replacing prior evidence. Source list and detail responses return metadata only and do not expose stored source content.

Derived pages include Markdown, typed metadata, review state, confidence, stable keys and aliases, outgoing links, computed backlinks, bounded revision history, and claim-level citations to immutable source revision IDs. Page list and detail routes are read-only; reviewed ingestion proposals own mutation so generated synthesis cannot bypass the atomic review workflow.

Create a dry run with selected immutable source revision IDs and one or more page candidates. The response includes complete before and after pages, page and index changes, contradictions, expected digests, and the activity change that apply will append. Creating a proposal does not mutate pages.

{
  "operationId": "ingest-readme-2026-07-26",
  "sourceIds": ["knowledge_source_0123456789abcdef"],
  "pages": [
    {
      "stableKey": "architecture",
      "title": "Architecture",
      "pageKind": "concept",
      "aliases": ["system-design"],
      "tags": ["product"],
      "metadata": {
        "owner": "architecture",
        "reviewState": "pending"
      },
      "markdown": "# Architecture\n\nThe workspace uses a reviewed control plane.",
      "claims": [
        {
          "claimKey": "reviewed-control-plane",
          "text": "The workspace uses a reviewed control plane.",
          "citations": [
            {
              "sourceId": "knowledge_source_0123456789abcdef",
              "locator": {
                "kind": "line-range",
                "startLine": 10,
                "endLine": 14
              }
            }
          ],
          "confidence": 0.92
        }
      ],
      "links": [],
      "reviewState": "review-required",
      "confidence": 0.9
    }
  ]
}

Only administrators can apply or reverse. Send the exact current proposal digest:

{
  "proposalDigest": "sha256:..."
}

Apply atomically updates every affected page, proposal state, and append-only activity entry. Reverse requires the applied digest, restores complete prior page records, removes proposal-created pages, and appends reversal activity. Exact retries return the committed state. Blocking contradictions, stale page digests, later conflicting edits, and unsafe graph changes return 409 without partial mutation.

Run deterministic integrity lint with an optional fixed timestamp, freshness rules, and research-candidate discovery:

{
  "asOf": "2026-07-26T00:00:00.000Z",
  "freshnessRules": [
    { "target": "page-kind", "match": "decision", "maxAgeDays": 90 },
    {
      "target": "source-media-type",
      "match": "text/markdown",
      "maxAgeDays": 30
    }
  ],
  "includeResearchCandidates": true,
  "includeSemanticCandidates": true,
  "persistFindings": true,
  "runId": "nightly-product-knowledge-2026-07-26",
  "pageLimit": 500
}

The report counts inspected pages, sources, and claims and returns stable, severity-ranked findings for structural, provenance, freshness, and research gaps. It contains identifiers but never retained source text. Agent reports are limited to resources in the verified launch manifest.

Semantic candidates flag same-key disagreements, near-duplicates, source-revision supersession, and low-confidence evidence gaps with both page, claim, and source identities. Persisted runs require a stable runId. The response continuation returns nextPageCursor and complete; pass the cursor into the next request. Work is capped at 500 pages per call, and retrying the same run ID and cursor is idempotent. Scheduled workflows can therefore resume after interruption without duplicating observations.

Durable findings support open, acknowledged, remediating, and resolved statuses. Transition requests use expectedDigest plus an operation ID and can set an owner, acknowledgement reason, due date, and remediation task or proposal links. Acknowledgement requires a reason, remediation requires an owner, and only administrators resolve. /integrity/health exposes scoped counts, overdue work, last observation, and healthy, degraded, or critical status.

Transition a material claim by supplying compare-and-set page and lifecycle evidence:

{
  "operationId": "dispute-dispatch-claim",
  "expectedPageDigest": "sha256:...",
  "expectedState": "active",
  "to": "disputed",
  "reason": "Two retained sources support conflicting interpretations.",
  "evidenceSourceIds": ["knowledge_source_0123456789abcdef"]
}

States are active, needs-review, disputed, superseded, and retracted. Agents may flag needs-review or disputed; administrators finalize other transitions. The response is the newly versioned page. Each transition is attributed, digest-bound, retained in page history, idempotent for an exact retry, and rejected when the page digest, expected state, operation identity, or evidence access is stale.

Search one readable collection with a bounded query. scope can be all, raw-sources, or derived-pages. Results identify their kind, backend, effective classification, and source citations; derived results carry the citations from their material claims. Confidential and restricted snippets are withheld. backend: "auto" or "qmd" projects current eligible derived pages into an isolated, non-default QMD collection scoped by workspace, collection, and launch-manifest digest, and refreshes it only when page digests change. Raw source matches remain keyword-scored. If QMD is unavailable or the requested scope has no derived pages, the response uses backend: "keyword", sets degraded: true, and includes a reason.

{
  "query": "reviewed dispatch",
  "limit": 10,
  "scope": "all",
  "backend": "auto"
}

Every search response includes evidenceDigest, computed over the exact query, bounded result set, and optional launch context. To retain selected results as durable knowledge, send the unchanged response, selected result IDs, and proposed pages to /search/promotions. Veritas rejects a changed digest, another run’s launch binding, unknown selection, missing source revision, or a derived result whose current citations no longer match. A valid request creates the same dry-run ingestion proposal used by source ingestion; it does not mutate pages.

Agent calls to source, page, proposal, activity, search, promotion, and export operations must send all three headers below. The server verifies them against the persisted attempt manifest and filters every read and mutation to the exact resources in resources.shared. Administrator and read-only calls do not require these headers.

x-veritas-task-id: task_...
x-veritas-attempt-id: attempt_...
x-veritas-launch-manifest-digest: sha256:...
{
  "operationId": "promote-dispatch-query",
  "evidence": {
    "query": "reviewed dispatch",
    "backend": "keyword",
    "degraded": false,
    "results": [
      {
        "id": "knowledge_source_0123456789abcdef",
        "kind": "raw-source",
        "backend": "keyword",
        "title": "Dispatch specification",
        "snippet": "Reviewed dispatch is required.",
        "score": 0.9,
        "sourceId": "knowledge_source_0123456789abcdef",
        "citations": [{ "sourceId": "knowledge_source_0123456789abcdef" }]
      }
    ],
    "evidenceDigest": "sha256:..."
  },
  "selectedResultIds": ["knowledge_source_0123456789abcdef"],
  "pages": [
    {
      "stableKey": "dispatch-overview",
      "title": "Dispatch overview",
      "pageKind": "overview",
      "metadata": { "owner": "platform", "reviewState": "pending" },
      "markdown": "# Dispatch overview\n\nReviewed dispatch is required.",
      "claims": [
        {
          "claimKey": "reviewed-dispatch",
          "text": "Reviewed dispatch is required.",
          "citations": [{ "sourceId": "knowledge_source_0123456789abcdef" }],
          "confidence": 0.9
        }
      ],
      "reviewState": "review-required",
      "confidence": 0.9
    }
  ]
}

To create a durable work product instead, send title, the same unchanged evidence, selectedResultIds, and optional redaction (none, standard, or strict) to /exports. The Markdown body records each result’s kind, backend, score, redacted snippet, source ID, locator, and excerpt hash. Work-product source links point back to the collection objects, while metadata retains the collection ID, query, evidence digest, selected-result count, citation count, and export policy.

Collection exportPolicy is authoritative. forbidden rejects creation. redacted-only rejects redaction: "none", sets redacted export as the product default, and prevents the general work-product export endpoint from honoring ?redacted=false. allowed accepts the requested posture and defaults to standard redaction.


Traces

Distributed execution tracing — record and query traces for agent task attempts.

Mounted at /api/traces.

Method Path Description
GET /api/traces/status Check if tracing is enabled
POST /api/traces/enable Enable tracing
POST /api/traces/disable Disable tracing
GET /api/traces/:attemptId Get a trace by attempt ID
GET /api/traces/task/:taskId List all traces for a task

Check Tracing Status

GET /api/traces/status

Response 200:

{
  "enabled": true
}

Get Task Traces

GET /api/traces/task/TASK-001

Returns all execution traces associated with the given task.


Audit

Immutable, hash-chained audit log. Admin only.

Mounted at /api/audit.

Method Path Description
GET /api/audit Recent audit entries (?limit=N, default 100, max 1000)
GET /api/audit/verify Verify hash chain integrity of current month’s log

Auth: Requires admin role.

Query Audit Log

GET /api/audit?limit=50

Response 200:

{
  "entries": [
    {
      "timestamp": "2026-03-08T07:00:00Z",
      "action": "task.archive",
      "actor": "admin-key",
      "resource": "TASK-001",
      "details": { "title": "Fix auth bug" },
      "hash": "sha256:..."
    }
  ],
  "count": 50
}

Verify Integrity

GET /api/audit/verify

Verifies the hash chain of the current month’s audit log file. Returns chain validity and any broken links.


Common Workflows

Agent Task Lifecycle

# 1. Create task
TASK=$(curl -s -X POST http://localhost:3001/api/tasks \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: YOUR_KEY' \
  -d '{"title":"Fix bug","priority":"high"}' | jq -r '.id')

# 2. Start time tracking
curl -s -X POST http://localhost:3001/api/tasks/$TASK/time/start

# 3. Emit telemetry
curl -s -X POST http://localhost:3001/api/telemetry/events \
  -H 'Content-Type: application/json' \
  -d "{\"type\":\"run.started\",\"taskId\":\"$TASK\",\"agent\":\"veritas\"}"

# 4. Update status to in-progress
curl -s -X PATCH http://localhost:3001/api/tasks/$TASK \
  -H 'Content-Type: application/json' \
  -d '{"status":"in-progress"}'

# 5. Save checkpoint mid-work
curl -s -X POST http://localhost:3001/api/tasks/$TASK/checkpoint \
  -H 'Content-Type: application/json' \
  -d '{"state":{"step":3,"context":"halfway done"}}'

# 6. Add observation
curl -s -X POST http://localhost:3001/api/tasks/$TASK/observations \
  -H 'Content-Type: application/json' \
  -d '{"content":"Found root cause in auth middleware","type":"insight","agent":"veritas"}'

# 7. Complete
curl -s -X PATCH http://localhost:3001/api/tasks/$TASK \
  -H 'Content-Type: application/json' \
  -d '{"status":"done"}'

# 8. Stop timer + emit completion telemetry
curl -s -X POST http://localhost:3001/api/tasks/$TASK/time/stop
curl -s -X POST http://localhost:3001/api/telemetry/events \
  -H 'Content-Type: application/json' \
  -d "{\"type\":\"run.completed\",\"taskId\":\"$TASK\",\"agent\":\"veritas\",\"durationMs\":45000,\"success\":true}"

Agent Loop (Poll for Work)

# Get next available task
NEXT=$(curl -s http://localhost:3001/api/tasks?status=todo&limit=1 | jq -r '.tasks[0].id')
if [ "$NEXT" != "null" ]; then
  # Claim it
  curl -s -X PATCH http://localhost:3001/api/tasks/$NEXT \
    -H 'Content-Type: application/json' \
    -d '{"status":"in-progress","assignee":"agent-1"}'
fi

Blocker Tracking

# Add a blocker observation
curl -s -X POST http://localhost:3001/api/tasks/TASK-001/observations \
  -H 'Content-Type: application/json' \
  -d '{"content":"Blocked: waiting on API key from vendor","type":"blocker","agent":"veritas"}'

# Add a dependency
curl -s -X POST http://localhost:3001/api/tasks/TASK-001/dependencies \
  -H 'Content-Type: application/json' \
  -d '{"targetId":"TASK-002","type":"blocked-by"}'

Webhook Hook Setup

# Fire a webhook when any task moves to "done"
curl -s -X POST http://localhost:3001/api/hooks \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "done-notify",
    "event": "task.status.changed",
    "filter": {"newStatus":"done"},
    "action": {"type":"webhook","url":"https://example.com/hook"}
  }'

Versioning & Deprecation


Rate Limits

Tier Limit Applies To
Global 300 req/min All endpoints (localhost exempt)
Read 300 req/min GET endpoints
Write 60 req/min POST/PUT/PATCH/DELETE
Upload 20 req/min File upload endpoints

Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.


Additional Endpoint Groups

These endpoints follow the same auth/error patterns documented above:

Mount Purpose
/api/projects Project CRUD
/api/sprints Sprint management
/api/backlog Backlog operations
/api/agents Agent CRUD, routing
/api/agents/register Agent self-registration
/api/agents/permissions Agent permission management
/api/templates Task templates
/api/task-types Custom task type definitions
/api/activity Activity feed
/api/notifications User notifications
/api/broadcasts Broadcast messages
/api/changes Efficient agent polling (change feed)
/api/diff Task diff comparisons
/api/automation Automation rules
/api/summary Board summaries
/api/github GitHub integration
/api/integrations/trackers External tracker schema introspection and mapping profiles
/api/conflicts Merge conflict detection
/api/watcher-policies Agent continuation guardrail decisions
/api/metrics Prometheus-style metrics
/api/traces Distributed tracing
/api/cost-prediction Token cost forecasting
/api/errors Error pattern learning
/api/reflections Reviewed reflection-to-memory promotion
/api/reports Generated reports
/api/deliverables Scheduled deliverables
/api/doc-freshness Documentation freshness tracking
/api/docs Docs endpoint
/api/shared-resources Shared resource management
/api/status-history Task status history
/api/digest Digest generation
/api/audit Audit log
/api/lessons Lessons learned
/api/delegation Task delegation
/api/workflows Workflow engine (details)
/api/ceremonies Design-review and failure-retrospective requirements
/api/tool-servers Run-scoped MCP and tool-server definitions, catalogs, and calls
/api/tool-policies Tool access policies
/api/sandbox-policies Agent sandbox policy presets
/api/integrations External integrations, outbound delivery audit, and human reply adapters
/api/settings/transition-hooks Status transition hooks
/api/feedback User feedback & sentiment analytics
/api/decisions Decision audit trail
/api/drift Behavioral drift detection
/api/policies Agent policy & guard engine
/api/scoring/profiles Output evaluation profiles
/api/scoring/evaluate Run an output evaluation
/api/scoring/history Evaluation history
/api/prompt-registry Prompt template registry
/api/v1/system/health Global system health

v4.0 API Reference

User Feedback (/api/feedback)

Collect feedback on agent outputs and query aggregate sentiment analytics.

List Feedback

GET /api/feedback
Query params: agent, taskId, sentiment (positive neutral negative), since (ISO timestamp), limit (default 50), offset.

Response: Array of feedback objects.

[
  {
    "id": "fb_abc123",
    "content": "The summary was concise and accurate.",
    "sentiment": "positive",
    "category": "output-quality",
    "agent": "TARS",
    "taskId": "task_20260321_abc",
    "createdAt": "2026-03-21T14:00:00.000Z"
  }
]

Submit Feedback

POST /api/feedback
{
  "content": "The response missed the key point.",
  "sentiment": "negative",
  "category": "accuracy",
  "agent": "CASE",
  "taskId": "task_20260321_xyz"
}

Response: 201 with created feedback object.

Get Feedback Item

GET /api/feedback/:id

Response: Single feedback object.

Delete Feedback

DELETE /api/feedback/:id

Response: 204 No Content.

Feedback Analytics

GET /api/feedback/analytics

Query params: agent, since, until.

Response:

{
  "total": 42,
  "sentimentBreakdown": {
    "positive": 30,
    "neutral": 8,
    "negative": 4
  },
  "topCategories": [
    { "category": "output-quality", "count": 18 },
    { "category": "accuracy", "count": 12 }
  ],
  "trend": [{ "date": "2026-03-21", "positive": 5, "neutral": 1, "negative": 0 }]
}

Decision Audit Trail (/api/decisions)

Log agent decisions with assumptions and record outcomes.

List Decisions

GET /api/decisions

Query params: agent, taskId, minConfidence (0–100), maxConfidence (0–100), minRisk (0–100), maxRisk (0–100), startTime, endTime.

Response: Array of decision objects.

[
  {
    "id": "dec_abc123",
    "decision": "Use Redis for session caching",
    "confidence": 0.85,
    "reasoning": "Redis has sub-ms latency and supports TTL natively.",
    "evidence": ["benchmark results", "existing infra"],
    "assumptions": ["Redis cluster is available", "TTL of 1h is sufficient"],
    "agent": "VERITAS",
    "taskId": "task_20260321_abc",
    "createdAt": "2026-03-21T14:00:00.000Z",
    "outcome": null
  }
]

Log a Decision

POST /api/decisions
{
  "decision": "Refactor auth to use JWT instead of sessions",
  "confidence": 0.9,
  "reasoning": "Sessions require sticky routing; JWT is stateless.",
  "evidence": ["architecture review notes"],
  "assumptions": ["Clients will store tokens securely"],
  "agent": "VERITAS",
  "taskId": "task_20260321_abc"
}

Response: 201 with created decision object.

Get Decision

GET /api/decisions/:id

Update an Assumption

PATCH /api/decisions/:id/assumptions/:idx

Update a specific assumption by its zero-based index.

{
  "text": "Clients will store tokens securely (confirmed via security review)",
  "held": true
}

Response: Updated decision object with the assumption patched.

Decision Review Sessions

Task-launched review sessions capture independent participant responses, ordered critique rounds, and a final packet linked to a versioned work product plus a decision audit record.

Method Path Description
GET /api/decisions/reviews?taskId=<id> List decision review sessions
POST /api/decisions/reviews Start a review session with at least two participants
GET /api/decisions/reviews/:id Get a review session
POST /api/decisions/reviews/:id/responses Record an independent initial response
POST /api/decisions/reviews/:id/critiques Record a participant critique round
POST /api/decisions/reviews/:id/finalize Create the final packet, work product, and decision
POST /api/decisions/reviews/:id/cancel Cancel a review session
GET /api/decisions/reviews/:id/export Export the session packet as Markdown
{
  "taskId": "task_20260618_abc",
  "title": "Release readiness approach",
  "prompt": "Should we cut v5.1 after the remaining PRs merge?",
  "context": "Open issues require docs, packaging, and tap validation.",
  "rounds": 1,
  "participants": [
    { "id": "architect", "label": "Architect", "model": "gpt-5" },
    { "id": "qa-reviewer", "label": "QA Reviewer", "profileId": "qa-reviewer" }
  ]
}

Ceremony Requirements (/api/ceremonies)

Ceremony requirements are durable review records created by enforcement gates or operators. They link back to tasks, runs, workflows, pull requests, or CI runs.

Method Path Description Permissions
GET /api/ceremonies List ceremony requirements workflow:read
POST /api/ceremonies Create a ceremony requirement workflow:write
POST /api/ceremonies/:id/complete Complete a pending ceremony requirement workflow:write

List Ceremony Requirements

GET /api/ceremonies?status=pending&kind=design_review&taskId=task_123&limit=20

Query params: status, kind, taskId, limit.

Create Ceremony Requirement

{
  "kind": "design_review",
  "enforcementMode": "block",
  "reason": "Task coordinates multiple agents.",
  "target": { "taskId": "task_20260626_review" },
  "trigger": "manual",
  "requiredArtifacts": ["decision-packet", "risk-list", "action-items"]
}

Complete Ceremony Requirement

{
  "completedBy": "brad",
  "artifacts": [
    {
      "kind": "decision-packet",
      "title": "Design review notes",
      "body": "Reviewed scope, risks, rollback, and follow-up actions."
    }
  ],
  "actionItems": [
    {
      "title": "Track hardening follow-up",
      "priority": "high",
      "issueUrl": "https://github.com/BradGroux/veritas-kanban/issues/123"
    }
  ]
}

completedBy defaults to the authenticated actor when omitted. Completion keeps the record for audit and satisfies future blocking evaluations for the same task and ceremony kind.


Governance Decision Traces (/api/governance/traces)

Inspect policy, tool-policy, sandbox-policy, budget-policy, agent-permission, routing, workflow-gate, and ceremony decisions with evaluated rules, matched rules, remediation, and redacted raw detail.

List Governance Traces

GET /api/governance/traces

Query params: kind, outcome, agent, taskId, actionType, startTime, endTime, limit.

kind values: policy, tool-policy, sandbox-policy, budget-policy, agent-permission, routing, workflow-gate, ceremony.

outcome values: allowed, warned, blocked, approval-required, routed, fallback, skipped.

Response: Array of trace records.

[
  {
    "id": "govtrace_1760000000000_ab12cd",
    "kind": "policy",
    "outcome": "blocked",
    "title": "Policy evaluation: git.push",
    "summary": "Production deploy requires approval.",
    "remediation": "Request approval from a lead agent.",
    "subject": {
      "agentId": "codex",
      "taskId": "task_123",
      "actionType": "git.push"
    },
    "evaluatedRules": [
      {
        "id": "policy:prod-risk",
        "label": "Production risk gate",
        "type": "policy",
        "status": "matched",
        "outcome": "blocked",
        "message": "Risk score exceeded the blocking threshold."
      }
    ],
    "matchedRules": [],
    "steps": [],
    "redacted": true,
    "createdAt": "2026-06-01T12:00:00.000Z"
  }
]

Get Governance Trace

GET /api/governance/traces/:id

Returns one trace record including raw detail when present. All persisted trace values are redacted before write.


Behavioral Drift Detection (/api/drift)

Track agent metric baselines and detect behavioral deviations.

List Drift Alerts

GET /api/drift/alerts

Query params: agent, acknowledged (boolean), since, limit.

Response: Array of drift alert objects.

[
  {
    "id": "drift_abc123",
    "agent": "TARS",
    "metric": "task_completion_rate",
    "baseline": 0.92,
    "current": 0.71,
    "deviation": 0.21,
    "threshold": 0.1,
    "severity": "high",
    "acknowledged": false,
    "detectedAt": "2026-03-21T14:00:00.000Z"
  }
]

Acknowledge Drift Alert

POST /api/drift/alerts/:id/acknowledge
{
  "notes": "Agent was rate-limited by upstream API — not a behavior change."
}

Response: Updated alert with acknowledged: true.

List Baselines

GET /api/drift/baselines

Query params: agent, metric.

Response: Array of baseline records showing current metric norms per agent.

Reset Baselines

POST /api/drift/baselines/reset
{
  "agent": "TARS",
  "metric": "task_completion_rate"
}

Response: 200 with updated baseline record.

Run Drift Analysis

POST /api/drift/analyze
{
  "agent": "TARS"
}

Compares current metrics against baselines and creates alerts for any out-of-threshold deviations.

Response: 200 with analysis summary including number of alerts created.


Agent Policy Engine (/api/policies)

Define configurable guard rules for agent tool and action access.

List Policies

GET /api/policies

Query params: agent, project, enabled (boolean).

Response: Array of policy objects.

[
  {
    "id": "pol_abc123",
    "name": "No web access for Intern agents",
    "description": "Intern-level agents cannot use browser or fetch tools.",
    "enabled": true,
    "scope": { "agentLevel": "intern" },
    "rules": [
      {
        "tool": "browser",
        "action": "*",
        "effect": "deny"
      }
    ],
    "precedence": "deny-first",
    "createdAt": "2026-03-21T14:00:00.000Z"
  }
]

Create Policy

POST /api/policies
{
  "name": "Require approval for file deletion",
  "enabled": true,
  "scope": { "global": true },
  "rules": [
    {
      "tool": "exec",
      "action": "rm",
      "effect": "require-approval"
    }
  ],
  "precedence": "deny-first"
}

Response: 201 with created policy object.

Get Policy

GET /api/policies/:id

Update Policy

PUT /api/policies/:id

Delete Policy

DELETE /api/policies/:id

Response: 204 No Content.

Evaluate Policy

POST /api/policies/evaluate
{
  "agent": "TARS",
  "project": "core",
  "actionType": "tool.browser.navigate",
  "riskScore": 72,
  "metadata": { "url": "https://example.com" }
}

Response:

{
  "decision": "require-approval",
  "matches": [
    {
      "policyId": "pol_abc123",
      "policyName": "Production risk gate",
      "policyType": "risk-threshold",
      "responseAction": "require-approval",
      "message": "Risk score requires approval."
    }
  ],
  "warnings": [],
  "blockedBy": [],
  "approvalRequiredBy": ["pol_abc123"],
  "traceId": "govtrace_1760000000000_ab12cd"
}

Output Evaluation & Scoring (/api/scoring)

Create scoring profiles and evaluate agent outputs against weighted criteria.

List Scoring Profiles

GET /api/scoring/profiles

Query params: limit, offset.

Response: Array of scoring profile objects.

Create Scoring Profile

POST /api/scoring/profiles
{
  "name": "Code Quality Baseline",
  "description": "Checks for common quality indicators in generated code.",
  "compositeMethod": "weightedAvg",
  "scorers": [
    {
      "id": "s1",
      "name": "No hardcoded secrets",
      "type": "RegexMatch",
      "pattern": "(password|secret|api_key)\\s*=\\s*['\"][^'\"]+['\"]",
      "flags": "i",
      "invert": true,
      "weight": 2,
      "scoreOnMatch": 0,
      "scoreOnMiss": 1
    },
    {
      "id": "s2",
      "name": "Has error handling",
      "type": "KeywordContains",
      "keywords": ["try", "catch", "error"],
      "matchMode": "any",
      "weight": 1
    }
  ]
}

Response: 201 with created profile.

Get Scoring Profile

GET /api/scoring/profiles/:id

Update Scoring Profile

PUT /api/scoring/profiles/:id

Delete Scoring Profile

DELETE /api/scoring/profiles/:id

Response: 204 No Content.

Evaluate Output

POST /api/scoring/evaluate
{
  "profileId": "prof_abc123",
  "output": "function getUser(id) {\n  try {\n    return db.find(id);\n  } catch (e) {\n    throw e;\n  }\n}",
  "action": "generate_function",
  "agent": "TARS",
  "taskId": "task_20260321_abc"
}

Response:

{
  "id": "eval_xyz789",
  "profileId": "prof_abc123",
  "score": 0.88,
  "compositeMethod": "weightedAvg",
  "scorerResults": [
    { "id": "s1", "name": "No hardcoded secrets", "score": 1.0, "weight": 2 },
    { "id": "s2", "name": "Has error handling", "score": 1.0, "weight": 1 }
  ],
  "agent": "TARS",
  "taskId": "task_20260321_abc",
  "evaluatedAt": "2026-03-21T14:00:00.000Z"
}

Evaluation History

GET /api/scoring/history

Query params: profileId, agent, taskId, since, limit, offset.

Response: Array of past evaluation results.


Prompt Template Registry (/api/prompt-registry)

Manage version-controlled prompt templates with variable extraction and usage tracking.

List Templates

GET /api/prompt-registry

Query params: tag, search, limit, offset.

Response: Array of template summaries (without full content for performance).

Create Template

POST /api/prompt-registry
{
  "name": "Task Completion Summary",
  "description": "Generates a completion summary for a finished task.",
  "content": "You completed task . Summarize what was done in 2-3 sentences, referencing the acceptance criteria: ",
  "tags": ["completion", "summary"],
  "changelog": "Initial version"
}

Response: 201 with created template including auto-extracted variables (task_title, acceptance_criteria).

Get Template

GET /api/prompt-registry/:id

Response: Full template with current content, version number, variables list, and metadata.

Update Template

PATCH /api/prompt-registry/:id

Body: Partial template fields. Triggers automatic version creation.

{
  "content": "You completed task  (ID: ). Summarize...",
  "changelog": "Added task_id variable"
}

Delete Template

DELETE /api/prompt-registry/:id

Response: 204 No Content.

List Versions

GET /api/prompt-registry/:id/versions

Response: Array of version objects (id, versionNumber, changelog, createdAt). Does not include full content for performance.

Get Usage History

GET /api/prompt-registry/:id/usage

Query params: limit, offset.

Response: Array of usage records with model, token counts, and timestamps.

Template Stats

GET /api/prompt-registry/:id/stats

Response:

{
  "totalUses": 42,
  "averageInputTokens": 312,
  "averageOutputTokens": 128,
  "lastUsedAt": "2026-03-21T14:00:00.000Z"
}

Aggregate Stats (All Templates)

GET /api/prompt-registry/stats/all

Response: Aggregate usage stats across all templates.

Preview Template

POST /api/prompt-registry/:id/render-preview
{
  "variables": {
    "task_title": "Add OAuth login",
    "acceptance_criteria": "Users can log in with Google."
  }
}

Response: { "rendered": "You completed task Add OAuth login. Summarize..." }

Record Usage

POST /api/prompt-registry/:id/record-usage
{
  "model": "anthropic/claude-sonnet-4-6",
  "inputTokens": 320,
  "outputTokens": 145,
  "renderedOutput": "You completed task Add OAuth login...",
  "variables": { "task_title": "Add OAuth login" }
}

Response: 201 with logged usage record.


SQLite Portability (/api/v1/sqlite)

Admin-only endpoints for v5 file-to-SQLite migration and backup portability.

Dry-Run File Migration

POST /api/v1/sqlite/migration/dry-run
{
  "sourceRoot": "/path/to/project",
  "sqlitePath": "/path/to/.veritas-kanban/veritas.db",
  "journalPath": "/path/to/.veritas-kanban/sqlite-migration-journal.json"
}

Returns entity counts and warnings without creating or mutating the database.

Run File Migration

POST /api/v1/sqlite/migration/run
{
  "sourceRoot": "/path/to/project",
  "sqlitePath": "/path/to/.veritas-kanban/veritas.db",
  "backupDir": "/path/to/pre-migration-backup",
  "journalPath": "/path/to/.veritas-kanban/sqlite-migration-journal.json"
}

Creates a timestamped source backup, imports supported file-backed data into SQLite, and returns a migration report.

Migration Recovery State

GET /api/v1/sqlite/migration/recovery?sourceRoot=/path/to/project&sqlitePath=/path/to/.veritas-kanban/veritas.db

Returns the latest migration journal, safe-mode recommendation, backup restore availability, source-file availability, SQLite readability, next actions, and artifacts to preserve for support.

Restore Pre-Migration Backup

POST /api/v1/sqlite/migration/restore-backup
{
  "backupPath": "/path/to/pre-migration-backup",
  "targetRoot": "/path/to/project",
  "journalPath": "/path/to/.veritas-kanban/sqlite-migration-journal.json",
  "replaceExisting": true,
  "dryRun": false
}

Restores the file-backed tasks/ and .veritas-kanban/ content from the pre-migration backup. Non-empty targets require replaceExisting: true; use dryRun: true to verify the paths and file count before overwriting.

Export Backup Bundle

POST /api/v1/sqlite/export
{
  "sqlitePath": "/path/to/.veritas-kanban/veritas.db",
  "outputDir": "/path/to/backup-bundle",
  "workspaceId": "local"
}

Writes raw SQLite table snapshots plus human-readable Markdown/JSON/YAML files. Omit workspaceId for a full database export. When workspaceId is supplied, the export includes only rows scoped to that workspace plus member user records; global app configuration tables are exported as empty arrays. Derived task Markdown and workflow YAML files use the same workspace boundary, and unscoped derived files such as global settings JSON are omitted.

The generated manifest.json includes table row counts, data lifecycle classes, retention/export/delete behavior, sensitivity flags, scope, and redaction state.

Data Lifecycle Policy

GET /api/v1/sqlite/lifecycle-policy

Returns the machine-readable v5 lifecycle policy used by backup manifests and future Maintenance Center cleanup previews.

Import Backup Bundle

POST /api/v1/sqlite/import
{
  "sqlitePath": "/path/to/fresh.db",
  "bundleDir": "/path/to/backup-bundle",
  "replaceExisting": true
}

Restores the bundle into SQLite and rebuilds derived search indexes.


Maintenance Center (/api/v1/maintenance)

Admin/backup endpoints that power Settings -> Maintenance. The full contract is documented in v5.0 Maintenance Center.

Summary

GET /api/v1/maintenance/summary

Returns health checks, storage categories, lifecycle policy metadata, work product maintenance preview data, safe cleanup preview items, and allowlisted log sources with redacted local paths. Cleanup is preview-only; the endpoint does not delete data. When SQLite is active, the response also includes the optional sqlite posture object documented under Health.

Redacted Log Tail

GET /api/v1/maintenance/logs?source=server&tail=200

Returns redacted lines and redacted source metadata from an allowlisted source. tail is capped at 500.

Debug Bundle

POST /api/v1/maintenance/debug-bundle

Creates a redacted debug bundle under the runtime debug-bundles directory and returns the output path plus a manifest of included categories, excluded sensitive categories, redaction rules, and redacted file metadata. The phase-authority.json member includes at most 200 phase-bound runs with phase identity, authority scope counts, source kinds, transition expansion counts, and completion bindings. Exact authority scopes, paths, credential references, and full digests are not exported. The admission-queue.json member includes at most 200 entries from the same bounded inspection projection used by the API, CLI, and Operations UI. It identifies launch source, queue state, readiness, retry posture, and redacted navigation keys without exporting the durable replay target, prompts, messages, override text, credentials, or raw idempotency keys.

SQLite Export and Import

POST /api/v1/maintenance/sqlite/export
POST /api/v1/maintenance/sqlite/import

Wrappers around the SQLite portability export/import handlers. They return the same portability report used by /api/v1/sqlite.


System Health (/api/v1/system/health)

Get a real-time snapshot of system health across resources, agents, and operations.

GET /api/v1/system/health

No query params required.

Response:

{
  "status": "stable",
  "level": 0,
  "signals": {
    "system": {
      "status": "stable",
      "storageUsedPercent": 42,
      "diskFreeGb": 120,
      "memoryUsedPercent": 58
    },
    "agents": {
      "status": "stable",
      "online": 3,
      "offline": 0,
      "total": 3
    },
    "operations": {
      "status": "stable",
      "successRate": 0.97,
      "recentRuns": 50,
      "recentFailures": 1
    }
  },
  "timestamp": "2026-03-21T14:00:00.000Z"
}

Health levels: stable (0) · reviewing (1) · drifting (2) · elevated (3) · alert (4)


For workflow engine endpoints, see API-WORKFLOWS.md.
For MCP server tools, see MCP Server Guide.
For agent workflow SOPs, see SOP-agent-task-workflow.md.