HomeSDK Documentation

SDK Documentation

Canonical reference for the DashClaw SDK (Node v2.13.0 / Python v2.13.0). Node.js and Python parity across all core governance features.

Quick Start

1

Install

npm install dashclaw
2

Initialize

import { DashClaw } from 'dashclaw';

const claw = new DashClaw({
  baseUrl: process.env.DASHCLAW_BASE_URL,
  apiKey: process.env.DASHCLAW_API_KEY,
  agentId: 'my-agent'
});
3

Governance Loop

// 1. Ask permission — abort on hard block
const decision = await claw.guard({
  action_type: 'deploy',
  risk_score: 85,
  declared_goal: 'Update auth service to v2.1.1'
});

if (decision.decision === 'block') {
  throw new Error(`Blocked: ${decision.reason || decision.reasons?.join(', ')}`);
}

// 2. Log intent. The server re-evaluates policy here and is the
//    authoritative source for HITL gating.
const { action, action_id } = await claw.createAction({
  action_type: 'deploy',
  declared_goal: 'Update auth service to v2.1.1'
});

// 3. If the server flagged this, wait for a human operator.
//    Pass createAction's action_id — NOT decision.action_id.
if (action?.status === 'pending_approval') {
  await claw.waitForApproval(action_id);
}

try {
  // 4. Log evidence
  await claw.recordAssumption({
    action_id,
    assumption: 'Tests passed'
  });

  // ... deploy ...

  // 5. Record outcome
  await claw.updateOutcome(action_id, { status: 'completed' });
} catch (err) {
  await claw.updateOutcome(action_id, { status: 'failed', error_message: err.message });
}

MCP Server

@dashclaw/mcp-server exposes DashClaw governance over Model Context Protocol. Any MCP-compatible client gets 23 governance tools across 7 groups (core governance, optimal files, session continuity, credential hygiene, skill safety, open loops, learning + retrospection) plus 4 read-only resources.

Tools (23)

ToolDescriptionKey Inputs
Core governance
dashclaw_guard
Evaluate policies before risky actionsaction_type, declared_goal, risk_score
dashclaw_recordLog action to audit trailaction_type, declared_goal, status
dashclaw_invokeExecute governed capabilitycapability_id, declared_goal, payload
dashclaw_capabilities_listDiscover available APIscategory, risk_level, search
dashclaw_policies_listList active policiesagent_id
dashclaw_wait_for_approvalWait for human decisionaction_id, timeout_seconds
dashclaw_session_startRegister agent sessionagent_id, workspace
dashclaw_session_endClose sessionsession_id, status, summary
Optimal files
dashclaw_optimal_files_preview
Preview optimizer output for a sessionsession_id
dashclaw_optimal_files_manifestGenerate optimal-files manifestsession_id, selections
Session continuity
dashclaw_handoff_create
Write handoff bundle for next sessionbundle, agent_id, project_id
dashclaw_handoff_latestFetch latest unconsumed handoffagent_id, project_id
dashclaw_handoff_consumeMark handoff consumed (idempotent)id, session_id
Credential hygiene
dashclaw_secret_list
List tracked secrets (metadata only)agent_id
dashclaw_secret_dueSecrets coming due for rotationwithin_days, agent_id
dashclaw_secret_mark_rotatedMark secret rotated (operator-confirmed)id
Skill safety
dashclaw_skill_scan
Static safety scan of skill filesskill_name, files
Open loops
dashclaw_loop_add
Register action-scoped commitmentaction_id, loop_type, description
dashclaw_loop_listList open/resolved loopsaction_id, status, priority
dashclaw_loop_closeResolve an open loopid, resolution
Learning + retrospection
dashclaw_learning_log
Log non-obvious decision + outcomedecision, context, outcome
dashclaw_learning_queryQuery prior decisions/lessonsquery, agent_id, limit
dashclaw_decisions_recentRecent governed-action ledgeragent_id, action_type, decision, since

Resources (4)

URIDescription
dashclaw://policiesActive policy set
dashclaw://capabilitiesAvailable capabilities and health
dashclaw://agent/{agent_id}/historyRecent action history (last 50)
dashclaw://statusInstance health + operational metrics

Configuration

Config resolution: CLI args > env vars > defaults. Three config values: url (DASHCLAW_URL, default localhost:3000), apiKey (DASHCLAW_API_KEY), agentId (DASHCLAW_AGENT_ID).

stdio — Claude Code / Desktop (claude_desktop_config.json)
{
  "mcpServers": {
    "dashclaw": {
      "command": "npx",
      "args": ["@dashclaw/mcp-server"],
      "env": {
        "DASHCLAW_URL": "https://your-instance.vercel.app",
        "DASHCLAW_API_KEY": "oc_live_..."
      }
    }
  }
}
Streamable HTTP — Managed Agents (Python)
mcp_servers=[{
    "type": "url",
    "url": "https://your-instance.vercel.app/api/mcp",
    "headers": {"x-api-key": "oc_live_..."},
    "name": "dashclaw"
}]

CLI & Doctor

@dashclaw/cli handles terminal approvals and self-host diagnostics. npm run doctor runs the same engine locally with filesystem-level fix powers.

dashclaw doctor

Diagnoses database, configuration, auth, deployment, SDK reachability, governance staleness, and livingcode shape drift — auto-fixing safe issues. Invokes GET /api/doctor and POST /api/doctor/fix. For operators, npm run doctor on the host adds .env writes, migrations, and default-policy seeding (backs up .env before any write).

dashclaw doctor
npm install -g @dashclaw/cli

dashclaw doctor                          # rich terminal output, auto-fix safe issues
dashclaw doctor --json                   # CI / scripts
dashclaw doctor --no-fix                 # diagnose only
dashclaw doctor --category database,config

