Agent APIs
API endpoints for agent registration, lifecycle management, and task coordination.
Status: Partial implementation (registration/lifecycle endpoints, durable Postgres agent store, internal CA bootstrap, external task polling/result runtime, and local compose bootstrap implemented; full containerized E2E remains tracked) Source: Architecture Advisory Section 6.3.2 Related Modules: Agents Module, Agent Security
Overview
The Agent API provides endpoints for registering deployment agents, managing their lifecycle, and coordinating task execution. Agents use mTLS for secure communication after initial registration.
Implementation note (2026-05-09): the standalone ReleaseOrchestrator WebApi registers the production IAgentTaskTransport DI boundary and an unhealthy fail-closed readiness check when no verified transport implementation is present. When ReleaseOrchestrator:AgentTaskTransport:Enabled=true, WebApi registers the mTLS polling runtime below. Agent registration and task state are durable through PostgresAgentStore / PostgresAgentTaskStore; registered agents are written to release.agents, which is the same table read by the topology catalog. Registered agents receive short-lived client certificates signed by the Release Orchestrator internal agent CA. Certificate issuance must complete before an active agent row is persisted; the store writes the agent row and public certificate PEM together so CA/bootstrap failures do not leave certificate-less active agents. The CA root is file-backed through ReleaseOrchestrator:AgentCertificateAuthority:RootPfxPath; set RootPfxPassword from a mounted secret and mount the PFX path on durable storage in compose or production.
Registration Endpoints
Create Registration Token
Endpoint: POST /api/v1/release-orchestrator/agents/registration-tokens
Creates a one-time registration token. Requires Release Orchestrator operate authorization and tenant context.
Headers:
Authorization: Bearer {operator-token}
X-StellaOps-TenantId: {tenant-id}
Request:
{
"name": "agent-prod-01",
"displayName": "Agent Prod 01",
"capabilities": ["docker", "compose"],
"validFor": "24:00:00"
}
Response: 201 Created
{
"id": "uuid",
"name": "agent-prod-01",
"displayName": "Agent Prod 01",
"capabilities": ["Docker", "Compose"],
"token": "one-time-token",
"expiresAt": "2026-01-11T14:23:45Z",
"createdAt": "2026-01-10T14:23:45Z"
}
Register Agent
Endpoint: POST /api/v1/release-orchestrator/agents
Consumes a one-time registration token and returns the agent id plus a one-time PEM bundle. The standalone agent-core host stores the returned certificate and key on a private local secret mount before it starts polling.
Request:
{
"token": "one-time-token",
"hostInfo": {
"hostname": "agent-prod-01",
"platform": "compose",
"version": "dev"
},
"labels": {
"datacenter": "us-east-1",
"role": "deployment"
}
}
Response: 201 Created
{
"id": "uuid",
"name": "agent-prod-01",
"displayName": "Agent Prod 01",
"status": "Active",
"capabilities": ["Docker", "Compose"],
"hostname": "agent-prod-01",
"version": "dev",
"certificateThumbprint": "ABC123",
"certificateExpiresAt": "2026-01-11T14:23:45Z",
"certificatePem": "-----BEGIN CERTIFICATE-----...",
"privateKeyPem": "-----BEGIN PRIVATE KEY-----...",
"registeredAt": "2026-01-10T14:23:45Z"
}
Notes:
- Registration token is single-use and expires after 24 hours unless the token request supplies a shorter
validFor. - After registration, agent must use mTLS for all subsequent requests
- Certificate is short-lived (24h by default). Automatic rotation is still a tracked follow-up; local compose reruns bootstrap or restarts daily.
Local Compose Launch
The local compose lane provides agent-core under the agents profile in devops/compose/docker-compose.stella-services.yml. The normal local entry point is the launcher:
devops\compose\scripts\launch-agent-core.ps1
The launcher reuses devops/compose/env/agent-core.local.env when local enrollment material already exists, registers only when needed, starts agent-core through the canonical compose chain, verifies the container Agent__AgentId, and waits for a Release Orchestrator heartbeat. Use agent-bootstrap.ps1 -NoComposeUp or agent-bootstrap.sh --no-compose-up only when another installer owns process launch.
The Console’s Enroll a host and topology-wizard surfaces expose this bootstrap path and the real enrolled-agent registry. They deliberately do not offer a registration-only “in-process lab agent”: minting a token and creating an agent row without starting a polling process produces an executor that cannot claim deployment tasks. A host is presented as enrolled execution capacity only after the real bootstrap/launcher starts its agent and the registry reports its heartbeat.
The scripts write ignored local files under devops/compose/env/agent-core.local.env and devops/compose/offline/agent-core/. See devops/compose/agent-bootstrap.md.
For Bash-only lanes, the bootstrap script can still enroll and start the service through the compose helper:
devops/compose/scripts/agent-bootstrap.sh
Execution Plugin Overlays
First-party execution plugin payloads are mounted bundles, not agent image contents. Agent-core image rebuilds still carry the host runtime, required CLIs, and the sandbox guest launcher at /app/tools/StellaOps.Plugin.Host; publish-first image helpers stage that tool separately from the mounted execution plugin bundles. Generate the local signed profiles before rendering or starting an execution-plugin agent:
powershell -NoProfile -ExecutionPolicy Bypass -File .\devops\build\package-execution-plugins.ps1 -Profile default
powershell -NoProfile -ExecutionPolicy Bypass -File .\devops\build\package-execution-plugins.ps1 -Profile optional-build-script
Render the default profile through the compose wrapper with the profile-gated agent service enabled:
$env:COMPOSE_PROFILES = 'agents'
$env:COMPOSE_EXTRA_FILES = 'docker-compose.plugins.execution.yml'
.\devops\compose\scripts\compose-cli.ps1 config --format json
The default overlay mounts devops/plugins/execution/default and the execution trust root read-only into agent-core, and declares only build.docker, deploy.compose.up, deploy.exec, deploy.artifact-extract, and deploy.docker-plugin beyond the platform transport capabilities. The launcher defaults to its historical lab overlay for compatibility; use -ExecutionPluginOverlay default when proving the production-shaped default bundle set. Add the optional overlay only when the operator accepts operator-supplied build-script containers and Ansible playbook obligations:
$env:COMPOSE_PROFILES = 'agents'
$env:COMPOSE_EXTRA_FILES = 'docker-compose.plugins.execution.yml;docker-compose.plugins.execution-optional.yml'
.\devops\compose\scripts\compose-cli.ps1 config --format json
Do not persist full compose renders as QA artifacts; they can include local environment values. Store sanitized agent-core plugin env keys and plugin-related volume targets instead.
For Sprint 017 BLD-003 live proof, build an explicit reachability-capable agent image and enable the runtime switches only for that run:
$env:AGENT_CORE_INCLUDE_BUILD_REACHABILITY = 'true'
$env:AGENT_CORE_BUILD_COPRODUCTION_ENABLED = 'true'
$env:AGENT_CORE_BUILD_REACHABILITY_PRODUCER_ENABLED = 'true'
Lifecycle Endpoints
List Agents
Endpoint: GET /api/v1/release-orchestrator/agents
Query Parameters:
status(string): Filter by status (Active,Pending,Stale,Revoked)capability(string): Filter by capability (docker,compose,ssh,winrm,ecs,nomad)
Response: 200 OK
[
{
"id": "uuid",
"name": "agent-prod-01",
"version": "1.0.0",
"status": "online",
"capabilities": ["docker", "compose"],
"lastHeartbeat": "2026-01-10T14:23:45Z",
"resourceUsage": {
"cpu": 15.5,
"memory": 45.2
}
}
]
Get Agent
Endpoint: GET /api/v1/release-orchestrator/agents/{id}
Response: 200 OK - Full agent details including assigned targets
Update Agent
Endpoint: PUT /api/v1/release-orchestrator/agents/{id}
Request:
{
"labels": {
"datacenter": "us-west-2"
},
"capabilities": ["docker", "compose", "ssh"]
}
Response: 200 OK - Updated agent
Delete Agent
Endpoint: DELETE /api/v1/release-orchestrator/agents/{id}
Revokes agent credentials and removes registration.
Response: 200 OK
{ "deleted": true }
Heartbeat Endpoints
Send Heartbeat
Endpoint: POST /api/v1/agents/{id}/heartbeat
Agents must send heartbeats at the configured interval to maintain online status and receive pending tasks.
Request:
{
"status": "healthy",
"resourceUsage": {
"cpu": 15.5,
"memory": 45.2,
"disk": 60.0
},
"capabilities": ["docker", "compose"],
"runningTasks": 2
}
Response: 200 OK
{
"tasks": [
{
"taskId": "uuid",
"taskType": "docker.pull",
"payload": {
"image": "myapp",
"tag": "v2.3.1",
"digest": "sha256:abc123..."
},
"credentials": {
"registry.username": "user",
"registry.password": "token"
},
"timeout": 300
}
],
"certificateRenewal": {
"cert": "-----BEGIN CERTIFICATE-----...",
"expiresAt": "2026-01-11T14:23:45Z"
}
}
Notes:
- Certificate renewal is included when current certificate is within 1 hour of expiration
- Tasks array contains pending work for the agent
- Missing heartbeats for 3 intervals marks agent as
offline
Task Endpoints
Poll Task (implemented)
Endpoint: POST /api/v1/release-orchestrator/agent-runtime/agents/{agentId}/tasks:poll
Agents poll this endpoint over mTLS to receive one pending assignment. The route agentId, body agentId, and mTLS certificate thumbprint must all bind to the same registered active agent. The agent must declare the assignment’s required capability before the assignment is returned.
Request:
{
"contractVersion": "agent-task-transport/v1",
"agentId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"capabilities": ["compose"]
}
Response: 200 OK
{
"contractVersion": "agent-task-transport/v1",
"assignment": {
"contractVersion": "agent-task-transport/v1",
"assignmentId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"taskId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"agentId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"requiredCapability": "compose",
"taskType": "compose.up",
"payload": "{\"projectName\":\"payments\",\"composeLock\":\"services: {}\"}",
"credentials": {},
"variables": { "projectName": "payments" },
"assignedAt": "2026-04-29T11:00:00Z",
"timeout": "00:05:00"
}
}
Other responses:
204 No Contentwhen no task is pending.401 Unauthorizedwhen the mTLS client certificate is missing.403 Forbiddenwhen the agent id or certificate thumbprint does not match the pending assignment.400 Bad Requestwhen the contract version is unsupported.503 Service Unavailablewhen the transport is disabled/unavailable.
Submit Signed Task Result (implemented)
Endpoint: POST /api/v1/release-orchestrator/agent-runtime/agents/{agentId}/tasks/{taskId}/result
The result is accepted only when the route task id equals the body taskId, the agent id matches the mTLS-bound assignment, and signature verifies over the stable result payload with the mTLS certificate public key. Supported signature algorithms are rsa-sha256 and ecdsa-sha256.
Request:
{
"contractVersion": "agent-task-transport/v1",
"agentId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"taskId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"success": true,
"message": "completed",
"resultData": "{\"container\":\"ok\"}",
"duration": "00:00:03",
"completedAt": "2026-04-29T11:00:03Z",
"signatureAlgorithm": "rsa-sha256",
"signature": "base64-signature"
}
Response: 202 Accepted
{
"status": "accepted",
"taskId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}
Failure behavior: unsigned, invalidly signed, mismatched-agent, mismatched-task, stale, timed-out, or unconfigured transport results are rejected and cannot advance deployment state to success.
Complete Task
Endpoint: POST /api/v1/agents/{id}/tasks/{taskId}/complete
Reports task completion status back to the orchestrator.
Request:
{
"success": true,
"result": {
"imageId": "sha256:abc123...",
"containerId": "container-uuid"
},
"logs": [
{ "timestamp": "2026-01-10T14:23:45Z", "level": "info", "message": "Pulling image..." },
{ "timestamp": "2026-01-10T14:23:50Z", "level": "info", "message": "Image pulled successfully" }
]
}
Response: 200 OK
{ "acknowledged": true }
Get Pending Tasks
Endpoint: GET /api/v1/agents/{id}/tasks
Alternative to heartbeat for polling pending tasks.
Response: 200 OK
{
"tasks": [
{
"taskId": "uuid",
"taskType": "docker.run",
"priority": 10,
"createdAt": "2026-01-10T14:20:00Z"
}
]
}
WebSocket Endpoints
Task Stream
Endpoint: WS /api/v1/agents/{id}/task-stream
Real-time task assignment stream for agents.
Messages (Server to Agent):
{ "type": "task_assigned", "task": { "taskId": "uuid", "taskType": "docker.pull", ... } }
{ "type": "task_cancelled", "taskId": "uuid" }
Messages (Agent to Server):
{ "type": "task_progress", "taskId": "uuid", "progress": 50, "message": "Pulling layer 3/5" }
{ "type": "task_log", "taskId": "uuid", "level": "info", "message": "..." }
Error Responses
| Status Code | Description |
|---|---|
401 | Invalid or expired registration token |
403 | Agent not authorized for this operation |
404 | Agent not found |
409 | Agent name already registered |
503 | Agent offline or unreachable |
