Skip to main content
The executor contract is simple: any HTTP service you run that exposes four endpoints becomes a WattSwarm executor. The kernel does not care what language you use, what model you call, or how complex your internal logic is — as long as your service responds correctly to /health, /capabilities, /execute, and /verify, you can plug it into any task or run in your swarm. The reference wattswarm-runtime binary demonstrates this pattern as a minimal toy implementation you can use as a starting point.

The Four Required Endpoints

GET /health

Returns a liveness signal. The kernel calls this before using an executor and when you run executors check.

GET /capabilities

Declares what task types, profiles, and model identity this executor supports. The kernel uses this to validate that a task’s task_type is handled before dispatching.

POST /execute

Accepts an ExecuteRequest and returns an ExecuteResponse. This is where your agent does the actual work.

POST /verify

Accepts a VerifyRequest and returns a VerifyResponse. This is where your agent validates a candidate output against the task policy.

GET /health

{ "status": "ok" }

GET /capabilities

{
  "task_types": ["swarm", "topic_interpretation"],
  "profiles": ["default", "careful"],
  "provider_family": "my-runtime",
  "model_id": "my-model-v1"
}
FieldPurpose
task_typesList of task_type strings this executor can handle
profilesNamed execution profiles (passed back in ExecuteRequest.profile)
provider_familyA stable identifier for your runtime family
model_idThe specific model or version within the family

ExecuteRequest Fields

The kernel sends this payload to POST /execute when it claims a task and is ready for execution:
{
  "task_id": "task-demo-001",
  "execution_id": "exec-uuid-abc123",
  "task_type": "swarm",
  "inputs": {
    "prompt": "Summarize the key risks.",
    "agent_id": "analyst-a"
  },
  "profile": "default",
  "task_contract": { "...full TaskContract..." },
  "stage": "explore",
  "attempt_id": "attempt-1",
  "seed_bundle": null
}
FieldTypePurpose
task_idstringThe task being executed
execution_idstringUnique ID for this execution attempt; use for idempotency
task_typestringMatches your declared capability
inputsobjectMerged inputs from TaskContract.inputs (includes shared_inputs for run steps)
profilestringExecution profile to apply
task_contractobjectFull TaskContract including output_schema and budget
stagestringCurrent lifecycle stage: explore, verify, or finalize
attempt_idstringAttempt identifier for retry tracking
seed_bundleobject or nullOptional knowledge seed from prior decisions on similar tasks

ExecuteResponse Fields

Your /execute handler must return this shape:
{
  "candidate_output": {
    "answer": "The three key risks are ...",
    "confidence": 0.92
  },
  "evidence_inline": [
    {
      "mime": "text/plain",
      "content": "trace:attempt-1"
    }
  ],
  "evidence_refs": [
    {
      "uri": "https://runtime.local/task-demo-001/exec-uuid-abc123",
      "digest": "sha256:abc123...",
      "size_bytes": 1024,
      "mime": "application/json",
      "created_at": 1710000000000,
      "producer": "my-runtime/my-model-v1"
    }
  ]
}
FieldTypePurpose
candidate_outputJSON objectThe agent’s answer. Must satisfy output_schema from the task contract.
evidence_inlinearray of {mime, content}Small evidence payloads carried inline (kept under payload size caps)
evidence_refsarray of ArtifactRefReferences to external evidence artifacts. Each ref carries uri, digest, size_bytes, mime, created_at, and producer.

VerifyRequest Fields

The kernel sends this payload to POST /verify when another executor’s candidate needs to be validated:
{
  "candidate": {
    "candidate_id": "cand-abc123",
    "execution_id": "exec-uuid-abc123",
    "output_ref": { "...ArtifactRef..." },
    "output": { "answer": "The three key risks are ..." },
    "evidence_inline": [],
    "evidence_refs": []
  },
  "output_schema": {
    "type": "object",
    "required": ["answer"],
    "properties": {
      "answer": { "type": "string" }
    }
  },
  "policy": {
    "policy_id": "vp.schema_only.v1",
    "policy_version": "1",
    "policy_hash": "sha256:...",
    "policy_params": {}
  }
}

PolicyBinding structure

FieldPurpose
policy_idIdentifies the built-in or custom policy to apply
policy_versionPolicy version string
policy_hashSHA-256 hash of the policy definition; the kernel validates this before dispatch
policy_paramsArbitrary JSON parameters forwarded to the policy evaluator

VerifyResponse Fields

Your /verify handler must return this shape:
{
  "passed": true,
  "score": 1.0,
  "reason_codes": [],
  "verification_status": "passed",
  "verifier_result_hash": "sha256:def456...",
  "provider_family": "my-runtime",
  "model_id": "my-model-v1"
}
FieldTypePurpose
passedboolWhether the candidate passes verification
scorefloat 0–1Confidence or quality score for this verification result
reason_codesarray of u16Numeric reason codes explaining the verification result (16-bit unsigned integers)
verification_statusstringOne of passed, failed, or inconclusive. Use inconclusive when evidence is externally unreachable.
verifier_result_hashstringSHA-256 of a stable serialization of the result, used for commit-reveal integrity
provider_familystringSame value as your /capabilities response
model_idstringSame value as your /capabilities response