# Config resolution: env vars → ~/.dashclaw/config.json (600) → interactive prompt
dashclaw logout                          # remove saved config

# Self-host operator (filesystem-level fixes)
npm run doctor

Claude Code Plugin

plugins/dashclaw/.claude-plugin/plugin.json is the Claude Code plugin manifest. Distributes the DashClaw MCP server (.mcp-claude.json) plus the dashclaw-governance and dashclaw-platform-intelligence skills as one installable bundle. Full step-by-step at /guides/claude-code.

Install
# From the DashClaw repo root:
npm run hooks:install                # PreToolUse / PostToolUse / Stop hooks
# Then add the plugin to ~/.claude/plugins/ via Claude Code's plugin loader:
ln -s "$(pwd)/plugins/dashclaw" ~/.claude/plugins/dashclaw

# Or just copy:
cp -r plugins/dashclaw ~/.claude/plugins/dashclaw

Codex Plugin

dashclaw install codex wires the same governance surface DashClaw ships for Claude Code into Codex's ~/.codex/config.toml — MCP server config, PreToolUse / PostToolUse / Stop hooks, and the governance protocol in AGENTS.md. Idempotent; re-run after every git pull. Full step-by-step at /guides/codex.

Install
# One command from the DashClaw repo root:
node cli/bin/dashclaw.js install codex --project /path/to/your/project

# Optional: opt in to legacy notify config for turn-complete records
node cli/bin/dashclaw.js install codex --project /path/to/your/project --include-notify

# Backfill existing rollouts for analytics
node cli/bin/dashclaw.js code ingest-codex --dry-run
node cli/bin/dashclaw.js code ingest-codex

Hermes Agent Plugin

plugins/dashclaw/.hermes-plugin/ ships eight lifecycle hooks for Hermes Agent: pre/post tool, pre/post LLM call with per-turn governance context injection, on-session start/end with live ingest finalize, secret redaction in tool output, and subagent_stop ROI tracking. Full step-by-step at /guides/hermes.

Install
# macOS / Linux — symlinks the plugin, appends 8 hook entries to
# ~/.hermes/config.yaml between sentinel markers (idempotent).
bash scripts/install-hermes-plugin.sh

# Windows
powershell -File scripts/install-hermes-plugin.ps1

# 4-section sanity check
hermes dashclaw doctor

OpenClaw Plugin

@dashclaw/openclaw-plugin wires governance into the OpenClaw agent framework. Intercepts PreToolUse / PostToolUse lifecycle hooks, calls guard / record / wait-for-approval automatically, and ships a HOOK.md pack the openclaw CLI installs. Tool classification vocabulary aligns with DashClaw guard action types.

Governance Skill

dashclaw-governance teaches governed agents how to use DashClaw correctly — risk thresholds, decision handling (allow / warn / block / require_approval), action recording, approval-wait protocol, and session lifecycle. Pairs with @dashclaw/mcp-server. Auto-installed by the Claude Code, Codex, and Hermes plugins; also downloadable as a standalone zip.

Download
# Zip download from this instance:
curl -O `https://<your-deployment>/downloads/dashclaw-governance.zip`

# Or copy the source dir directly:
cp -r public/downloads/dashclaw-governance ~/.claude/skills/

# Already auto-installed if you ran one of the plugin installers above.

Platform Intelligence Skill

dashclaw-platform-intelligence gives an agent a live reference to DashClaw's API surface, governance vocabulary, integration patterns, and troubleshooting playbooks. Regenerated from the codebase via npm run livingcode:refresh so the skill never drifts from the runtime. Distributed as a zip download, mirrored into ~/.claude/skills/ by the refresh, and shipped inside the Claude Code / Codex / Hermes plugin manifests.

Download
# Zip download (regenerated on every livingcode refresh):
curl -O `https://<your-deployment>/downloads/dashclaw-platform-intelligence.zip`

# Source files:
ls public/downloads/dashclaw-platform-intelligence/
#   SKILL.md            (auto-generated from livingcode shape)
#   references/         (api-surface, platform-knowledge, troubleshooting)
#   scripts/            (bootstrap-agent-quick, diagnose, validate-integration)

# Or just run the refresh — installs to ~/.claude/skills/ automatically:
npm run livingcode:refresh

Constructor

const claw = new DashClaw({ baseUrl, apiKey, agentId, agentName, authToken });
ParameterTypeRequiredDescription
baseUrl / base_urlstringYesDashboard URL
apiKey / api_keystringYesAPI Key
agentId / agent_idstringYesUnique Agent ID
agentName / agent_namestringNoHuman-readable agent label stored in audit trail for attribution. Automatically included on guard() calls if not overridden.
authToken / auth_tokenstringNoPhase 2 — JWT bearer token from your OIDC provider. When set, the server verifies the signature via JWKS and returns verification_status on every guard response; the JWT sub claim overrides agent_id in the audit record. See docs/agent-identity.md.

Behavior Guard

claw.guard(context)

Evaluate guard policies for a proposed action. Call this before risky operations. The guard response includes a `learning` field with historical performance context when available (recent scores, drift status, learned patterns, feedback summary).

ParameterTypeRequiredDescription
action_typestringYesProposed action type
risk_scorenumberNo0-100

Returns: Promise<{ decision: string, reasons: string[], risk_score: number, agent_risk_score: number | null }>

const result = await claw.guard({ action_type: 'deploy', risk_score: 85 });

Action Recording

claw.createAction(action) / claw.create_action(**kwargs)

Create a governance action record. The server re-evaluates policy at this point, so this call is the authoritative source for HITL gating: if policy requires human review, the response is HTTP 202 with action.status='pending_approval'. Always check action.status before assuming the action is clear to execute.

