# App Runtime Developer Guide

> Canonical product contract: [`specs/app/app-runtime.md`](../specs/app/app-runtime.md)
>
> Terminology (the App runtime as an app-kind capability form, App Runtime API, transports) is authoritative in [`specs/service/service-actor-platform.md`](../specs/service/service-actor-platform.md).

This guide describes how an App runtime receives AgentTeam deliveries and writes replies. It is for developers who operate an App service or runner after creating an App in a AgentTeam workspace. The repository example at [`kits/app-example/echo-app`](../kits/app-example/echo-app/README.md) demonstrates the same callback, runner stream, reply, ack, and fail contract.

## Runtime Model

An App runtime receives durable service event deliveries from AgentTeam. Each delivery represents one `agentteam.message.mention` event for one App and one target binding. The App receives the original message text and structured metadata, then calls the App Runtime API to reply, ack, or fail the delivery.

AgentTeam controls the visible author for replies. Runtime code does not write directly to message tables; it calls a delivery-bound reply API and AgentTeam writes the message as the App service member.

## Provision and Connect a Runtime

Creating a self-authored App in the current workspace is one user operation in Apps. App definitions and manifest snapshots do not have their own draft/review/approval lifecycle. AgentTeam chooses the authorization path from the caller's current workspace role:

- A workspace owner or administrator creates the App directly. The response contains the App and its one-time callback signing secret or initial runner token; no approval request or Approval card is created.
- A normal member submits a pending App creation request. This request does **not** create or return a runtime credential. The creator becomes the first active App manager. After the Approval reports that the request was approved, an active App manager or a workspace owner or administrator can open the App details and claim the initial credential.

Initial credential claim is limited to a workspace-created third-party App with
its current workspace attribution. Product Apps do not enter this lifecycle.

After the direct command completes or the member request is approved:

1. An active App manager or a workspace owner or administrator opens the App details and claims the initial credential once. A successful claim immediately completes setup; "I've saved it" only clears the one-time plaintext from the page. Administrator direct creation already returns the initial credential and skips this claim step.
2. For `runner_stream`, save the one-time App runner token in the runtime host's secret manager or a local uncommitted environment file, then start the runner.
3. For `callback`, save the one-time definition-level callback signing secret on the runtime host, deploy the manifest-declared HTTPS callback service, and keep its `/events` endpoint reachable.
4. Have an active App manager or a workspace owner or administrator add it to each ordinary channel where it should be available. Creating an App does not auto-add it to a channel or to the system `#all` channel.
5. Mention the App in the channel or any child thread. Threads inherit the parent channel's App membership, so the App does not need to be added to each thread separately.

A pending App creation request has no usable App, runner token, callback secret, or target membership. If the request is rejected or withdrawn, the App is removed from Apps; the terminal approval message remains as audit. Do not provision runtime credentials from definition authoring output.

The initial credential lifecycle is:

```text
pending approval -> approved / ready to claim -> claimed
```

App setup reads the non-secret state with `GET /api/v1/workspaces/{workspace_id}/app-installations/{installation_id}/initial-credentials`. An active App manager or a workspace owner or administrator uses `POST .../initial-credentials/claim` once. The claim transaction creates the transport credential and changes the state to `claimed`; another claim is rejected. Claim responses use `Cache-Control: no-store`; the plaintext never appears in the status read, the approval request, the Approval card, or a URL.

```http
POST /api/v1/workspaces/{workspace_id}/app-installations/{installation_id}/initial-credentials/claim

{
  "runnerToken": "<shown once for runner_stream>"
}
```

Callback-enabled claims include `callbackSigningSecret` (and a claim can include
both transport credentials). Copy the returned value before clearing or closing
the page; AgentTeam does not display it again.

## Build Checklist

An App runtime implementation provides these pieces:

1. A runtime-capable App manifest with `agentteam.message.mention`, `app.mentions.read`, and `messages.write`.
2. A runtime configuration model for callback or runner stream transport.
3. A callback handler or runner process that validates runtime authentication and delivery identity.
4. Command parsing over the source message text supplied in the delivery payload.
5. Reply API calls for progress and final output.
6. Explicit ack after business work succeeds.
7. Fail API calls with stable error codes for business or runtime failures.
8. Retry-safe idempotency keys for repeated reply calls.
9. Heartbeat and operational logging that let operators verify the runtime independently of credential state.

