<!-- Generated by `just docs` from proto/automaton/v1/execution.proto. Edit the source, not this file. -->

# ExecutionService

Run one tool, and read back what happened. Every call leaves a receipt, refusals included, and the receipt is what these calls return: the status, the result, and an error code that is empty on success. Nothing here throws when a call is refused; a refusal is an answer.

Every call is a POST to `https://api.atmon.ai/automaton.v1.ExecutionService/<Call>` with a JSON body, and authenticates with `Authorization: Bearer <your project key>`. Field names in JSON are lowerCamelCase, so the field written `tool_slug` below is `toolSlug` on the wire. [How to call the API](./index.md) has the whole convention.

## Calls

| Call | Request | Response | Summary |
| --- | --- | --- | --- |
| `ExecuteTool` | `ExecuteToolRequest` | `ExecuteToolResponse` | Runs one tool for one entity, in a fixed order: resolution against the project's pinned catalog snapshot, mutation gate, policy gate, the resource lease, credential resolution, provider rate-limit pacing, the HTTP call (retried only for read tools), response shaping, then the ledger write. |
| `GetToolCall` | `GetToolCallRequest` | `GetToolCallResponse` | Reads one ledger row by id. |
| `ListToolCalls` | `ListToolCallsRequest` | `ListToolCallsResponse` | Lists an entity's ledger rows, newest first, optionally for one tool. |

### ExecuteTool

Runs one tool for one entity, in a fixed order: resolution against the
project's pinned catalog snapshot, mutation gate, policy gate, the resource
lease, credential resolution, provider rate-limit pacing, the HTTP call
(retried only for read tools), response shaping, then the ledger write. A
refusal is a successful call whose ToolCall says it was refused, not an
error: a gate is an answer. A denied or parked call resolves no credential
and reaches no external app.

Request `ExecuteToolRequest`, response `ExecuteToolResponse`.

```http
POST /automaton.v1.ExecutionService/ExecuteTool HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "entityId": "...",
  "toolSlug": "...",
  "argumentsJson": "{}",
  "idempotencyKey": "...",
  "resolutionId": "...",
  "approvalId": "..."
}
```

The response:

```json
{
  "toolCall": {
    "id": "...",
    "entityId": "...",
    "toolSlug": "...",
    "connectedAccountId": "...",
    "argumentsJson": "{}",
    "status": "TOOL_CALL_STATUS_RUNNING",
    "resultJson": "{}",
    "errorCode": "...",
    "errorDetail": "...",
    "startedAt": "2026-01-31T09:15:00Z",
    "finishedAt": "2026-01-31T09:15:00Z",
    "approvalId": "...",
    "resultTruncated": true,
    "resourceUrn": "...",
    "leaseWaitMs": 0,
    "principalChain": ["..."],
    "jobId": "...",
    "stepId": "...",
    "relayId": "..."
  }
}
```

### GetToolCall

Reads one ledger row by id.

Request `GetToolCallRequest`, response `GetToolCallResponse`.

```http
POST /automaton.v1.ExecutionService/GetToolCall HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "id": "..."
}
```

The response:

```json
{
  "toolCall": {
    "id": "...",
    "entityId": "...",
    "toolSlug": "...",
    "connectedAccountId": "...",
    "argumentsJson": "{}",
    "status": "TOOL_CALL_STATUS_RUNNING",
    "resultJson": "{}",
    "errorCode": "...",
    "errorDetail": "...",
    "startedAt": "2026-01-31T09:15:00Z",
    "finishedAt": "2026-01-31T09:15:00Z",
    "approvalId": "...",
    "resultTruncated": true,
    "resourceUrn": "...",
    "leaseWaitMs": 0,
    "principalChain": ["..."],
    "jobId": "...",
    "stepId": "...",
    "relayId": "..."
  }
}
```

### ListToolCalls

Lists an entity's ledger rows, newest first, optionally for one tool. Every
outcome is a row: successes, failures, denials, and parked calls alike.

Request `ListToolCallsRequest`, response `ListToolCallsResponse`.

```http
POST /automaton.v1.ExecutionService/ListToolCalls HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "entityId": "...",
  "toolSlug": "...",
  "pageSize": 0,
  "pageToken": "...",
  "principalChainPrefix": ["..."]
}
```

The response:

