Skip to content

Expose Maple Agent Mode as an ACP harness - #714

Closed
AnthonyRonning wants to merge 4 commits into
masterfrom
codex-maple-buzz-acp-maple
Closed

Expose Maple Agent Mode as an ACP harness#714
AnthonyRonning wants to merge 4 commits into
masterfrom
codex-maple-buzz-acp-maple

Conversation

@AnthonyRonning

@AnthonyRonning AnthonyRonning commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Status

This is an exploratory draft and an architectural decision request.

The original question was: can Buzz control the actual Maple Agent—not a separately configured Goose process—without forking Buzz or Goose?

The answer is yes. This branch has completed three manual end-to-end Buzz GUI tests, including a substantive local-file task. The larger question for review is whether Maple should maintain this optional adapter and, if so, which parts should become durable internal abstractions.

This PR does not propose ACP as Maple's primary Agent architecture. Directly embedded Goose remains Maple's primary runtime.

Summary

This PR:

  • exposes the signed-in Maple Desktop Agent through a local ACP v1 endpoint;
  • adds a maple acp mode to the packaged Maple executable for stdio-based harnesses such as Buzz;
  • routes ACP tasks through Maple's existing account-scoped runtime, provider, tasks, tools, permissions, and UI timeline;
  • adds an Agent connections settings page for starting/stopping the service, choosing its permission policy, inspecting status, and copying the Buzz harness configuration;
  • preserves Buzz credentials as ephemeral, connection- and session-scoped in-memory context instead of putting them in harness JSON, argv, Maple's persisted configuration, or Maple's process-global environment;
  • adds a transport-neutral host facade around the existing Maple Agent runtime; and
  • leaves Buzz and Goose source unchanged.

Most of the implementation complexity is not basic ACP JSON-RPC. It is runtime ownership, lifecycle, account isolation, cancellation, event delivery, and bounded handling of Buzz's signing credentials.

The durable design and threat-model notes are also checked in as docs/agent-mode-acp.md.

What this proves

flowchart LR
    B["Buzz Desktop"] --> H["Buzz ACP harness"]
    H -->|"spawns packaged Maple executable<br/>ACP v1 over stdio"| C["maple acp connector"]
    C -->|"owner-only Unix socket"| S["Maple Desktop ACP service"]
    S --> R["Maple runtime facade"]
    R --> G["embedded Goose AgentManager"]
    G --> P["MapleProvider"]
    P --> A["authenticated MapleApiSession"]
    A --> O["OpenSecret inference"]
    G --> T["Maple developer, web, and skills clients"]
    T -->|"ephemeral BUZZ_* environment"| CLI["Buzz CLI"]
    CLI -->|"signed durable reply"| B
    G -->|"Maple timeline events"| R
    R -->|"ACP session/update"| H
Loading

The important distinction is that Buzz is not controlling a second Goose installation. It is controlling the same Maple Agent runtime the desktop UI uses.

That preserves:

  • Maple's authenticated provider transport;
  • Maple-owned model and task state;
  • Maple's developer tools, web tools, and skills behavior;
  • Maple's permission policy and approval UI;
  • Maple task history and UI visibility; and
  • Maple logout, data-clearing, runtime-restart, update, and shutdown boundaries.

Request flow

sequenceDiagram
    participant U as Owner in Buzz
    participant R as Buzz relay
    participant H as Buzz ACP harness
    participant C as maple acp connector
    participant D as Maple Desktop
    participant M as Maple Agent / embedded Goose
    participant B as Buzz CLI

    U->>R: Mention Maple in a channel
    R->>H: Owner-authorized channel event
    H->>C: Spawn with Buzz context and credentials
    C->>D: Private bridge hello over Unix socket
    H->>D: initialize, session/new, session/prompt
    D->>M: Create a real Maple task and start its run
    M-->>D: Thought, message, and terminal events
    D-->>H: ACP session/update and prompt result
    M->>B: Run Buzz CLI with scoped credentials
    B->>R: Publish signed durable reply
    R-->>U: Show reply in the Buzz thread
Loading

ACP streaming and durable Buzz publication are different paths. Buzz's prompt instructs the agent to publish useful results with the Buzz CLI. The ACP stream carries progress and completion; the CLI creates the signed channel reply.

