# Quickstart

> Start tapes serve, point tapesctl at :8081, seed demo data, list sessions, and capture one real agent against :8082.

- Repository: papercomputeco/tapes
- GitHub: https://github.com/papercomputeco/tapes
- Human docs: https://grok-wiki.com/public/docs/papercomputeco-tapes-020596f21750
- Complete Markdown: https://grok-wiki.com/public/docs/papercomputeco-tapes-020596f21750/llms-full.txt

## Source Files

- `docs/introduction.md`
- `cmd/tapes/serve/serve.go`
- `cmd/tapes/serve/stack.go`
- `cmd/tapes/local/local.go`
- `cmd/tapes/status/status.go`
- `README.md`

---

---
title: "Quickstart"
description: "Start tapes serve, point tapesctl at :8081, seed demo data, list sessions, and capture one real agent against :8082."
---

`tapes serve` is the all-in-one local runtime: capture proxy on `:8080`, public read API on `:8081`, private ingest API on `:8082`, an in-process derive worker (2s debounce), and — unless `--embed-spans=false` — an in-process embed worker (10s interval). `tapes` owns PostgreSQL. `tapesctl` is the client: it never guesses a host, and it talks to two different ports for read versus capture.

## Prerequisites

Install both binaries and bootstrap local PostgreSQL (pgvector) plus Ollama as described in [Installation](/installation). Docker is required for `tapes local up`. Confirm:

```bash
tapes version
tapesctl version
tapes local up
tapes local status
```

`tapes local up` writes `storage.postgres_dsn`, `vector_store.target`, `proxy.upstream`, and `embedding.{provider,target,model}` into the active `.tapes/config.toml` (creating `~/.tapes` when no project dir exists). Defaults after bootstrap: DSN `postgres://tapes:tapes@localhost:5432/tapes?sslmode=disable`, embedding provider `ollama`, model `embeddinggemma` (768 dims), Ollama at `http://localhost:11434`. Native Ollama is reused when it is already serving; `--docker-ollama` forces the container.

<Note>
`tapes local up` pulls the embedding model, not a chat model. A completion model is only needed if you send traffic through the `:8080` proxy to Ollama.
</Note>

## Default ports

| Listen | Process | Client use |
| --- | --- | --- |
| `:8080` | Capture proxy | Optional fixed-port `ANTHROPIC_BASE_URL` / generic provider base URL |
| `:8081` | Read API | `tapesctl seed`, `sessions`, `export`, `search` |
| `:8082` | Private ingest API | `tapesctl start`, `capture`, `sync` |

A capture pointed at `:8081` reports success and stores nothing. Configure the read URL once; pass `--tapes-url http://localhost:8082` on every capture command.

```mermaid
flowchart LR
  subgraph Client
    tapesctl
    Agent[agent process]
  end
  subgraph Serve["tapes serve"]
    P["proxy :8080"]
    R["read API :8081"]
    I["ingest :8082"]
    D["derive worker"]
    E["embed worker"]
  end
  PG[("PostgreSQL + pgvector")]
  OL["Ollama embeddings"]
  tapesctl -->|"seed / list / search / export"| R
  tapesctl -->|"start / capture / sync"| I
  Agent -->|"optional fixed-port"| P
  P --> PG
  I --> PG
  D --> PG
  E --> PG
  E --> OL
  R --> PG
```

## Start the stack

<Steps>
<Step title="Run tapes serve">

Leave this in a foreground terminal. `SIGINT` / `SIGTERM` stops the in-process workers and tears the HTTP servers down.

```bash
tapes serve
```

Defaults: `proxy.provider=ollama`, `proxy.upstream=http://localhost:11434`, `proxy.project` from the current Git repo name when unset, `api.web_ui=false`, span embedding on.

Useful overrides:

```bash
tapes serve --api-web-ui
tapes serve --embed-spans=false
tapes serve --provider anthropic --upstream https://api.anthropic.com
```

</Step>
<Step title="Verify the read API">

```bash
curl http://localhost:8081/ping
tapes status
```

`GET /ping` returns the JSON string `"pong"`. `tapes status` probes `client.api_target` (default `http://localhost:8081`) with a 3s `/v1/stats` call and prints config dir, provider → upstream, whether a Postgres DSN is set, and `N sessions · M turns · $… captured`. Unreachable API output tells you to run `tapes local up` then `tapes serve`.