```json
{
  "toolCalls": [{
    "id": "...",
    "entityId": "...",
    "toolSlug": "...",
    "connectedAccountId": "...",
    "argumentsJson": "{}",
    "status": "TOOL_CALL_STATUS_RUNNING",
    "resultJson": "{}",
    "errorCode": "...",
    "errorDetail": "...",
    "startedAt": "2026-01-31T09:15:00Z",
    "finishedAt": "2026-01-31T09:15:00Z",
    "approvalId": "...",
    "resultTruncated": true,
    "resourceUrn": "...",
    "leaseWaitMs": 0,
    "principalChain": ["..."],
    "jobId": "...",
    "stepId": "...",
    "relayId": "..."
  }],
  "nextPageToken": "..."
}
```

## Messages

### ExecuteToolRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `entity_id` | `string` | 1 |  |
| `tool_slug` | `string` | 2 |  |
| `arguments_json` | `string` | 3 |  |
| `idempotency_key` | `string` | 4 | same key returns the original call, not a rerun |
| `resolution_id` | `string` | 5 | links the call to the router decision, if any |
| `approval_id` | `string` | 6 | An approved policy approval, releasing a call that parked earlier. It is single use and only covers the same tool with the same arguments. |

### ExecuteToolResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `tool_call` | `ToolCall` | 1 |  |

### GetToolCallRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `id` | `string` | 1 |  |

### GetToolCallResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `tool_call` | `ToolCall` | 1 |  |

### ListToolCallsRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `entity_id` | `string` | 1 |  |
| `tool_slug` | `string` | 2 | empty lists all tools |
| `page_size` | `int32` | 3 |  |
| `page_token` | `string` | 4 |  |
| `principal_chain_prefix` | repeated `string` | 140 | principal_chain_prefix lists every call whose chain opens with these hops, in this order: one key's effects, one skill's, one job's, one step's. It is as narrow a filter as entity_id and it crosses entities, because a job does, so a request carrying it may leave entity_id empty. A request carrying neither is refused. |

### ListToolCallsResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `tool_calls` | repeated `ToolCall` | 1 |  |
| `next_page_token` | `string` | 2 |  |

### ToolCall

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `id` | `string` | 1 |  |
| `entity_id` | `string` | 2 |  |
| `tool_slug` | `string` | 3 |  |
| `connected_account_id` | `string` | 4 |  |
| `arguments_json` | `string` | 5 |  |
| `status` | `ToolCallStatus` | 6 |  |
| `result_json` | `string` | 7 |  |
| `error_code` | `string` | 8 | "rate_limited", "auth_expired", "invalid_arguments", ... |
| `error_detail` | `string` | 9 |  |
| `started_at` | `google.protobuf.Timestamp` | 10 |  |
| `finished_at` | `google.protobuf.Timestamp` | 11 |  |
| `approval_id` | `string` | 12 | The policy approval this call waits on when status is PENDING_APPROVAL, or the approval the call was released by. Empty when no gate applied. |
| `result_truncated` | `bool` | 13 | A shaping cap cut data the tool's output schema declared. The proto3 default carries the meaning that matters: absent means complete. |
| `resource_urn` | `string` | 110 | The resource this call leased, rendered from the tool's lease_key rule (docs/toolkit-format.md, Resource leases). Empty when the tool declares no rule, which is most of the catalog. A call refused with resource_leased carries it too, so a contention names what it could not have. |
| `lease_wait_ms` | `int64` | 111 | How long taking that lease took, in milliseconds. Zero for a call that leased nothing. |
| `principal_chain` | repeated `string` | 140 | Who ultimately caused this call, oldest cause first: "key:<id>", "skill:<slug>", "job:<id>", "step:<id>", "account:<id>". It is an audit record and never an authorization input. Empty for a row written before chains existed; those rows recorded no cause. |
| `job_id` | `string` | 141 | The job step this call ran for, empty for a call made directly over the wire. Both are recorded by the engine and neither is ever read from a request: a caller-supplied job id would let one caller attribute its effects to another's job. |
| `step_id` | `string` | 142 |  |
| `relay_id` | `string` | 150 | The on-prem relay that executed this call, empty for every call the platform sent itself. A row carrying it carries no connected_account_id: a relay call resolves none, because the credential it used never left the customer's network. |

## Enums

### ToolCallStatus

| Value | # | Meaning |
| --- | --- | --- |
| `TOOL_CALL_STATUS_UNSPECIFIED` | 0 |  |
| `TOOL_CALL_STATUS_RUNNING` | 1 |  |
| `TOOL_CALL_STATUS_SUCCEEDED` | 2 |  |
| `TOOL_CALL_STATUS_FAILED` | 3 |  |
| `TOOL_CALL_STATUS_DENIED` | 4 | blocked by policy before reaching the app |
| `TOOL_CALL_STATUS_PENDING_APPROVAL` | 5 | parked on the policy approval gate |