Why we could not simply enable Goose ACP

Goose already has substantial ACP support. The problem is ownership and injection, not absence.

About the proposed goose-acp crate

A natural embedding model would let a Goose Development Kit (GDK)-based agent add a goose-acp crate, instantiate a server, and plug in its existing loop. That is directionally the API Maple wants, but it is not the API exposed by Maple's pinned Goose commit.

At c3111c71cd682ed1d115741677f0ca9946c51499:

  • there is no standalone ACP runtime package named goose-acp;
  • the similarly named goose-acp-macros package is a proc-macro crate for Goose custom-method dispatch and schema generation, not an ACP server or agent-loop abstraction;
  • the actual server implementation is the public goose::acp module inside the main goose crate; and
  • Goose's AcpProvider goes in the opposite direction: it lets Goose consume another ACP agent as a model provider, rather than exposing an existing Goose/Maple loop as an ACP agent.

If goose-acp meant the upstream agent-client-protocol wire crate, then the suggestion is correct at the protocol layer—and this prototype already uses that crate directly. What is missing is the next layer down: a Goose ACP backend trait or constructor that can accept a host-owned loop.

The generic parameters on serve<R, W> apply to the byte streams, not the agent backend. The function and its handler require a concrete Arc<GooseAcpAgent>; there is no AcpBackend, AgentLoop, or equivalent trait for Maple to implement.

At Maple's pinned Goose commit, Goose exposes:

  • GooseAcpAgent;
  • GooseAcpAgentOptions;
  • serve(...) over arbitrary byte streams;
  • goose acp; and
  • the ACP portion of goose serve.

However, GooseAcpAgent::new constructs fresh instances of:

  • SessionManager;
  • PermissionManager;
  • AgentManager;
  • provider inventory;
  • extension/tool setup; and
  • a relationship to process-global Goose configuration and paths.

The server factory takes the same standalone path. Goose's serve accepts an already-created GooseAcpAgent; it does not adapt an arbitrary existing Goose runtime.

Its public options do not let a host inject Maple's already-running managers, authenticated provider object, tool clients, task lifecycle, permission broker, or UI event projection.

Therefore:

  • running goose acp would create a standalone Goose runtime using Goose configuration and credentials;
  • starting goose serve after Maple initializes would still create a second Goose brain, store, permission boundary, and tool surface;
  • running Goose ACP in Buzz's connector subprocess would lose Maple Desktop's in-memory MapleApiSession, Tauri handle, account state, and tool clients;
  • pointing two independent managers at Maple's session database would not safely make them one runtime; and
  • a custom provider factory would solve only provider construction, not Maple's session, permission, tool, lifecycle, or UI semantics.

Goose ACP could therefore be reused only by accepting “Goose controlled by Buzz,” not “Maple controlled by Buzz.”

The current implementation uses the standard agent-client-protocol crate and adapts a deliberately narrow surface into Maple-owned operations.

Goose improvements that would make this substantially easier

The most useful upstream change would be separating Goose's ACP protocol mapping from ownership of the Goose runtime.

Prioritized seams:

  1. Inject an existing runtime. Add a constructor or builder accepting existing AgentManager, SessionManager, and PermissionManager handles plus host-owned provider/model services.
  2. Use a provider resolver consistently. The caller-supplied resolver needs to cover new sessions, restoration, provider recreation, model changes, and inventory—not only provider setup UI.
  3. Add session lifecycle hooks. Embedders need admission, create/load/activate, pre-prompt configuration, close, and cleanup hooks.
  4. Make permission routing pluggable. A host should be able to keep decisions in its UI, delegate them to ACP, or combine the two.
  5. Preserve host developer/tool clients. Goose ACP's capability adaptation should not have to replace a host-installed developer client.
  6. Support transient per-session context. ACP-provided MCP/environment context needs explicit child-process scoping and cleanup.
  7. Extract Goose's event-to-ACP projector. Maple could reuse Goose's mature text, thought, tool, permission, usage, resource, error, and stop-reason mapping without giving it runtime ownership.
  8. Remove global configuration/path assumptions from the embedding path. State roots and optional subsystems should be explicit instance dependencies.
  9. Optionally extract an embedding-oriented goose-acp crate. A small library with a generic AcpBackend trait, with Goose's current runtime as its default implementation, would make the “plug in your loop” model real for GDK embedders.

