Skip to content

feat(agent): lk agent debugger, a text-mode test harness for coding agents - #977

Merged
bcherry merged 13 commits into
mainfrom
bcherry/agent-debugger
Sep 18, 2026
Merged

bcherry merged 13 commits into
mainfrom
bcherry/agent-debugger

Conversation

@bcherry

@bcherry bcherry commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

This (re)introduces a new text-mode test harness designed for coding agents to test livekit agents themselves. The goal of this change is to allow coding agents to rapidly iterate in goal loops on livekit, without overpsending cash on the new simulations feature. Simulations is reserved for integration testing, pre-production checklists, CI, etc.

This feature is built on top of the existing undocumented lk agent daemon feature, which is now renamed lk agent debugger and its functionality is made more comprehensive.

The change was tested extensively against livekit-examples/agent-starter-python and livekit-examples/agent-starter-node.

Follow-up work includes:

  • new/updated agent skills
  • better integration into starter agent readmes/etc
  • docs

Here's the help entry for the new command:

NAME:
   lk agent debugger - Drive a text conversation with a local agent from a script or coding agent

USAGE:
   lk agent debugger [command [command options]]

CATEGORY:
   Build and test agents locally

DESCRIPTION:
   Runs your agent locally in text mode as a background process, then lets
   you drive a multi-turn conversation one command at a time. Each "say" sends a
   user turn and prints everything the agent did in response: tool calls with
   their arguments and results, handoffs, errors, and the reply. Nothing is sent
   to a LiveKit room. The agent runs in console mode with STT/TTS disabled, so a
   turn costs only what your LLM (and tools) cost.

   This is built for coding agents (Claude Code, Codex, Cursor, ...) and shell
   scripts: the caller stands in for the user, decides the next line based on the
   reply, and can inspect logs and history between turns. Output, exit codes, and
   --json are shaped for a program driving it. To talk to your agent yourself, by
   voice or typing, use "lk agent console" instead.

   Typical flow, run from the agent project directory:

      lk agent debugger start                  # starts the agent, prints its greeting (if any)
      lk agent debugger say "Hi, what can you do?"
      lk agent debugger say "Book me a table for two tonight"
      lk agent debugger listen --timeout 15s   # wait for the agent to speak unprompted (timers, follow-ups)
      lk agent debugger logs -n 40             # agent process logs (tracebacks, warnings)
      lk agent debugger history                # full transcript so far
      lk agent debugger stop --transcript      # closing summary, plus the conversation

   The agent is found the same way as for "lk agent console": the project in the
   current directory (or the nearest parent) with its default entrypoint, or the
   file you name explicitly:

      lk agent debugger start src/my_agent.py
      lk agent debugger start agent.ts -- --env-file=.env   # args after -- go to node/python

   After editing the agent's code, run "lk agent debugger restart" to relaunch it
   with a fresh conversation. Add --json to any command for machine-readable
   output: each turn is a document with "text", "reply", "duration_ms", and an
   "events" list of message, tool_call, handoff, config, error, and log entries.
   Exit codes are non-zero when a turn fails or times out, or no session is
   running. Use --port to run several agents side by side (one session per port).

COMMANDS:
   start                Start the agent in the background and wait until it is ready
   say                  Send one user turn and print the agent's tool calls and reply
   listen               Wait for the agent to say something on its own (a greeting, timer, or follow-up) without sending a turn
   history, transcript  Print the conversation so far, as the agent recorded it
   status               Show whether a session is running, which agent is active, and its tools
   logs                 Print the agent process's recent log output
   restart              Stop the session and start it again with the same agent (picks up code changes)
   stop                 Stop the running session and its agent, printing a closing summary

OPTIONS:
   --help, -h  show help

