Browse the docs

The plain-HTTP surface and executor

The `/health`, `/execute`, and cancel endpoints, the NDJSON event stream, and how the executor and entry point wire them up.

Before you startRead [The agent core and prompt strategies](/developers/self-hosted-agent-core/).View as Markdown

The creator's default surface is a plain HTTP server built on Starlette. The skill calls it A2A; the server's own docstring describes it as a plain-HTTP server that replaces A2A. It streams newline-delimited JSON. AMC does not call this surface: for AMC, use the AMC variant.

Endpoints

Method and pathBehavior
GET /healthCalls the health probe and returns its JSON, always with HTTP 200. Readiness is in the body's ready field. A probe that raises returns status: "down".
POST /executeRuns one turn and streams events as application/x-ndjson.
POST /tasks/{task_id}/cancelBest-effort cancel. Returns {"ok": true, "task_id": ..., "in_flight": ...}.

The /execute request

POST /execute
{
  "context_id": "ctx-1",
  "task_id": "task-1",
  "message": "Build this week's report",
  "files": [{ "name": "brief.pdf", "uri": "https://<your-storage>.example.com/brief.pdf", "mime": "application/pdf" }],
  "metadata": { "triggered_by_email": "user@contoso.com" },
  "resume_token": null
}

context_id and task_id are required; a missing one returns 400, as does a body that is not a JSON object. The other fields are optional.

The NDJSON event stream

`kind`FieldsTerminal
statustextNo
filename, uri, mimeNo
token_usageprompt_tokens, completion_tokens, total_tokensNo
resulttext, summaryYes
errormessageYes
hitl_input_requiredwait_info, resume_tokenYes
pausedreason, resume_tokenYes
Example stream
{"kind": "status", "text": "🤖 Build this week's report"}
{"kind": "status", "text": "🛠️ Remote agent executing: Loading skill: report-generator"}
{"kind": "status", "text": "🛠️ Remote agent executing: Fetching sources"}
{"kind": "status", "text": "🛠️ Remote agent executing: Rendering output"}
{"kind": "token_usage", "prompt_tokens": 41200, "completion_tokens": 2300, "total_tokens": 43500}
{"kind": "result", "text": "Report written to /outputs/report.html ...", "summary": "Report written to /outputs/report.html ..."}
  • The run must yield exactly one terminal event. The server drops anything after it.
  • If the run ends without a terminal event, the server appends {"kind": "error", "message": "agent completed without terminal event"}.
  • An exception inside the run becomes error with <ExceptionType>: <message>.
  • summary is the first 200 characters of the final text.
  • The template never emits hitl_input_required or paused. They exist in the contract only.

Concurrency and cancellation

A semaphore caps concurrent /execute streams in one process at AGENT_MAX_CONCURRENCY (default 3, with a fallback to 3 if the value is not an integer). Extra requests queue instead of running in parallel without bound.

A cancel is recorded against the task_id and checked before each event is sent. When it is seen, the stream ends with {"kind": "error", "message": "cancelled"}. A cancel that arrives before /execute starts is kept for 300 seconds, so the race is handled and the map stays bounded.

The entry point

__main__.py loads .env, initializes the shared agent at startup, and starts uvicorn on AGENT_HOST (default 0.0.0.0) and AGENT_PORT (default {{PORT}}). If startup init fails, it prints a warning and starts anyway; the first request then tries to create the agent lazily.

__main__.py (health probe, outputs check filled in)
async def _health() -> Dict[str, Any]:
    use_foundry = os.getenv("CLAUDE_CODE_USE_FOUNDRY") == "1"
    has_api_key = bool(os.getenv("ANTHROPIC_API_KEY"))
    has_foundry_resource = bool(os.getenv("ANTHROPIC_FOUNDRY_RESOURCE"))
    has_foundry = use_foundry and has_foundry_resource and has_api_key
    has_anthropic_direct = has_api_key and not use_foundry
    outputs_dir = os.getenv("REPORT_OUTPUTS_DIR", "/outputs")
    outputs_writable = os.path.isdir(outputs_dir) and os.access(outputs_dir, os.W_OK)

    missing: List[str] = []
    if not (has_anthropic_direct or has_foundry):
        missing.append("ANTHROPIC_API_KEY (Anthropic-direct), or "
                       "CLAUDE_CODE_USE_FOUNDRY=1 + ANTHROPIC_FOUNDRY_RESOURCE + ANTHROPIC_API_KEY (Foundry)")
    if not outputs_writable:
        missing.append(f"writable outputs dir at {outputs_dir}")

    if not missing:
        return {"status": "ok", "ready": True, "detail": "Fully configured"}
    return {"status": "not_configured", "ready": False,
            "detail": f"Missing: {', '.join(missing)}"}

_run_turn maps the core's strings to events: tool lines become deduplicated status events, lines starting with the progress emoji (🤖, 🧠, 🔍, 📝, 📤, 📥) become status, an Error: line becomes a terminal error, and anything else is collected as response text. After the stream it emits token_usage (when the core captured it) and then result with the last response.

The executor

claude_skills_agent_executor.py holds one shared agent instance at class level, guarded by an asyncio.Lock. initialize_at_startup() builds it once. _get_or_create_thread() maps each context_id to a session id. Each /execute call is still an independent single turn: the generated README lists single-turn behavior and ephemeral state as known limitations.

Last updated 2026-10-21