The small connector and local IPC boundary would still be necessary. Buzz launches a subprocess containing its environment, while the authenticated Maple runtime lives in the already-running desktop process.

Implementation model

Connector and local transport

The packaged executable now has two modes:

  • normal invocation starts Maple Desktop;
  • maple acp forwards stdin/stdout to the running desktop service.

The connector computes the same per-executable Unix-socket path as the desktop app. This prevents one development build from accidentally connecting to another installed or worktree build.

The endpoint is:

  • Unix-only;
  • mode 0600;
  • scoped by the exact executable path;
  • placed in an owner-checked 0700 runtime directory on Linux; and
  • removed during shutdown.

The service is disabled by default and must currently be started manually. If its listener fails, Start drains the stale task and can bind a fresh listener rather than leaving the page permanently wedged.

ACP surface

No means "not implemented by this prototype," not "fundamentally impossible." Effort is relative to this branch:

  • Low is primarily ACP dispatch or projection over an existing Maple operation.
  • Medium adds an account-scoped Maple runtime-facade operation, richer event contract, or lifecycle tests.
  • High changes a security/product boundary or needs a broader structured-content/runtime abstraction. High does not necessarily imply a Buzz or Goose fork.

Goose already implements many of these semantics, but at Maple's pinned revision its history replayer, response builders, tool converters, permission mapping, usage mapping, and handlers are private or pub(crate) and operate on concrete GooseAcpAgent state. Maple can port that behavior, or Goose could extract it, but Maple cannot currently plug its host-owned runtime into those handlers.