Renames lk agent daemon to lk agent debugger (old name kept as a hidden alias) and rebuilds it into a text-mode harness that a coding agent (Claude Code, Codex, Cursor, …) or a script can use to converse with a local LiveKit agent one command at a time. The agent runs in console mode with STT/TTS off, so a turn costs only the agent's own LLM and tool calls and nothing touches a LiveKit room. It is an ad-hoc, local stand-in for simulations in which the coding agent plays the user.

lk agent debugger start                                                      # prints the agent's greeting, if any
lk agent debugger say "What's the weather in Tokyo?"
lk agent debugger say --logs "Check order 9001"         # agent log lines interleaved with the turn
lk agent debugger listen --timeout 15s                            # wait for unprompted speech (timers, follow-ups)
lk agent debugger history / status / logs -n 40 / restart
lk agent debugger stop --transcript

I've also updated the global help entry for the CLI to better capture that LiveKit is for building agents, and to unroll many of the agent subcommands for easier discovery:

NAME:
   lk agent - Build, test, and deploy agents

USAGE:
   lk agent command [command options]

DESCRIPTION:
   Everything for LiveKit agents, from a new project to production.

   Locally: "init" scaffolds a project, "dev" runs it with hot reload, "console"
   lets you talk to it by voice or text, "debugger" lets a coding agent or script
   converse with it turn by turn, and "simulate" runs judged simulations of it.

   On LiveKit Cloud: "create" and "deploy" ship it, then "status", "logs",
   "secrets", "versions", "rollback", and the rest manage it.

COMMANDS:
   Build and test agents locally:
     init      Create a new agent project from a template
     dev       Run an agent locally with hot reload
     start     Run an agent locally in production mode
     console   Talk to a local agent yourself, by voice or by typing
     debugger  Drive a text conversation with a local agent from a script or coding agent
     simulate  Run judged simulations of an agent on LiveKit Cloud

   Deploy and manage agents on LiveKit Cloud:
     create           Create and deploy a new agent on LiveKit Cloud
     dockerfile       Generate Dockerfile and .dockerignore for your project
     config           Write a livekit.toml for an existing agent into the working directory
     deploy           Deploy a new version of the agent
     promote          Promote an agent to a new deployment
     status           Get the status of an agent
     update           Update an agent's metadata and secrets (restarts the agent)
     restart          Restart an agent
     rollback         Roll back an agent to a previous version
     logs, tail       Tail an agent's logs
     delete, destroy  Delete an agent
     versions         List versions of an agent
     list             List the agents in the current project
     secrets          List an agent's secrets
     update-secrets   Update an agent's secrets (restarts the agent)
     private-link     Manage private links for agents

OPTIONS:
   --help, -h  show help

Rename `lk agent daemon` to `lk agent debugger` (the old name stays as a
hidden alias) and rebuild it around a shared agent-session core so a coding
agent such as Claude Code or Codex can test a LiveKit agent turn by turn from
the shell, without a human at a microphone and without LiveKit Cloud
simulations: the caller plays the user and pays only for the agent's own LLM
and tool calls.

New in the debugger:
- `listen`: wait for the agent to speak unprompted (greetings, timers,
  follow-ups) and print whatever it says.
- `history`, `status`, `logs [-n|-f]`, `restart`; `stop` prints a closing
  summary with `--transcript` and `--logs` options.
