live-log-viewer-next

0
0
0
TypeScript
public
Forked

Agent Log Viewer

agent-log-viewer is a local web UI that turns raw Codex / Claude Code agent
logs into a readable, live-updating chat feed. It discovers every session,
subagent and background shell task on your machine, links them into a
parent→child tree, and tails the selected one in real time.

From the overview board into a session and its live tail

The default setup runs locally against transcripts already on disk. It reads
~/.claude and ~/.codex directly, while operational Viewer state is kept in
a local SQLite database under its private configuration directory.
Optional outbound integrations stay disabled until you configure them:

bunx agent-log-viewer   # or: npx agent-log-viewer

See WakaTime activity integration for opt-in activity
export, its disclosure boundary, and disablement steps.

Prefer video? A 45-second cut of the full flow lives at
docs/media/demo.mp4.

The tour

Read any session as a chat

User bubbles, assistant prose, tool-call cards with ✓/✗ statuses, expandable
command output and diffs — for Claude Code sessions and their subagents
(~/.claude/projects/**/*.jsonl), Codex CLI rollouts
(~/.codex/sessions/**/rollout-*.jsonl) with command cards, patches and
service events, and background shell tasks (recovered from the transcript
and shown above the terminal output).

The chat feed: user bubbles, assistant prose, and tool-call cards with
statuses and expandable output

Hand a project to an orchestrator

The orchestrator dock beside its project board

Each project can designate one agent as its orchestrator. Press
Orchestrator in the project header, tell it what you want shipped, and it
opens a lane per issue, spawns the implementer, runs a fresh reviewer each
round and merges on APPROVE. Its mandate and any handoff from a predecessor are
written for you — there is no “you are an orchestrator” prompt to compose — and
you come back to the board when an agent needs a decision.

docs/orchestrator.md walks the whole flow on a fresh
install: the dock, the board, pipelines, tasks, the attention queue, the
model/effort matrix the conveyor uses, and when three tasks a week does not
need any of it.

Spawn agents from the board

Each project is a pannable, zoomable scheme — root conversations on top,
spawned agents one generation below, arrows colored by engine. Draft a new
agent right on the board: pick Claude or Codex, a model and reasoning effort,
type the first prompt, and it launches.

Drafting and configuring a new agent on the project board

Run implement → review loops

The viewer orchestrates review cycles: a long-lived implementer, a fresh
read-only reviewer each round over the full diff, findings relayed
automatically, and a verdict deck in the scheme view.

A review loop: round 1 requested changes, round 2 re-checks live

Answer a blocked agent from the browser

When an agent stops on an AskUserQuestion, the question surfaces as a card
with clickable options. The answer is delivered to the agent and confirmed
against the transcript — the agent just keeps going.

Answering a pending AskUserQuestion from the feed