ACP surface Preview Remaining effort Feasibility and limiting layer
initialize Yes Implemented Negotiates ACP v1 and deliberately advertises only the narrow implemented capability set. New capabilities should be advertised only with their handlers and wire tests.
session/new Yes Implemented Requires an absolute cwd and creates a real Maple task. Optional additional workspace directories and generic ACP-provided MCP servers are graded separately below.
Additional workspace directories No Medium Supportable, but Maple currently has a single-root task and Skills-trust model. Correct support needs canonical admission against the configured roots, an explicit per-session root set, tool and Skills semantics for those roots, and consistent persistence/reporting across list, load, resume, and fork. This is a Maple workspace-model change, not a Goose loop limitation. The tested Buzz path does not send additional directories.
session/prompt text Yes Implemented Accepts text blocks and permits one active prompt per session. Text annotations are not currently preserved.
session/update text and thought Partial Low–Medium Assistant text and thought chunks are projected. Maple could incrementally add actual ACP variants such as user chunks, plans, available commands, modes/config, and session info. Full Goose fidelity is the sum of the richer tool, usage, and media rows below; Goose's mature projector cannot currently be called independently of GooseAcpAgent.
session/cancel Yes Implemented Cancels Maple's underlying run and retains its terminal state.
session/list No Low Supportable without rearchitecture. Maple already lists account-scoped persisted tasks and can filter by cwd. The adapter needs ACP field mapping and pagination plus a policy decision: may a same-user ACP client enumerate all Desktop tasks, or only ACP-created tasks? Restricting by origin would first require durable provenance because preview tasks are ordinary Maple user tasks.
session/resume No Medium Supportable. Attach an existing same-account task without replay, validate its cwd, allowed roots, and active-run state, preserve its persisted model, and restore Maple provider/tool/permission state plus the current connection's transient environment. A service-global session lease is needed so two ACP clients cannot overwrite one task's credentials or tool context. This is a Maple lifecycle seam, not an agent-loop redesign.
session/load No Medium–High Supportable. This is resume plus the protocol-required ordered replay before responding. Maple already loads a normalized timeline; faithful replay needs a separate ACP history projection for user/assistant/thought/tool rows, stable tool correlation, bounded content, and explicit treatment of media that Maple's UI timeline omits. Goose's existing replayer is useful upstream code to extract, but its activation path owns concrete Goose managers.
session/close No Low–Medium Supportable. Cancel active work, revoke connection-scoped credentials, release ACP ownership, and detach/unload only what ACP owns while preserving the persisted Maple task. The main work is race-safe cleanup without disrupting the same task if Maple Desktop is using it.
session/delete No Low code; high policy Maple already deletes tasks, so the handler is straightforward. The product decision is consequential: whether ACP may destroy Desktop history, whether deletion is limited to ACP-created tasks, and whether an explicit opt-in is required.
session/fork No Medium Goose's underlying SessionManager already has copy primitives and its ACP implementation shows copy, truncation, and activation. Maple needs an account-scoped wrapper, root/policy validation, fresh transient context, activation, and rollback for partial failures. ACP fork is still unstable, so enabling it also accepts an unstable wire commitment; no Goose fork is otherwise required.
Session mode No Medium Maple already changes per-session permission mode. ACP support needs advertised mode state, a handler, update projection, and an explicit rule about whether a client may select unattended approval. This is principally a Maple trust-policy decision.
Model selection and other config No Medium–High A Maple model option is feasible before the first prompt using Maple's authenticated catalog. Maple intentionally locks a task's model once it has history, so arbitrary mid-session switching should be rejected or treated as a product change. The native ACP path also needs authoritative vision/context metadata. Goose's config builder cannot be reused because it assumes Goose's global provider inventory rather than Maple's caller-owned MapleProvider; provider switching should remain out of scope. The tested Buzz build tolerates this surface being absent and uses Maple's default.
ACP permission requests No High Technically supportable, but it changes the trust boundary. Maple already exposes pending one-shot decisions and can feed a response back to Goose. It would need exactly one authoritative broker per session—Maple UI, ACP client, or a deliberately designed hybrid—plus timeout, disconnect, cancellation, and double-response handling. The tested Buzz build automatically chooses allow_once for forwarded requests, so delegation would materially weaken the current local-approval boundary. Goose's mapping exists, but it is not an injectable broker.
Basic structured tool calls and results No Low–Medium Maple's timeline already carries stable IDs, title, input, output, status, and errors, which is enough for ordinary ACP ToolCall and ToolCallUpdate cards. This is mostly projection work. Buzz also treats the initial tool_call notification as activity, so projecting tool-call start would reset its idle watchdog during a long otherwise-silent tool, improving liveness as well as UI visibility.
Rich tool/resource/location/MCP projection No Medium–High Rich image/resource content, file locations, progressive MCP notifications, and faithful replay need data below Maple's deliberately summarized UI timeline. Goose already has rich converters; a public transport-neutral AcpEventProjector would avoid maintaining this nuanced mapping twice.
Terminal and diff parity No Medium–High; architectural choice Goose's terminal handles and diff updates are not projection alone: its ACP filesystem layer replaces/delegates developer operations through ACP-client filesystem and terminal RPCs. Maple currently executes its own local tools. Maple would need either to synthesize bounded metadata from those results or delegate execution to the client, which would change its tool and permission architecture; an event projector by itself is insufficient.
Usage and context updates No Medium Supportable. Goose emits and persists usage, but Maple currently discards ephemeral usage notifications before its public event stream. Maple must retain a protocol-neutral usage event or query post-turn totals, pair usage with the persisted context limit, and define cumulative semantics. ACP cost is optional and should remain absent until it matches Maple billing semantics. Buzz can display standard usage for observability, but its durable turn-accounting path currently consumes a Goose-private cumulative-usage notification instead; neither path creates the signed channel reply.
Prompt images No Medium The embedded Goose message model and MapleProvider already carry images. Maple's non-UI send facade is text-only and ACP forces vision_capable: false; support needs structured prompt input, MIME/base64/size limits, native model-capability resolution, and replay/UI policy. This is mainly a Maple facade change.
Prompt audio No High Native audio is not a small adapter change. Pinned Goose has no audio path and its core message type has no audio variant. Maple could define a transcription-to-text pipeline, but native multimodal audio requires a Goose/provider/message abstraction change. This is the clearest current Goose-level content limitation.
Embedded text resources No Low–Medium Supportable by flattening bounded content into a clearly labeled, untrusted prompt block while preserving URI/provenance metadata.
Embedded binary resources No Medium–High Known image or document types could use explicit Maple ingestion paths. Arbitrary blobs have no generic model-facing representation and need type, decoding, size, staging, persistence, and rejection rules.
Resource links No Medium–High Supportable with policy work, and currently a baseline ACP v1 gap because resource links have no opt-out capability. Local and remote links need separate scheme, root, symlink, size, encoding, permission, and provenance rules so prompt ingress cannot bypass Maple's filesystem/web controls. Goose's private helper only performs an unbounded local file:// text read, which Maple should not copy literally.
Arbitrary ACP-provided MCP servers No High Technically supportable, but production-safe support expands a code-execution and secret boundary. Maple must validate client-supplied commands, URLs, headers, and environments; define authorization and name-collision rules; attach them transiently without persisting secrets; and guarantee per-session process cleanup across close, disconnect, crash, and Flatpak constraints. Generic stdio MCP is an ACP v1 baseline, so the current Buzz-only adaptation is a real conformance gap. Goose's converter is private; its high-level Agent add/remove path persists state, while lower-level public ExtensionManager mutations still lack a validated, authorized, per-session overlay with rollback and guaranteed cleanup. The ideal upstream seam is that overlay plus host authorization.
Goose/Buzz native steering No Medium code; high coupling Supportable but intentionally non-standard. Buzz uses _goose/unstable/session/steer, not an ACP v1 method. Goose's underlying Agent::steer queue is public; Maple needs an active-agent/run facade, expectedRunId validation, correlation updates, and prompt-end/cancel race tests. Buzz's cancel-and-merge fallback means this is not required for the tested flow, and adopting it would couple Maple to an unstable extension.

