HTTP turn observation
seif.http-turn.v1 is an optional HTTP integration boundary for an application that runs an AI model. It works with any model provider because the application, not the model, opens a turn, requests context and reports a technical completion.
It provides an enforceable perimeter only when the application routes its context delivery through these endpoints with a named SEIF client token. It does not control a model process, prove that the model read the context, or observe tools and context sources outside SEIF.
What SEIF can prove
For a client token configured with turnProtocol: "required", SEIF can prove that it observed:
- a turn start from that named client;
- one or more HTTP context deliveries correlated to that server-issued turn id;
- a completion status reported by that client;
- a bounded pre-tool intention for an
mcp__seif__*tool, when the Claude Code hook reports one before execution.
A pre-tool observation says only that the hook saw an intention before the tool ran. It does not prove that the MCP request was made, that it completed, that a result was read, or that the model understood or used it. SEIF does not correlate a hook tool_use_id with its own MCP calls unless a runtime provides a separate verified link.
A completed turn with no recorded HTTP context delivery means only: SEIF did not observe a context delivery through this HTTP perimeter during that turn. It does not mean that the model ignored context, did not answer, or used no other source.
A token without this opt-in remains compatible with the normal HTTP Context API. It has delivery records but no turn-level claim. The shared project token cannot use the turn protocol because it does not identify one invoker.
Configure a named client
Create a named agent token through the application API. Only a project owner can do this. Keep the returned token in the application's secret store; SEIF stores only its hash.
POST /api/projects/<project-id>/mcp/clients
Content-Type: application/json
Cookie: <owner-session>
{
"name": "My application gateway",
"kind": "custom",
"profile": "support",
"environment": "production",
"turnProtocol": "required"
}
The response shows the bearer token once. Give it only to the application component that performs context delivery. Do not put it in browser code, prompts, source control or logs.
Protocol
All public requests use the named client token:
Authorization: Bearer <SEIF_CLIENT_TOKEN>
Content-Type: application/json
Use an HTTPS production address. Replace <base> below with:
https://app.seifprotocol.com/<handle>/<project-slug>
For staging, insert the environment before /context, for example .../<slug>/staging/context/....
1. Start a turn
POST <base>/context/turns/start
Idempotency-Key: <16-160 URL-safe random characters>
{
"clientRef": "optional opaque correlation label"
}
The server returns 201 with a server-generated turn.id such as trn_..., its 15-minute expiry and status: "OPEN". Repeating the same request with the same key returns the existing turn with 200; it does not create a duplicate. clientRef is optional, redacted and limited. Do not send a user prompt or model response in this payload.
2. Request context during that turn
POST <base>/context/compass
X-SEIF-Turn-Id: <turn.id>
{
"task": "The user question in their own words"
}
The normal HTTP tools remain available: compass, resolve, find, why, report-gap, test, and doctor. With turnProtocol: "required", every HTTP context request must carry an open turn id from the same client, profile and environment. Otherwise it is refused before SEIF resolves or delivers context.
The response carries interaction and turnId. The Flight Recorder stores the delivered context, selected Build or draft, verdict and turn id. It still does not observe model comprehension.
3. Record a bounded pre-tool observation (optional)
A runtime that has a verified hook boundary may record a pre-execution intention while the turn is open:
POST <base>/context/turns/<turn.id>/observations
Idempotency-Key: <another random key>
{
"event": "pre_tool_use",
"tool": "mcp__seif__context_compass",
"toolUseId": "opaque-runtime-tool-use-id"
}
Only event, tool and toolUseId are accepted. event must be pre_tool_use; tool must name an mcp__seif__* tool; all three values are bounded. The endpoint rejects tool input, prompts, results, customer data, secrets and arbitrary metadata. It is authenticated, idempotent and bound to the same named client, profile, environment and open turn as context delivery.
4. Report technical completion
POST <base>/context/turns/<turn.id>/complete
Idempotency-Key: <another random key>
{
"status": "completed",
"note": "optional technical outcome"
}
Allowed statuses are completed, cancelled and failed. They are reported by the application. The optional note is redacted and limited; do not send model output, customer data or secrets. A repeated completion key for the same turn returns the original receipt. Reusing a completion key for another turn is refused.
Enforcement and responses
The host validates the bearer, client identity, bound profile and environment before it accepts a turn or context call. For a required client it rejects:
| Status | Code | Meaning |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED, TURN_ID_INVALID, TURN_STATUS_INVALID, TURN_OBSERVATION_INVALID | The integration request does not meet the protocol shape, or attempted to retain data outside the bounded pre-tool observation. |
| 403 | TURN_CLIENT_TOKEN_REQUIRED | A shared project token cannot use this protocol. |
| 409 | TURN_NOT_OWNED, TURN_CLOSED, TURN_EXPIRED, IDEMPOTENCY_KEY_CONFLICT | The turn/key belongs elsewhere, is closed or expired, or a key was reused inconsistently. |
| 428 | TURN_ID_REQUIRED | This named client requires an open X-SEIF-Turn-Id before HTTP context is delivered. |
The enforcement point is the hosted HTTP context gateway. A caller that does not use this gateway remains outside the protocol. This is intentional: SEIF reports an evidence boundary, not invisible control over arbitrary runtimes.
States and limits
- OPEN: SEIF received a start. The completion is not yet observed.
- COMPLETED_WITH_CONTEXT_DELIVERY: the client reported completion and SEIF has one or more correlated HTTP context deliveries.
- COMPLETED_WITHOUT_CONTEXT_DELIVERY: the client reported completion but SEIF recorded no correlated HTTP context delivery.
- EXPIRED_OR_INCOMPLETE: the 15-minute turn expired before a completion report. The outcome is inconclusive.
- REPORTED_CANCELLED / REPORTED_FAILED: technical state reported by the client, not a statement about model reasoning or user-visible outcome.
- observations: a count of accepted
pre_tool_usehook events. It does not count tool execution, an MCP request, a result, or model comprehension.
- observed: start, a context delivery that reached the HTTP gateway, and an accepted bounded
pre_tool_useevent. - reported:
completed,cancelledandfailedstatuses supplied by the client. - not observed: a tool invocation or outcome, an MCP request made after the hook, tool-result consumption, the model's reasoning/comprehension, or its final/customer-visible response.
Turn ids expire after 15 minutes. A closed or expired turn cannot receive more context or observations. These limits bound storage and prevent a stale credential from keeping a turn open indefinitely.
Reference implementation pattern
Put this sequence in the code that invokes your model, before the model receives the context and after its technical call returns. Do not make the model responsible for calling these endpoints.
const headers = { authorization: `Bearer ${process.env.SEIF_CLIENT_TOKEN}`, 'content-type': 'application/json' };
const start = await fetch(`${base}/context/turns/start`, {
method: 'POST', headers: { ...headers, 'idempotency-key': crypto.randomUUID().replaceAll('-', '') },
body: JSON.stringify({}),
}).then(response => response.json());
try {
const context = await fetch(`${base}/context/compass`, {
method: 'POST', headers: { ...headers, 'x-seif-turn-id': start.turn.id },
body: JSON.stringify({ task: userQuestion }),
}).then(response => response.json());
const answer = await runYourModel(context.text);
await fetch(`${base}/context/turns/${start.turn.id}/complete`, {
method: 'POST', headers: { ...headers, 'idempotency-key': crypto.randomUUID().replaceAll('-', '') },
body: JSON.stringify({ status: 'completed' }),
});
return answer;
} catch (error) {
await fetch(`${base}/context/turns/${start.turn.id}/complete`, {
method: 'POST', headers: { ...headers, 'idempotency-key': crypto.randomUUID().replaceAll('-', '') },
body: JSON.stringify({ status: 'failed' }),
}).catch(() => {});
throw error;
}
For a start failure, retry only before allowing the model to receive turn-bound context. If it cannot be started, continue under the runtime's uncertainty policy and leave the turn absent. For completion and pre-tool observation network failures, keep a bounded outbox with the original idempotency keys and retry them. Until the host receives an event, SEIF displays the turn as absent or incomplete; it does not infer one.
See Context Traces, Integration API, Privacy and retention and Trinity.
Related local delivery adapters
OpenBot delivery observation applies the same evidence-boundary principle to work delivered through the OpenBot local bridge. Its receipts describe only bridge-observed filesystem, process and HTTP transitions; they do not establish model or tool activity inside OpenBot.