Returns: Promise<{ action: { action_id, status, ... }, action_id, decision, security }>

const { action, action_id } = await claw.createAction({ action_type: 'deploy' });
if (action?.status === 'pending_approval') {
  // gate execution on waitForApproval — see the method below
}

claw.waitForApproval(actionId, { timeout?, interval? }) / claw.wait_for_approval(action_id, timeout=300, interval=5)

Wait for a human operator to approve or deny an action. Opens an SSE stream on /api/stream and falls back to polling /api/actions/:id every 5 seconds. Resolves when action.approved_by is set; throws ApprovalDeniedError when the operator denies; throws on timeout. IMPORTANT: pass the action_id returned by createAction() — NOT the action_id returned by guard(). They refer to different database tables and waiting on a guard decision ID will never resolve. Approvals can be resolved from the dashboard (/approvals), the CLI (dashclaw approve <id>), the mobile PWA (/approve), or — if the instance has Telegram configured (TELEGRAM_BOT_TOKEN) — via an inline Approve/Reject button pushed to the admin Telegram chat. All four surfaces call the same /api/approvals/:id endpoint, so waitForApproval unblocks the agent within ~1 second regardless of which surface was used.

// Correct — wait on createAction's action_id
const { action, action_id } = await claw.createAction({ action_type: 'deploy' });
if (action?.status === 'pending_approval') {
  await claw.waitForApproval(action_id, { timeout: 600_000 });
}

claw.updateOutcome(id, outcome) / claw.update_outcome(id, **kwargs)

Log final results. Accepts status, output_summary, error_message, duration_ms, tokens_in, tokens_out, model, cost_estimate. When tokens + model are supplied without cost_estimate, the server derives cost from the pricing table.

await claw.updateOutcome(action_id, {
  status: 'completed',
  tokens_in: result.usage.input_tokens,
  tokens_out: result.usage.output_tokens,
  model: result.model,
});

claw.recordAssumption(asm) / claw.record_assumption(asm)

Track agent beliefs.

await claw.recordAssumption({ action_id, assumption: '...' });

Signals

claw.getSignals() / claw.get_signals()

Get current risk signals across all agents.

Returns: Promise<{ signals: Object[] }>

const { signals } = await claw.getSignals();

Agent Lifecycle

claw.heartbeat(status, metadata) / claw.heartbeat(status=..., metadata=...)

Report agent presence and health to the control plane. Call periodically to indicate the agent is alive.

ParameterTypeRequiredDescription
statusstringNoAgent status — 'online', 'busy', 'idle'. Defaults to 'online'
metadataobjectNoArbitrary metadata to include with the heartbeat
await claw.heartbeat('online', { cycle: 42, uptime_ms: 360000 });

claw.reportConnections(connections) / claw.report_connections(connections)

Report active provider connections and their status. Appears in the agent's Fleet profile.

ParameterTypeRequiredDescription
connectionsArray<Object>YesList of { name, type, status } connection objects
await claw.reportConnections([
  { name: 'OpenAI', type: 'llm', status: 'connected' },
  { name: 'Postgres', type: 'database', status: 'connected' },
]);

Loops & Assumptions

claw.registerOpenLoop(actionId, type, desc) / claw.register_open_loop(...)

Register an unresolved dependency for a decision. Open loops track work that must be completed before the decision is fully resolved.

ParameterTypeRequiredDescription
action_idstringYesAssociated action
loop_typestringYesThe category of the loop
descriptionstringYesWhat needs to be resolved
await claw.registerOpenLoop(action_id, 'validation', 'Waiting for PR review');

claw.resolveOpenLoop(loopId, status, res) / claw.resolve_open_loop(...)

Resolve a pending loop.

await claw.resolveOpenLoop(loop_id, 'completed', 'Approved');

claw.recordAssumption(asm) / claw.record_assumption(asm)

Record what the agent believed to be true when making a decision.

await claw.recordAssumption({ action_id, assumption: 'User is authenticated' });

Learning Analytics

claw.getLearningVelocity() / claw.get_learning_velocity()

Compute learning velocity (rate of score improvement) for agents.

Returns: Promise<{ velocity: Array<Object> }>

const { velocity } = await claw.getLearningVelocity();

claw.getLearningCurves() / claw.get_learning_curves()

Compute learning curves per action type to measure efficiency gains.

const curves = await claw.getLearningCurves();

claw.getLessons({ actionType, limit }) / claw.get_lessons(action_type=..., limit=...)

Fetch consolidated lessons from scored outcomes — what DashClaw has learned about this agent's performance patterns.

ParameterTypeRequiredDescription
actionTypestringNoFilter by action type
limitnumberNoMax lessons to return (default 10)

Returns: Promise<{ lessons: Object[], drift_warnings: Object[], agent_id: string }>

const { lessons, drift_warnings } = await claw.getLessons({ actionType: 'deploy' });
lessons.forEach(l => console.log(l.guidance));

Prompt Management

claw.renderPrompt() / claw.render_prompt()

Fetch rendered prompt from DashClaw.

const { rendered } = await claw.renderPrompt({
  template_id: 'marketing',
  variables: { company: 'Apple' }
});

Evaluation Framework

claw.createScorer(name, type, config) / claw.create_scorer(...)

Create a reusable scorer definition for automated evaluation.

ParameterTypeRequiredDescription
namestringYesScorer name
scorer_typestringYesType (llm_judge, regex, range)
configobjectNoScorer configuration
await claw.createScorer('toxicity', 'regex', { pattern: 'bad-word' });

Scoring Profiles

claw.createScoringProfile(config) / claw.create_scoring_profile(...)

Define weighted quality scoring profiles across multiple scorers.