This is Maple behavioral parity for the tested task path, not Goose ACP feature parity.

Two ACP v1 baseline caveats are worth making explicit: agents must accept ResourceLink prompt blocks and stdio MCP definitions. The prototype handles neither generically. The tested Buzz custom-harness path uses text prompts and does not depend on arbitrary MCP definitions; Maple also contains one exact Buzz compatibility adaptation. Image, audio, and embedded-resource capabilities are correctly advertised as unavailable.

None of the No rows blocks the tested Buzz harness. That Buzz build does not call list/load/resume/fork/close, tolerates absent model/config options by using the agent default, and falls back from native steering to cancel-and-merge. Initial tool_call notifications would improve Buzz Desktop visibility and reset its idle watchdog during long tools. Standard usage would improve observability, while durable turn metrics currently rely on a Goose-private notification. Its signed channel reply remains a separate tool/CLI path.

Runtime integration

The implementation adds narrow non-UI seams to Maple Agent:

  • ensure the account-scoped runtime exists;
  • create/delete a Maple task;
  • start/cancel a run;
  • subscribe to Maple Agent events;
  • observe a retained terminal result even if the bounded broadcast stream lags; and
  • attach and revoke ephemeral per-session tool context.

The existing Tauri UI commands retain their current public return types. Agent runtime Stop/Restart drains ACP first so a live ACP connection cannot retain references to a replaced runtime or silently lose its Buzz environment.

Desktop configuration

A new Preview settings page on macOS/Linux provides:

  • start/stop controls;
  • client, session, and active-run counts;
  • a saved permission policy;
  • the exact packaged executable path and acp argument;
  • Buzz harness JSON;
  • parallelism guidance; and
  • protocol, endpoint, and credential diagnostics.

Logout, Agent-data/history clearing, Agent-runtime stop/restart, update restart, and app exit stop ACP before stopping or clearing Agent state.

The UI fences configuration and authenticated mutations to the current account. Status polling and emergency Stop stay local and do not refresh credentials. A failed configuration load keeps policy mutations locked rather than replacing the saved policy with permissive defaults.

Permission model

Two persisted policies are currently exposed:

  • read_only is labeled Require local approvals and maps to Maple/Goose smart_approve.
  • allow_all maps to Maple/Goose auto for unattended operation.

The read_only identifier is historical shorthand, not a filesystem sandbox. Read operations can proceed, and a write-capable action can still occur after the user approves it in Maple Desktop. This mode is unsuitable for unattended Buzz operation.

allow_all lets the connected client ask Maple to run commands, modify files, and perform external actions without another local prompt.

Permission changes require Stop → change and save → Start. This closes an admission race where a new session could otherwise pin the old permissive policy while a restrictive save reported success.

Native configuration contains optional allowed project roots, but the preview UI does not expose them. Empty roots means any absolute cwd is accepted within Maple's OS permissions. Even a configured root only gates session admission; it does not confine every later file or shell path. Neither mode is an OS sandbox.

