Skip to content

perf(acp): parallelize extension loading in ACP server#8098

Merged
DOsinga merged 3 commits into
aaif-goose:mainfrom
exitcode0:perf/parallelize-extension-loading
Apr 2, 2026
Merged

perf(acp): parallelize extension loading in ACP server#8098
DOsinga merged 3 commits into
aaif-goose:mainfrom
exitcode0:perf/parallelize-extension-loading

Conversation

@exitcode0
Copy link
Copy Markdown
Contributor

Summary

The ACP server loaded extensions sequentially in create_agent_for_session and add_mcp_extensions, making total startup time the sum of all extension init times.
The CLI session builder (builder.rs) and session reload path (load_extensions_from_session) already parallelize extension loading, this PR brings the ACP server in line

Parallelize both paths using futures::future::join_all:

  • create_agent_for_session: Load all platform extensions concurrently via
    ExtensionManager::add_extension, keeping the developer extension sequential (it uses a special ACP-wrapped
    client)
  • add_mcp_extensions: Split into sequential config conversion (fail-fast on bad config) and parallel
    loading via a new Agent::add_extensions_bulk() method that resolves working_dir and container once
    upfront to avoid lock contention, then persists extension state in a single transaction

Testing

  • cargo check -p goose -p goose-acp — compiles cleanly with no warnings
  • Existing ACP server tests are unaffected

Related Issues

N/A

Screenshots/Demos (for UX changes)

N/A

The ACP server loaded extensions sequentially in create_agent_for_session
and add_mcp_extensions, making total startup time the sum of all extension
init times. The CLI session builder and session reload path already
parallelize — this brings the ACP server in line.

Parallelize both paths using futures::future::join_all:

- create_agent_for_session: load all platform extensions concurrently via
  ExtensionManager::add_extension, keeping the developer extension
  sequential (it uses a special ACP-wrapped client)

- add_mcp_extensions: split into sequential config conversion (fail-fast)
  and parallel loading via a new Agent::add_extensions_bulk() method that
  resolves working_dir and container once upfront to avoid lock contention,
  then persists extension state in a single transaction

Benchmark with 9 real extensions: 31.5s sequential → 6.1s parallel (5.2x)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae775fe626

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/agents/agent.rs Outdated
Comment on lines +821 to +823
if let Err(e) = self.persist_extension_state(session_id).await {
warn!("Failed to persist extension state after bulk load: {}", e);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Propagate extension state persistence failures

add_extensions_bulk logs and ignores errors from persist_extension_state, so add_mcp_extensions can return success even when extension state was never saved. This matters when session storage is unavailable (e.g., I/O/permission issues): MCP servers appear added for the current process but are lost on session reload, creating inconsistent runtime vs persisted state. The previous agent.add_extension path surfaced these persistence failures to the caller, so this is a behavioral regression in ACP session setup.

Useful? React with 👍 / 👎.

Douwe Osinga added 2 commits April 1, 2026 20:24
…stating comments

- Change add_extensions_bulk to return anyhow::Result so persistence
  failures propagate to callers instead of being silently logged
- Update add_mcp_extensions to handle the new Result
- Remove comments that restate what the code does

Signed-off-by: Douwe Osinga <douwe@squareup.com>
Copy link
Copy Markdown
Collaborator

@DOsinga DOsinga left a comment

Choose a reason for hiding this comment

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

Looks good — the parallelization is a clean improvement that brings the ACP server in line with the CLI path.

I pushed a fixup commit that:

  • Changes add_extensions_bulk to return anyhow::Result so persistence failures propagate to callers (addresses the Codex P1 — previously these were silently logged)
  • Updates add_mcp_extensions to handle the new Result
  • Removes comments that restate what the code does

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce97ae6100

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +487 to +490
let extension_futures = extensions
.into_iter()
.map(|ext| {
let ext_manager = Arc::clone(ext_manager);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Deduplicate extension keys before parallel startup

Loading all configs via join_all here makes startup order-dependent when the same extension key appears more than once (e.g., a builtin also enabled in config). create_agent_for_session appends self.builtins to config-derived extensions, and ExtensionManager::add_extension does a pre-check and insert in separate phases, so duplicate keys can initialize concurrently and whichever future finishes last overwrites the other. The old sequential loop had deterministic behavior; this change introduces nondeterministic final extension config/tool state across runs.

Useful? React with 👍 / 👎.

@DOsinga DOsinga added this pull request to the merge queue Apr 2, 2026
Merged via the queue into aaif-goose:main with commit 776d92c Apr 2, 2026
22 checks passed
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.

2 participants