await claw.createScoringProfile({ 
  name: 'prod-quality', 
  dimensions: [{ scorer: 'toxicity', weight: 0.5 }] 
});

Agent Messaging

claw.sendMessage(params) / claw.send_message(**kwargs)

Send a point-to-point message or broadcast to all agents in the organization.

ParameterTypeRequiredDescription
tostringNoTarget agent ID (omit for broadcast)
bodystringYesMessage content
typestringNoaction|info|lesson|question
urgentbooleanNoMark as high priority
await claw.sendMessage({
  to: 'scout-agent-01',
  body: 'I have finished indexing the repository. You can start the analysis.',
  type: 'status'
});

claw.getInbox(options?) / claw.get_inbox(**kwargs)

Retrieve messages from the agent inbox with optional filtering.

ParameterTypeRequiredDescription
typestringNoFilter by message type
unreadbooleanNoOnly return unread messages
limitnumberNoMax messages to return

Returns: Promise<{ messages, total, unread_count }>

const { messages } = await claw.getInbox({ unread: true, limit: 10 });

Session Handoffs

claw.createHandoff(handoff) / claw.create_handoff(**kwargs)

Create a session handoff document to persist state between agent sessions or transfer context to another agent.

await claw.createHandoff({
  summary: 'Completed initial data collection from Jira.',
  key_decisions: ['Prioritize high-severity bugs', 'Ignore closed tickets'],
  open_tasks: ['Run security scan on src/', 'Draft fix for #123'],
  next_priorities: ['Security audit']
});

claw.getLatestHandoff() / claw.get_latest_handoff()

Retrieve the most recent handoff for the current agent.

Returns: Promise<Object|null>

const handoff = await claw.getLatestHandoff();

Security Scanning

claw.scanPromptInjection(text) / claw.scan_prompt_injection(text)

Scan untrusted input for potential prompt injection or jailbreak attempts.

ParameterTypeRequiredDescription
textstringYesUntrusted input to scan

Returns: Promise<{ clean: boolean, risk_level: string, recommendation: string }>

const result = await claw.scanPromptInjection(userInput);
if (!result.clean) {
  console.warn('Injection risk:', result.risk_level);
}

User Feedback

claw.submitFeedback(params) / claw.submit_feedback(**kwargs)

Submit feedback for a specific agent action. Used for human evaluation of agent performance.

ParameterTypeRequiredDescription
action_idstringYesTarget action ID
ratingnumberYes1-5 star rating
commentstringNoTextual feedback
categorystringNoGrouping tag
await claw.submitFeedback({
  action_id: 'act_4b2s8...',
  rating: 4,
  comment: 'Action was safe and effective but took longer than expected.',
  category: 'performance_review'
});

Context Threads

claw.createThread(options) / claw.create_thread(**kwargs)

Create a new context thread to track a multi-step reasoning chain or investigation.

ParameterTypeRequiredDescription
namestringYesThread name
summarystringNoInitial thread summary

Returns: Promise<{ thread, thread_id }>

const { thread } = await claw.createThread({ name: 'Deploy analysis', summary: 'Evaluating safety' });

claw.addThreadEntry(threadId, content, entryType) / claw.add_thread_entry(...)

Append an observation, conclusion, or decision to an existing context thread.

ParameterTypeRequiredDescription
threadIdstringYesThread ID to append to
contentstringYesEntry content
entryTypestringYes'observation' | 'conclusion' | 'decision'
await claw.addThreadEntry('ct_abc123', 'Staging checks passed', 'observation');

claw.closeThread(threadId, summary?) / claw.close_thread(thread_id, summary=None)

Close a context thread, optionally providing a final summary.

ParameterTypeRequiredDescription
threadIdstringYesThread ID to close
summarystringNoFinal summary of the thread
await claw.closeThread('ct_abc123', 'Deploy approved after staging check');

Bulk Sync

claw.syncState(state) / claw.sync_state(**kwargs)

Bulk-sync agent state including decisions, lessons, goals, context, relationships, memory, and preferences in a single call.

ParameterTypeRequiredDescription
stateobjectYesState object with keys: decisions, lessons, goals, context, relationships, memory, preferences
await claw.syncState({ decisions: [...], lessons: [...], goals: [...] });

Agent Identity

Enroll agents via public-key pairing and manage approved identities. Pairing requests are created by agents; approval is an admin action. Once approved, the agent's public key is registered as a trusted identity for signature verification.

POST /api/pairings

Create an agent pairing request. The agent submits its public key and waits for operator approval.

ParameterTypeRequiredDescription
public_keystringYesPEM-encoded RSA public key
algorithmstringNoKey algorithm. Default: RSASSA-PKCS1-v1_5
agent_namestringNoHuman-readable label for the agent

Returns: { pairing: { id, status, agent_name, created_at } }

Create pairing request
// Node SDK (v1 legacy)
import { DashClaw } from 'dashclaw/legacy';
const claw = new DashClaw({ baseUrl, apiKey, agentId });

const { pairing } = await claw.createPairing(publicKeyPem, 'RSASSA-PKCS1-v1_5', 'my-agent');
console.log(pairing.id); // pair_...

GET /api/pairings

List all pairing requests for the organization. Admin API key required.

Returns: { pairings: Array<{ id, status, agent_name, created_at, approved_at }> }

List pairings (admin)
const res = await fetch('/api/pairings', {
  headers: { 'x-api-key': adminApiKey }
});
const { pairings } = await res.json();

GET /api/pairings/:id

Get a specific pairing request by ID. Used by agents to poll for approval status.

Returns: { pairing: { id, status, agent_name, created_at, approved_at } }

Poll pairing status
// Node SDK (v1 legacy)
const status = await claw.getPairing(pairingId);
console.log(status.pairing.status); // pending | approved | expired

