# Threads & Conversation

Create threads, append messages, and replay the timeline — pure storage, no runtime.

## Pure storage

Threads are conversation storage over `/v1/threads`. The facade forces `dispatch:false`, so appending a message is **pure storage** — it never provisions a runtime and never produces an assistant turn. Reads need `seaplane:read`; writes need `seaplane:write`.


## Threads CRUD

`client.threads.create` (`POST /v1/threads`), `client.threads.list` (`GET /v1/threads`), `client.threads.get` / `client.threads.update` / `client.threads.delete`, plus `archive`, `fork`, `pin`, and `unpin`. Create returns `{ thread }`.

```bash
const created = await client.threads.create({
  title: "API integration smoke",
  metadata: { source: "sdk-example" },
});
const threadId = created.thread!.id;
```

## Messages

`client.threads.messages.create(threadId, body)` (`POST /v1/threads/{threadId}/messages`) appends a message. `content` is required; `role` defaults to `user` and is constrained to non-runtime roles. `clientMessageId` is the idempotency key — the SDK auto-generates a UUID when omitted, and re-posting the same id replays with `status:"duplicate"`. List with `client.threads.messages.list(threadId, query?)`.

```bash
await client.threads.messages.create(threadId, {
  role: "user",
  content: "Create a release checklist",
});
const messages = await client.threads.messages.list(threadId, { limit: 50 });
```

## Timeline

`client.threads.timeline(threadId, query?)` (`GET /v1/threads/{threadId}/timeline`) replays the append-only event log; `client.threads.timelineWindow(threadId, query?)` returns a windowed slice anchored at `latest` or `before`.

```bash
const timeline = await client.threads.timeline(threadId, { limit: 50 });
for (const ev of timeline.events ?? []) console.log(ev.seq, ev.kind);
```
