# Remote Tools contract (`generic-rest`)

The **Remote Tools contract** is how a host application gives a Context
Assistant assistant its tools and grounding context. It generalizes
founder-ai's original host-bridge callbacks
(`/internal/assistant/{tools,context,tools/preview,tools/execute}`) into a
product contract any host can implement — not just FounderOS
(ARCHITECTURE.md ADR-9).

You (the host) run an HTTP server exposing the four endpoints below. You
register it once per workspace via
`POST /v1/integrations/{provider}/connect` (base URL + shared secret +
optional tool allowlist). From then on, Context Assistant calls your server
during a chat turn to discover tools, pull page/workspace context, preview a
proposed action, and — after the user confirms — execute it.

Context Assistant never imports your code and you never import its. The only
coupling is this wire contract. The Go types these shapes serialize from live
in `apps/backend/pkg/contracts`.

## Authentication

Every request Context Assistant makes to your server carries:

```
Authorization: Bearer <secret>
```

where `<secret>` is exactly the value you supplied at connect time. Reject
any request whose bearer token does not match with `401`. The secret travels
server-to-server only; it never reaches a browser (the embed widget speaks to
Context Assistant, which speaks to you).

The `base_url` you register is a prefix; the paths below are appended to it.
A base URL of `https://app.example.com/internal/assistant` yields
`https://app.example.com/internal/assistant/tools`, and so on.

## Execution scope

Every call tells you **who it is acting for**. The POST endpoints carry it in
the body as `execution` (see below); the GET endpoints — which have no body —
carry the same facts as query parameters:

```
GET {baseURL}/tools?workspaceId=ws_123&externalUserId=user_42&requestId=req_xyz
```