POST /api/pairings/:id/approve

Approve a pending pairing request. Admin API key required. On approval, the agent's public key is registered as a trusted identity.

Returns: { pairing: { id, status, approved_at } }

Approve pairing (admin)
const res = await fetch(`/api/pairings/${pairingId}/approve`, {
  method: 'POST',
  headers: { 'x-api-key': adminApiKey }
});

POST /api/identities

Directly register an agent's public key as a trusted identity. Admin API key required. Bypasses the pairing flow.

ParameterTypeRequiredDescription
agent_idstringYesUnique agent identifier
public_keystringYesPEM-encoded RSA public key
algorithmstringNoKey algorithm. Default: RSASSA-PKCS1-v1_5

Returns: { identity: { agent_id, algorithm, created_at } }

Register identity (admin)
// Node SDK (v1 legacy)
await claw.registerIdentity('agent-007', publicKeyPem, 'RSASSA-PKCS1-v1_5');

GET /api/identities

List all registered agent identities for the organization. Admin API key required.

Returns: { identities: Array<{ agent_id, algorithm, created_at }> }

List identities (admin)
// Node SDK (v1 legacy)
const { identities } = await claw.getIdentities();

DELETE /api/identities/:agentId

Revoke a registered agent identity. Admin API key required. The agent's public key is removed and signature verification will fail for future actions.

Returns: { success: true }

Revoke identity (admin)
const res = await fetch(`/api/identities/${agentId}`, {
  method: 'DELETE',
  headers: { 'x-api-key': adminApiKey }
});

Execution Studio (HTTP API)

Governance packaging: workflow templates, model strategies, knowledge collections, a capability registry, and a read-only execution graph on actions. Every surface here has a canonical SDK wrapper method in the v2 Node SDK (see sdk/dashclaw.js, 80 methods total). The HTTP examples below are shown first because they're language-agnostic; the equivalent SDK calls (claw.listWorkflowTemplates, claw.execution.capabilities.invoke, etc.) are in sdk/README.md → Execution Studio. Full OpenAPI definitions are at docs/openapi/critical-stable.openapi.json.

Execution Graph

GET /api/actions/:actionId/graph

Read-only execution graph (nodes + edges) for any action. Reuses the existing trace data plus correlated assumptions and open loops — zero schema change. Powers the Graph tab on decision replay.

Returns: { rootActionId, nodes: Array<{ id, type, status, riskScore, ... }>, edges: Array<{ source, target, type, label }> }

Fetch graph
const res = await fetch(`${baseUrl}/api/actions/${actionId}/graph`, {
  headers: { 'x-api-key': apiKey }
});
const { rootActionId, nodes, edges } = await res.json();
// node ids: action:<id>, assumption:<id>, loop:<id>
// edge types: parent_child | related | assumption_of | loop_from

Action Outcome

Five-state terminal outcome on every action — closes the audit-trail gap between "what was approved" and "what actually completed." See durable-execution-finality.md.

POST /api/actions/:actionId/outcome

Record the terminal outcome of an approved action. One-shot: the first successful POST wins, subsequent POSTs return 409 with the current state. status must be one of completed | partial | failed. error_message is required when status=failed; progress (object) is required when status=partial. lost_confirmation is reserved for the system sweep.

Returns: { outcome: { action_id, status, outcome_at, summary, error_message, progress, elapsed_ms }, security: { clean, findings_count } }

Report success
await fetch(`${baseUrl}/api/actions/${actionId}/outcome`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    status: 'completed',
    summary: 'Deployed dashclaw 2.13.4 to production'
  })
});

GET /api/actions/:actionId/outcome

Read the current outcome state. Returns the full outcome shape including elapsed_ms (outcome_at − created_at, or now − created_at while still pending). Agents call this before retrying to avoid re-executing already-completed actions.

Returns: { action_id, status, outcome_at, summary, error_message, progress, elapsed_ms }

Retry-safe poll
const outcome = await fetch(
  `${baseUrl}/api/actions/${actionId}/outcome`,
  { headers: { 'x-api-key': apiKey } }
).then(r => r.json());

// completed → SKIP, failed | lost_confirmation → RETRY,
// pending → WAIT, partial → CLEANUP_THEN_RETRY

Workflow Templates

Package a repeatable operational pattern as a reusable, versioned asset linking policies, prompts, knowledge, capabilities, and a model strategy.

GET /api/workflows/templates

List all workflow templates for the current org. Supports ?status=draft|active|archived, ?limit, ?offset.

List templates
const { templates } = await fetch(`${baseUrl}/api/workflows/templates`, {
  headers: { 'x-api-key': apiKey }
}).then(r => r.json());

POST /api/workflows/templates

Create a workflow template. Slug auto-generated from name if omitted. Starts as v1, status=draft. Body fields: name (required), description, objective, steps, linked_prompt_template_ids, linked_policy_ids, linked_knowledge_collection_ids, linked_capability_ids, linked_capability_tags, model_strategy_id, status.

Create template
await fetch(`${baseUrl}/api/workflows/templates`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Release Hotfix',
    description: 'Ship urgent production patches safely',
    objective: 'Deploy with full policy + approval coverage',
    linked_policy_ids: ['pol_prod_deploy'],
    linked_capability_tags: ['deploy'],
    model_strategy_id: 'mst_balanced_default'
  })
});

GET | PATCH /api/workflows/templates/:templateId

Fetch or partially update a template. PATCH bumps version by 1 when the steps array changes; all linked arrays and metadata can be updated in the same call.

Update steps (bumps version)
await fetch(`${baseUrl}/api/workflows/templates/${templateId}`, {
  method: 'PATCH',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    steps: [{ id: 'plan' }, { id: 'test' }, { id: 'deploy' }]
  })
});