Trust and credential boundaries

Boundary Current guarantee Important limitation
Buzz channel admission Tested with Buzz owner-only mode Maple does not independently verify the channel author; it trusts the local Buzz harness
Local IPC Owner-only Unix socket No additional application token; any same-user process that reaches the endpoint can drive signed-in Maple while enabled
Maple account Bound to one authenticated user and stopped during account/lifecycle transitions Maple Desktop must remain signed in and running
Project cwd Optional absolute-root admission check Empty roots is unrestricted; admission is not per-tool filesystem confinement
Maple credentials Stay in Maple's authenticated provider session The ACP client is authorized to use that session while connected
Buzz credentials Not persisted in harness JSON, argv, or Maple config; cleared on disconnect They intentionally reach shell children so the agent can publish signed replies
Background children Credential-bearing process trees are terminated after each command This limits retention but cannot stop a trusted command from reading or transmitting credentials

The credential path is intentionally narrow:

  1. Buzz starts the connector with BUZZ_* environment values.
  2. The connector sends a private _maple/bridge/hello notification across the owner-only socket before forwarding normal ACP.
  3. The bridge filters to five BUZZ_* variables plus PATH, rejecting null bytes and values over 16 KiB.
  4. Filtered values remain in the ACP connection's in-memory context and are copied into per-session tool context; session installation revalidates the allowlist and enforces the six-key, 16-KiB-per-value, and 32-KiB-total bounds.
  5. They are applied to Maple shell children, not Maple's process-global environment.
  6. They are revoked on session/connection teardown.

This must not be described as “the agent never sees the Buzz key.” The trusted Maple Agent's shell commands can use it; that is how durable signed replies work. A command can inspect or transmit it. allow_all should be used only with trusted clients, prompts, projects, and toolchains.

Credential-bearing Flatpak shell sessions fail closed because safe host-child lifetime handling has not been established there.

Buzz-specific behavior

Although the external wire format is standard ACP v1, the first adapter is deliberately Buzz-specialized:

  • private bridge hello;
  • BUZZ_* environment allowlist;
  • exact buzz-dev-mcp compatibility handling;
  • hard-coded Buzz ACP task title;
  • Buzz custom-harness serialization and setup guidance; and
  • a recommended parallelism of one.

For buzz-dev-mcp, Maple recognizes a narrowly shaped absolute stdio definition—matching server name and executable basename, no arguments, and an existing executable file—but adapts its environment into Maple's existing developer shell instead of launching a second general-purpose shell server.

If this feature is maintained, Buzz constants and environment/MCP behavior should move into an explicit compatibility module instead of gradually becoming generic Agent-runtime behavior.

Manual end-to-end validation

Tested source state:

  • Goose pin: c3111c71cd682ed1d115741677f0ca9946c51499;
  • Buzz: 3a4bf513df0e0c258587bfcbed9463d63723b56b;
  • ACP wire: v1;
  • platform: packaged arm64 macOS development app;
  • Buzz channel policy: owner-only;
  • Buzz parallelism: 1; and
  • Maple policy: allow all for unattended execution.

Three Buzz GUI tests completed:

  1. A deterministic channel mention produced exactly:

    MAPLE-GUI-OK
    
  2. Buzz asked Maple to read the branch's actual README.md from disk and explain the project.

    Maple used its real local-file tooling and returned a substantive explanation covering:

    • Tauri/Bun architecture and supported platforms;
    • local TTS and PDF OCR;
    • updates and signing;
    • developer prerequisites and local commands;
    • VITE_OPEN_SECRET_API_URL;
    • Linux ONNX setup;
    • headless rendering; and
    • iOS and Windows caveats.

    It also correctly observed that the README's privacy discussion primarily covers signing/update integrity rather than Maple's broader confidential-compute model.

  3. After the final rebase and workspace-specific packaged-app rebuild, a second deterministic mention produced exactly:

    MAPLE-PR-READY
    

    Buzz showed the signed reply in the triggering thread. Maple finished with one connected client, one session, and zero active runs.

The substantive run took roughly two to three minutes and ended with zero active runs. The timing has not been profiled and should not be attributed to ACP. The response remained visible in the Buzz thread.