## Echo App Example

`kits/app-example/echo-app` demonstrates the runtime contract with a small Echo App:

- `manifest.json` declares the App runtime capability and runtime config schema.
- `callback-server.mjs` verifies callback signatures and handles callback delivery.
- `runner.mjs` connects with runner stream.
- `lib/runtime.mjs` calls reply, ack, and fail APIs.
- `test/runtime.test.mjs` validates command parsing, signatures, multi-reply, ack, fail, and runner camelCase envelopes.

Run the example contract tests:

```bash
cd kits/app-example/echo-app
npm test
```

## Callback Transport

Callback transport sends each delivery envelope to the configured App endpoint.

```http
POST <app_callback_url>/events
Authorization: Bearer <delivery_token>
AgentTeam-Signature: <signature>
AgentTeam-Delivery-ID: del_123
AgentTeam-Attempt-ID: att_123
AgentTeam-Event-Type: agentteam.message.mention
Content-Type: application/json
```

The request body contains:

```json
{
  "envelope_id": "env_123",
  "delivery_id": "del_123",
  "attempt_id": "att_123",
  "event_type": "agentteam.message.mention",
  "delivery_token": "dtok_123",
  "issued_at": "2026-06-01T05:30:00Z",
  "payload": {
    "workspace": { "id": "w_123" },
    "app": {
      "app_id": "app_123",
      "service_definition_id": "svcdef_123",
      "installation_id": "svcinst_123",
      "service_member_id": "svcmem_123"
    },
    "target": { "kind": "scope", "ref": "#operations:thread123" },
    "message": {
      "id": "msg_123",
      "text": "@ops-app run command --flag value",
      "author": { "type": "human", "id": "mem_123", "display_name": "Alice" },
      "mentions": [
        { "type": "service", "service_member_id": "svcmem_123", "range": { "start": 0, "end": 8 } }
      ]
    },
    "reply": { "default_target": "#operations:thread123", "reply_to_message_id": "msg_123" }
  }
}
```

A successful HTTP response confirms that the envelope reached the App runtime. The App completes business work through reply, ack, and fail APIs.

Callback handlers verify `Authorization`, `AgentTeam-Signature`, `AgentTeam-Delivery-ID`, `AgentTeam-Attempt-ID`, and `AgentTeam-Event-Type` before processing the envelope. The `delivery_token` and bearer token authorize delivery-bound reply, ack, and fail API calls. An administrator receives the signing secret in the one-time direct-create response. A normal member receives no secret while approval is pending; after approval, an active App manager or a workspace owner or administrator claims it once from the App details.

## Runner Stream Transport

Runner stream transport is for private runtime hosts that connect outbound to AgentTeam.

```http
GET /api/v1/workspaces/{workspace_id}/app-runtime/stream
Authorization: Bearer <runner_token>
```

The stream emits newline-delimited JSON events. Runner stream events use the runtime API DTO shape in camelCase. Callback delivery keeps its signed HTTP envelope in snake_case because it is the external callback contract.

A delivery stream event is shaped as:

```json
{
  "type": "delivery",
  "envelope": {
    "envelopeId": "env_123",
    "deliveryId": "del_123",
    "attemptId": "att_123",
    "eventType": "agentteam.message.mention",
    "payload": {},
    "deliveryToken": "dtok_123",
    "issuedAt": "2026-06-01T05:30:00Z"
  }
}
```

The runner sends heartbeat metadata so AgentTeam can record the current runtime session independently of the installation and credential lifecycle.

```http
POST /api/v1/workspaces/{workspace_id}/app-runtime/heartbeat
Authorization: Bearer <runner_token>
Content-Type: application/json

{
  "runnerId": "host-1",
  "runnerVersion": "1.0.0",
  "capabilities": {
    "agentteam.message.mention": true,
    "maxConcurrency": 2
  },
  "metadata": {
    "region": "staging"
  }
}
```

Each App receives one initial runner token. An administrator direct-create response contains it immediately. A member request contains no token; an active App manager or a workspace owner or administrator claims it from App setup only after approval. The token is displayed only in its create or claim response, and Store and runtime APIs persist its hash rather than plaintext.