POST /api/workflows/templates/:templateId/duplicate

Clone a template as a new draft (version resets to 1, status='draft'). Accepts optional name and slug overrides in the body.

Duplicate
const { template } = await fetch(
  `${baseUrl}/api/workflows/templates/${templateId}/duplicate`,
  { method: 'POST', headers: { 'x-api-key': apiKey } }
).then(r => r.json());

POST /api/workflows/templates/:templateId/launch

Launch a template. Creates a new row in action_records with trigger='workflow:<templateId>' and reasoning='WORKFLOW_LAUNCH_META=<json>' carrying the full template context. If the template links a model_strategy_id, the resolved config is fetched and snapshotted onto the launched action and the template. No schema columns were added to action_records — Phase 1 piggybacks on existing trace primitives.

Returns: { launch: { action_id, template_id, template_version, launched_at, resolved_strategy } }

Launch and link to replay
const { launch } = await fetch(
  `${baseUrl}/api/workflows/templates/${templateId}/launch`,
  {
    method: 'POST',
    headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
    body: JSON.stringify({ agent_id: 'deploy-bot' })
  }
).then(r => r.json());

// The launched action is immediately traceable in decision replay
console.log(`${baseUrl}/decisions/${launch.action_id}`);

GET /api/workflows/templates/:templateId/runs

List past workflow executions for a template. Each run is a parent action_record with step counts from workflow_step_results. Supports status, agent_id, limit, and offset query params.

Returns: { template_id, runs: [{ run_action_id, template_id, status, agent_id, declared_goal, duration_ms, started_at, finished_at, step_count, steps_completed, steps_failed }], total }

List recent runs
const runs = await fetch(
  `${baseUrl}/api/workflows/templates/${templateId}/runs?limit=10`,
  { headers: { 'x-api-key': apiKey } }
).then(r => r.json());

runs.runs.forEach(r =>
  console.log(`${r.status} — ${r.steps_completed}/${r.step_count} steps — ${r.duration_ms}ms`)
);

GET /api/workflows/templates/:templateId/runs/:runActionId

Fetch full run detail including all step results with complete input/output JSON. Powers the run detail page. Each step includes the resolved input after variable interpolation and the full output (no truncation).

Returns: { run_action_id, template_id, template_name, status, agent_id, declared_goal, duration_ms, started_at, finished_at, error_message, steps: [{ step_id, step_index, step_type, step_name, status, input, output, error_message, retry_count, duration_ms }] }

Inspect a failed run
const run = await fetch(
  `${baseUrl}/api/workflows/templates/${templateId}/runs/${runActionId}`,
  { headers: { 'x-api-key': apiKey } }
).then(r => r.json());

const failed = run.steps.filter(s => s.status === 'failed');
failed.forEach(s =>
  console.log(`Step ${s.step_name} failed: ${s.error_message}`)
);

Model Strategies

Reusable provider/model strategy records (primary + fallback chain, cost/latency sensitivity, budget cap). Linked from workflow templates and snapshotted at launch.

GET | POST /api/model-strategies

List all strategies or create a new one. Config is validated server-side: primary.provider and primary.model are required; costSensitivity must be one of low | balanced | high-quality; latencySensitivity must be low | medium | high; maxBudgetUsd must be a number; maxRetries must be an integer; fallback, allowedProviders, and disallowedProviders must be arrays if provided.

Create strategy
await fetch(`${baseUrl}/api/model-strategies`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Balanced Default',
    description: 'GPT-4.1 primary, Claude Sonnet 4 fallback',
    config: {
      primary: { provider: 'openai', model: 'gpt-4.1' },
      fallback: [{ provider: 'anthropic', model: 'claude-sonnet-4' }],
      costSensitivity: 'balanced',
      latencySensitivity: 'medium',
      maxBudgetUsd: 0.5,
      maxRetries: 2,
      allowedProviders: ['openai', 'anthropic']
    }
  })
});

GET | PATCH | DELETE /api/model-strategies/:strategyId

Fetch, update, or delete a strategy. PATCH merges config patches over the existing config (primary fields preserved unless overridden). DELETE nulls out the soft reference on any linked workflow_templates rather than orphaning them.

Patch budget only
await fetch(`${baseUrl}/api/model-strategies/${strategyId}`, {
  method: 'PATCH',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ config: { maxBudgetUsd: 1.0 } })
});

POST /api/model-strategies/:strategyId/complete

Execute a chat completion using this strategy. Resolves BYOK provider credentials from org settings, walks the fallback chain (primary provider first, then each fallback), enforces maxBudgetUsd, and returns a normalized response. Supports task_mode to override primary with the corresponding taskModes entry. Providers supported: openai, anthropic, groq, together, perplexity. Returns 502 with provider_errors array when all providers fail.

ParameterTypeRequiredDescription
messagesArray<{ role, content }>YesChat messages (system, user, assistant)
max_tokensnumberNoMax output tokens (default 1024)
temperaturenumberNoSampling temperature (default 0.7)
task_modestringNoOverride primary with taskModes[mode] if defined in strategy config

Returns: { content, provider, model, usage: { input_tokens, output_tokens }, cost_usd, fallback_used, attempts, strategy_id, strategy_name }

Execute completion with fallback
const result = await claw.completeWithStrategy(strategyId, [
  { role: 'user', content: 'Summarize the deploy plan' }
], { max_tokens: 512, task_mode: 'reasoning' });

console.log(result.content);       // LLM response
console.log(result.provider);      // which provider handled it
console.log(result.cost_usd);      // estimated cost
console.log(result.fallback_used); // true if primary failed

Knowledge Collections

Lightweight metadata layer for knowledge sources that workflows and agents can bind to. No embedding or retrieval in Phase 1 — metadata + tags only.