</Step>
<Step title="Point tapesctl at :8081">

```bash
tapesctl config set tapes-url http://localhost:8081
```

That writes `tapes-url` in `~/.tapes/config.toml` only. A project `.tapes/` is invisible to the client. Precedence: `--tapes-url` > `TAPES_URL` > configured value. With none of the three, a command that needs a server fails instead of guessing.

</Step>
<Step title="Seed demo sessions">

```bash
tapesctl seed
```

The client calls the read API `POST /v1/admin/seed/demo`. The server replays two bundled corpora (`corpus-cb9a87e5`, `corpus-9fec0da7`) through an in-process ingest (`POST /v1/ingest` then `POST /v1/ingest/transcript`), tags them `project=demo`, and derives each session **synchronously**. Seeded rows are indistinguishable from live capture.

Re-run is a no-op: raw-turn dedup (`org_id` + `request_id`) and transcript content-hash absorb replays. `overwrite` is rejected (`400`).

<ResponseExample>

```json
{
  "sessions": 2,
  "raw_turns": 0,
  "raw_turns_inserted": 0,
  "raw_turns_deduped": 0
}
```

</ResponseExample>

`raw_turns` is the replayed corpus row count. After the first seed, later runs report insertions as `0` and increment `raw_turns_deduped`.

</Step>
<Step title="List sessions">

```bash
tapesctl sessions list
```

Equivalent HTTP:

```bash
curl http://localhost:8081/v1/sessions
```

Use the Tapes session UUID from this list for `sessions get`, `export`, and traces — not the harness session id `tapesctl start` prints on exit.

```bash
tapesctl sessions get <session-id>
tapesctl sessions traces <session-id>
tapesctl sessions raw-turns <session-id>
tapesctl export <session-id> -o session.jsonl
```

`--detail traces` on export is turn headers only; the default is the full span tree (`GET /v1/sessions/{id}/export`).

</Step>
<Step title="Optional: search seeded spans">

Embedding is a separate loop. After seed, wait for the in-process embed worker (~10s) then:

```bash
tapesctl search "explain the retry logic"
```

Empty results exit 0 (`No results found.`). Disable embedding only if you do not need search: `tapes serve --embed-spans=false`. Embed setup failures log a warning and leave search unavailable; they do not fail `tapes serve`.

</Step>
<Step title="Capture one real agent on :8082">

Clear demo data only if you want an empty store (`--wipe` deletes the local Postgres data directory under `.tapes/postgres`):

```bash
# stop tapes serve first
tapes local down --wipe && tapes local up
tapes serve
```

Launch a supported harness against **ingest**, not the read API:

<Tabs>
<Tab title="Claude">

```bash
tapesctl start claude --tapes-url http://localhost:8082
tapesctl start claude --tapes-url http://localhost:8082 -- --worktree
```

`start` binds a just-in-time loopback capture proxy, sets `ANTHROPIC_BASE_URL`, launches `claude`, and POSTs completed turns to `:8082`. On exit it prints the harness session id — find the Tapes id with `tapesctl sessions list`.

Claude transcripts on disk carry subagent structure the wire path cannot. Sweep them after a session that ran without capture:

```bash
tapesctl sync --tapes-url http://localhost:8082
```

Default window is the last seven days; `--since-days 0` sweeps everything. Re-push is safe (server dedup).

</Tab>
<Tab title="Codex CLI">

```bash
tapesctl start codex --tapes-url http://localhost:8082
```

</Tab>
<Tab title="pi">

```bash
tapesctl plugin install pi
tapesctl start pi --tapes-url http://localhost:8082
tapesctl start pi --tapes-url http://localhost:8082 --schema openai
```

`start pi` refuses to run without the plugin. `--schema` is valid only on `pi`; it is an error on `claude` or `codex`.

</Tab>
<Tab title="Codex desktop">

```bash
tapesctl plugin install codex-app
tapesctl capture codex-app --tapes-url http://localhost:8082
```

The app launches itself. Install first — `capture` without the plugin fails. `capture` reports sessions seen, not turn counts.

</Tab>
</Tabs>

The agent brings its own provider credentials. `tapes auth` is for server-side embedding and skill generation, not capture.

After a live ingest, session rows exist immediately; traces and spans appear after the derive debounce (2s in-process). Then:

```bash
tapesctl sessions list --tapes-url http://localhost:8081
```

</Step>
</Steps>

## OpenAI embeddings (optional)

