# Wave runtime
```bash
lf wave product
lf chat --wave product "What needs attention?"
lf reply product "Should the wave answer this?"
lf stop product
```
`lf wave <name>` starts one resident Wave as a listener and a resident process.
The split is runtime plumbing behind one command:
```text
lf wave <name> lf __resident <name>
┌──────────────────────┐ spawns ┌──────────────────────┐
│ listener │────────▶│ resident │
│ journal · HTTP │◀────────│ cadence · agent │
└──────────────────────┘ deltas └──────────────────────┘
```
The listener owns the durable channel, HTTP routes, local discovery, and typed
Project and Task observations. The resident owns two independent paths: a
lightweight chat observer runs `wave/chat` over unread channel messages, while
the governance scheduler runs `wave/operate` for cadence and Work observations.
Both return ordered deltas; neither writes the journal directly. `lf reply`
runs the same reply capability once without a listener or governance pass.
The listener keeps the canonical checkout as its journal and control plane.
The resident runs from the long-lived sibling `<repo>.wave-<name>` worktree.
Inline `GOAL.md` and `MEMORY.md` curation commits there and arms a normal
CI-gated pull request; every other repository mutation still belongs to a Task
in its own sibling worktree. The Wave UUID is stable identity; canonical
checkout plus normalized name is its mutable locator. Two repositories may
each own a Wave named `product` without sharing state.
## Persistence and discovery
The Wave's current canonical repository holds its durable files:
```text
.lf/journal/waves/<name>/journal.jsonl
wave/<name>/MEMORY.md
wave/<name>/metrics/*.md
wave/<name>/.wave-endpoint
wave/<name>/.wave-resident-token
```
The journal rebuilds the thread and loop state after restart. The
endpoint and resident token exist only while the listener owns that boot and
are removed on shutdown.
Metric Markdown owns reviewed meaning and one stable Project owner. Registered
instruments push typed observations into the local store; `lf status`,
`lf roadmap`, Wave/Project turns, and Apple clients consume one Rust-derived
portfolio. No read executes an instrument query, and no metric completes a KR.
Inspect historical Flow work before deciding its disposition:
```bash
lf wave recover product
lf wave recover product --cancel 42 --reason "Replan this continuation as Tasks"
```
Recovery prints the original saved snapshot, its source sequence and disposition.
Cancellation names that exact sequence, records the reason and preserves the
original journal. Repeating it does not cancel newer work or append another receipt.
It does not run or recompile the saved Flow.
Startup retires an idle default `wave/operate` root once. Custom roots, nested
frames, queued work and definitions without captured Skill content remain
unresolved until explicitly cancelled. An active historical attempt with no
recorded termination stays unresolved; a missing listener does not prove its
provider is dead, and cancellation cannot supply that proof.
Each governance wake captures `wave/operate` once and runs one harness attempt.
The turn journal owns its input claims, provider session and terminal outcome.
Failed or interrupted attempts restore their input claims in that same terminal
event. A restart with an unclosed attempt blocks until its termination is known.
Chat replies append independently without replacing an active governance turn.
New governance captures `wave/operate` directly. A custom `wave` Flow is no longer
resident control; run multi-step work through an ordinary bound Flow. Chat,
pending Work observations and failure/session history survive cutover.
Wave Chat is local when `GOAL.md` has no `chat` block. A Discord channel binding
replaces that backing on the next listener start. Each change appends one
conversation epoch; reopening the same backing resumes its epoch. The listener
preflights Discord before opening the journal, receives new messages through a
persistent Gateway connection, and catches up over REST from the committed
cursor after each connect. It journals external inputs before advancing that
cursor and journals deterministic send intents before posting. The resident
never inherits `LF_DISCORD_TOKEN`. Binding ownership is explicit and must agree
with the Wave's durable Home placement; an OS-held lease prevents concurrent
listeners across its checkouts.
The shared `~/.lf/loopflow.db` stores the Wave UUID, its repository-scoped
locator, and typed Project and Task observations. Interactive commands resolve the
name only inside the invoking repository; a diagnostic bare-name lookup fails
when several repositories own that name. A Wave can still run when that store
does not exist, but child observations are unavailable. The live endpoint and
locator lock enforce one listener per repository-scoped Wave; `--force`
explicitly takes over a live endpoint.
Rename or rehome a stopped Wave by UUID:
```bash
lf wave relocate <wave-id> --name platform
lf wave relocate <wave-id> --repo ../moved-repository
```
Relocation preserves the UUID, PM projection, Work and Run history, Home
placement, authored files, and journal. It moves a complete Wave chord when the
repository changes, carries nested descendants through a rename, requires
compatible configured PM Teams, and refuses live Work or divergent target
files. A dirty, unpublished, or open-PR resident worktree also blocks the move;
after merge, relocation fast-forwards canonical main before copying the authored
Wave state. Retry completes verified source cleanup after a crash at the commit
boundary.
## Thread
`lf chat` writes channel messages when the active epoch is local. Give a Wave
operation guidance with `lf --wave <wave> wave/operate "instructions"`. A Discord epoch rejects authored text with a typed Open-in-Discord action;
it never writes a parallel local turn. A bare interrupt remains available.
`lf chat --follow` replays the latest 12 source-bearing messages and follows new
ones, printing epoch boundaries and local/Discord provenance. Local history
folds from the journal even while stopped. Discord history is projected from
the provider through the active listener without copying transcript pages into
the journal. `lf chat --history --json -w <wave> --epoch <id>` reads one earlier
epoch without stitching it into the active conversation.
## Listener HTTP surface
The endpoint file contains `127.0.0.1:<port>`. User-facing routes are local and
do not require the resident token:
| `GET /health` | Reports listener/resident state plus the active chat epoch and backing health. |
| `GET /channel` | Reads unified channel messages after an optional `?since=<journal-seq>` cursor, including the Wave's own replies. |
| `GET /conversation` | Returns one source-bearing epoch; `?limit=N` tails it and `?epoch=<id>` selects history. |
| `GET /events` | Emits epoch and backing health, replays source-bearing messages, then streams message, local-only message-delta, and state events. |
| `POST /messages` | Sends locally, or returns `409` with Open in Discord when Discord is active. |
| `POST /stop` | Gracefully stops the listener and resident. |
The hidden `/resident/*` routes carry listener/resident coordination and require
the per-boot token. Their Rust DTO fixture is
`tests/fixtures/dto/resident_deltas.json`.
Run the process-level smoke test with:
```bash
cargo test -p loopflow --test wave_live_smoke -- --ignored
```