- `workspaceId` — the Context Assistant workspace the assistant belongs to.
- `externalUserId` — the id **you** gave the end user when you minted their
  embed token. This is the field a multi-tenant host resolves its own tenancy
  from: if your workspaces are per-user (FounderOS's are), this is the only
  thing that tells you which one is asking.
- `permissions` (repeatable), `requestId` — as in `execution` below.

Do not infer the workspace from the bearer secret alone. One secret can serve
many of your tenants, and it does in practice.

## Endpoints

### `GET {baseURL}/tools`

Return the tools this workspace may use. Called once per chat turn (the tool
set is workspace-scoped and may change between turns), before the model runs.
The execution scope arrives in the query string (see above).

**Response `200`:**

```json
{
  "tools": [
    {
      "name": "create_task",
      "description": "Create a task in the current project",
      "verb": "create",
      "inputSchema": { "type": "object", "properties": { "title": { "type": "string" } }, "required": ["title"] }
    }
  ]
}
```

- `name` — unique within your server; the model calls the tool by this name.
- `description` — handed to the model; make it action-oriented and specific.
- `verb` — one of `get` | `create` | `update` | `delete`. This drives the
  confirmation policy (see **Verb semantics**), so classify honestly: a tool
  that mutates state must never be `get`.
- `inputSchema` — a JSON Schema object for the arguments. The model uses it
  to construct the call.

Context Assistant additionally filters this list against the per-integration
`allowed_tools` allowlist you set at connect time (an empty allowlist means
"all tools this endpoint returns are allowed").

### `GET {baseURL}/context`

Return opaque grounding context for the caller in the **execution scope**
(query string, see above) — whatever the assistant should know about where the
user is (current project, selection, recent activity…). Called once per turn.
Context Assistant treats the body as **untrusted JSON**: it is embedded in the
prompt as data the model may read, never as instructions, and its internal
shape is never inspected.

**Response `200`:** any JSON object. Return `{}` if you have nothing to add.

### `POST {baseURL}/tools/preview`

Render a short, human-readable summary of what executing a tool *would* do,
shown to the user before they confirm. **Must not** perform the action or
cause any side effect.

**Request:**

```json
{
  "tool": "create_task",
  "callId": "call_abc123",
  "arguments": { "title": "Ship the release" },
  "execution": {
    "workspaceId": "ws_123",
    "externalUserId": "user_42",
    "permissions": ["tasks:write"],
    "requestId": "req_xyz"
  }
}
```

**Response `200`:**

```json
{ "preview": "Create a task “Ship the release” in Project Apollo" }
```

### `POST {baseURL}/tools/execute`

Perform the action and return its result. Called only after the user has
confirmed the previewed action (and, for `delete`, confirmed a second time).
The request body is identical in shape to `/tools/preview`.

**Response `200`:**

```json
{ "output": { "id": "task_789", "title": "Ship the release", "status": "open" } }
```

`output` is fed back to the model so it can compose the final natural-language
reply ("Done — I created *Ship the release*."). Keep it compact and factual.

### Displayable results (`display`)

`output` MAY carry a top-level `display` array. Each entry is a navigable
object the widget renders as a framed preview card — a product, a post, a
menu entry — instead of the model retyping it as a prose list. It is purely
additive: keep the rest of `output` as the structured facts the model reasons
over.

```json
{
  "output": {
    "results": [ { "id": 42, "name": "GeForce RTX 4070", "price": "549" } ],
    "display": [
      {
        "title": "GeForce RTX 4070",
        "url": "https://shop.example.com/product/rtx-4070",
        "image": "https://shop.example.com/wp-content/uploads/rtx-4070-300x300.jpg",
        "subtitle": "$549 · In stock",
        "badges": ["8 GB GDDR6X", "PCIe 4.0"]
      }
    ]
  }
}
```

| Field | Required | Rule |
|---|---|---|
| `title` | yes | plain text |
| `url` | yes | absolute `http(s)` link; the card opens it in a new tab |
| `image` | no | absolute `https` link |
| `subtitle` | no | one short line |
| `badges` | no | array of short strings |

Context Assistant **sanitizes** `display` and treats it as advisory: an entry
with no absolute http(s) `url` is dropped; a non-https or relative `image` is
cleared (the card stays); text is trimmed (`title` ≤ 120, `subtitle` ≤ 100,
each `badge` ≤ 48 chars, ≤ 4 badges); at most 6 entries per tool result and 8
per turn are kept, de-duplicated by `url`. Every text field is rendered as
plain text, never markup. A malformed entry is skipped silently — do not rely
on `display` for anything the model's prose does not also convey.

## Verb semantics

`verb` classifies what a tool does and determines how Context Assistant's
orchestration engine treats it (ARCHITECTURE.md ADR-5):

| Verb | Confirmation | Behavior |
|---|---|---|
| `get` | none | **Auto-executed** inside the turn. The engine may call `/tools/execute` directly (no user confirmation) and feed the result back to the model, possibly several times per turn, bounded by iteration/time/token limits. `get` tools must be side-effect-free and safe to call repeatedly. |
| `create`, `update` | preview + Apply | The engine stops the turn with a `pending_action`; the user sees your `/tools/preview` text and clicks Apply, which triggers `/tools/execute`. |
| `delete` | preview + **double** confirm | As above, but the user must confirm a second, explicit time before `/tools/execute` is called. |

A `get` tool that is not actually read-only is the one classification error
that matters most: it will run without the user ever seeing it.

## Error format

On any failure, return a non-2xx status and a JSON body:

```json
{ "error": "project not found" }
```

Context Assistant surfaces failures to the user generically (it does not leak
your `error` string verbatim into the reply) but logs it for debugging. Use
conventional statuses: `401` (bad secret), `400` (bad arguments), `404`
(unknown tool or entity), `403` (caller lacks permission for the action),
`5xx` (your side failed).

## Reference implementation

`integrations/founder-os` documents FounderOS's server implementing exactly
these four endpoints over its own domain usecases. That server's code lives in
the FounderOS repository (it wraps FounderOS-internal usecases, which Context
Assistant may not import — ADR-9), so this directory carries the contract and
conformance expectations, not the adapter code.
