API Reference
Complete reference for task handlers, types, and interfaces used in the orchestrator.
Capability-truth rule:
- all declared non-template agents remain in scope for full-capability uplift
- current route exposure and runtime evidence must stay honest about what is already mature versus what is still partial
- when capability, runtime truth, operator exposure, or task-promotion truth changes materially, update this reference together with the canonical architecture capability pack in the same change set
Current Operator-Facing Route Contract (Runtime Truth)
Related scope policy:
- see ../architecture/OPERATOR_SURFACE_CAPABILITY_MATRIX.md for the backend-first decision matrix that separates implemented routes/tasks from current frontend exposure policy
- see ../architecture/AGENT_CAPABILITY_MODEL.md for the intended agent-maturity gate that should be satisfied before broad task promotion is treated as product truth
- see ../architecture/AGENT_CAPABILITY_IMPLEMENTATION_MATRIX.md for the concrete per-agent implementation and promotion gate matrix used to sequence that work across the full declared agent set
Documentation maintenance:
- if a route becomes newly operator-facing, newly admin-only, newly observe-only, or materially changes its runtime caveats, update this file in the same change set
- if a task or route is promoted because an agent matured, reflect that here without flattening remaining maturity gaps elsewhere into “fully capable”
Primary private operator console route family:
GET /operator(served by orchestrator runtime)GET /operator/*(SPA route family for the canonical privateoperator-s-consolebundle)
Public monitoring/read-only:
GET /healthGET /api/persistence/healthGET /api/knowledge/summaryGET /api/openapi.json
Public proof/read-only:
GET /api/command-center/overviewGET /api/command-center/controlGET /api/command-center/demandGET /api/command-center/demand-liveGET /api/milestones/latestGET /api/milestones/dead-letter
Protected operator routes (bearer token):
GET /api/auth/meGET /api/dashboard/overviewGET /api/companion/overviewGET /api/companion/catalogGET /api/companion/incidentsGET /api/companion/runsGET /api/companion/approvalsGET /api/runtime/factsGET /api/tasks/catalogPOST /api/tasks/triggerGET /api/tasks/runsGET /api/tasks/runs/:runIdGET /api/approvals/pendingPOST /api/approvals/:id/decisionGET /api/incidentsGET /api/incidents/:idGET /api/incidents/:id/historyPOST /api/incidents/:id/acknowledgePOST /api/incidents/:id/ownerPOST /api/incidents/:id/remediateGET /api/agents/overviewGET /api/skills/registryGET /api/skills/policyGET /api/skills/telemetryGET /api/skills/auditGET /api/skills/registrynow reflects live governed-skill runtime truth, including executability, executor binding, persistence mode, and provenance snapshot for each intake record.GET /api/memory/recallGET /api/health/extendedPOST /api/knowledge/queryGET /api/knowledge/exportGET /api/persistence/historicalGET /api/persistence/summaryGET /api/persistence/export
Internal ingest:
POST /webhook/alerts(HMAC signature required)
Boundary reminder:
- the retired proof/community lane is not part of the active local-runtime API contract.
- the active public proof surface is now sourced from orchestrator-owned public routes rather than
openclawdbot.
Operator truth reminder:
- this reference describes current route contract truth, not the final agent maturity state
- broader operator exposure should follow the same order documented in the capability pack: agent maturity first, runtime evidence second, task/route promotion third
Mission Control implementation note:
- the larger Mission Control blueprint remains future operator-console work only
- this phase adds persistent incidents, incident history/remediation routes, deeper workflow graphs, knowledge graphs, topology relationship edges, and agent capability readiness inside the existing console rails
- the canonical agent maturity target for these surfaces now lives in
docs/architecture/AGENT_CAPABILITY_MODEL.md - it does not adopt a new global shell, Three.js background, or starfield environment yet
Operator Console contract truth:
GET /api/health/extended: authoritative protected operator-health surface.GET /api/dashboard/overview: protected operator aggregation only. Useful for queue, approvals, governance, and recent-task visibility, but not authoritative system health.GET /api/runtime/facts: protected operator/runtime truth for effective persistence mode, heartbeat schedule, internal task classification, and resident-service expectations.GET /api/companion/overview,GET /api/companion/catalog,GET /api/companion/incidents,GET /api/companion/runs, andGET /api/companion/approvals: protected, bounded, read-only companion summaries for bridge and channel clients. These are the canonical external read surfaces; do not scrape operator-only payloads to recreate the same summary layer.GET /health: shallow public liveness only. It returns helper URLs for metrics, knowledge summary, and persistence health using the request host; the metrics helper uses the configured Prometheus port.GET /api/command-center/overview: public proof overview surface sourced directly from orchestrator runtime state.GET /api/command-center/control: public proof control-cluster surface for curated agent/control-lane visibility.GET /api/command-center/demandandGET /api/command-center/demand-live: public proof demand summary surfaces sourced from live orchestrator demand state.GET /api/milestones/latestandGET /api/milestones/dead-letter: public orchestrator milestone feed surfaces for latest visible proof and proof-risk items.GET /api/auth/me: protected auth identity surface.GET /api/persistence/health: public persistence dependency truth, now including the active persistence store (fileormongo) plus first-slice coordination status for Redis-backed claims, locks, and shared helper budgets.GET /api/tasks/catalog: protected operator capability surface for non-internal task types only.GET /api/tasks/runs: protected task-run surface that hides internal task types by default and acceptsincludeInternal=truewhen diagnostics need them./system-health: not a backend route; it is a frontend-only page path.
Operator Console Rendering Guardrails
Do not render these nested objects directly:
- the
controlPlanefield inGET /api/health/extended - the
controlPlane.queuefield inGET /api/health/extended - the
workersfield inGET /api/health/extended - the
repairsfield inGET /api/health/extended - the
dependenciesfield inGET /api/health/extended - the
dependencies.persistencefield inGET /api/health/extended - the
dependencies.knowledgefield inGET /api/health/extended - the
dependencies.githubfield inGET /api/health/extended - the
healthfield inGET /api/dashboard/overview - the
persistencefield inGET /api/dashboard/overview - the
accountingfield inGET /api/dashboard/overview - the
queuefield inGET /api/dashboard/overview - the
approvalsfield inGET /api/dashboard/overview - the
selfHealingfield inGET /api/dashboard/overview - the
governancefield inGET /api/dashboard/overview - the
incidentsfield inGET /api/dashboard/overview - the
recentTasksfield inGET /api/dashboard/overview - the
truthLayersfield inGET /api/health/extended - the
incidentsfield inGET /api/health/extended - the
topologyfield inGET /api/agents/overview
Safe leaf fields to render:
/api/auth/me:actor,role,apiKeyLabel,apiKeyExpiresAt/api/health/extended:status,controlPlane.routing,controlPlane.queue.queued,controlPlane.queue.processing,workers.declaredAgents,workers.spawnedWorkerCapableCount,workers.serviceExpectedCount,workers.serviceAvailableCount,workers.serviceInstalledCount,workers.serviceRunningCount,workers.serviceExpectedGapCount,workers.serviceOperationalCount(legacy compatibility alias),repairs.activeCount,repairs.verifiedCount,repairs.failedCount,dependencies.persistence.status,dependencies.persistence.database,dependencies.persistence.collections,dependencies.persistence.coordination.status,dependencies.persistence.coordination.store,dependencies.persistence.coordination.redisConfigured,dependencies.persistence.coordination.redisReachable,dependencies.persistence.coordination.detail,dependencies.knowledge.indexedEntries,dependencies.knowledge.conceptCount,dependencies.github.status,dependencies.github.summary,dependencies.github.repository,dependencies.github.branch,dependencies.github.latestRun.workflowName,dependencies.github.latestRun.conclusion,dependencies.github.latestRun.url,truthLayers.configured.status,truthLayers.configured.summary,truthLayers.observed.status,truthLayers.observed.summary,topology.status,topology.counts.totalNodes,topology.counts.totalEdges,topology.counts.relationshipEdges,topology.relationshipHistory.totalObservations,topology.relationshipHistory.lastObservedAt,topology.relationshipHistory.windows.short.totalObservations,topology.relationshipHistory.windows.long.totalObservations,topology.relationshipHistory.graph.totalNodes,topology.relationshipHistory.graph.totalEdges,topology.hotspots[],incidents.overallStatus,incidents.openCount,incidents.activeCount,incidents.bySeverity.critical,incidents.bySeverity.warning,incidents.bySeverity.info/api/dashboard/overview:health.fastStartMode,queue.queued,queue.processing,queue.pressure[],queue.pressure[].type,queue.pressure[].label,queue.pressure[].source,queue.pressure[].queuedCount,queue.pressure[].processingCount,queue.pressure[].totalCount,approvals.pendingCount,selfHealing.summary.totalCount,selfHealing.summary.activeCount,selfHealing.summary.verifiedCount,governance.taskRetryRecoveries.count,governance.taskRetryRecoveries.nextRetryAt,accounting.totalCostUsd,accounting.currentBudget,incidents.overallStatus,incidents.openCount,incidents.activeCount,incidents.watchingCount,incidents.bySeverity.critical,incidents.bySeverity.warning,incidents.bySeverity.info,incidents.topClassifications[],incidents.topClassifications[].classification,incidents.topClassifications[].label,incidents.topClassifications[].count,incidents.topClassifications[].activeCount,incidents.topClassifications[].watchingCount,incidents.topClassifications[].highestSeverity,recentTasks[],/api/agents/overview:agents[].id,agents[].orchestratorTask,agents[].spawnedWorkerCapable,agents[].serviceExpected,agents[].lifecycleMode,agents[].hostServiceStatus,agents[].serviceUnitName,agents[].serviceAvailable,agents[].serviceInstalled,agents[].serviceRunning,agents[].serviceUnitState,agents[].serviceUnitSubState,agents[].serviceUnitFileState,agents[].capability.currentReadiness,agents[].memory.totalRuns,agents[].memory.lastRunAtincidents.incidents[].linkedRunIds[],incidents.incidents[].linkedRepairIds[],incidents.incidents[].linkedProofDeliveries[],incidents.incidents[].recommendedSteps[],incidents.incidents[].policy.policyId,incidents.incidents[].policy.autoRetryBlockedRemediation,incidents.incidents[].policy.maxAutoRemediationAttempts,incidents.incidents[].policy.autoEscalateOnBreach,incidents.incidents[].policy.remediationTaskType,incidents.incidents[].policy.verifierTaskType,incidents.incidents[].policy.escalationTaskType,incidents.incidents[].policy.targetSlaMinutes,incidents.incidents[].escalation.level,incidents.incidents[].escalation.summary,incidents.incidents[].escalation.dueAt,incidents.incidents[].verification.required,incidents.incidents[].verification.status,incidents.incidents[].verification.summary,incidents.incidents[].remediation.status,incidents.incidents[].remediation.owner,incidents.incidents[].remediation.nextAction,incidents.incidents[].remediation.blockers[],incidents.incidents[].remediationPlan[],incidents.incidents[].policyExecutions[],recentTasks[].handledAt,recentTasks[].type,recentTasks[].result,recentTasks[].messageApproval payloads can now include review-gated Reddit lead promotions:manual-reviewleads require explicit approval, and the top10draftleads can be optionally promoted intoreddit-responsethrough the same replay surface./api/tasks/runsand/api/tasks/runs/:runId:repair.repairId,repair.classification,repair.status,repair.verificationMode,repair.verificationSummary,workflow.stage,workflow.graphStatus,workflow.currentStage,workflow.blockedStage,workflow.stopReason,workflow.stopClassification,workflow.awaitingApproval,workflow.retryScheduled,workflow.nextRetryAt,workflow.repairStatus,workflow.eventCount,workflow.stageDurations,workflow.timingBreakdown,workflow.nodeCount,workflow.edgeCount,workflowGraph.causalLinks[],workflowGraph.crossRunLinks[],workflowGraph.relatedRuns[],workflowGraph.dependencySummary,approval.required,approval.status,approval.requestedAt,approval.decidedAt,events[],proofLinks[], andworkflowGraph.{nodes,edges,events,proofLinks,stopClassification,timingBreakdown}plusworkflowGraph.{crossRunLinks,relatedRuns,dependencySummary}when present/api/incidentsand/api/incidents/:id: stable incident IDs, first/last-seen timestamps, acknowledgement state, owner,history[],policyExecutions[],acknowledgements[],ownershipHistory[],remediationTasks[], linked service/task/run/proof references, and current remediation guidance, policy, escalation, verification, and remediation plan state/api/incidents/:id/history: isolated incident history stream withhistory[],acknowledgements[],ownershipHistory[], and remediation task lifecycle records including assignment/execution/verification/resolution/api/knowledge/summaryand/api/knowledge/query: provenance, contradiction, and freshness graphs plusruntime.repairLoop/meta.repairLoopfor knowledge-repair posture/api/agents/overview: relationship history windows, observed relationship graph, target capability set, and evidence profiles showing how close an agent is to the capability target timestamps/api/approvals/pending:impact.riskLevel,impact.approvalReason,impact.dependencyClass,impact.affectedSurfaces,impact.dependencyRequirements,impact.caveats,payloadPreview.keyCount,payloadPreview.keys,payloadPreview.internalKeyCount/api/knowledge/summary:diagnostics.freshness,diagnostics.provenance,diagnostics.contradictionSignals,diagnostics.graphs.{provenance,contradictions,freshness},runtime.index,runtime.coverage,runtime.freshness,runtime.signals.coverage,runtime.signals.staleness,runtime.signals.contradictions,runtime.graphs.{provenance,contradictions,freshness}/api/knowledge/query: top-levelmeta(query-scoped freshness, provenance, contradiction signals, and knowledge graphs) andruntime(repo/runtime knowledge signals and knowledge graphs)/api/agents/overview:modelTier,allowedSkills[],capability.role,capability.spine,capability.currentReadiness,capability.targetCapabilities[],capability.evidence[],capability.presentCapabilities[],capability.missingCapabilities[],capability.evidenceProfiles[],capability.runtimeEvidence.latestSuccessfulRunId,capability.runtimeEvidence.latestSuccessfulTaskId,capability.runtimeEvidence.latestHandledAt,capability.runtimeEvidence.highlightKeys[],capability.runtimeEvidence.signals[],capability.ultraGapSummary,topology.edges[].relationship,relationshipHistory.totalObservations,relationshipHistory.lastObservedAt,relationshipHistory.byRelationship,relationshipHistory.byStatus,relationshipHistory.timeline[],relationshipHistory.recent[]/api/agents/overview:skill-audit-agentnow promotes governed-skill trust-state signals throughcapability.runtimeEvidence.signals[](trustPosture,policyHandoff,telemetryHandoff,intakeCoverage,restartSafetySummary) and agovernance-depthevidence profile.GET /api/tasks/runsandGET /api/tasks/runs/:runIdnow preserve the shared specialist-agent result contract for adapted lanes, includingoperatorSummary,recommendedNextActions[], andspecialistContract.{role,workflowStage,deliverable,status,refusalReason,escalationReason}when the owning agent emits them./health:status,timestamp
Auth persistence requirement:
- External operator-console frontends must persist the bearer token across preview/auth-bridge redirects. In-memory-only token state is not reliable for protected fetch flows when the hosting shell can redirect before protected route calls complete.
Operational worker-proof workflow used in the 2026-03-07 spawned-worker sweep:
POST /api/tasks/triggerGET /api/tasks/runsorGET /api/tasks/runs/:runIdGET /api/skills/audit(ToolGate preflight / execute evidence where present)GET /api/memory/recall?agentId=...
Run identity contract:
runIdreusespayload.idempotencyKeyonly when the caller supplies one- otherwise the task id becomes the run id, so normal trigger calls stay visible as distinct entries in
/api/tasks/runs
Interpretation note from the 2026-03-07 repair follow-up:
GET /api/dashboard/overviewnow exposes bounded repair evidence under theselfHealingfield, andGET /api/health/extendedexposes the parallel repair summary underrepairs, for the livedoc-drift -> drift-repair -> knowledge-pack verificationloop.GET /api/dashboard/overviewis intentionally lean and now returns the summary fields used by the Overview page only. Incident detail, agent topology, memory drill-down, and truth-layer detail stay on their dedicated routes so the overview surface remains fast on large ledgers.GET /api/tasks/runsandGET /api/tasks/runs/:runIdnow includerepairmetadata when a task run belongs to a tracked repair attempt.GET /api/tasks/runsandGET /api/tasks/runs/:runIdnow also exposeworkflow,approval, and orderedevents[]so frontends can render run replay state from real queue/approval/retry/repair evidence instead of inventing it client-side.GET /api/tasks/runsandGET /api/tasks/runs/:runIdnow also exposeproofLinks[]and canonicalworkflowGraphpayloads so the operator console can show where execution stopped across ingress, queue, approval, agent, result, and public-proof publication.- Those
proofLinks[]records are now derived from canonical proof workflow events, proof relationship observations, and linked incidents, so run detail can show concrete proof ids, type, status, target, summary, and last-attempt time. GET /api/tasks/runsandGET /api/tasks/runs/:runIdnow also exposeworkflowGraph.crossRunLinks[],workflowGraph.relatedRuns[], andworkflowGraph.dependencySummaryso consumers can inspect upstream/downstream remediation handoffs and run dependencies instead of inferring them from stage events alone.GET /api/tasks/runsandGET /api/tasks/runs/:runIdnow also expose boundedresultSummarypayloads with top-level agent result keys plus sanitizedhighlightsfor promoted capability fields such asqueueBudgetFusion,routingDecision,acceptanceCoverage, andcomparisonReadiness, so operator surfaces can render real worker output contracts without shipping full raw agent artifacts through the API.GET /api/tasks/runsandGET /api/tasks/runs/:runIdnow also expose deterministic execution accounting:model,cost,latency,usage,budget, andaccounting. Local-only runs reportcost=0with an explicit unmetered note; metered helper runs can attach provider model and token-based spend when the worker reports usage.GET /api/approvals/pendingnow includesimpactandpayloadPreviewmetadata so approval review UIs can surface risk, affected surfaces, and replay semantics without reconstructing those fields on the client.GET /api/agents/overviewnow exposesserviceExpected,lifecycleMode, andhostServiceStatusso operator clients can tell the difference between service-expected agents and worker-first agents without inferring that contract from booleans alone.serviceUnitNameis also returned as the canonical host hint for systemd-backed troubleshooting.GET /api/agents/overviewalso exposesserviceAvailableseparately fromserviceInstalledandserviceRunning. The olderserviceImplementationandserviceOperationalfields remain compatibility aliases and should not be treated as stronger truth than the explicit split fields.serviceRunning=falseis valid host truth when the unit is absent or inactive;nullis reserved for probe-unavailable cases. Per-agent host hints now also includeserviceUnitState,serviceUnitSubState, andserviceUnitFileState.GET /api/health/extendednow exposes aggregate service-mode truth asworkers.serviceExpectedCountandworkers.serviceExpectedGapCount, and the paralleltruthLayers.observed.serviceModeblock distinguishes service-mode expectations from raw service entrypoint presence. This keeps worker-only agents from being mistaken for unmet service installs.GET /api/dashboard/overviewnow also exposes anaccountingblock with aggregate spend, metered/unmetered run counts, token totals, average latency, per-model rollups, and the latest Reddit-helper budget posture when that helper has emitted a budget snapshot.POST /api/incidents/:id/acknowledgeandPOST /api/incidents/:id/ownernow mutate the persistent incident ledger. Incident payloads returned by overview and extended health retain stable IDs, first/last seen timestamps, acknowledgement state, owner, linked service/task/run/proof references, and remediation steps.GET /api/incidentsandGET /api/incidents/:idnow expose incident list and detail views from the persistent ledger. The detail route materializes the current incident plus embedded lifecycle arrays including acknowledgement history, explicit ownership lifecycle history, policy execution history, and linked remediation task records.GET /api/incidents/:id/historynow provides the incident lifecycle stream as a dedicated route for consumers that only need lifecycle progression (history,acknowledgements,ownershipHistory,policyExecutions, and remediation task status) without relying on the broader materialized incident detail payload.POST /api/incidents/:id/remediatenow creates a linked remediation task using an allowlisted remediation mapping (drift-repair,build-refactor,qa-verification, orsystem-monitor) and persists that linkage back into the incident ledger.- Manual
build-refactorincident remediation now carries approval-bounded code-remediation payloads with verifier-linked constraints so code surgery can enter the incident/remediation/verifier loop without bypassing approval gates. - Incident remediation policy is now operational rather than descriptive: policy records include automatic retry / escalation controls, and
policyExecutions[]captures when the orchestrator automatically assigned an owner, queued a primary remediation lane, retried a blocked remediation, queued a verifier lane, or escalated a breached incident. GET /api/agents/overviewnow also exposes capability readiness derived from real runtime evidence. It distinguishes declared/foundation/operational/ advanced readiness and reports remaining capability gaps instead of labeling agents “ultra” prematurely.GET /api/agents/overviewnow also exposescapability.runtimeEvidence, a bounded latest-successful-run view that promotes agent-specific readiness signals into the capability surface. Wave 1 examples includetaskSpecificKnowledge,entityFreshnessLedger,contradictionGraph,partialCompletion,dependencyPlan,workflowMemory,queueBudgetFusion,operatorClosureEvidence,trendSummary,regressionReview,exploitabilityRanking,remediationClosure,acceptanceCoverage,closureContract, andreproducibilityProfile. Wave 2 examples now includeproviderPosture,publicationPolicy,operationalCompression,artifactCoverage,comparisonReadiness, anddeltaCapture. Wave 3 examples now includescopeContract,surgeryProfile,verificationLoop,impactEnvelope,refusalProfile,trustPosture,policyHandoff,telemetryHandoff,intakeCoverage, andrestartSafetySummary.- Those Wave 2 readiness signals now correspond to durable runtime artifacts in the underlying agent contracts, not just summary hints. Current examples include systematic community routing from
reddit-helper, evidence-attached publishing schema fromcontent-agent, replay artifacts fromsummarization-agent, uniform artifact records fromdata-extraction-agent, explainable schema/dedupe decisions fromnormalization-agent, and durable internal signal packs frommarket-research-agent. - Those Wave 3 readiness signals now correspond to governed code and skill runtime artifacts rather than hidden worker-only metadata. Current examples include bounded code-surgery contracts plus verifier handoff posture from
build-refactor-agent, and governed-skill trust-state depth fromskill-audit-agent. GET /api/agents/overviewnow also exposestopology, a derived graph oforchestrator -> task -> agent -> skillrelationships plus boundedagent -> agentrelationship edges (feeds-agent,verifies-agent,monitors-agent,audits-agent,coordinates-agent). This is derived from manifests, task/skill contracts, declared ultra-agent roles, and current runtime evidence.GET /api/agents/overviewnow also exposesrelationshipHistory, a bounded history view of observed delegation, tool-use, verification, and monitoring edges over time.topologyremains the current graph shape;relationshipHistorypreserves the underlying observed events and their hourly aggregation window for operator replay.GET /api/health/extendedexposestruthLayersso frontends can distinguish declared control-plane intent, current runtime configuration, and observed operator state without inventing those distinctions client-side.GET /api/dashboard/overviewandGET /api/health/extendedboth expose runtime-governedincidentssummaries refreshed from persistence, repairs, retry recovery, agent service gaps, approval backlog, and knowledge freshness/contradiction signals. Full incident detail remains onGET /api/incidentsandGET /api/incidents/:id.GET /api/knowledge/summaryandPOST /api/knowledge/querynow expose knowledge freshness, provenance, contradiction signals, and runtime coverage/staleness signals. They now also expose first-class provenance, contradiction, and freshness graphs so consumers can reason over topology and drift rather than just scalar signals. These are deterministic diagnostics based on the current knowledge base and indexed doc/runtime state; they are not speculative AI summaries.
The route contract above is authoritative for operator-console integration. Generic handler/type sketches lower in this file are legacy orientation only; runtime code wins if those examples diverge.
Companion Read Contract
The companion route family is the canonical read-first contract for OpenClaw plugins and channel clients.
Use these bounded routes instead of scraping /operator or reconstructing the control plane from multiple operator-only payloads:
GET /api/companion/overviewGET /api/companion/catalogGET /api/companion/incidentsGET /api/companion/runsGET /api/companion/approvals
These routes are intentionally:
- read-only
- bounded
- explicit about role and rate-limit posture through
x-openclaw-access - suitable for read-first channel integration while keeping
POST /api/tasks/triggeras the explicit write path
Machine-Readable Contract
GET /api/openapi.jsonis the machine-readable companion to this document.- It now covers the active public proof routes and the protected operator route families, including request bodies, query/path parameters, success/error response schemas, security schemes, and response headers that matter to real clients.
- Protected operations also carry explicit role and limiter metadata in the
x-openclaw-accessextension so RBAC and bucket policy stay attached to the route contract instead of being implied only by prose. - Companion operations also carry explicit
companionViewmetadata in the samex-openclaw-accessextension so bridge and channel clients can bind to the canonical external view contract rather than infer it from route names alone. - Current supporting clients (
operator-s-console/src/lib/api.ts) are aligned to those same authoritative routes, andoperator-s-console/is the canonical tracked/operatorclient in this repo.
CORS Contract (Direct Frontend Integration)
- CORS policy is backend-owned and deny-by-default.
- Cross-origin requests from origins not on the allowlist are rejected (
403). - No wildcard origin (
*) policy is used. - Required header for protected routes:
Authorization: Bearer <token>. - Default preflight-allowed methods:
GET, POST(+OPTIONShandling). - Default preflight-allowed request headers:
Authorization, Content-Type. - Default exposed response headers:
X-Request-Id, X-API-Key-Expires, ratelimit-limit, ratelimit-remaining, ratelimit-reset, Retry-After. - Credentials are disabled by default (
corsAllowCredentials=false) unless explicitly enabled.
Configuration keys (JSON config or env override):
corsAllowedOrigins/ORCHESTRATOR_CORS_ALLOWED_ORIGINScorsAllowedMethods/ORCHESTRATOR_CORS_ALLOWED_METHODScorsAllowedHeaders/ORCHESTRATOR_CORS_ALLOWED_HEADERScorsExposedHeaders/ORCHESTRATOR_CORS_EXPOSED_HEADERScorsAllowCredentials/ORCHESTRATOR_CORS_ALLOW_CREDENTIALScorsMaxAgeSeconds/ORCHESTRATOR_CORS_MAX_AGE_SECONDS
Response Cache Contract
The orchestrator now uses a bounded response cache for repeated read-heavy API surfaces.
Runtime truth:
- If
REDIS_URLis reachable, cached responses are stored in Redis. - If Redis is unavailable or not configured, the orchestrator falls back to a bounded in-memory cache instead of failing startup.
- Cache invalidation is tag-based and tied to orchestrator runtime state writes. Knowledge surfaces are invalidated separately from broader runtime state.
Operator-visible headers:
X-OpenClaw-Cache: hit|missX-OpenClaw-Cache-Store: redis|memoryCache-Control: public|max-age=*for public cached readsCache-Control: private|max-age=*plusVary: Authorizationfor protected cached reads
Current cached read surfaces:
- Public:
GET /api/knowledge/summaryGET /api/openapi.jsonGET /api/persistence/health
- Protected:
GET /api/companion/overviewGET /api/companion/catalogGET /api/companion/incidentsGET /api/companion/runsGET /api/companion/approvalsGET /api/runtime/factsGET /api/tasks/catalogGET /api/approvals/pendingGET /api/incidentsGET /api/incidents/:idGET /api/incidents/:id/historyGET /api/dashboard/overviewGET /api/agents/overviewGET /api/memory/recallPOST /api/knowledge/queryGET /api/tasks/runsGET /api/tasks/runs/:runIdGET /api/skills/registryGET /api/skills/policyGET /api/skills/telemetryGET /api/skills/auditGET /api/health/extendedGET /api/persistence/summary
The response cache is intended to reduce repeated operator-console fetch cost for identical route/query/body/auth combinations over short windows. It is not a claim of browser-side offline caching or immutable API payloads.
Rate Limits And 429 Handling
Current runtime limiter policy:
- Public monitoring endpoints:
/health:1000 requests / 60s / IP/api/persistence/health:1000 requests / 60s / IP
- Public read endpoints:
/api/knowledge/summary,/api/openapi.json:30 requests / 60s / IP
- Protected endpoints:
- pre-auth abuse guard:
300 requests / 60s / IP - bucket A (
viewer-read):120 requests / 60sper authenticated actor/key label for protected read routes (GETvisibility endpoints including/api/skills/audit,/api/health/extended,/api/persistence/summary, and the read-first/api/companion/*summaries) - bucket B (
operator-write):30 requests / 60sper authenticated actor/key label for protected write routes (POST /api/tasks/trigger,POST /api/approvals/:id/decision,POST /api/knowledge/query) - bucket C (
admin-export):10 requests / 60sper authenticated actor/key label for admin export routes (GET /api/knowledge/export,GET /api/persistence/export) - authenticated bucket key precedence:
req.auth.actor->req.auth.apiKeyLabel[:version]-> IP fallback
- pre-auth abuse guard:
Client contract:
- Treat
429as expected flow-control, not a fatal API outage. - On
429, respectRetry-Afterfirst. - If
Retry-Afteris absent, useratelimit-resetas the minimum wait. - Normal operator-console polling is supported by bucket A, but avoid synchronized parallel bursts; stagger polling intervals with jitter.
Task Handler Interface
All task handlers follow this signature:
async function taskHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult>Where:
state(OrchestratorState): Current system stateconfig(OrchestratorConfig): Loaded configuration- Returns:
TaskResultwith status, result, and optional error
Example Handler
async function myTaskHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult> {
const startTime = Date.now();
try {
// Do work
const result = {
itemsProcessed: 42,
success: true
};
return {
status: 'completed',
result,
durationMs: Date.now() - startTime
};
} catch (error) {
return {
status: 'error',
error: String(error),
durationMs: Date.now() - startTime
};
}
}Core Types
OrchestratorState
interface OrchestratorState {
lastStartedAt: string; // ISO 8601
tasksProcessed: number; // Total count
taskHistory: TaskRecord[]; // Last 50
docsIndexed: string[]; // File paths
redditResponses: RedditRecord[]; // Last 100
rssDrafts: RSSRecord[]; // Last 200
deployedAgents: DeploymentRecord[]; // This session
notes?: string; // User notes
}TaskRecord
interface TaskRecord {
type: string;
status: 'pending' | 'completed' | 'error';
timestamp: string; // ISO 8601
durationMs: number; // Milliseconds
result?: any; // Task-specific
error?: string; // Error message
}TaskResult
interface TaskResult {
status: 'pending' | 'completed' | 'error';
result?: any; // Task-specific output
error?: string; // Error message if status="error"
durationMs?: number; // How long took
}OrchestratorConfig
interface OrchestratorConfig {
docsPath: string; // Path to docs
logsDir: string; // Where to write logs
stateFile: string; // Where to persist state
deployBaseDir?: string; // Where agents deploy
rssConfigPath?: string; // RSS filter config
redditDraftsPath?: string; // Reddit drafts log
knowledgePackDir?: string; // Knowledge pack dir
notes?: string; // Custom notes
}RedditRecord
interface RedditRecord {
timestamp: string; // ISO 8601
postId: string; // Reddit ID
postTitle: string; // Post title
subreddit: string; // Subreddit name
draftResponse: string; // Proposed response
confidence: number; // 0-1 score
approved?: boolean; // Human approval
posted?: string; // When posted (ISO 8601)
}RSSRecord
interface RSSRecord {
timestamp: string; // ISO 8601
feedUrl: string; // Feed URL
itemTitle: string; // Item title
itemUrl: string; // Item link
publishedAt: string; // ISO 8601
relevanceScore: number; // 0-100
urgency: 'high' | 'medium' | 'low';
notes?: string; // Summary/reason
}DeploymentRecord
interface DeploymentRecord {
timestamp: string; // ISO 8601
agentName: string; // Template name
deployPath: string; // Deployment path
metadata?: {
version?: string;
tags?: string[];
config?: any;
};
}Built-in Task Handlers
startupHandler()
async function startupHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult>What it does: Initialize orchestrator, load config, build doc index
Result structure:
{
"configLoaded": true,
"docsIndexed": 42,
"stateInitialized": true
}Spawns agents: No
docSyncHandler()
async function docSyncHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult>What it does: Check for doc changes, regenerate knowledge pack if needed
Result structure:
{
"filesIndexed": 42,
"changeDetected": true,
"knowledgePackGenerated": true
}Spawns agents: Yes (doc-specialist if changes detected)
drift-repairHandler()
async function driftRepairHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult>What it does: Full audit of docs, regenerate knowledge pack
Result structure:
{
"filesAudited": 42,
"driftDetected": false,
"knowledgePackRegenerated": true,
"agentAuditResult": { ... }
}Spawns agents: Yes (doc-specialist)
redditResponseHandler()
async function redditResponseHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult>What it does: Monitor Reddit, draft responses using knowledge pack
Result structure:
{
"postsEvaluated": 12,
"draftedResponses": 3,
"draftsLogPath": "logs/reddit-drafts.jsonl",
"agentResult": { ... }
}Spawns agents: Yes (reddit-helper)
rssSweepHandler()
async function rssSweepHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult>What it does: Parse RSS feeds, score/filter, generate drafts
Result structure:
{
"feedsParsed": 3,
"entriesParsed": 127,
"entriesScored": 127,
"highPriorityItemsCount": 5,
"draftsLogPath": "logs/rss-drafts.jsonl"
}Spawns agents: No
heartbeatHandler()
async function heartbeatHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult>What it does: Internal control-plane maintenance and diagnostics collection
Result structure:
{
"uptime": 3600000,
"memoryUsageMb": 127,
"taskQueueDepth": 2,
"healthStatus": "ok"
}Spawns agents: No
Operator exposure: Internal-only. The scheduler uses it every 5 minutes, but POST /api/tasks/trigger does not accept it as a normal public task type.
agentDeployHandler()
async function agentDeployHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult>What it does: Deploy template agent to deploy directory
Result structure:
{
"agentName": "doc-specialist",
"deployPath": "agents-deployed/doc-specialist-1705416768123",
"deploymentMetadata": { ... }
}Spawns agents: No (creates copy)
Agent Spawning
When a handler needs to spawn an agent:
import { spawn } from 'child_process';
const result = spawn('tsx', [
'src/index.ts',
'--payload', '/tmp/payload-123.json'
], {
cwd: '/path/to/agent',
stdio: ['pipe', 'pipe', 'inherit'] // ignore stdin, capture stdout, inherit stderr
});
// Collect stdout (agent output)
const chunks = [];
result.stdout.on('data', chunk => chunks.push(chunk));
// Wait for completion
result.on('close', (code) => {
const output = Buffer.concat(chunks).toString();
const agentResult = JSON.parse(output);
// ... handle agentResult
});The orchestrator passes task context via JSON file in --payload argument.
Utility Functions
State Persistence
// Load state from the configured persistence target
const state = await loadState(config.stateFile);
// Save state back to the configured persistence target
await saveState(state, config.stateFile);Documentation Indexing
// Watch docs directory and emit changes
const indexer = new DocIndexer(config.docsPath);
indexer.on('fileChanged', (path) => {
console.log(`Doc changed: ${path}`);
// Trigger doc-sync or doc-change task
});
// Get current index
const docs = indexer.getIndexedDocs();Task Queue
// Add task to queue
queue.add({
type: 'doc-sync',
priority: 'normal'
});
// Listen for completions
queue.on('completed', (task, result) => {
console.log(`Task ${task.type} completed`);
});
// Listen for errors
queue.on('error', (task, error) => {
console.error(`Task ${task.type} failed: ${error}`);
});Error Handling
All task handlers should wrap their work in try-catch:
try {
// Do work
const result = await doSomething();
return { status: 'completed', result };
} catch (error) {
return {
status: 'error',
error: error instanceof Error ? error.message : String(error)
};
}Errors are logged and recorded in state history. The orchestrator continues running (doesn't crash).
Custom Task Handler Template
// In taskHandlers.ts
export async function myCustomHandler(
state: OrchestratorState,
config: OrchestratorConfig
): Promise<TaskResult> {
const startTime = Date.now();
try {
// Validate config
if (!config.myCustomField) {
throw new Error('Missing myCustomField in config');
}
// Do work
const result = {
itemsProcessed: 0,
successCount: 0
};
// Optional: spawn agent
// const agentResult = await spawnAgent(...);
// result.agentResult = agentResult;
// Update state
state.taskHistory.push({
type: 'my-custom-task',
status: 'completed',
timestamp: new Date().toISOString(),
durationMs: Date.now() - startTime,
result
});
return { status: 'completed', result };
} catch (error) {
return {
status: 'error',
error: String(error),
durationMs: Date.now() - startTime
};
}
}
// Register in handlers map
export const handlers: Record<string, TaskHandler> = {
startup: startupHandler,
'doc-sync': docSyncHandler,
'my-custom-task': myCustomHandler, // ← Add here
// ... other handlers
};Then add schedule in index.ts:
setInterval(async () => {
queue.add({
type: 'my-custom-task'
});
}, 1000 * 60 * 10); // Every 10 minutesSee Task Types for detailed task descriptions.