Local Postgres is still required. Switch the embedder and store a key (or export `OPENAI_API_KEY`):

```bash
tapes auth openai
tapes config set embedding.provider openai
tapes serve
```

`tapes serve` constructs the embedder at startup. OpenAI with no key fails immediately: `OPENAI_API_KEY is required for openai embeddings`. Model and dimensions must match the provider output (`text-embedding-3-large` is the OpenAI embedder default).

## Fixed-port proxy instead of `tapesctl start`

For a long-lived proxy on `:8080` (not the just-in-time client proxy):

```bash
tapes serve --provider anthropic --upstream https://api.anthropic.com
ANTHROPIC_BASE_URL=http://localhost:8080 claude
```

Default `tapes serve` forwards Ollama-compatible traffic to `http://localhost:11434`:

```bash
curl http://localhost:8080/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"model":"qwen3-coder:30b","messages":[{"role":"user","content":"hello"}],"stream":false}'
```

Pull that chat model yourself (`ollama pull qwen3-coder:30b` or `docker exec -it tapes-local-ollama ollama pull …`).

## Stop and reset

| Command | Effect |
| --- | --- |
| `Ctrl-C` on `tapes serve` | Stops proxy, read API, ingest, derive, embed |
| `tapes local down` | Removes `tapes-local-postgres` and `tapes-local-ollama`; keeps Postgres files |
| `tapes local down --wipe` | Also deletes the `.tapes/postgres` data dir — all captured sessions |

<Warning>
`--wipe` is permanent. Recreate storage with `tapes local up` before `tapes serve` again.
</Warning>

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| Capture “succeeds”, `sessions list` empty | `start` / `capture` / `sync` aimed at `:8081` | Use `--tapes-url http://localhost:8082` |
| `tapesctl` fails with no host | No `--tapes-url`, `TAPES_URL`, or `tapes-url` | `tapesctl config set tapes-url http://localhost:8081` |
| `tapes status` API unreachable | Serve or Postgres down | `tapes local status`, then `tapes serve` |
| `docker is required for 'tapes local'` | Docker missing from `PATH` | Install Docker; `tapes local` shells out to it |
| Native Ollama installed but idle | `tapes local up` will not start it | `ollama serve` and `ollama pull embeddinggemma:latest` |
| `OPENAI_API_KEY is required…` at serve | `embedding.provider=openai` with no key | `tapes auth openai` or `OPENAI_API_KEY` |
| Search empty after seed | Embed worker not finished, or `--embed-spans=false` | Wait ~10s; `curl http://localhost:11434/api/tags` |
| `sessions get` 404 on printed id | Used harness session id | Take the UUID from `tapesctl sessions list` |
| Seed `400` overwrite | Old client sent `overwrite: true` | Omit it; seed is idempotent |
| Live session listed, no traces yet | Derive debounce | Wait 2s on all-in-one `tapes serve` |

<AccordionGroup>
<Accordion title="What tapes serve starts versus split processes">

Bare `tapes serve` is the local convenience process. The same binaries can run separately (`tapes serve proxy`, `api`, `ingest`, `derive-worker`, `embed-worker`) with their own DSNs and failure domains. Split deployment is not required for this quickstart.

</Accordion>
<Accordion title="Seed HTTP contract">

:::endpoint POST /v1/admin/seed/demo Replay bundled demo corpora through ingest and derive
Optional JSON body: `{ "overwrite": false }`. `overwrite: true` → `400`. Success → `seed.Result`. Missing raw-turn driver → `501`.
:::

</Accordion>
</AccordionGroup>

## Next

<CardGroup>
<Card title="Capture an agent" href="/capture-an-agent">
Harness matrix, JIT vs fixed-port `:8080`, and ingest on `:8082`.
</Card>
<Card title="Read API vs ingest" href="/read-vs-ingest">
Why `:8081` and `:8082` are different contracts and a trust boundary.
</Card>
<Card title="Capture and derive" href="/capture-and-derive">
`raw_turns` append, reduction beside `raw_response`, idempotent projection.
</Card>
<Card title="Inspect and export" href="/inspect-and-export">
Session list, traces, raw_turns, optional web UI, JSONL export.
</Card>
<Card title="Search spans" href="/search-spans">
`GET /v1/search/spans` after the embed worker runs.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Wrong-port capture, missing embed keys, ingest 413, `--wipe`.
</Card>
</CardGroup>
