# Run an agent turn

An async Space does not keep an agent process alive. A member posts a turn; the render opens the sealed room state,
runs the agent, writes the reply, and goes cold again.

That is the core runtime loop.

---

## The turn flow

<div class="lane" aria-label="Agent turn flow">
  <div class="lt">One request-scoped render</div>
  <div class="lr">
    <div class="box">Member sends <code>turn</code> with room key and message</div>
    <div class="arrow">-&gt;</div>
    <div class="box">Render checks admission, opens the ledger, runs tools, re-seals, and drops the key</div>
  </div>
</div>

1. A member sends `{ op:'turn', room, mk, message }`.
2. The transport checks `commit(mk)` against the Space record.
3. If the Space is gated, identity is verified against the sealed admission policy.
4. The render opens the sealed ledger.
5. The member entry is appended.
6. The agent runs with the Space's persona and tools.
7. The reply, widgets, files, or pending proposals are appended.
8. The ledger is re-sealed and stored.
9. The room key is dropped.

Read the deeper version in [Async Spaces](./async-spaces.md).

## Minimal turn

```js
const r = await client.turn(room, mk, {
  from: 'Ada',
  text: 'Summarize our plan and list the unresolved decisions.'
})
```

For an open Space, that is enough.

## Signed member turns

A signed turn proves which admitted member wrote the entry. The client signs the entry core and sends the signature
with the message. For an email-gated Space, the message also carries an ID token and the member signing public key.

Conceptually:

```js
await client.turn(room, mk, {
  text: 'I approve the itinerary.',
}, {
  email: 'ada@example.com',
  googleIdToken,
  sign: { priv: memberPrivateKey, pub: memberPublicKey }
})
```

The render verifies the identity and stores only the verified attribution, not the raw ID token.

## Tools during a turn

A Space's tools are declared in its sealed config. During a turn, the agent can call those tools. Tool outputs may
become:

- plain text in the reply
- a shared widget such as a map, chart, diagram, itinerary, or flights card
- a file attached to the reply
- a pending proposal requiring human approval

High-stakes calls are not run directly. They become proposals under [Delegated authority](./delegated-authority.md).

## Reads after a turn

Clients normally watch a Space with `pollSealed`. When the sealed ledger changes, the client opens it locally and
renders the new entries.

```js
const r = await client.pollSealed(room, mk, previousEtag, previousCount)
```

For gated Spaces, read operations also include the caller's identity so the server can refuse unauthorized reads before
returning sealed content.

## What plaintext exposure means

In the current production tier, the server-side render receives `mk` during turns. That is why the model can reason
over the Space. The key is request-scoped and dropped afterward, and storage remains ciphertext at rest.

This is not the same as "the operator can never see plaintext in use." The attested server tier is the path to making
that stronger property checkable for production traffic; it is built and verifiable, but not yet serving ordinary
Spaces.

Read: [Status](./status.md), [The double blind](./the-double-blind.md), [The attested tier](./the-attested-tier.md)

## Code map

| Concern | File |
|---|---|
| HTTP turn op | `agent/spaceHandler.mjs` |
| Runtime service | `agent/spaceService.mjs` |
| Decrypt-once render | `agent/asyncTurn.mjs` |
| Agent tool loop | `agent/brain.mjs`, `agent/brainTools.mjs` |
| Delegated authority | `agent/delegatedAuthority.mjs` |
| Client turn wrapper | `spaces/public/spaceClient.js` |