## Reply API

App runtimes write progress and final messages with the reply API.

```http
POST /api/v1/workspaces/{workspace_id}/app-runtime/deliveries/{delivery_id}/reply
Authorization: Bearer <runtime_or_delivery_token>
Content-Type: application/json

{
  "attemptId": "att_123",
  "content": "收到，开始处理。",
  "idempotencyKey": "progress-1",
  "metadata": { "phase": "start" }
}
```

Reply behavior:

- `attemptId` identifies the current leased or delivered attempt.
- `idempotencyKey` makes retrying the same reply safe.
- AgentTeam writes the visible message as the App service member.
- The message source references the service event delivery.
- Each reply creates a `service_event_delivery_effects` row.
- A delivery can have multiple reply effects for progress and final output.

## Ack API

Ack marks the delivery as completed successfully after the App has finished its work.

```http
POST /api/v1/workspaces/{workspace_id}/app-runtime/deliveries/{delivery_id}/ack
Authorization: Bearer <runtime_or_delivery_token>
Content-Type: application/json

{
  "attemptId": "att_123",
  "result": { "status": "success" }
}
```

Ack is explicit. A callback `2xx` response means the envelope was delivered to the App runtime; the App still calls ack when the business delivery is complete.

## Fail API

Fail marks the delivery as a terminal App-reported failure.

```http
POST /api/v1/workspaces/{workspace_id}/app-runtime/deliveries/{delivery_id}/fail
Authorization: Bearer <runtime_or_delivery_token>
Content-Type: application/json

{
  "attemptId": "att_123",
  "code": "command_failed",
  "message": "command execution failed",
  "result": { "exitCode": 1 }
}
```

Use a stable `code` for diagnostics and a short human-readable `message` for Apps UI. The result object can carry App-specific structured details.

## Context Read API

When the installation grants history access, a runtime can read delivery-bound context.

```http
GET /api/v1/workspaces/{workspace_id}/app-runtime/deliveries/{delivery_id}/context?attemptId=att_123&limit=50&beforeSeq=0
Authorization: Bearer <runtime_or_delivery_token>
```

The response uses the App message read model and the same target visibility checks as AgentTeam messages. The source message is included as pinned context and history rows are deduplicated by message id.

## Retry Semantics

AgentTeam creates one attempt for each delivery lease. Callback failures, stream disconnects, expired leases, and transport errors are recorded on attempts. Retry scheduling is controlled by the delivery policy attached to the App subscription and installation.

Runtime implementations should treat reply, ack, and fail calls as retryable API operations. Use idempotency keys for repeated progress/final replies. Repeated ack or fail for the same attempt returns the current terminal delivery state.

## Field Naming Reference

Callback delivery is an external signed HTTP callback and uses snake_case envelope fields such as `delivery_id`, `attempt_id`, `delivery_token`, and `issued_at`.

Runner stream, heartbeat, reply, ack, and fail endpoints are AgentTeam Runtime APIs and use camelCase DTO fields such as `runnerId`, `runnerVersion`, `leaseSeconds`, `deliveryId`, `attemptId`, and `deliveryToken`.

Runtime implementations keep these shapes separate while using the same delivery, attempt, reply, ack, and fail lifecycle.

## Error Codes

Runtime APIs return typed error codes for common operational cases:

| Code family | Meaning |
| --- | --- |
| Authentication errors | Runtime token, delivery token, or runner credential is invalid, expired, disabled, or mismatched. |
| Authorization errors | Installation, service member, target binding, grant, or target policy does not allow the requested operation. |
| Lease errors | Attempt is not the current leased/delivered attempt for the delivery. |
| Delivery state errors | Delivery is already terminal, canceled, or not available for the requested operation. |
| Validation errors | Required fields such as `attemptId`, `content`, or stable error code are missing or malformed. |

## Operational Controls

The creator is the first active App manager. Active App managers and workspace
administrators use the same App detail page. They can change the App avatar,
add or remove Human managers, claim the initial credential once, follow the
connection Quickstart, add the App to channels, or permanently delete the App.
The last active manager cannot be removed. Deleting the App removes its runtime
credential, configuration, manager assignments, avatar assets, and channel
connections.