GET | POST /api/knowledge/collections

List collections (filter by ?source_type) or create a new one. source_type must be one of files | urls | external | notes. New collections start with ingestion_status='empty' and doc_count=0.

Create collection
await fetch(`${baseUrl}/api/knowledge/collections`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Runbook Library',
    description: 'Incident response runbooks',
    source_type: 'files',
    tags: ['ops', 'oncall']
  })
});

GET | PATCH /api/knowledge/collections/:collectionId

Fetch or update a collection's metadata (name, description, source_type, tags, ingestion_status).

Fetch
const { collection } = await fetch(
  `${baseUrl}/api/knowledge/collections/${collectionId}`,
  { headers: { 'x-api-key': apiKey } }
).then(r => r.json());

GET | POST /api/knowledge/collections/:collectionId/items

List or add items in a collection. Adding an item increments the parent collection's doc_count atomically and transitions ingestion_status from 'empty' to 'pending' on the first item. Items carry source_uri (required), title, mime_type, status, and a metadata object.

Add an item
await fetch(
  `${baseUrl}/api/knowledge/collections/${collectionId}/items`,
  {
    method: 'POST',
    headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      source_uri: 'https://docs.example.com/runbook.md',
      title: 'Deploy runbook',
      mime_type: 'text/markdown'
    })
  }
);

POST /api/knowledge/collections/:collectionId/sync

Caller-invoked ingestion: fetches source_uri content for each pending item, chunks text (~500 tokens with overlap), generates embeddings via BYOK OpenAI key (text-embedding-3-small, 1536 dims), and stores in the knowledge_chunks table (pgvector). Updates item status (pending → indexed/failed) and collection ingestion_status. Bounded to 50 items per call — designed for Vercel free tier (no cron required).

Returns: { sync: { ingested, failed, chunks_created, errors } }

Sync a collection
// SDK
const { sync } = await claw.syncKnowledgeCollection(collectionId);
console.log(sync.ingested, sync.chunks_created);

POST /api/knowledge/collections/:collectionId/search

Semantic search over chunked + embedded content. Embeds the query via BYOK OpenAI key, then uses pgvector cosine distance to find the most relevant chunks. Returns top-k results with similarity scores, chunk content, and source item metadata.

ParameterTypeRequiredDescription
querystringYesNatural language search query
limitnumberNoMax results (default 5, max 20)

Returns: { query, collection_id, results: Array<{ chunk_id, item_id, content, score, position, token_count, title, source_uri }>, count }

Search a collection
const { results } = await claw.searchKnowledgeCollection(
  collectionId,
  'How do I roll back a deploy?',
  { limit: 5 }
);
results.forEach(r => console.log(`${(r.score * 100).toFixed(1)}%: ${r.content.slice(0, 80)}`));

Capability Registry

Governed registry of callable capabilities with risk, approval, health, and (future) pricing metadata. Workflow templates can reference capabilities by id or by tag.

GET | POST /api/capabilities

Search or register a capability. GET supports combinable filters: ?category, ?risk_level (low|medium|high|critical), ?search (ILIKE on name/description/tags). source_type must be one of internal_sdk | http_api | webhook | human_approval | external_marketplace. (org_id, slug) is unique — POST returns 409 on duplicate slug.

Register a capability
await fetch(`${baseUrl}/api/capabilities`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Send Slack Message',
    description: 'Posts to a configured Slack channel',
    category: 'messaging',
    source_type: 'http_api',
    auth_type: 'oauth',
    risk_level: 'medium',
    requires_approval: false,
    tags: ['notify', 'slack'],
    health_status: 'healthy',
    docs_url: 'https://docs.example.com/slack'
  })
});

GET | PATCH /api/capabilities/:capabilityId

Fetch or update a capability. PATCH validates risk_level and source_type enums on change.

Mark degraded
await fetch(`${baseUrl}/api/capabilities/${capabilityId}`, {
  method: 'PATCH',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ health_status: 'degraded' })
});

Capability Runtime

Governed capability invocation with retry policies, circuit breaker, and health tracking. Capabilities with retry_policy retry transient failures automatically. Capabilities with circuit_breaker auto-block after consecutive failures (reset via test route).

POST /api/capabilities/:capabilityId/invoke

Execute a governed capability invocation. Evaluates guard policies, scans for sensitive data, enforces quota, runs the HTTP call with optional retry, and records a full action audit trail. Returns retry_metadata when retry_policy is configured. Returns 503 circuit_breaker_open when the circuit breaker is tripped.

Invoke with payload
const res = await fetch(`${baseUrl}/api/capabilities/${capabilityId}/invoke`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: 'What is x402?' })
});
const data = await res.json();
// data.success, data.action_id, data.result, data.elapsed_ms, data.governed
// data.retry_metadata (when retry_policy configured): { total_attempts, retried, attempts }

POST /api/capabilities/:capabilityId/test

Run a non-production validation call. Bypasses guard policies and circuit breaker. Updates capability health_status and certification_status based on the result. Use this to certify a capability or reset an open circuit breaker.

Test a capability
const res = await fetch(`${baseUrl}/api/capabilities/${capabilityId}/test`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: 'test input' })
});
const data = await res.json();
// data.tested, data.health_status, data.certification_status

GET /api/capabilities/:capabilityId/health

Fetch derived health summary including success rates (1d/7d), p95 latency, certification status, recent errors, and stale check. Computed from action_records over the past 7 days.

Check capability health
const res = await fetch(`${baseUrl}/api/capabilities/${capabilityId}/health`, {
  headers: { 'x-api-key': apiKey }
});
const health = await res.json();
// health.status (healthy|degraded|failing|untested)
// health.certification_status (certified|stale|failed|uncertified)
// health.success_rate_1d, health.success_rate_7d, health.p95_latency_ms