- `say --timeout`, `say --logs` (interleaves the agent's log lines with the
  turn so a tool's traceback sits next to the sanitized error), `say`
  from stdin, `--verbose`, and `--json` on every command with a stable
  event schema (message, tool_call, handoff, config, error, log).
- Tool calls render with arguments and results; handoffs and config
  changes are shown; silent turns say so.
- Events the agent produces between turns are buffered instead of
  dropped, and reported with the next `say`/`listen` or at `start`.
- Opening greetings print at `start`; a handoff target's introduction is
  held in the same turn as the handoff.

Internals: `debugger_agent.go` owns the console IPC connection (read loop,
response routing by request id, event normalization, undelivered buffer),
`debugger_daemon.go` is the detached process and control port,
`debugger.go` the CLI. Unit tests drive the core against a fake agent over
an in-memory pipe; the opt-in e2e test also checks history/status/logs.

Help text: `lk --help` is now sectioned (AGENTS, AGENT DEPLOYMENT, PROJECTS,
ROOMS AND MEDIA, TELEPHONY, TOOLS) with agent subcommands listed inline;
`lk agent --help` groups local vs cloud commands; console and debugger
state their audiences; agent subcommand summaries are tidied. README gains
"Agent console" and "Agent debugger" sections.
A caller that never runs `stop` (a coding agent whose task ended, a script
that crashed) used to leave the agent process running indefinitely. The
daemon now stops itself after --idle-timeout (default 30m) without any
control command; a turn in progress counts as activity. `status` shows the
idle time and limit, `restart` preserves the setting, and 0 disables it.
@u9g

u9g commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Demo of every lk agent debugger feature against a small Python agent with tools, a failing tool, a timer, and a handoff: the regrouped lk agent --help, start (greeting, tools, log path), say with a tool call, say --logs with the traceback beside the sanitized error, say --verbose latency, listen catching unprompted speech, stdin + --json showing the handoff event, status, history, logs -n, listen --json silent, --port for a second session with an explicit entrypoint, the hidden daemon alias, restart with a fresh conversation, stop --transcript, and exit code 1 with no session.

lk-agent-debugger-demo.mp4

…er their step; logs --last

- --verbose is gone; its two effects are separate flags: --metrics (per-reply
  latency and turn duration) and --full-output (untruncated tool results).
- say --logs now prints a turn's log lines beneath the event they belong to,
  so a failing tool's traceback appears under the tool's error line rather
  than above it.
- logs --lines/-n is now logs --last (with -n kept as the short form).
Seeing exactly what a tool returned is the point of the debugger, and --json
already carried the full text, so the 600-byte cap and the flag that lifted
it only got in the way.
…y → chat-history

`events` prints the session as a flat, timestamped, one-line-per-event stream
(messages, tool calls, handoffs, config changes, errors, agent state
transitions), replaying the most recent events by default and streaming new
ones with --follow; --logs adds agent log lines and --json emits NDJSON so
another program can consume the stream live. It observes through a separate
tap, so it never affects what `say` and `wait-for-reply` see.

Events now carry a `time` field (arrival time for live events, the SDK's
created_at for chat-history items), and agent state transitions are reported
on the stream as `state` events.

`listen` is renamed `wait-for-reply` and `history` is renamed `chat-history`
(the `transcript` alias stays).
Polling chat-history (or following events) covers the need with fewer edge
cases. The greeting wait at start and the between-turn buffering that say
reports are unchanged.

@theomonnom theomonnom left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't test myself but lgtm

@theomonnom

Copy link
Copy Markdown
Member

Let's just make sure it is using our shared components between console and debugger, otherwise it's going to be a PITA to maintain (e.g the way we render transcripts, etc etc)

# Conflicts:
#	autocomplete/fish_autocomplete
Review feedback: console and debugger each rendered the same conversation
with their own code. The debugger's event schema and proto normalization now
live in transcript.go, its renderer in transcript_render.go (colors from the
theme palette instead of hardcoded), and the console TUI prints user turns,
agent messages, tool calls, handoffs, config changes, errors, and its
metrics status line through them. console_tui.go loses formatChatItem, its
function-tool block, formatMetrics/formatMs, summarizeOutput/truncateOutput,
and two style helpers. Transcript tests move to transcript_test.go.

VRT frames are unaffected: they capture the console's status area, not the
scrollback transcript.
@bcherry

bcherry commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

@theomonnom I did some cleanup to improve code sharing, gonna merge this

@bcherry
bcherry merged commit de57070 into main Sep 18, 2026
25 checks passed
@bcherry
bcherry deleted the bcherry/agent-debugger branch September 18, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants