Skip to content

chore: extract ACP stdio transport pre-patch#3310

Open
ssfdust wants to merge 2 commits into
tailcallhq:mainfrom
ssfdust:feat/acp-pre
Open

chore: extract ACP stdio transport pre-patch#3310
ssfdust wants to merge 2 commits into
tailcallhq:mainfrom
ssfdust:feat/acp-pre

Conversation

@ssfdust
Copy link
Copy Markdown

@ssfdust ssfdust commented May 11, 2026

ACP stdio transport pre-patch

Extracts independently-mergable fixes from the ongoing ACP work in #2858.

Background

When spawning forge machine stdio as an ACP subprocess, stdin is a pipe
that remains open for bidirectional JSON-RPC communication. This patch
resolves three classes of bug discovered during ACP integration testing.

Changes

  1. stdin pre-read encapsulation (cli.rs, main.rs)
    Added Cli::uses_stdin() method that centralizes the decision of
    which subcommands own stdin. Subcommands opt in via uses_stdin()
    rather than having the startup pipeline guess from a hardcoded
    exclusion list. This makes it easy to add new stdin-consuming
    subcommands (e.g. an ACP stdio server) without touching the
    startup logic.

  2. tool output leaking to stdout (context.rs, tool_executor.rs,
    chat_request.rs, orch.rs, app.rs, agent_executor.rs,
    executor.rs)
    Shell tool execution always streamed raw output to io::stdout(),
    corrupting the ACP JSON-RPC stream. Fixed in two layers:

    • Lower layer (pre-patch): Added silent: bool on ToolCallContext
      and an if silent { io::sink() } guard in the command executor.
      Default false; call sites opt in via .silent(true) to route
      output to io::sink() instead of stdout.

    • Upper layer (propagation): Added tool_silent: bool on ChatRequest
      (the per-invocation DTO) and threaded it through the entire call
      stack — ForgeApp::chat() forwards it to Orchestrator, which
      sets it on ToolCallContext. Agent-as-tool recursion inherits the
      parent's flag automatically. The propagation chain:

      ChatRequest.tool_silent → ForgeApp → Orchestrator.tool_silent → ToolCallContext.silent
                                                            ↓
                                                   tool_executor → executor.rs
                                                            ↓
                                                   agent_executor (recursive)
      
  3. stderr vs stdout discipline (main.rs, sandbox.rs)
    Panic hook and worktree status messages used println!, which
    writes to stdout and contaminates structured transport. Changed
    to eprintln! (stderr).

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions github-actions Bot added type: chore Routine tasks like conversions, reorganization, and maintenance work. type: fix Iterations on existing features or infrastructure. labels May 11, 2026
@ssfdust ssfdust force-pushed the feat/acp-pre branch 4 times, most recently from 3f70ae9 to ffde445 Compare May 11, 2026 07:57
Extracts independently-mergable fixes from the ongoing ACP work
(tailcallhq#2858) that can land on main immediately.

Three classes of fix:

1. stdin pre-read encapsulation (cli.rs, main.rs)
   Add Cli::uses_stdin() method that centralizes the decision of which
   subcommands own stdin. Subcommands opt in via uses_stdin() rather
   than having the startup pipeline guess from a hardcoded exclusion
   list. This makes it easy to add new stdin-consuming subcommands
   (e.g. an ACP stdio server) without touching the startup logic.

2. Tool output leaking to stdout (context.rs, tool_executor.rs)
   Shell tool execution streamed raw output to io::stdout(), corrupting
   the ACP JSON-RPC stream. Add silent: bool field on ToolCallContext
   (the correct home for per-invocation runtime flags). Default false;
   ACP sets .silent(true) to route output to io::sink() instead.

3. Stderr vs stdout discipline (main.rs, sandbox.rs)
   Panic hook and worktree status messages used println! which writes
   to stdout. Changed to eprintln! (stderr) to avoid contaminating
   structured transport channels.

Signed-off-by: ssfdust <ssfdust@gmail.com>
Co-authored-by: ForgeCode <noreply@forgecode.dev>
Co-authored-by: DeepSeek <deepseek-ai@deepseek.com>
@ssfdust ssfdust force-pushed the feat/acp-pre branch 2 times, most recently from c24650f to b355433 Compare May 12, 2026 04:10
Add a `tool_silent` flag to `ChatRequest` that propagates through the entire
tool execution chain down to the command executor. When `true`, shell
tool output is suppressed on stdout to protect JSON-RPC transports.

- chat_request: add `tool_silent: bool` field (default false)
- orch: add `tool_silent` field, propagate to ToolCallContext
- app: forward ChatRequest.tool_silent to Orchestrator
- agent_executor: inherit silent flag from parent ToolCallContext
- executor: clean up TODO(acp), add inline comment

Signed-off-by: ssfdust <ssfdust@gmail.com>
Co-Authored-By: ForgeCode <noreply@forgecode.dev>
Co-Authored-By: DeepSeek <contact@deepseek.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: chore Routine tasks like conversions, reorganization, and maintenance work. type: fix Iterations on existing features or infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants