Skip to content

agent_ui: Add CLI support for sending agent prompts - #62060

Open
jutaz wants to merge 1 commit into
zed-industries:mainfrom
jutaz:agent-cli
Open

agent_ui: Add CLI support for sending agent prompts#62060
jutaz wants to merge 1 commit into
zed-industries:mainfrom
jutaz:agent-cli

Conversation

@jutaz

@jutaz jutaz commented Aug 1, 2026

Copy link
Copy Markdown

Objective

Zed's agent is driven from inside the app. Making it reachable from the command
line opens up a category of "kick off the agent when X happens" workflows —
scripts, git hooks, CI, webhooks.

There's an existing zed://agent?prompt=… URL, but it's built for a different
purpose: zed:// is an OS-registered scheme (osx_url_schemes = ["zed"],
x-scheme-handler/zed), so any web page can navigate to it. It pre-fills the
message editor and shows a "Review Before Sending" callout, deliberately leaving
the final keystroke to a human. That's the right behavior for an untrusted
caller.

Requested in #58262, which asks for exactly these two capabilities — enumerating
threads and sending a user message to an existing one — for automation and
orchestration workflows. Adjacent asks: #54221 (scheduled prompts, which needs a
way to deliver a prompt later) and #49313 (controlling agent runs remotely).
Distinct from #59146, which proposes running the agent as a standalone headless
binary; this drives the agent already running in your editor.

Solution

Add a --agent flag group to the zed CLI. It travels over the existing
zed-cli:// IPC channel, which is not OS-registered and is only reachable from a
local process, so it can submit directly. The zed://agent path is not touched
by this change.

zed --agent "Summarize the changes on this branch"
zed --agent --agent-new "Investigate the flaky CI job"   # prints the new thread id
zed --agent --agent-thread 567079b5 "Now check the tests"
zed --agent --agent-wait "Run the tests and fix what breaks"
zed --agent-list
curl -s https://example.com/task | zed --agent -

Highlights

  • New agent_ui::agent_cli module resolves a thread and dispatches a prompt into
    it. Threads are addressed by ThreadId (in full or as a unique fragment, with
    hyphens optional), by ACP session id, or implicitly as the most recently
    updated thread for the working directory.
  • Prompts take the same route as pressing Enter in the panel:
    ThreadView::send_content when the thread is idle, ThreadView::add_to_queue
    when it's generating. A queued message shows up as a real queue entry that you
    can steer or delete, and it drains on AcpThreadEvent::Stopped.
  • Resolution checks in-memory threads first. AgentPanel is per-Workspace
    rather than per-window, so this iterates MultiWorkspace::workspaces() instead
    of MultiWorkspace::panel(), which only sees the active workspace. A thread
    that's open in a background project is reachable without stealing focus or
    reloading it from disk.
  • --agent-list prints id age title (open), and
    --agent-list-format json emits an array with id, title, updated_at,
    interacted_at, is_open, and the thread's worktree paths. The array shape
    holds even when nothing matches, so it pipes straight into jq. The prompt
    paths keep stdout to the bare thread id so id=$(zed --agent …) works.
  • New CliRequest::Agent variant, appended after SetOpenBehavior so the
    existing Open and SetOpenBehavior variants keep their wire representation.
  • ThreadId gains Display/FromStr. Prompts run through the existing
    ExternalSourcePrompt sanitizer, which strips bidi and control characters —
    worth keeping for a path that's often fed webhook payloads.
  • --agent-profile and --agent-model configure a thread at creation and
    require --agent-new. A thread persists both, and changing the profile also
    swaps the model and cascades into any running subagents, so applying them to an
    existing thread would silently reconfigure something the user already owns.
    Both are validated before anything is dispatched — an unknown profile lists the
    configured ones, an unconfigured model is an error — so a typo can't leave a
    thread running under the wrong tool set.
  • Tool permissions are untouched: a CLI-triggered turn still asks before running
    tools, and --agent-wait blocks until you answer, matching what happens when
    you send the same prompt by hand.
  • docs/src/reference/cli.md documents the new flags alongside the existing
    ones.

Testing

Verified manually against a debug bundle (script/bundle-mac -d -i), driving a
running instance over the real IPC path:

  • --agent-new creates a thread and prints its id; --agent-list then shows it
    marked (open).
  • --agent-thread resolves a full id, an 8-character prefix, and a hyphenless
    fragment to the same thread.
  • Sending while the agent was generating printed agent is busy; message queued
    on stderr with the bare id on stdout, and the message appeared in the panel as
    a queue entry.
  • With two projects open in one window, each directory's --agent-list showed
    only its own threads. --agent-project reached across to the other project,
    and a mismatched scope failed with exit code 1.
  • Running from a subdirectory of a project still resolves that project's threads.
  • In a repo with a linked git worktree, --agent-list --agent-list-format json
    reported the linked worktree's path, and resolving a branch to its worktree
    then piping the id into --agent-thread delivered a message to the right
    thread. --agent-list-format is rejected outside --agent-list, and an
    unmatched project still yields [].
  • echo … | zed --agent - reads the prompt from stdin.
  • --agent-wait blocked for the duration of the turn, then exited 0.
  • --agent-profile and --agent-model are rejected without --agent-new, and
    with it a bad profile reports unknown agent profile "nope"; available profiles: ask, coding-agent, ..., write while --agent-model bogus/model and
    --agent-model notaslash each fail with their own message. A valid profile
    creates the thread, and sending to an existing thread still works.
  • Argument validation rejects --agent-project without --agent, --agent
    together with --agent-list, and file-opening flags (--diff, --wait,
    --new, …) in agent mode.

A user's unsent draft is left alone: the CLI calls send_content, which unlike
send_impl never touches the message editor. There's a test pinning this, and I
confirmed it fails if the editor is cleared.

Automated coverage: 21 unit tests in agent_cli (id parsing and normalization,
prefix ambiguity, most-recent tie-breaking, path matching, prompt sanitization),
28 in cli (argument validation), and 4 in open_listener (list formatting).
The dispatch functions themselves are covered by the manual runs above rather
than by tests. ./script/clippy and cargo fmt --check are clean.

Reviewers can try this on macOS with script/bundle-mac -d -i, then use the CLI
inside the bundle at /Applications/Zed Dev.app/Contents/MacOS/cli so requests
attach to the running instance.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • Added zed --agent for starting an agent turn from the command line

@cla-bot

cla-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @jutaz on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@zed-community-bot zed-community-bot Bot added the first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions label Aug 1, 2026
@jutaz jutaz changed the title Feature - Send agent prompts from the CLI agent_ui: Add CLI support for sending agent prompts Aug 1, 2026
@jutaz

jutaz commented Aug 1, 2026

Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Aug 1, 2026
@cla-bot

cla-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

@jutaz
jutaz force-pushed the agent-cli branch 6 times, most recently from 37c94c1 to 5cc306d Compare August 2, 2026 07:00
Adds `zed --agent` so an agent turn can be started in a running Zed
instance from a script, git hook, or webhook.

Prompts are routed through the same paths the agent panel uses when you
press Enter: an idle thread sends immediately, and a busy one queues the
message so it drains when the current turn stops. Threads are addressed
by a durable id (in full or as a unique fragment), by ACP session id, or
implicitly as the most recent thread for the working directory.

The request travels over the existing `zed-cli://` IPC channel rather
than the OS-registered `zed://` scheme, which deliberately refuses to
auto-submit because any web page can reach it. Prompts are still run
through the same sanitizer, since webhook payloads are untrusted input.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant