# The plain-HTTP surface and executor

Agent Mission Control developer docs > Claude skill, self-hosted

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

**Before you start:** Read [The agent core and prompt strategies](/developers/self-hosted-agent-core/).

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](https://www.agentmissioncontrol.ai/developers/self-hosted-amc-sdk/).

## Endpoints

| Method and path | Behavior |
| --- | --- |
| `GET /health` | Calls 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 /execute` | Runs one turn and streams events as `application/x-ndjson`. |
| `POST /tasks/{task_id}/cancel` | Best-effort cancel. Returns `{"ok": true, "task_id": ..., "in_flight": ...}`. |

## The /execute request

`POST /execute`
```json
{
  "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` | Fields | Terminal |
| --- | --- | --- |
| `status` | `text` | No |
| `file` | `name`, `uri`, `mime` | No |
| `token_usage` | `prompt_tokens`, `completion_tokens`, `total_tokens` | No |
| `result` | `text`, `summary` | Yes |
| `error` | `message` | Yes |
| `hitl_input_required` | `wait_info`, `resume_token` | Yes |
| `paused` | `reason`, `resume_token` | Yes |

`Example stream`
```text
{"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)`
```python
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.

> **Important:** Never edit `http_agent_server.py`. It is copied verbatim into every agent. If you need different request or response shapes, change the contract at the platform level.

## Related

- [The agent core and prompt strategies](https://www.agentmissioncontrol.ai/developers/self-hosted-agent-core/)
- [Test the agent locally](https://www.agentmissioncontrol.ai/developers/self-hosted-testing/)
- [Serve the agent to AMC with amc_sdk](https://www.agentmissioncontrol.ai/developers/self-hosted-amc-sdk/)

---
Source: https://www.agentmissioncontrol.ai/developers/self-hosted-http-surface/ · Last updated 2026-10-21 · Full index: https://www.agentmissioncontrol.ai/llms.txt