And everything around it

  • Parentage tree: session → subagents → rollouts → background tasks, built
    server-side by scanning transcripts (append-only incremental,
    cached — the warm /api/files poll stays around 100 ms).
  • Live activity: content-based badges — a transcript reads working while
    it is mid-turn and done once the final assistant message lands.
  • Deep links: every selection is reflected in the URL (#f=<path>), so a
    link opens that exact log.
  • English or Ukrainian UI, model chips (opus, gpt-5.6-sol, sonnet…),
    collapsible tree with persisted state, follow-mode autoscroll, service-event
    toggle, and a line filter.

The session parentage tree, wiring root conversations to their spawned agents:

Session parentage tree

A Codex CLI session The overview board
Codex session with command cards and patches Overview board across projects

All media above is regenerated deterministically from a synthetic fixture —
see docs/media/README.md.

Run

The package is published to npm, so the quickstart above needs no clone:

bunx agent-log-viewer
# or
npx agent-log-viewer

This starts the server on 127.0.0.1:8898 and opens your browser. The CLI also
starts and supervises the packaged structured runtime host, including restart
backoff and Ctrl-C cleanup. Pipelines and the orchestrator work from this
installation without Docker. Both launch paths require Bun because the runtime
journal and authoritative state stores use Bun SQLite.

From a local clone

bun install
bun run build
bun bin/cli.mjs --no-open --port 8898 --hostname 127.0.0.1
# open http://127.0.0.1:8898/

The CLI serves the output of the last build, so run build first. For
development, bun dev runs the app with hot reload and expects a separately
managed runtime host (it needs a high OS file-watch limit for large home
directories).

The Viewer server runs on Bun. The runtime journal and hot state collections
use bun:sqlite, and macOS process ownership uses the kernel’s microsecond
start token. The Docker runtime and agent-log-viewer CLI select Bun for every
feature-flag configuration.

Spawn transport

Agents launch through a structured runtime host. The installed CLI supervises
that host with the same Bun executable as the Viewer and places its Unix socket
and runtime journal under the Viewer state directory with installation-specific
names. Ambient deployment runtime settings are ignored so separate bunx
installations cannot attach to each other’s host or claim each other’s journal
epoch. Startup fails clearly when Bun, the packaged host entry, the managed
socket, or its directory permissions are unavailable.
The CLI log carries the host failure and the pipeline card directs the operator
to it. There is no tmux fallback.

Connect an orchestrator through MCP

The package includes agent-log-viewer-mcp, a local stdio MCP server. It
invokes Viewer services in-process and shares their durable stores, locks, and
idempotency rules. Keep the Viewer package and the MCP process under the same
OS user so they resolve the same state directory.

For an installed package, add this server to the orchestrator’s standard MCP
configuration:

{
  "mcpServers": {
    "viewer": {
      "command": "agent-log-viewer-mcp"
    }
  }
}

For a local clone, point the client at the launcher:

{
  "mcpServers": {
    "viewer": {
      "command": "bun",
      "args": ["/absolute/path/to/live-log-viewer-next/bin/mcp-server.mjs"]
    }
  }
}

Quick install from the CLI:

# Claude Code (user scope)
claude mcp add viewer -s user -- bun /absolute/path/to/live-log-viewer-next/bin/mcp-server.mjs

# Codex — append to ~/.codex/config.toml
[mcp_servers.viewer]
command = "bun"
args = ["/absolute/path/to/live-log-viewer-next/bin/mcp-server.mjs"]

To register the server everywhere at once — the operator’s Claude Code and
Codex configs plus every Viewer-managed account under
~/.config/agent-log-viewer/accounts (their spawned agents each run with
their own CLAUDE_CONFIG_DIR/CODEX_HOME, so each account needs its own
registration) — run the idempotent installer and re-run it after adding
accounts:

scripts/install-mcp.sh                     # uses the managed stable launcher when present
LLV_MCP_BIN=/path/to/mcp-server.mjs \
  scripts/install-mcp.sh                   # select an explicit launcher

Exact deployments keep the managed executable at
~/.agents/tools/llv-mcp-runtime/bin/mcp-server.mjs. Existing Claude and
Codex registrations retain that path. Each fresh MCP process reads the atomic
Viewer release target and starts the runtime bundle staged from the same
revision. Deployment receipts expose the staged runtime digest plus durable
activation or restore evidence.

The server name must stay viewer (or another isViewerMcpServer() match:
viewer-*, agent-log-viewer*) — transcript calls attributed to other names
do not render as Viewer cards.

The MCP surface includes:

  • conversations and the board: board_snapshot, list_conversations,
    get_conversation, send_message, message_receipt (what became of an
    accepted send, by its operation id), and conversation_action for
    interrupt, kill, resume, compact, and dialog-key;
  • review flows: list_flows, get_flow, and flow_action;
  • pipelines: create_pipeline, list_pipelines, get_pipeline,
    pipeline_action, and link_task_to_pipeline;
  • tasks: create_task, list_tasks, get_task, and update_task;
  • operator/runtime reads: operator_snapshot, deployment_status, and
    resources;
  • agent liveness and lifecycle: agent_activity for the per-conversation
    {lastRecordAt, turnState, host alive/gone, stalledForMs} stall snapshot, and
    lifecycle_events for the durable lifecycle journal (mode: "query", by
    project/pipeline/conversation and cursor) and its bounded relay digest
    (mode: "digest", terminal events immediately, routine progress coalesced and
    rate-limited to one per five minutes per subscriber);
  • agent/runtime mutations: spawn_agent, conversation_migration, and
    deploy_exact_sha;
  • the operator’s attention: request_attention, which offers to move their
    Viewer to a target and waits for their answer. It only asks — nothing moves
    until they agree on a device, and the request names the root agent by an
    identity the server resolves, never one the caller supplies.

Every call requires a stable clientRequestId. Reusing that id with the same
arguments returns the durable result as a replay. Reusing it with different
arguments returns an idempotency conflict. Read tools are inert, bounded, and
secret-redacted. Mutating tools return stable operation receipts, and their
durable MCP receipt prevents a replay from applying the action twice.
deploy_exact_sha accepts a full 40-character commit SHA and requires
confirm: "deploy".

The package exposes the MCP launcher as agent-log-viewer/mcp-server in
addition to the agent-log-viewer-mcp executable.

Tool results contain the durable entity identifiers available for that action,
including conversation ids, transcript paths, pipeline ids, task ids, and
runtime operation ids. Viewer transcripts render calls attributed to the viewer MCP
server as live cards and turn those identifiers into navigation chips.

Prerequisites: Node ≥ 20.9, and bun or npm/pnpm. tmux is optional — see
Platform support.

Docker (reproducible runtime)

For a pinned, reproducible deployment the repo ships a Dockerfile and
docker-compose.yml that build .next inside the image and run the Viewer
with host parity. The container reuses your real tmux, claude, codex, and
home directory. Runtime-host owns production releases and the listener.
Complete the bootstrap listener migration
before the first runtime-host activation.

export LLV_DOCKER_GID="$(stat -c %g /var/run/docker.sock)"
LLV_RUNTIME_EVENTS=1 LLV_VIEWER_DEPLOYMENTS=1 docker compose --profile runtime-host up -d runtime-host
scripts/rebuild.sh
LLV_TEST_PORT=8901 docker compose --profile test up -d viewer-test

scripts/rebuild.sh is the whole release command, run from any checkout of the
repository — a worktree included — with nothing wrapping it and no git pull
first. It posts a revision to the runtime host, which builds that revision from
its own canonical Git mirror rather than from the working tree. With no argument
and no LLV_DEPLOY_REVISION override, it resolves the canonical
refs/heads/main tip and deploys that exact commit; a full 40-character commit
SHA in either case pins a redeploy or a rollback and is posted lowercase.

See docs/docker.md for the parity model, the nsenter shims,
and volume/port details.

Attach to a live tmux pane

The Viewer resolves and copies a complete command for each live pane. Paste that command into a normal shell; it selects the supervisor endpoint and the current pane coordinate. The read-only variant adds -r.

TMUX_TMPDIR='/run/user/1000/agent-log-viewer' tmux attach-session -t 'agents:2.0'
TMUX_TMPDIR='/run/user/1000/agent-log-viewer' tmux attach-session -r -t 'agents:2.0'

Detach with Ctrl-b d and the agent keeps running. An unqualified tmux command may use a different server. Refresh and copy again after a stale-pane or restarted-server message; the fresh command accounts for window renumbering. See the Docker guide for the supervisor migration context.

CLI options

agent-log-viewer [options]
Option Description
-p, --port <n> Port for the local server (default 8898).
-H, --hostname <h> Bind address (default 127.0.0.1).
--tailscale Expose the viewer inside your tailnet (see below).
--new-token Generate a fresh access key and invalidate old cookies.
--no-open Don’t open the browser on start.
-v, --version Print the version.
-h, --help Show usage.

Platform support

Linux is the native target: process discovery reads /proc directly. macOS is
supported through a portable backend that shells out to ps and lsof
instead — same live-process detection, composer host targeting, agent
spawn/kill and background-task discovery, just a bit more subprocess overhead
per scan. Windows has its own backend: one Get-CimInstance Win32_Process
snapshot per five seconds for pids, lineage, command lines and memory, plus two
values read from the kernel — the process creation time that makes up the
identity token, and each agent’s working directory. The backend is chosen
automatically by process.platform (see src/lib/proc/); VIEWER_PROC_BACKEND
forces one of linux, portable or windows, for testing.

Windows

The package installs on Windows. Install Claude Code with its native
installer
, or put a claude.exe on PATH: an npm-only install exposes
claude.cmd, and a shim is not something the Viewer will run without a shell.
State lives under %USERPROFILE%\.config\agent-log-viewer, transcripts under
%USERPROFILE%\.claude\projects. Run the Viewer where the agents run — a
Claude installed inside WSL writes into the WSL filesystem, and a native Viewer
does not see those transcripts (or the reverse).

These stay WSL routes on Windows, and WSL still works exactly as Linux:

  • Codex — hosting, review flows, and everything Codex-side. Upstream calls
    native Windows experimental and recommends WSL 2.
  • The Telegram connector and local dictation (cloud dictation backends
    are unaffected).
  • Workflow setup commands, which are sh -c.
  • Docker deployments and staging, which are Linux by nature.
  • The MCP server for orchestrator agents, and --tailscale.

These work natively but narrower than on Linux:

  • No open-handle scan. A transcript reads as live from mtime recency, and
    its owning process from the --session-id in its argv or from its working
    directory — never from a live writer holding the file open. Background-task
    .output files cannot be mapped back to a pid.
  • Termination is immediate. Windows has no signals; every stop is
    TerminateProcess after the child’s stdin is closed, and the “force” step in
    the task header is a second immediate kill. A host’s process tree is walked
    and killed descendants-first, each member’s identity checked, in place of the
    process-group signal Linux sends.
  • A process the Viewer cannot open stays invisible. Reading an agent’s
    working directory needs a handle on it, so an elevated console’s claude, or
    one running as another user, is not listed.
  • Memory shows the working set only, with no swap figure.
  • Managed (multi) Claude accounts, the in-app login supervisor and composer
    image attachments
    are not available; log in from a terminal with claude,
    and use the Main account.
  • Claude background tasks and scratchpad sessions have no project. The
    background-task root depends on a Windows directory layout nobody has
    observed, so it is simply absent. A scratchpad session is still recognised as
    one, but the walk that turns the encoded path in its container back into a
    repository starts at the filesystem root and a Windows path starts at a drive
    letter, so no repository is found and the session lands in “Unresolved
    project” along with every other one. Sessions started from an ordinary
    directory are unaffected — they group by that directory.
  • A session whose recorded path differs from a root only by drive-letter
    case
    forms its own project.

tmux is optional, and nothing needs it to launch or message an agent —
agents run on a structured runtime host (see Spawn transport),
which is why none of it is required on Windows. It is needed only to attach a
terminal to a legacy pane that predates that host (brew install tmux on macOS,
or your distro’s package on Linux).

Language

The UI defaults to English and shows a compact EN/UK toggle in the project
list header. The locale is resolved as localStorage key llv_lang first,
then the browser language (Ukrainian if the browser prefers it), then English.

CLI messages are English by default, and switch to Ukrainian with
LLV_LANG=uk or a uk_* value in LANG/LC_ALL.

Dictation / voice input

Composers that talk to agents have a mic button for dictating messages. By
default transcription runs fully locally via faster-whisper — no audio leaves
the machine. Run scripts/setup-whisper.sh once to install the local engine.

Two cloud backends are available as an explicit per-machine opt-in (never a UI
toggle): ChatGPT (reuses your local Codex login) and ElevenLabs Scribe (the
only one with live, streaming transcription). Select with
LLV_TRANSCRIBE_BACKEND=local|chatgpt|elevenlabs or by writing the backend
name to ~/.config/agent-log-viewer/transcribe-backend; local is the default.

See docs/transcription.md for setup, key locations,
and troubleshooting.

Review loops

The viewer orchestrates implement→review cycles: a long-lived implementer
agent, a fresh read-only reviewer per round over the full diff,
automatic relay of findings, and a verdict deck in the scheme view. Start one
from the Flow chip above a conversation pane; presets pair engines and
reasoning efforts per role (e.g. Terra high → Sol xhigh). New Codex agents
also expose explicit GPT-5.6-Sol and GPT-5.6-Terra choices beside effort and
speed.

See docs/review-loop.md for the round protocol,
presets, the HTTP automation API, and troubleshooting. A Claude Code skill
for driving flows from an agent ships in .claude/skills/review-loop/
agents working in a clone pick it up automatically.

Tailscale access

bunx agent-log-viewer --tailscale

--tailscale starts the local server on 127.0.0.1 and exposes it inside your
tailnet through a foreground tailscale serve <port> process. The public
internet (Funnel) is never used.

The CLI generates a 32-character access key, appends it to the tailnet URL as
?k=..., and after the first visit the server sets an llv_auth cookie for 30
days. --new-token generates a fresh key and immediately invalidates every old
cookie — each request compares the hash against the current token, so a cookie
minted with a previous key no longer passes.

The terminal prints the tailnet URL along with a QR code to scan with a phone.
The same QR is available inside the web UI: the QR-icon button in the project
list header opens a popover with the code and the link as text (with a copy
button). The QR is rendered entirely client-side (the qrcode package, no
external requests) and is served only to already-authorized clients — the same
token gate from src/proxy.ts also protects /api/access. When the server
runs without --tailscale, the button shows a hint to start
bunx agent-log-viewer --tailscale.

Anyone with tailnet access to this URL can read all agent transcripts,
including any sensitive data that landed in a session, and can execute commands
through /api/conversation-host and /api/spawn. Treat the tailnet URL as a
secret — do not forward it to anyone else.

Security model

The log APIs refuse any path that does not resolve into one of the whitelisted
log roots (see src/lib/scanner/roots.ts). Mutating endpoints exist:
/api/conversation-host resumes or respawns a conversation’s host and delivers
a message to it, and /api/spawn starts commands. The same handlers are still
mounted at the legacy path /api/tmux; that name is historical and no tmux is
involved in delivery — the engine is spawned into the host namespace through
nsenter with privileges dropped.

By default the CLI binds to 127.0.0.1. With --tailscale, access is exposed
inside the tailnet via tailscale serve and guarded by the token gate in
src/proxy.ts. Non-loopback binds also force token mode. Treat any URL
containing ?k= as a credential.

A Docker-deployed runtime host on a personal workstation can keep LLV_TOKEN
for the tailnet while serving plain http://127.0.0.1:8898/ token-free, by
splitting its stable listener into a local entry and an authenticated remote
entry; see docs/docker.md.

Environment variables

All optional. Transcription variables are documented in full in
docs/transcription.md.

Variable Effect
VIEWER_PROC_BACKEND portable, linux or windows — force the process-discovery backend (auto-selected by default).
LLV_LANG uk or en — force the CLI message language.
LLV_TRANSCRIBE_BACKEND local, chatgpt, or elevenlabs — pick the dictation backend (default local).
LLV_WHISPER_MODEL faster-whisper model size (default small).
LLV_WHISPER_DEVICE cpu (default) or cuda.
LLV_WHISPER_VENV Path to the whisper virtualenv (default ~/.cache/agent-log-viewer/whisper-venv).
LLV_ELEVENLABS_STT_MODEL ElevenLabs batch model override.
ELEVENLABS_API_KEY ElevenLabs API key for the ElevenLabs backend.
LLV_REAPER_ENABLED 1 enables verified pane and detached-reviewer process cleanup by the deterministic agent reaper. Unset keeps dry-run mode and exposes its latest report at GET /api/lifecycle/reaper.
LLV_SCHEME_PROJECT_CAP Number of most-recent projects rendered by the scheme feed (default 10). List and search remain complete.
LLV_SCHEME_CARDS_PER_PROJECT Maximum scanner entries rendered per scheme project (default 80). List and search remain complete.
NEXT_PUBLIC_LLV_SCHEME_AGE_HORIZON_HOURS Age horizon in hours for automatic card placement on the project scheme (default 48). A root conversation with activity inside the horizon keeps an automatic card even while idle; older roots leave the canvas for quiet history and «All conversations». Live or running conversations and manually placed cards are never removed by the horizon. Inlined at build time (NEXT_PUBLIC_*).
LLV_HEADLESS_REAPER_THRESHOLD_MS Minimum age in milliseconds for the always-active leaked Codex/MCP safety reaper (default 7200000, two hours; minimum accepted value 60000).
LLV_HOST_RETIREMENT_IDLE_HOURS How long a structured host’s transcript must have been quiet before the automatic retirement sweep may end it (default 6). 0 turns the sweep off. Staleness is transcript modification time, never process age, and the sweep still refuses any host with a turn in flight, a pending question, an undelivered handoff entry, an open spawn receipt, an unflushed event tail, a realtime binding, or an orchestrator seat. Every qualification is re-proved one step before the signal, and each retirement is written to state/host-retirement-report.json and appended to state/host-retirement-journal.ndjson.
LLV_HOST_RETIREMENT_GRACE_MS How long a retiring host may take to honour SIGTERM before its tree is force-killed (default 5000, capped at 60000). Raising it lowers how many hosts one sweep attempts, so a sweep’s terminations still fit well inside the interval before the next one; whatever is skipped is reported as deferred and picked up by the following sweep.
LLV_DOCKER_NSENTER_SHIMS 1 makes the agent CLI resolver prefer the container’s /usr/local/bin nsenter shims for host CLIs. Set automatically by the Docker image; leave unset on a host runtime. See docs/docker.md.

Config paths

The viewer keeps its state under the standard XDG directories, named after the
package:

  • ~/.config/agent-log-viewer/ — access token, transcribe-backend, and
    elevenlabs-api-key.
  • ~/.cache/agent-log-viewer/whisper-venv — the local transcription
    virtualenv.

Legacy live-log-viewer paths remain valid fallbacks. When a legacy config or
cache file is the resolved existing file, subsequent updates keep using that
same path so existing setups continue without a forced move.

Architecture

See ARCHITECTURE.md: route handlers under src/app/api/*, a
pure scanner pipeline under src/lib/scanner/* (discover → describe → activity
→ model → links), React components under src/components/*. Caches live on
globalThis and survive dev hot-reload.

License

MIT

v0.3.3[beta]