This is manual local integration evidence, not an automated compatibility, conformance, performance, load, billing, or security test.

Setup issues encountered

Exploration log
  • Buzz's managed runtime initially connected to 127.0.0.1, while the local relay tenant was registered under localhost. Because the host participates in relay community lookup, this produced an HTTP 404. Aligning the local relay/community URL fixed it without source changes.
  • A mention sent before the managed agent had successfully connected produced no reply.
  • Buzz defaults to ten ACP workers; Maple defaults to one local connection. Reducing Buzz parallelism to one was required.
  • The exact packaged Maple executable path matters. Using a name or executable from another worktree can target the wrong runtime.
  • Restarting Maple stops the endpoint. The persisted enabled flag does not auto-start it on the next launch.
  • Buzz's configured model is not applied through this ACP subset. The successful test used Maple's own current/default model.
  • Durable reply publication required passing Buzz's scoped environment into Maple's shell tool; a final streamed ACP message alone does not create a signed Buzz channel event.

Tradeoffs and limitations

Benefits

  • No Buzz fork.
  • No Goose fork.
  • Real Maple provider and authentication.
  • Real Maple tasks, tools, policy, and UI history.
  • Maple-owned lifecycle and account isolation.
  • A small stable ACP surface instead of Goose-specific private methods.
  • A reusable non-UI runtime facade that could serve other host integrations.

Costs and current gaps

  • Maple owns a protocol adapter that partially overlaps Goose ACP.
  • The adapter depends on Maple's internal runtime lifecycle and event semantics.
  • The current implementation is significantly Buzz-coupled.
  • ACP capability breadth is deliberately much smaller than Goose's server.
  • The shared bounded event bus can drop intermediate chunks under lag; retained terminal state prevents a hung prompt but cannot reconstruct missed text.
  • A client that half-closes input immediately after its last request may race final response delivery.
  • Session setup failures without a session/run identity are not yet projected cleanly to Buzz.
  • Disconnecting preserves Maple tasks, while ACP cannot reload them. Repeated harness restarts can accumulate Buzz ACP tasks.
  • allowedProjectRoots and maxConnections exist in native config but are not exposed in the UI.
  • Live maximum changes do not resize a listener that is already running.
  • An idle same-user socket can occupy the default one-connection limit; there is no initialize/idle timeout yet.
  • Local-approval mode can leave unattended Buzz waiting for approval in Maple.
  • macOS is the only platform validated end to end.
  • Non-Flatpak Linux remains untested; Flatpak credential-bearing execution is unsupported.
  • Windows, mobile, and web are unsupported.
  • Manual startup is required after each Maple restart.
  • There is no checked-in ACP wire, socket, reconnect, policy-transition, credential-revocation, or Buzz GUI integration fixture yet.

Alternatives considered

Run goose acp

Rejected for Maple parity. This would expose an independently configured Goose process, not Maple's authenticated desktop runtime.

Turn on goose serve after Maple initializes

Not currently a switch on the existing runtime. Goose still creates its own managers, store, tools, permissions, and global configuration relationship. It would also introduce an HTTP/authentication surface while Buzz's custom harness contract is stdio-based.

Construct GooseAcpAgent with a Maple provider factory

Closer, but still produces a second AgentManager, SessionManager, PermissionManager, extension surface, and task lifecycle. The provider factory is necessary but insufficient and is not consistently used by all restoration/recreation paths.

Point Goose ACP at Maple's data directory

Rejected. Sharing persistence does not make independently cached runtimes one runtime and risks conflicting ownership.

Fork Goose

Would allow injection points, but creates exactly the maintenance burden this experiment was trying to avoid.

Fork Buzz

Unnecessary. Buzz already supports arbitrary custom ACP commands.

Maple-owned thin ACP adapter

Chosen because it is the only current option that makes Maple—not standalone Goose—the authoritative runtime.

Maintenance recommendation

Do not make ACP Maple's primary Agent abstraction.

The direct embedded-Goose path should remain:

Maple UI -> Maple runtime -> embedded Goose -> MapleProvider

ACP should remain an edge adapter:

External harness -> ACP adapter -> Maple-owned runtime facade

