# A2A Local HTTP Adapter Contract V1

This document freezes the local interface between `a2a-runtime` and the user's
local agent capability service.

It is not shown as a technical branch to ordinary users. Platform Web should
generate natural-language instructions and let the AI assistant execute the
runtime setup.

## Environment binding / API base rule

- Platform Web in this repo defaults to `http://127.0.0.1:3000` when `VITE_API_BASE_URL` is unset. Production uses the value baked in at build time (e.g. `https://api.ysee.tech`).
- The **Platform REST API base URL** (`api_base`) is **not** the same thing as the **local agent HTTP endpoint** (`local_agent_endpoint`). Pairing and runtime control-plane calls always go to `api_base`.
- **`pairing_token` values are bound to the database of the API that issued them.** Local development generates codes against the local API; production generates codes against production. Mixing them (e.g. pairing with the CLI default `https://api.ysee.tech` while the user copied a code from `http://127.0.0.1:3000`) produces **Invalid pairing token**.
- The official `a2a-runtime` stores **`api_base` in the local profile** after a successful `pair`. **`start` / `status` use that stored value** when `--api-base` is omitted, so a correctly paired local profile does not fall back to production for control-plane requests.

## Official Runtime Default

The official runtime default local endpoint is:

```text
http://localhost:18789/run-task
```

The endpoint is configurable in the local runtime profile, but ordinary
onboarding UI does not expose endpoint selection.

When multiple runtime profiles exist locally, `a2a-runtime` resolves the
profile before reading `local_agent_endpoint`: explicit `--profile`, then
`active_profile`, then the only profile if there is exactly one. It does not
randomly choose a profile. Successful `pair` sets `active_profile` to the newly
paired runtime profile.

## OpenClaw Compatibility

OpenClaw examples may use:

```text
http://127.0.0.1:18789/execute
```

This is an internal compatibility/preset detail. It is not a separate A2A
Runtime type and should not be presented to ordinary users as a product branch.

## Request

The runtime calls the local endpoint with:

```http
POST /run-task
Content-Type: application/json
```

Request body:

```json
{
  "task_run_id": "task_run_id",
  "conversation_id": "conversation_id",
  "workspace_id": "workspace_id",
  "task_type": "chat",
  "input": {
    "text": "user request"
  },
  "initiator_agent_id": "initiator_agent_id",
  "target_agent_id": "target_agent_id",
  "platform": {
    "name": "A2A",
    "origin": "ysee.tech"
  }
}
```

Field rules:

- `task_run_id` is the platform task id.
- `conversation_id` is the execution/context boundary.
- `workspace_id` is the 1:1 workspace for the conversation when available.
- `task_type` is the task type from the platform task run.
- `input` is copied from `task_runs.input_snapshot`.
- `initiator_agent_id` and `target_agent_id` preserve Agent identity.

Local agents must not treat user id as the execution subject.

### Platform origin field

The `platform` field is a public origin identifier included by the runtime in every local task payload. It allows the local Agent to know which A2A platform the task came from.

- `platform.name` — human-readable platform name (e.g. `"A2A"`).
- `platform.origin` — platform domain (e.g. `"ysee.tech"`).

This is **not** an authentication credential. The adapter / Agent must not:
- Use `platform` to bypass the runtime and call the platform API directly.
- Expect `api_base` or `runtime_token` to appear in the local task payload.
- Treat `platform.origin` as an API base URL.

Old adapters that do not recognise the `platform` field will ignore it and continue to work.

## Successful Response

Two successful shapes are accepted by the official runtime.

Preferred explicit envelope:

```json
{
  "success": true,
  "output": {
    "text": "result"
  }
}
```

Legacy/direct output:

```json
{
  "text": "result"
}
```

For the explicit envelope, the runtime sends `output` to:

```http
POST /task-runs/{task_run_id}/complete
```

For the legacy/direct output shape, the runtime sends the full JSON object as
`output`.

## Failed Response

Explicit failure shape:

```json
{
  "success": false,
  "error": "failure reason"
}
```

The runtime sends:

```json
{
  "output": {
    "error": "failure reason"
  }
}
```

to:

```http
POST /task-runs/{task_run_id}/fail
```

The runtime also fails the task when the local endpoint:

- returns non-2xx HTTP status,
- returns non-JSON,
- returns a non-object JSON value,
- cannot be reached,
- times out.

## Artifact Write Requests (V1)

A local adapter may return artifact write requests when the task produces
file-type deliverables. The adapter does **not** call the platform artifact API
directly; it only describes what should be written. The runtime materializes
the artifacts using its own `runtime_token` and converts the results to
`artifact_ref` parts.

### Adapter artifact output shape

```json
{
  "success": true,
  "output": {
    "text": "short summary",
    "artifacts": [
      {
        "artifact_type": "report",
        "filename": "reports/result.md",
        "title": "Result Report",
        "summary": "short description",
        "keywords": ["optional"],
        "mime_type": "text/markdown",
        "content_text": "# Report content..."
      }
    ]
  }
}
```

Field rules:

- `artifacts` — array of artifact write requests. The field `artifact_writes` is
  also accepted as an alias.
- `artifact_id` is **not** set by the adapter; the platform generates it.
- `content_text` is the full file body. The runtime strips it before completing
  the task\_run; it never appears in `task_result` or `output_ref`.
- If `artifacts` is not present or empty, the runtime keeps its original
  behaviour (plain text complete).
- The adapter must **not** accept, store, or forward `runtime_token`.
- Artifact write is optional. Adapters without this capability return plain
  `output.text` as before.

### Runtime materialization

1. Runtime detects `output.artifacts` (or `output.artifact_writes`).
2. Runtime calls `POST /task-runs/:taskRunId/artifacts` using its own
   `runtime_token` for each artifact.
3. Runtime removes `output.artifacts` / `output.artifact_writes` and all
   `content_text` from the payload.
4. Runtime appends `artifact_ref` parts to `output.parts`.
5. Runtime calls `POST /task-runs/:id/complete` with the cleaned output.
6. If any artifact write fails, the runtime calls `POST /task-runs/:id/fail`
   with `ARTIFACT_WRITE_FAILED` and does **not** complete the task.

## Security boundary

- The local adapter must bind to loopback by default.
- Do not expose `/run-task` to LAN/public networks.
- The adapter is not an authentication boundary.
- `runtime_token` must remain in the a2a-runtime profile only.
- The adapter must not read `~/.a2a/runtime.json`.
- The adapter must not call the A2A API directly.
- If a remote adapter is needed in the future, it requires a separate authenticated
  transport such as adapter auth, mTLS, or SSH tunnel.

## Non-Goals

V1 does not define:

- desktop GUI,
- tray/background service,
- automatic startup,
- plugin marketplace,
- multi-workspace local routing,
- WebSocket task execution.

The local adapter is only the last hop after the platform task has already been
claimed by the official runtime.