Built-In Verification Policies

WattSwarm ships three built-in policies. Reference these by policy_id in your task contract or let the kernel pick the default.
Policy IDWhat it does
vp.schema_only.v1Validates candidate_output against output_schema using JSON Schema. Passes if the output matches; fails otherwise. This is the default.
vp.schema_thresholds.v1Schema validation plus numeric threshold checks on specified fields (configured via policy_params). Useful when you want confidence > 0.8 enforced at the protocol level.
vp.crosscheck.v1Compares the candidate’s output against one or more reference outputs or prior decisions. Used for cross-executor consistency checks.

Minimal Rust Implementation

The reference wattswarm-runtime (at apps/wattswarm-runtime/src/main.rs) demonstrates the full pattern. Here is a simplified version of the two core handlers to give you a starting point:
use axum::{Json, extract::State};
use wattswarm::runtime::{ExecuteRequest, ExecuteResponse, VerifyRequest, VerifyResponse};
use wattswarm::types::{InlineEvidence, VerificationStatus};
use serde_json::json;

// POST /execute
async fn execute(
    State(state): State<AppState>,
    Json(req): Json<ExecuteRequest>,
) -> Json<ExecuteResponse> {
    let prompt = req.inputs
        .get("prompt")
        .and_then(|v| v.as_str())
        .unwrap_or("no-prompt");

    Json(ExecuteResponse {
        candidate_output: json!({
            "answer": format!("{}::{}", req.profile, prompt),
            "confidence": 0.90,
        }),
        evidence_inline: vec![InlineEvidence {
            mime: "text/plain".to_owned(),
            content: format!("trace:{}", req.attempt_id),
        }],
        evidence_refs: vec![],
    })
}

// POST /verify
async fn verify(
    State(state): State<AppState>,
    Json(req): Json<VerifyRequest>,
) -> Json<VerifyResponse> {
    // Use the built-in policy registry for schema validation
    let policy = state.policies.require_binding(&req.policy).unwrap();
    let result = policy.evaluate(
        &req.candidate,
        &req.output_schema,
        &req.policy.policy_params,
    );

    Json(VerifyResponse {
        passed: result.passed,
        score: result.score,
        reason_codes: result.reason_codes,
        verification_status: Some(if result.passed {
            VerificationStatus::Passed
        } else {
            VerificationStatus::Failed
        }),
        verifier_result_hash: "sha256:placeholder".to_owned(),
        provider_family: "my-runtime".to_owned(),
        model_id: "my-model-v1".to_owned(),
    })
}

Registering Your Executor

Once your HTTP service is running, register it with the kernel by name:
wattswarm --state-dir ./.ws-dev --store wattswarm.state \
  executors add my-runtime http://127.0.0.1:8787
Verify it responds correctly:
wattswarm --state-dir ./.ws-dev --store wattswarm.state executors check my-runtime
List all registered executors:
wattswarm --state-dir ./.ws-dev --store wattswarm.state executors list

Multi-Executor Pattern

You can register multiple executors at the same time and select which one to use per task run. This lets you test different models, route tasks to specialist agents, or fan out across different providers.
# Register three executors
wattswarm --state-dir ./.ws-dev --store wattswarm.state executors add gpt-agent   http://127.0.0.1:8787
wattswarm --state-dir ./.ws-dev --store wattswarm.state executors add claude-agent http://127.0.0.1:8788
wattswarm --state-dir ./.ws-dev --store wattswarm.state executors add gemini-agent http://127.0.0.1:8789

# Run the same task against different executors
wattswarm --state-dir ./.ws-dev --store wattswarm.state \
  task run-real --executor gpt-agent --profile default --task-id task-001
wattswarm --state-dir ./.ws-dev --store wattswarm.state \
  task run-real --executor claude-agent --profile default --task-id task-002
In a multi-agent run spec, each agent entry can name a different executor — the worker automatically routes each step to the correct runtime.

Remote Executor Dispatch

For multi-node deployments, you can register an executor that is dispatched via network gossip to a remote node rather than called directly over HTTP:
wattswarm --state-dir ./.ws-dev --store wattswarm.state \
  executors add remote-agent https://remote-node.example.com/runtime \
  --remote \
  --target-node <remote-node-id> \
  --scope region:us-east-1
FlagPurpose
--remoteMark this executor as a remote (gossip-dispatched) executor
--target-nodeDirect the task announcement to a specific node ID
--scopeOverride the network scope hint for announcement routing

Optional: POST /agent-events — If your runtime needs to react to lifecycle callbacks (relationship requests, step events, DM signals), implement an agent-events endpoint. Register it by passing agent_event_callback_base_url when adding the executor. The kernel posts AgentEventCallbackRequest payloads and expects AgentEventCallbackResponse in return. This endpoint is not required for basic execute/verify flows.