GET /api/capabilities/:capabilityId/history

Fetch invocation and test event history for a capability. Filter by action_type (capability_invoke, capability_test) and status (completed, failed, running, pending_approval). Supports limit and offset pagination.

Fetch recent failures
const res = await fetch(`${baseUrl}/api/capabilities/${capabilityId}/history?status=failed&limit=10`, {
  headers: { 'x-api-key': apiKey }
});
const history = await res.json();
// history.events[].action_id, action_type, status, error_message, duration_ms

Analytics

GET /api/analytics

Fetch aggregated governance analytics for the organization over a rolling window. Includes action counts, guard decision totals, signal summaries, and assumption stats. Supports ?days (1–365, default 30).

ParameterTypeRequiredDescription
daysnumberNoRolling window in days (1–365). Defaults to 30.

Returns: { actions_total, actions_by_status, guard_decisions_total, guard_decisions_by_outcome, signals_total, assumptions_total }

Fetch 7-day analytics
const res = await fetch(`${baseUrl}/api/analytics?days=7`, {
  headers: { 'x-api-key': apiKey }
});
const data = await res.json();
// data.actions_total, data.guard_decisions_total, data.signals_total

Guard Decisions

GET /api/guard/decisions

List guard evaluation records for the organization. Returns paginated decisions with matched policies and declared goal context. Supports filtering by ?decision (allow|block|flag), ?agent_id, ?limit (max 200), and ?offset.

ParameterTypeRequiredDescription
decisionstringNoFilter by outcome: allow | block | flag
agent_idstringNoFilter to a specific agent
limitnumberNoPage size (max 200, default 50)
offsetnumberNoPagination offset (default 0)

Returns: { decisions: Array<{ id, agent_id, action_type, decision, matched_policies, declared_goal, agent_name, created_at }>, total, stats }

List blocked decisions
const res = await fetch(`${baseUrl}/api/guard/decisions?decision=block&limit=25`, {
  headers: { 'x-api-key': apiKey }
});
const { decisions, total, stats } = await res.json();
// decisions[].decision, decisions[].matched_policies, decisions[].declared_goal

Agent Profile

GET /api/agents/:agentId/profile

Fetch the full governance profile for a specific agent. Includes identity, presence (heartbeat state), trust posture, computed risk signals, and assumptions summary. Returns 404 if the agent has not been seen by the instance.

ParameterTypeRequiredDescription
agentIdstringYesThe agent identifier (path parameter)

Returns: { agent: { agent_id, agent_name, action_count, last_active, presence: { status, last_heartbeat_at, current_task_id } }, trust, signals, assumptions_summary }

Fetch agent profile
const res = await fetch(`${baseUrl}/api/agents/my-agent/profile`, {
  headers: { 'x-api-key': apiKey }
});
const { agent, trust, signals, assumptions_summary } = await res.json();
// agent.presence.status, trust.risk_score, signals, assumptions_summary

Hosted Provisioning (operator)

Operator-facing routes exposed only when DASHCLAW_HOSTED=true. These are not SDK methods — they produce the API key that downstream SDKs consume. Self-host deploys are unaffected; all routes return 404 when the flag is unset.

POST /api/hosted/workspaces

Mint a new trial workspace. Public, gated by DASHCLAW_HOSTED flag + Turnstile + IP rate limit. Returns the workspace ID, a one-time API key, and onboarding URL.

ParameterTypeRequiredDescription
turnstile_tokenstringNoCloudflare Turnstile challenge token. Required in production; omit in dev bypass mode.

Returns: { workspace_id, api_key, endpoint, expires_at, trial_action_cap, key_prefix, next_steps_url }

Mint a trial workspace
curl -X POST https://hosted.example.com/api/hosted/workspaces \
  -H "content-type: application/json" \
  -d '{"turnstile_token": "..."}'
# → { "workspace_id": "org_...", "api_key": "oc_live_...", "endpoint": "...",
#     "expires_at": "...", "trial_action_cap": 10000, "key_prefix": "oc_live_",
#     "next_steps_url": "https://hosted.example.com/connect?hosted=org_..." }

GET /api/hosted/workspaces/:id

Admin: inspect a trial workspace. Requires an admin-role API key.

ParameterTypeRequiredDescription
idstringYesWorkspace (org) ID, e.g. org_abc

Returns: { workspace_id, status, expires_at, actions_used, trial_action_cap, created_at }

Inspect a trial workspace
curl https://hosted.example.com/api/hosted/workspaces/org_abc \
  -H "x-api-key: <admin_key>"

DELETE /api/hosted/workspaces/:id

Admin: manually delete a trial workspace and revoke its API key.

ParameterTypeRequiredDescription
idstringYesWorkspace (org) ID to delete

Returns: { deleted: true, workspace_id }

Delete a trial workspace
curl -X DELETE https://hosted.example.com/api/hosted/workspaces/org_abc \
  -H "x-api-key: <admin_key>"

POST /api/hosted/cleanup

Cron-safe sweeper for expired trial workspaces. Accepts admin-role API key OR X-Cleanup-Secret header. Safe to run repeatedly — idempotent.

ParameterTypeRequiredDescription
X-Cleanup-SecretheaderNoShared secret set via HOSTED_CLEANUP_SECRET env var. Alternative to admin API key.

Returns: { swept: number, workspace_ids: string[] }

Sweep expired trials (cron)
curl -X POST https://hosted.example.com/api/hosted/cleanup \
  -H "X-Cleanup-Secret: $HOSTED_CLEANUP_SECRET"

Error Handling

Error shape
{ message: "Validation failed", status: 400 }