If external harness interoperability is strategically useful, I recommend:

  1. keep the feature experimental, desktop-only, default-off, and clearly trust-gated;
  2. keep investing in the small transport-neutral runtime facade already proven here;
  3. keep ACP wire types inside the adapter rather than leaking them into Maple domain/frontend types;
  4. move Buzz environment and MCP behavior into an explicit compatibility module;
  5. support only stable ACP capabilities required by real consumers;
  6. pursue Goose runtime/provider/policy/event injection before duplicating more Goose ACP functionality; and
  7. add protocol-level fixtures before presenting the feature as generally supported.

I would maintain this as an experimental adapter if Buzz and other external harnesses are strategically useful. I would not build Maple Agent around ACP or pursue complete Goose ACP parity speculatively.

Related prior work:

Automated validation

The final rebased branch was validated with:

nix develop -c just rust-lint
  • Passed strict Clippy validation.
  • Only the existing patched tao dependency warnings were emitted.
nix develop -c cargo test --manifest-path frontend/src-tauri/Cargo.toml --all-targets --locked
  • 224 passed, 0 failed, 1 ignored model-backed OCR test.
cd frontend
nix develop .. -c zsh -lc 'bun run lint && bun run build && bun test'
  • ESLint: 0 errors and 13 pre-existing warnings.
  • Production frontend build: passed.
  • Frontend tests: 397 passed, 0 failed, 1,377 assertions.
nix flake check --no-build --print-build-logs
  • Passed for the host aarch64-darwin checks, apps, and development shells.
  • Checks for incompatible systems were skipped by Nix.
cd frontend
nix develop .. -c src-tauri/scripts/run-with-desktop-onnxruntime.sh \
  bun tauri build --debug --config ../.local/tauri-workspace.json
  • Produced the debug binary, .app, .dmg, and unsigned updater .app.tar.gz with the managed workspace's unique Tauri identifier and backend URL. The command then exited non-zero at updater signing because this disposable workspace has no release private key.
  • The standalone .app was ad-hoc signed and passed codesign --verify --deep --strict before GUI testing.
  • The exact workspace bundle was launched, the signed-in Agent connections page and policy lock were verified, the ACP service started, Buzz connected, and the MAPLE-PR-READY round trip completed.

The existing patched tao dependency emits known warnings during native builds; no new Maple warnings were accepted.

Required before merge consideration

  • Rebase onto current origin/master at draft creation time.
  • Repeat native/frontend tests and packaged macOS GUI validation after the rebase.
  • Add an in-process or socket-level ACP round-trip fixture.
  • Add cancellation, disconnect, reconnect, and policy-transition race coverage.
  • Decide whether persisted enabled should auto-start after relaunch.
  • Decide whether this remains a preview page or becomes feature-gated.
  • Expose project-root controls or keep the lack of confinement prominent.
  • Complete a dedicated credential/threat-model review.
  • Decide the non-Flatpak Linux, Flatpak, and Windows support posture.
  • Decide whether task accumulation requires ACP session reload/close support.
  • Consider an upstream Goose issue or PR for runtime injection seams.

Questions for reviewers

  1. Is external harness interoperability a surface Maple wants to maintain?
  2. Should the first supported contract be explicitly “Buzz compatibility,” or a general minimal ACP server?
  3. Is same-user Unix-socket trust sufficient for a default-off developer preview?
  4. Should unattended allow_all remain available, and how prominently should its Buzz credential implications be presented?
  5. Should Maple auto-start a previously enabled service after login, or require explicit activation on every launch?
  6. Should we maintain this wrapper while pursuing Goose injection support, or retain it only as design input until Goose can host an existing runtime?
  7. Which missing ACP capability is actually needed next: permissions, model switching, session reload, tool streaming, or none?

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploying maple with  Cloudflare Pages  Cloudflare Pages

Latest commit: 69d246f
Status: ✅  Deploy successful!
Preview URL: https://c7bea48d.maple-ca8.pages.dev
Branch Preview URL: https://codex-maple-buzz-acp-maple.maple-ca8.pages.dev

View logs

@AnthonyRonning

Copy link
Copy Markdown
Contributor Author

Closing this exploratory draft because the implementation has been superseded by the merged Maple agent service refactor and follow-up ACP feature-flag work. Keeping the remote branch for historical reference.

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.

1 participant