Skip to content

Maple Agent on the Pi crates: the runtime, then every feature, then no Goose - #1109

Merged
AnthonyRonning merged 32 commits into
masterfrom
agent-pi/integrate
Oct 13, 2026
Merged

AnthonyRonning merged 32 commits into
masterfrom
agent-pi/integrate

Conversation

@AnthonyRonning

@AnthonyRonning AnthonyRonning commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1104 (base claude/kind-turing-gzv8nl). Do not merge: nothing in the stack merges until the end result is accepted.

What this is

Phase 2 of replacing Maple Agent's embedded Goose runtime: Maple's runtime rebuilt on the Pi crates from #1104 (pi-ai, pi-agent-core, pi-coding-agent). The app switches to it, every feature moves over, and Goose is deleted. Where Pi has a built-in, Maple uses it; Maple keeps its own code only for what Pi does not have.

Every feature has moved: the vertical slice (a desktop task runs end to end on Pi, and the app no longer calls the Goose runtime), Pi's built-in tools, Maple's own tools (read_image, plan, questions, web), the side models (generated titles, summaries, /btw), instruction files, skills and slash commands on Pi's resources, image attachments, custom MCP servers per task (on rmcp, by Pi's conventions), the integration cards, the context ring, external agents (Codex and Claude Code), built-in CUA, and ACP agent mode (maple-agent acp). Live checks ran on 2026-10-09 and again against the restacked implementation on 2026-10-10 (see Live validation). It stays a draft until the stack's merge is decided.

Goose is gone: nothing called its runtime once the app switched, so commit 5 deleted it, and the later features were ported from its code in history.

Commits

  1. Pi crates: timestamps never repeat within a process, because Maple keys its timeline rows by a message's timestamp. A Maple-driven adaptation Pi does not have; see Changes to the Pi crates.
  2. The Goose runtime moves to crates/maple-agent-goose, as the package maple-agent-goose. Code unchanged.
  3. The runtime on Pi, and the app switch.
  4. Pi's built-in tools, with the login shell's PATH.
  5. Goose deleted: crates/maple-agent-goose, the Goose and Cua git dependencies (Cua returns with embedded CUA), their Nix hashes, and 241 packages from Cargo.lock. No package was added or changed version; a few lose features only Goose turned on. The default log filter no longer names goose, and the README describes the Pi runtime.
  6. The integration cards on the Pi runtime.
  7. The plan and questions tools (todo_write, request_user_input).
  8. The web tools (web_search, open_url).
  9. Side models: generated task titles, tool and thinking summaries, /btw.
  10. read_image.
  11. Instruction files, skills and slash commands on Pi's resources.
  12. A project's APPEND_SYSTEM.md does not ask for trust, since Maple's opening instructions always take its place.
  13. Image attachments.
  14. Custom MCP servers on rmcp, per task. New dependency: rmcp 3.4.1 (client only: its async read/write transport for stdio, and the Streamable HTTP client over reqwest).
  15. The context ring counts a task's context with Pi: the last reply's reported usage plus an estimate of what came after it, from the loaded session or the task's session file. The app drops its read-only handle on Goose's sessions database, whose usage ledger nothing writes now.
  16. External agents on the Pi runtime (Codex and Claude Code).
  17. Built-in CUA on the Pi runtime. The Cua Driver SDK returns at the same pin, with its Nix hash and the vendored GNOME Shell helper (byte-identical at the pin).
  18. ACP agent mode on the Pi runtime. agent-client-protocol returns at 2.2.0. Runs publish Compacting and Compacted, which the app ignores (it reloads on HistoryReplaced, as before).
  19. The lockfile keeps the versions Maple shipped with: cargo resolved twelve of the packages commits 17 and 18 brought back at newer patch versions; they are pinned back.
  20. The port's leftovers go: the crate's temporary allow(dead_code), with what the Goose runtime left unused, the tool context's ephemeral flag among them. Renaming a task refuses a signed-out session again.
  21. The scenario checklist follows the Pi runtime: rows that named Goose-era tests or Goose-era tasks now name what the Pi runtime has, and the tools row says bash.
  22. Images stay consistent for a model without vision. A Pi extension (tool_result, model_select and before_agent_start) gives such a model Goose's answer from read for an image and keeps read_image describing exactly while the task's model cannot see.
  23. No code that only computer use calls on Windows: the screenshot descriptions and a Unix test helper build where their callers do, since Windows builds without built-in CUA.
  24. Runs take the model's vision and context window from the catalog. Neither host sent the window, so every run compacted at the 128,000-token default. ACP also looked up vision only for a prompt with images, as under Goose. ACP now sends both on every turn, from one catalog lookup per session, and its usage_update reports that window. The desktop sends the window with each message, as it sent vision.
  25. The chat hears the runtime's events while Settings is open, so a reply that ends there no longer shows as still running on return. Master's app drops them the same way.
  26. The docs record the live run: the scenario checklist's results, what MAPLE_CONTEXT_LIMIT does now, and using a task in one place at a time over ACP.
  27. Ordered steering regression coverage: steers accepted before session setup keep their order and survive an early Stop.
  28. Catalog thinking levels and a composer picker: Pi supplies the supported levels, clamps the default, and records changes in each task.
  29. System One and response headers over Maple's transport. Maple's model registry serves Agent core in Rust after Pi: pi-ai, pi-agent-core, pi-coding-agent #1104's System One classifier over the account's encrypted transport, and responses carry their headers to Pi. Nothing in Maple classifies yet.
  30. What only Buzz needed leaves ACP: the bridge's hello and variables, with the tool context that carried them and its BUZZ_* scrubbing; session/new's systemPrompt and _meta.sessionTitle; and protocol version 2, which only told Buzz about systemPrompt. An ACP task's commands keep the PATH the client started Maple with.
  31. No ACP config migration: the code is greenfield, so a missing ACP config means the defaults. Copying one from the proof of concept's account folder was all that wrote the file, so saving goes too, and Maple only reads it.
  32. A new task shows in the sidebar when its first run starts, not when that run ends: the sidebar leaves out a task without messages only while it has no run, since the cached message count updates when a run ends.

Changes to the Pi crates

The Pi features this phase first needed moved down to #1104 on 2026-10-10, since they are Pi's: a transport's cancellation and a host-chosen session id, resources replaced between prompts, images in steering and follow-ups, truncate_middle and write_output_file, tools registered after the session starts, and a custom message that starts a turn. The stack's result is unchanged.

One change that Pi does not have stays here, a Maple-driven adaptation the owner reviewed and kept:

  • Timestamps never repeat (commit 1), because Maple keys its timeline rows by a message's timestamp. Pi stamps messages with the plain clock.

The owner dropped the other two on 2026-10-11, so the stack now does as Pi does:

  • unregister_tool is gone. As in Pi, a tool is never taken from a session (see MCP servers and Built-in CUA).
  • contain is gone, and commit 30 removes the ACP bridge whose credentials it contained (see ACP).

The runtime on Pi (crates/maple-agent)

Provider (agent/provider.rs)

  • Pi's Chat Completions provider over OpenSecret's encrypted transport, with Maple's catalog as Pi models (context window and vision from the catalog; MAPLE_CONTEXT_LIMIT overrides).
  • Pi's session owns every retry: 10 retries, 1 s doubling to 30 s, about today's budget. The adapter itself never retries. It turns each failure into one fixed Maple message per category, worded so Pi's retry and overflow checks classify it: rate limits, server and network failures read as transient, an oversized request as an overflow, and authentication, credits and secure-connection failures as neither. Response bodies, which may echo decrypted content, never reach the timeline or logs.
  • Response-start and stream-idle timeouts (300 s each), HTTP-200 error frames, truncated encrypted streams, and OpenSecret's lost-session contract each have a fixture test. Pi's own request timeout is off for task sessions, so a timeout keeps Maple's message.
  • Task models use the catalog's reasoning metadata. Supported efforts map to Pi thinking levels; mandatory reasoning hides Off, as does Kimi K3. Models without reasoning metadata expose only Off and send no reasoning field. The default is Pi's Medium, clamped to a supported level. No per-model recommendations are configured. Titles, summaries and /btw continue to send no thinking control.
  • Kept from today: only the newest three tool-result images stay pixels in a request, and the temporary Kimi K3 tool-id rewrite.
  • Agent core in Rust after Pi: pi-ai, pi-agent-core, pi-coding-agent #1104's System One classifier, written after Maple's System One API, runs over the same transport, so any Maple code or extension with the registry can classify text and images. Its requests go to /v1/systemone and ask for a JSON reply, with the one model Maple's API serves for it, glm-5-3-flash. As Pi's classifier requests do, one the server fails or asks to wait is sent up to two more times, after the server's retry-after or x-should-retry. A failed reply keeps Maple's fixed message, so the error carries no part of the server's body. Nothing in Maple classifies yet: a result carries its own usage for the feature that asks, and the server counts what Maple bills.
  • Responses carry their headers to Pi, names in lowercase, as Pi's transports give them: Pi's response hook sees them, and the classifier's retries follow them. Chat requests still send nothing again in the adapter, so headers change no retry there.

Tasks (agent/store.rs, agent/tasks.rs)

  • One JSONL session file per task, through Pi's store, in the account's owner-only folder. No Goose-era data is read.
  • A SQLite index (WAL, owner-only) holds the title and whether the user set it, project root, kind (desktop or ACP), state (active, settled, archived), web switch and settings, and caches model, message count and last update. Tasks list, read, rename, settle, archive and delete while the runtime is stopped.
  • Delete marks the row, removes the session file and attachments, then the row. A start finishes a deletion that was cut short.

Runs (agent/runtime.rs, agent/runs.rs)

  • One session factory builds every Pi session: Maple's model registry, settings (retry budget, queued messages delivered together, and the saved thinking default) and tools. The host's opening instructions go in Pi's appended prompt section, after Pi's own preamble, tools and rules.
  • One run per task. A run wakes a settled task, names an unnamed task from its first prompt, and keeps a task on the model it started with.
  • Stop keeps the reply so far and adds "Stopped by user". It repeats its abort until the prompt returns, since an abort that lands while Pi sets the run up would otherwise be lost. Steering the session never took goes back to the queue.
  • Messages sent during a run wait as chips the user can edit or cancel; when the session settles they go together, as one turn. Steering joins the running turn through a per-run ordered inbox, including messages submitted before its Pi session exists. Async steering never holds Maple's state mutex, and failures are logged.
  • Images attached in the composer are stored with the task, as before (owner-only; PNG, JPEG or WebP; at most ten per message and 10MB each). The model gets what the user typed, then each image's name and maple-attachment:// source, as Goose's runtime wrote them: a model without vision is told to look with read_image, and one with vision gets the images beside the text (now with their sources, so read_image can crop one). Chips and steering keep their images; a message Pi had already taken for steering when the run stopped comes back to the queue with its images by source only. The timeline reads the typed text and the images back from the message itself, live and after a reload. A task is named from its first message with text.
  • A message for the model alone, such as a background agent's result, is a Pi custom message the user does not see. Behind a running turn it waits out of sight in the task's queue, then goes as a turn of its own; with no run it starts one. A run stopped before it went leaves it in the task's history, where the model reads it with the next message, as Goose wrote it to history on Stop.
  • Stopping the runtime stops its runs and returns once they have ended.

Settings (agent/mcp.rs, agent/integrations.rs, agent.rs)

  • The account's MCP server list is checked and saved as Goose's runtime did: names of at most 64 characters, distinct by Goose's key rules and not Maple's reserved ones; a command or an endpoint; a timeout; and named, distinct environment variables and headers, none of which changes how programs load (PATH, LD_PRELOAD, NODE_OPTIONS and the rest of Goose's list). A new server cannot take the computer use integration's name; one an older release saved stays saveable. Listing checks the saved list the same way.
  • The integration cards (built-in Cua, Codex, Claude Code) list and switch as Goose's did. A card joins what Maple finds on the device with the choice saved for new tasks in the device-local integrations.json (version 2; a newer build's file is reported and left alone, a retired standalone CuaDriver choice is dropped). Codex and Claude Code are looked for on the login shell's PATH once it has been read, else the app's; their version is read by a three-second probe with bounded output, in its own process session that is ended afterwards (on Windows, its process tree is ended if it overruns), and on the PATH the CLI was found on (Goose's version probe used the app's). A Codex older than 0.143.0 needs an update; sign-in is reported but does not gate the switch. An external agent switches on only while its CLI can run, and off at any time. Built-in CUA's card reads the device's permissions, the GNOME helper's state and the SDK version (see below).
  • Every read and save of the account's settings takes one lock, as each took Goose's runtime lock, so a project, model, trust, MCP or integration save cannot overwrite a change made since its read.

MCP servers (agent/mcp/)

  • Which servers a task has is saved with the task, by name. A new task gets those switched on in Settings, or exactly the ones the composer names (an unknown one fails the create, as before; computer use's names are left to that integration). The composer's menu lists every saved server, switched on when the task has it, then a chosen server no longer configured, switched on and unavailable, which can only be switched off. A task runs the servers' current settings, so an edit applies from its next run; Goose's runtime froze a copy of each server, secrets included, into the task.
  • The servers start with the task's run, in the background; the run waits up to ten seconds for them before its first prompt, as Pi's first prompt does, and Stop ends the wait. A server that connects later joins from the next prompt. A server that fails is named in the run's notice with Goose's wording (Some MCP servers could not connect: …, at most three), once; later runs try it again without waiting for it. Goose's runtime reported it the same way at a run, but then switched it off for the task, and said nothing when it failed as the task was created.
  • Switching a server on for a loaded task connects it at once and waits up to ten seconds: a failure keeps it off and shows the reason, as before. A task that is not loaded connects it at its next run. Switching off stops it; its tools stay (see the next item) and its instructions leave the prompt. Neither is allowed while the task runs.
  • Tools: mcp__<server>__<tool> (everything outside [A-Za-z0-9_] as _, at most 64 characters, a hash for a name too long or taken), the server's description (or title) and input schema, all declared to the model directly. Results become text and images: audio and resource links as a line, embedded text as text, blobs as images, text, or a file the model can open; structured content when there is nothing else; an error result stays an error. Text beyond 20KB is cut in the middle in Codex's format and saved whole to a file. A tool list change is read again: new tools join and changed ones are replaced. As in Pi, a tool is never taken from the session, and it keeps its name for the session's life: one the server withdrew, or one of a server switched off or stopped, stays declared, and calling it fails with Pi's message (MCP server "…" is disabled., … is still starting., or MCP tool "…/…" is no longer available.) until the server offers it again.
  • The connected servers' instructions join the system prompt in an mcp_servers section, as Goose's did.
  • Calls time out by the server's timeout, which progress resets, and Stop sends the server a cancellation. A dropped connection opens again on the next call; a call is never sent twice.
  • A stdio server runs in the task's folder with the login shell's PATH and its environment variables, its command and arguments with ~ expanded, in its own process group. It stops by its input closing, then SIGTERM after 500ms, then SIGKILL after 2s, to the group, which ends what it started (Goose's runtime killed only the direct child, so npx could leave node behind). A failed start shows the end of its error output.
  • An HTTP server's URL and header values take $NAME and ${NAME} from its environment variables, as Goose's did; redirects are not followed, so headers do not travel to another host.
  • The servers live with the task's loaded session (its Pi extension stops them when the session shuts down: on delete, trust change, runtime stop). The four idle tasks used last keep theirs; the others' stop until the task runs again.
  • MCP tool rows read as server: tool.

External agents (agent/external_agents/)

  • The Goose runtime's Codex app-server client, Claude Code stream-json client, and the host that runs them, with Goose's types replaced: approvals accepted at once (Codex accept, Claude allow), questions on Maple's card (async Codex questions answered in a turn of Maple's own), activity bounded as before, at most four agents per task, a process group per agent that Stop and shutdown kill, and the login shell's PATH.
  • The five tools (agent_start, agent_send, agent_status, agent_cancel, list_agent_providers) keep Goose's schemas, descriptions, result text and errors (without Goose's Error: prefix). Every desktop task's Pi session has them; a run declares them only while the task may use a provider, one switched on in Settings and for the task, and a call must name such a provider.
  • A task's choices are saved with it, as externalAgents in its settings; the composer's menu lists the agents switched on in Settings after the MCP servers and switches them per task, with Goose's rules and messages. Each task starts with none, as before.
  • A turn's progress repaints the row of the tool call that started it; the tool finds that row from the reply Pi stored before running it. A task reopened while a turn runs shows the row as it stands, and the agent's row above the composer and its Stop button come back.
  • A background turn's end goes into the task's session as an entry the transcript projects onto the call's row, then a notice; the result reaches the model as a hidden message (see Runs). Deleting the task, stopping the runtime or signing out ends its agents without a report.
  • The /handoff, /committee and /advisor skills are written to the account's skills folder, which Pi reads, while any agent is on in Settings, and removed when none is; files without Maple's marker are left alone. Goose kept them in its own config folder.
  • One change from Goose: an agent whose command could not start no longer keeps one of the task's four places.
  • Not implementing, yet: agents that outlive the runtime (Goose's did not either; a restart starts new agents, and codex resume or claude --resume still reach the threads), and a Windows job object (the process tree is ended with taskkill /T instead).

Built-in CUA (agent/cua/)

  • Computer use runs in desktop tasks again, with the Cua Driver SDK in Maple's process (macOS, and Linux with portal input; elsewhere the card reads as not available).
  • Each run binds a task that has CUA on to its own account-and-task scoped CUA session anew, which renews its authorization, and registers the catalog's tools on the task's Pi session as cua-driver__<tool>, with the instructions in a prompt section. The lifecycle tools and every session argument stay hidden. The binding happens beside the MCP servers' start, and a failure is named in the same notice, as "Computer use (CUA)". The binding closes with the task's session, or when CUA is switched off for the task; its tools stay, as in Pi a tool is never taken from a session, and calling one fails until the task is bound again.
  • Every model gets an observation's structured grounding as bounded, image-free text; a model without vision, read from the run's model, gets each screenshot described by the side model in its place.
  • A task's choice is saved with it, as computerUse in its settings. A new desktop task records one once CUA is set up on the device: on when the composer names it, else the default for new tasks. The MCP menu ends with CUA's row, and switching it on binds a loaded task at once.
  • Setup asks for the permissions on the click's thread, so the system attributes the request to Maple, and installs GNOME's helper where it is missing.
  • Changes from Goose: Pi fits a screenshot like every tool image (2000 pixels, 4.5MB), with its note on mapping coordinates back; Goose sent screenshots as captured. Helper usage is not recorded.

ACP (acp/, agent/surface.rs)

  • maple-agent acp serves the Agent Client Protocol again, with the Goose runtime's transport (10 MiB frames, credit-tracked backpressure), handler, conversions and session state, and the same protocol surface, less what only Buzz used (commit 30): protocol 1; load, list, close and delete; image prompts and HTTP MCP servers; the model selector, locked after a task's first message, and no modes (session/set_mode is refused); /compact and the skills' commands; the turn's usage on each response and a usage_update.
  • Each session is a task the connection holds with a lease. While it lasts, the task's commands keep the PATH the client started Maple with, the desktop's own tools stay out of it (the plan, questions, external agents and computer use), and the client's HTTP MCP servers run beside the task's, in place of a saved one of the same name. The desktop cannot send to a held task. Closing the session ends the hold, stops the task's run and unloads its session.
  • A prompt is a run of its own, after any run the client stopped has ended, with events for the client alone; session/cancel stops it from its admission on.
  • A task created over ACP is listed from its first prompt; closed before that, it is removed, and a start removes those a crash left for more than ten minutes, as Goose's runtime did. Tasks created over ACP stay out of the desktop's list.
  • A project with guidance of its own asks the client's user once whether to trust it (a form elicitation where the client offers one, else a permission chooser that cannot be auto-accepted), and the task is built again with the answer.
  • How a turn reaches the client on Pi: text and thinking stream as they come, and the row that settles a message sends only what did not stream; a tool call is announced with its kind, which result updates keep. A failed reply is told when the run ends failed, since Pi may still retry it. Compaction is told as the reference adapters tell it.
  • The dispatcher no longer waits on slow requests: a prompt is admitted in order, so a session/cancel right behind it finds it, and the rest of it, close, load and config changes run in the background. In Goose's, a second prompt or a config change during a turn held the dispatcher, cancels included, until the turn ended.
  • Changes from Goose: every turn runs on the model's vision and context window from the catalog, where Goose's looked up vision only for a prompt with images and sent no window; usage_update counts the task's context as Pi does, where Goose sent the turn's tokens; /compact on a history too short to summarize says so instead of failing; and what only Buzz used is gone (commit 30): the bridge's hello, its variables and its buzz-dev-mcp definition, systemPrompt and _meta.sessionTitle, and protocol 2.

Side models (agent/side_models.rs)

  • Goose's side requests, with its models, prompts, limits and timeouts, through the runtime's model registry and so the task's transport:
    • a new task is named from its first prompt at once, then llama3-3-70b is asked for a three-to-five-word title, which replaces it unless the title changed meanwhile;
    • one-line summaries of a finished tool call or thinking block for the activity feed;
    • /btw: the task's model gets the task's context, the side thread so far and the framed question; the task's tools stay in the request for its cached prefix, with tool_choice: none. The answer streams as events and is not saved;
    • images described for a model without vision, by gemma4-31b with thinking turned off in the request body, as Goose sent it.
  • Titles and side questions end with the runtime; a request that times out is cancelled and drained.

Instructions, skills and slash commands (agent/resources.rs)

  • Pi's resources, from the account's folder and a project's .maple folder where Pi has ~/.pi/agent and .pi:
    • instruction files: AGENTS.md (or CLAUDE.md) from the project folder and every folder above it, the account's, and ~/.agents/AGENTS.md, which Goose's runtime read too. They load in every project, as in Pi. Goose's .goosehints is not read;
    • skills: the account's, ~/.agents/skills, and in a trusted project .maple/skills and .agents/skills up to the repository root. The other folders Goose's runtime read (~/.claude/skills, ~/.config/agents/skills, and a trusted project's .claude/skills and .goose/skills) are Pi's extra skill paths, so they come after the user's own;
    • prompt templates from the account's prompts folder and a trusted project's .maple/prompts, and SYSTEM.md and APPEND_SYSTEM.md where Maple gives no text of its own (Maple always appends its opening instructions, so APPEND_SYSTEM.md is ignored).
  • The model sees each skill's name, description and file and reads it with read, as in Pi; Goose's load_skill tool is gone.
  • Resources are read again at each run, so a skill or instruction file changed since shows from the next run, as Goose's skills did; only the prompt sections that changed go out again.
  • The composer's / list holds the skills, then templates whose names no skill has, with argument hints from a skill's frontmatter. A command expands into the skill's instructions as Pi's /skill:name does, with the arguments after them, or into the filled template; names match regardless of case. Resolving a command takes the account, so the app passes it.
  • Project trust covers everything a project adds besides its instruction files. The trust prompt asks when trusting would add a skill or template that does not load now, or a .maple/SYSTEM.md (two new protected features). Before, it looked only for .agents/skills/**/SKILL.md, and the / list offered and ran an untrusted project's skills.

Timeline (agent/timeline.rs)

  • Pi's events and session entries become Maple's timeline rows. Live rows and reloaded rows have the same ids, so a reload shows what the run streamed. Attempts Pi retried are hidden on reload, as are entries a context edit omits.

Tools (agent/tools/, agent/login_path.rs)

  • Tasks get Pi's built-in tools from Agent core in Rust after Pi: pi-ai, pi-agent-core, pi-coding-agent #1104: read, bash, edit and write, or powershell in place of bash on Windows without Git Bash. As in Pi, grep, find and ls are there but off. Maple's own read, edit and write are gone.
  • Maple sets up how commands start, through bash's spawn hook:
    • the login shell's PATH, so an app opened from the Dock or a desktop launcher finds what the user's terminal finds. It is read once per process, from when the runtime starts, behind a marker so profile banners cannot be mistaken for it, and the read is bounded and cleaned up. A task an ACP client holds keeps the PATH the client started Maple with;
    • the task's id as AGENT_SESSION_ID.
  • MAPLE_SHELL names the bash to run and replaces GOOSE_SHELL.
  • What changes for the model and the user, following Pi:
    • the tool is bash (was shell), and its timeout argument is timeout (was timeout_secs);
    • no default timeout (was 300 seconds); Stop still ends the command and everything it started;
    • long output keeps its last 2000 lines or 50KB, and the full output goes to a file the model can read (was the first 50,000 bytes, then the command was stopped);
    • on Windows, Git Bash or PowerShell (was cmd);
    • read also opens images for a model with vision. A model without vision gets Goose's answer instead, that the file is an image to inspect with read_image, rather than Pi's note that the image is left out.
  • Maple's own tools (agent/tools/read_image.rs, agent/tools/desktop.rs, agent/tools/web.rs), with Goose's schemas, descriptions and limits:
    • every task gets read_image: an image from a Maple attachment of the task, a local file or file URL, or an http(s) URL (public host, no credentials, 30 seconds, no redirects), at most 20MB, PNG, JPEG, GIF or WebP, optionally cropped to a PNG. Pi then fits the image as it fits every tool's images. For a model without vision the tool takes a required context and returns the side model's description instead, and the prompt carries Goose's line "Use read_image when you need to inspect an image." The tool follows the task's model: a task loaded before its run (for a side question or /compact) switches when the run's model can or cannot see images. Local paths resolve as read's do, macOS screenshot names included;
    • desktop tasks get todo_write, the plan the user watches, echoed back to the model, and request_user_input, one to three questions on a card. A stopped run takes the card back. As in Goose, near misses are accepted (questions as one object, one question at the top level, missing or extra fields), blank questions and options are dropped and ids made unique. The questions go through the broker of the service that started the runtime rather than the process-wide one Goose's tool clients had to use, so a later service or a parallel test cannot take another runtime's questions;
    • every task gets web_search and open_url while its web switch is on, from its next run, as in Goose. Search results come back as JSON marked as untrusted evidence, trimmed from the end to 64,000 characters; a page comes back as sanitized text, at most 32,000 characters, cut without reactivating inert image code. open_url takes public HTTPS URLs only: no credentials, and no loopback, private, link-local, documentation or internal host, embedded IPv4 included;
    • errors read as Pi's tools' do, without Goose's Error: prefix.

Not implementing, yet

MCP compared with Pi's

Maple's MCP keeps Pi's conventions where they touch the model and the server processes, on rmcp rather than Pi's own client, and keeps Maple's settings and per-task switches. For later comparison:

Pi Maple
Client Pi's TypeScript MCP package rmcp 3.4.1, the initialize handshake (Goose's runtime probed server/discover first)
Configuration mcp.json in the agent folder and a trusted project; values may name environment variables or a !command the account's Settings, as before; per-task switches in the composer; HTTP values take $NAME from the server's own environment variables, as Goose's did
Exposure codemode by default (tools reached from scripts), or deferred (tool_search), direct, hidden, per server or tool every tool direct
Server instructions kept out of the prompt for direct tools; codemode's describeNamespace() returns them in an mcp_servers prompt section, as Goose's runtime did
Tool names, descriptions, schemas, result conversion, 20KB middle cut with a full-output file as Pi as Pi
Timeouts per request, 60s by default, progress resets it per request, the server's timeout from Settings (300s by default), progress resets it
Reconnects and retries reconnect on the next call; calls never retried the same
Stdio stop close input, SIGTERM after 500ms, SIGKILL after 2s, to the process group; bounded error output the same
Startup the first prompt waits up to 10s for direct servers each run waits up to 10s for servers starting for it (new, or changed in Settings); failed servers retry at later runs without the wait
Failures one notice, "MCP servers need attention", and /mcp to fix the run's notice in Goose's wording; switching off and on retries at once
Withdrawn tools, and a switched-off server's re-registered hidden, which takes them out of the declared tools, since Pi never unregisters a tool; a call that reaches one fails with its message stay registered and declared, since the crates have no hidden exposure; a call fails with Pi's message
Lifetime the session's process the task's loaded session; four idle tasks keep their servers
Roots the session folder the task's folder

Not implementing, yet: sign-in (OAuth; Goose's runtime had a browser flow but cleared its tokens at every start), resources and prompts (Pi's list_mcp_resources, list_mcp_resource_templates and read_mcp_resource; Goose's runtime offered none), codemode and tool_search, elicitation and sampling (neither Pi nor Goose's runtime answered), Goose's OSV malware check of npx and uvx packages (it let a start through when OSV could not be reached), and Goose's _meta task ids on each request and AGENT_SESSION_ID in a stdio server's environment (bash still gets it).

The app

  • It imports the new crate. Its run events gain Compacting and Compacted, which it ignores. The new crate keeps the API the desktop UI calls, such as AgentIntegration::is_external_agent for the Codex and Claude Code cards, and session_subagents and cancel_external_agent for the agents' rows.
  • The context ring asks the runtime for a task's context instead of reading Goose's sessions database. When Pi reports unknown usage after compaction, the ring clears its old count until a reply measures context again.
  • A message carries the selected model's context window from the catalog, as it carried vision, so a run compacts at the window the context ring shows.
  • The chat hears the runtime's events while Settings is open (commit 25).
  • A new task shows in the sidebar when its first run starts (commit 32).
  • Its Goose wording for compaction notices goes, since the Pi runtime never sends those strings.
  • A Thinking menu beside the model selector shows only the selected model's supported levels and the task's current selection. A change goes through Pi's set_thinking_level, persists in the session, and becomes the account default for new tasks. Reopening a task keeps its own level. Stale menu reads are discarded and Send waits for a pending choice to save.

Testing

  • Current head (2026-10-12), e774410b: commit 32, the owner's, changes only the desktop sidebar and its test, which this container does not build. Hosted CI is green on this head: Agent on Linux, macOS and Windows (run), and the macOS and Linux app builds and packaging (run).
  • Previous head (2026-10-12), a2cae39c, in a Linux container without the GUI app: commit 31 changes only the ACP configuration and its tests. cargo fmt --all -- --check and cargo clippy --workspace --all-targets -- -D warnings passed, and so did the 38 ACP tests, with a test of loading the configuration in place of the two of saving it and of older builds' fields. Hosted CI was green: Agent on Linux, macOS and Windows (run), and the macOS and Linux app builds and packaging (run).
  • Previous head (2026-10-12), 4f76e26b, in a Linux container without the GUI app: commits 1 to 29 are unchanged from 32c08e71. At commit 30, cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings and cargo check -p maple-agent --no-default-features --all-targets passed, and 677 workspace tests passed, with 3 ignored. Commit 30 also removes three lines of the GUI app's code (it no longer passes a tool context or a system prompt) and changes two comments there, which this container does not build. Hosted CI was green: Agent on Linux, macOS and Windows (run), and the macOS and Linux app builds and packaging, which build those lines (run).
  • Previous head (2026-10-11), 32c08e71: every commit compiled with no warnings, the changed tests passed at their commits, and 683 workspace tests passed with 3 ignored. Hosted CI was green: Agent on Linux, macOS and Windows (run), and the macOS and Linux app builds and packaging (run).
  • Previous head (2026-10-11), b838666b, on macOS arm64 with the GUI app: all 31 commits independently passed cargo fmt --all -- --check, cargo clippy --workspace --all-targets --locked -- -D warnings, and RUSTFLAGS='-D warnings' cargo test --workspace --locked (their per-commit table is in this description's edit history). At that head, 1,121 workspace tests passed, with 3 ignored, plus 51 headless app tests. Full just ci also passed, including warning-denied GUI builds and the headless, ACP-only and proxy-only feature configurations.
  • Lower heads: full macOS just ci passes at Maple Agent phase 0: simplify the existing runtime #1124 5782e611 (873 workspace tests, 5 ignored; 51 headless) and Agent core in Rust after Pi: pi-ai, pi-agent-core, pi-coding-agent #1104 76309fcb (1,278 workspace tests, 5 ignored; 51 headless). Hosted Agent CI was green on Linux, macOS and Windows for the three heads of the master restack (Maple Agent on the Pi crates: the runtime, then every feature, then no Goose #1109's being b838666b): #1124, #1104, #1109.
  • Thinking tests cover catalog shapes and aliases, request effort values, side-call omission, task persistence and the default for new tasks.
  • The runtime's tests run end to end against Pi's scripted provider:
    • a task runs with the read tool, saves, lists with its prompt title, and after a restart lists and reads back the same rows while stopped; a follow-up carries the earlier context; its requests leave timeouts to the transport;
    • bash runs in the task's folder with the task's id, and a failed command reaches the model and the timeline with its exit code;
    • Stop keeps the partial reply and adds the notice;
    • queued chips follow as one turn, can be cancelled, and steering joins the turn;
    • an attached image reaches a model without vision as a read_image source and a model with vision beside the text, a chip keeps its image, and the user's rows show the typed text and the images the same live and after a reload; unit tests cover the prompt's round trip, and Pi's tests a steered image;
    • a failed reply fails the run with its error row;
    • a settled task wakes, the model stays locked, a running task cannot be deleted or settled, stopping the runtime ends its runs, and a cleared account revokes its handles;
    • MCP servers save trimmed and checked, and a project or model save made from an older copy of the settings keeps them. Unit tests cover the name, command, environment, header and computer use rules;
    • a task's MCP server, against a fake Streamable HTTP server driving rmcp's client: a new task gets the servers switched on in Settings; its tools are declared on the first request with the server's instructions in the prompt, the call reaches the server and its answer the model, and the row reads fake: echo; switched off, its tools stay declared and a call fails with Pi's message without reaching the server, while its instructions leave the prompt; a tool the server withdrew stays declared and fails as no longer available; switched on, it connects at once; removed from Settings, it stays chosen but unavailable and cannot be switched on. A server whose command does not exist is named once in the run's notice, not at the next run, and switching it on again fails with the reason. Of six tasks with servers, the idle one used longest stops its servers, which start again when it runs;
    • unit tests run rmcp's client against the fake server: header and URL substitution, calls, error results, progress, a stopped call's cancellation reaching the server, a timeout, a tool list change read again, and a stopped server that cannot connect again; on Unix, a stdio server that exits shows its error output, and stopping one ends the child it started. Tool naming, schemas and result conversion, the per-task choices, the menu's rows and the notice's wording have unit tests too;
    • Pi's tests (Agent core in Rust after Pi: pi-ai, pi-agent-core, pi-coding-agent #1104): an extension registers a tool through its context after the session started, and it reaches the next prompt;
    • the model shows its plan and asks the user: the plan's row carries the list the interface draws, the question reaches the interface as a card for its task, and the answer goes back to the model; a stopped run takes an unanswered card back;
    • the web tools are declared to the model while the task's web switch is on, and the switch takes effect from the next run. Unit tests cover the URL policy, bounded results, markdown-safe cuts and bounded errors against a scripted web provider;
    • a new task gets a generated title, unless the user renames it while the title is on its way; tool calls and thinking are summarized by the side model, not the task's; a side question streams its answer with the task's history and leaves the task as it was;
    • read_image reads local files, file URLs and the task's attachments, crops, refuses other formats, oversized files, private hosts and credentials in URLs, and for a model without vision requires a context and returns the description (unit tests against a scripted vision model). End to end, read sends a model without vision to read_image for an image, and that model's prompt says to look at images there, while a model with vision gets the image from read; a task loaded without vision gets read_image's image mode for a run that sees, and its description mode back for one that does not;
    • the catalog's entry for a model: its vision and window, an empty one for a model the catalog does not list, and none while it cannot be read;
    • instruction files reach the model in any project; a project's skills reach it once trusted, and a skill added while the task is loaded shows from its next run. Unit tests cover every skill and template folder and its precedence, trust, command names, argument hints and expansion, and what the trust prompt names;
    • the integration cards list Cua first, then Codex and Claude Code; Cua switches on only once set up, never over a saved server of its name, and its card follows the device's readiness. Unit tests cover switching on and off, saved files of other versions and retired backends, version parsing and sign-in, and, on Unix, probes of fake CLIs: their version, sign-in, failure, and a CLI that hangs, which is stopped with what it started;
    • the context ring's count is the same for a loaded task and its session file;
    • a hidden message starts a run of its own, waits out of sight behind a run and follows as its own turn, never shows in the timeline, and a run stopped before it went leaves it in the history the next prompt reads;
    • external agents, on Unix, against the Goose runtime's fake Codex app-server and fake Claude Code CLI (this test binary re-executed through shims on a private PATH): approvals accepted without a card; a stopped run interrupts the turn and kills the process group, and the next send resumes the thread in a fresh process; questions on the card, async ones answered in a turn of Maple's own; Claude's streaming, resume of confirmed sessions only, descendants killed on Stop, sanitized failures, and sign-in detection; validation, the four-agent limit, and a launch that fails keeping no place;
    • external agents end to end with the scripted model: the tools are declared only while the agent is on in Settings and for the task, and the menu's rows and errors follow; the model starts a background agent, the agent's progress joins the call's row, its result reaches the model as a hidden message, and the reloaded row shows what the agent did with the notice; a working agent shows above the composer and on a reloaded row, Stop from its row reaches the model, and deleting the task ends the agent without a report;
    • built-in CUA: Goose's unit tests on the new types (catalog projection and hidden lifecycle, bound arguments, grounding bounds, image redaction, screenshots described for a model without vision and a cancelled description, the session identity, the GNOME helper's files), the SDK's own runtime through a session's lifecycle, and end to end the switch, the menu's row, a new task's choice, and, where CUA can run, a real binding's tools, which stay and fail once CUA is switched off;
    • ACP over its framed stdio transport, against the scripted model: initialize and the model selector without modes; session/set_mode refused; a turn streamed once (thinking, text, a tool call whose kind updates keep, the locked selector, the title) and replayed by a later session/load; session/cancel stopping a turn and the next prompt running; a failed turn told once and ended; /compact on a short and a long history; an image prompt through read_image; a turn without images on a vision model's catalog entry, with its window in the run and in usage_update; the trust chooser asked once and its answer kept; a task closed before its first prompt removed. Goose's unit tests for the conversions, the trust prompts, the transport's credits and the configuration run unchanged where the shapes did;
    • the runtime's side of ACP: a held task is unlisted until prompted and discarded untouched; it runs without the desktop's tools, and the host hears none of its run; the desktop and a second surface are refused while it is held and the desktop gets it back after; a cancelled send stops its run; a client's HTTP MCP server runs with its headers; a start sweeps stranded provisional tasks;
    • a signed-out session cannot rename a task, and a command keeps the PATH it starts with when the task has no login PATH, as an ACP task has none;
    • System One over the account's transport: a classification goes to /v1/systemone asking for JSON, a 429 with retry-after-ms is sent again, and the answer's usage counts its input, cached and output tokens; a failed request's error leaves out the server's body; a chat response carries its headers to Pi.
  • In the previous restack, the macOS GUI build and native desktop picker smoke test passed. See the live validation section for the tested behaviors and its remaining limits.
  • Cargo.lock was resolved by cargo over the full workspace, GUI app included, and a locked resolution accepts it unchanged. The rename commit only renames the package; the runtime commits change the runtime crate's dependency list; the deletion only removes packages. The MCP commit adds rmcp 3.4.1 with sse-stream 0.2.6, tokio-stream 0.1.19 and base64 0.23.1 (rmcp's own); no other package was added or moved, and Maple Agent's dependency check passes. Before that no external crate was added or moved here: the web tools and read_image add edges to pulldown-cmark and image, both already in the lockfile, and Agent core in Rust after Pi: pi-ai, pi-agent-core, pi-coding-agent #1104 adds similar. The CUA commit brings back the Cua Driver SDK (141 packages) and the ACP commit agent-client-protocol 2.2.0 (16), and commit 19 pins the twelve cargo had resolved newer, so every package they add is one the lockfile Maple shipped with had, at its version; a locked resolution accepts the result, and the dependency check passes.

Live validation (2026-10-09)

Another agent ran the live checks on 680ca688 on macOS, against a local backend with real inference (report). The scenario checklist's results are in docs/scenario-checklist.md.

  • Worked: streaming, the file tools, images, cancellation, stdio and HTTP MCP, manual compaction, the proxy, a Goose-era profile's migration, and on the desktop the queue, steering, the plan and question cards, task management and settings. usage_update reported each model's catalog window.
  • Fixed: back from Settings, a reply that finished meanwhile showed as still running (commit 25).
  • Deferred to a later review of the ACP layer (comment):
    • one task run at once from the desktop and from a separate maple-agent acp process splits its history, so use a task in one place at a time;
    • session/delete leaves the session listed, a model change waits for the running turn, and an MCP server that fails to start is not reported to the ACP client, all as on master.
  • Not run live: automatic compaction near the window, external agents, built-in CUA, voice and background notifications. Linux and Windows have CI only.
  • Open: whether an image from an MCP tool is described for a model without vision, as images from read and read_image are, or stays a note. A model the catalog gives no window for compacts at the 128,000-token default, while the context ring assumes 200,000.

Restack and thinking validation (2026-10-10–11)

Restacked onto #1104 at df7bfbd1, with the three Pi adaptations it had then (two since dropped; see the end). Compared the old d433cdb9 GUI release build with the new GUI debug build against the same local backend using isolated test-account state and password login. These are behavior checks, not performance comparisons. The live-tested tree is identical to final head 98220ba6; the final head was rebuilt and checked after the history rewrite.

  • Passed on both: ACP handshake, model locking, file tools on reasoning/vision/text-only models, session replay and usage windows, cancellation of commands and their descendants, immediate cancellation and recovery, independent sessions, local/HTTPS/attachment images, HTTP and stdio MCP with cleanup, trusted and untrusted project behavior, manual compaction, normal background jobs, output truncation with the full output saved, client-disconnect cleanup, and streaming/non-streaming proxy requests.
  • Automatic compaction: both builds compacted at about 56,857 tokens with an explicit 64,000-token test window. This validates the trigger and recovery with real inference; it is not a full production-window stress test.
  • Unchanged observations on both: ACP config changes wait for a running prompt; deleting a session leaves an archived entry listed; failed MCP startup is not announced over ACP; the initial available-command update omits newly trusted project commands, although invoking those commands succeeds. MCP images for a text-only model remain a note, as previously documented. No additional scope was added for these observations.
  • Backend interruption: both replies completed after a brief local backend stop/restart. This confirms recovery in this scenario; it is not a complete retry-policy test.
  • Desktop: choosing a supported level, a live read/bash turn, changing an existing task, inheritance by a new task, clamping a text-only model to Off, reopening a task, and relaunch persistence all passed.
  • CUA quick smoke: its integration card loads and correctly reports that the isolated app needs Accessibility and Screen Recording permissions. No permissions were changed; a live computer action remains untested.
  • Request efforts: all 29 tested choices produced the expected reasoning_effort field (or its absence) and now have successful HTTP 200 results across the original run and a targeted DeepSeek rerun. The original run passed 24 choices; at 2026-10-11 00:46 UTC, DeepSeek Off (none), Low, High, Xhigh and Max all returned HTTP 200 and completed normally through local OpenSecret → Tinfoil. The earlier DeepSeek 502/503 failures at 2026-10-10 23:37 UTC coincided with the owner-reported Tinfoil outage window and were likely outage-related (rerun details).
  • Catalog discrepancy: Gemma currently has reasoning metadata without a supported-effort list. Following the specified mapping keeps Medium as its default, rather than the research note's anticipated High. GLM, K3 and DeepSeek clamp Medium to High; gpt-oss keeps Medium; Llama uses Off. No model recommendation or invented catalog tier was added.
  • Coverage limits: no new live external-agent, built-in CUA, voice, notifications, exhausted-credit/revoked-login, or Goose-profile migration run; those older results are not claimed as freshly retested. No Linux/Windows execution in this run.

Restack onto #1104's parity ports (2026-10-11)

Restacked onto #1104 at 76309fcb (head b838666b), which ports the rest of what the parity review left to decide: request options and retries after retry-after, classifiers and System One, the one-call setup and session runtime, the boundary drafts, and smaller session items.

  • What Maple's tasks get from it on their own: skill folders follow their ignore files, and in a linked worktree inside the main checkout, the worktree's own AGENTS.md replaces the main checkout's, both as in Pi.
  • What commit 3 adapts: the provider fills Pi's new model and response fields, and the retry budget takes the retry settings' new shape. Pi's sessions now also time out a response that does not start, after 300 s by default, just before Maple's transport would, with Pi's message in place of Maple's; Maple turns Pi's timeout off (httpIdleTimeoutMs 0, as Pi turns it off), so its transport's timeouts and messages stay as they were, and a test checks it.
  • What waits for later: System One's classifier is served by commit 29 but nothing calls it yet. Maple's runtime keeps building its sessions itself rather than through the one-call setup or the session runtime, since it hosts many at once; nothing in Maple handles the boundary events or sets the new request options.
  • Commit messages are rewrapped to 72 columns, and the leftovers commit's subject is shortened.
  • Live retested after the master restack at b838666b; see the dated validation below. The earlier results above remain historical results for 98220ba6.

Master restack and live validation (2026-10-11)

Restacked #1124 → #1104 → #1109 onto master 9e49de8738612537b24d17d877d689da331c49fb. The heads were then 5782e611, 76309fcb and b838666b; #1109 has since moved (see the next section). There were no conflicts or build fixes: range-diff keeps all 1 / 77 / 31 commits unchanged, messages and trailers unchanged, Pi crate trees identical to the old respective heads, and the top tree is the old top plus master's changes. Pushed in order with explicit leases after the local checks above; draft states are unchanged.

  • Live build: packaged GUI debug and matching CLI at b838666b, macOS 27.0.1 arm64, isolated Local test-account state and password login. Real inference used GLM 5.3, Gemma 4 31B, Llama 3.3 70B and GLM 5.3 Flash.
  • Passed: streaming; read/bash/edit/write across reasoning, vision and text-only models; cancellation and descendant cleanup; ACP handshake, model locking, replay, usage and independent sessions; local/HTTPS/attachment images; HTTP and stdio MCP with cleanup; trusted/untrusted projects; manual compaction; normal background jobs and full-output retention; desktop thinking-level changes, queue delivery and steering.
  • New behavior checks passed: ignored skills were absent from commands and the model's skill list while the unignored skill remained; a nested linked worktree used its own AGENTS.md and excluded the main checkout's. Automatic compaction ran at 56,856 tokens with an explicit 64,000-token test window, with notices and continuation.
  • Outages: a reply completed after a brief backend stop/restart. Retry exhaustion over ACP completed after 181.09 seconds with Network error: could not connect to Maple, not Request timed out.. The desktop run also settled to one row with that same message after retries ended. Both this build and 98220ba6 displayed multiple error rows while retries were in progress, then one row when the run settled. The backend was restored.
  • Compared with 98220ba6: config changes waiting for an active ACP prompt, archived deleted sessions remaining listed, missing failed-MCP-start notices over ACP, and the initial command-list update omitting newly trusted skills were reproduced on both builds. Invoking trusted commands succeeds. The long-output harness initially inspected the capped ACP display; persisted sessions and saved output confirmed the correct 2,000-line tail and complete 3,000-line file on both builds. These observations did not result in code changes.
  • Not run: hosted OAuth return-to-app (only Local credentials available), System One (no live caller), Buzz, and fresh external-agent/CUA/voice/notification/proxy/account-failure/migration checks. Live testing here is macOS; Linux/Windows coverage is CI. This is bounded behavior validation, not performance or full production-window/retry-policy coverage.

The Pi adaptations decided (2026-10-11)

The owner reviewed the three Pi adaptations: the timestamps stay, and unregister_tool and contain go, so the stack does what Pi does. Restacked as 29 commits on #1104 at 76309fcb; head 32c08e71.

  • Commits 14 and 21 are gone, so the Pi crates here differ from Agent core in Rust after Pi: pi-ai, pi-agent-core, pi-coding-agent #1104 only by commit 1's timestamps. Three commits changed, 14, 17 and 20 in the new numbering; the rest replay unchanged.
  • MCP (commit 14): as in Pi, a tool is never taken from the session. A tool the server withdrew, or one of a server switched off or stopped, stays registered and keeps its name. Calling it fails with the message Pi's MCP extension gives such a call, without reaching the server, until the server offers the tool again. Pi also hides such a tool, which takes it out of the declared tools; the crates have no hidden exposure, so here it stays declared. Switching a server off still takes its instructions out of the prompt, through Pi's section patches.
  • CUA (commit 17): switching CUA off for a task closes its binding, and its tools stay. Calling one fails with "Computer use (CUA) is not connected to this task." until the task is bound again.
  • Bridge commands (commit 20): no longer contained, and the tool context's flag for them is gone. A background job started by a command with the bridge's credentials can outlive the command, as any command's can.
  • Tests: a tool the server withdrew stays declared, and calling it fails without reaching the server; after switch-off, calling the server's tool fails as disabled; where CUA can run, a real binding's tools stay and fail once CUA is switched off. The contain tests are gone.
  • Not run live after this change; Testing lists the checks.

Buzz out of ACP (2026-10-12)

The owner: what is Buzz-specific in ACP goes, and ACP that any client might use stays. Commit 30, head 4f76e26b:

  • Removed: _maple/bridge/hello; the bridge's variables (Buzz's credentials), read from the process or from a buzz-dev-mcp server definition; the tool context that carried them into commands, with its BUZZ_* scrubbing in every task's commands and external agents, its revocation and its launch fence; session/new's systemPrompt and _meta.sessionTitle, with the runtime's per-task system prompts; protocol version 2, which only told Buzz about systemPrompt (Maple now answers with the stable version, 1); and the count of connections that carried credentials. The commit removes 860 lines and adds 153.
  • Kept, as general ACP: leases and their exclusive hold (a released lease ends its hold with a plain flag now), the client's HTTP MCP servers, trust prompts, list, load, close and delete, the model selector, /compact, usage updates, and a failed turn that ends without a JSON-RPC error, so a client never resends work Pi already did.
  • Kept on purpose: an ACP task's commands still run on the PATH the client started Maple with, as they did when that PATH travelled with Buzz's variables. Desktop tasks keep the login shell's PATH.
  • Checks are under Testing. Not run live.
  • Then no ACP config migration (commit 31, head a2cae39c). The owner: the code is greenfield, so the ACP configuration's copy from the proof of concept's account folder goes, and with it the only code that wrote the file; Maple only reads it now.

🤖 Generated with Claude Code

https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d

@AnthonyRonning

Copy link
Copy Markdown
Contributor Author

Local live validation of the Pi runtime

Tested 680ca688e20d2cd1b59bba9ed2bf80a2a91e262f on agent-pi/integrate on October 9, 2026. This is still #1109's head at posting time. Posting here to keep the observations together; I have not assigned their origins to individual PRs in the stack.

The core workflows exercised successfully with real inference: streaming, file tools, image handling, cancellation, stdio/HTTP MCP, manual compaction, standalone proxy mode, and basic Goose-profile migration. Desktop queueing, steering, plan/question cards, task management, and settings were also exercised.

The edge cases below record what happened and under which conditions. They are not severity rankings, requested changes, or merge criteria. Whether each usage pattern is worth supporting, and what complexity is appropriate, is left to the owner and implementation agent. There is no claim that these observations all originated in the Pi rewrite.

Environment and scope

  • Full optimized desktop release via the repository's Nix environment and just release; version 0.1.0 (680ca688). A separate local validation app bundle kept state isolated. Its window title includes “Debug,” but the executable tested was the release build.
  • macOS 27.0.1 (26A434), arm64 Apple M3 Max VM, 8 cores, 48 GiB RAM.
  • Fresh Local MDE, OpenSecret at the same monorepo commit; billing 5b1004e9c1063fcfb3556a35c6d19ed32b1dd223; MDE manager 966a16010720d1363721e683b89ea04d340ab571.
  • Local Pro/Free test fixtures, isolated config/data, PTY login. Direct encrypted SDK inference succeeded before Agent testing. The release launcher reused MDE's generated environment rather than its default debug executable.
  • Main live matrix: glm-5-3 (text, 262144 context), gemma4-31b (vision, 262144), llama3-3-70b (text, 131072). Both side models were available and exercised. The catalog also listed gpt-oss-120b, glm-5-3-flash, kimi-k3, and deepseek-v4-1-flash; those were not all exercised in the tool matrix.
  • Buzz excluded at the owner's request. External agents were not launched. Hosted CI was left alone. No application source changes, commits, pushes, or merges were made for this test.

Observed edge cases

1. Separate desktop and ACP processes can produce different history branches for one task

Conditions: two processes use the same isolated account/data root and the same existing task. This is specifically a cross-process observation; it does not establish what happens between two surfaces inside one runtime.

Reproduction:

  1. Open an existing task in the desktop.
  2. Start a separate maple-agent acp process and session/load that task.
  3. Through ACP, ask bash to run touch acp-shared-running; sleep 60, then answer ACP_CONCURRENT_DONE.
  4. During that sleep, send Reply exactly DESKTOP_CONCURRENT_DONE. Do not use tools. in the desktop.
  5. Let both finish, then quit and relaunch the desktop.

Observed once with confirmed overlap: both prompts were accepted. The desktop visibly completed its turn during ACP's sleep. After relaunch, the desktop prompt/reply were absent from the displayed conversation and the ACP turn was present. Both remain in the JSONL file on sibling parent chains; the desktop records were not irreversibly deleted. Before relaunch, the desktop still displayed its own completed turn.

Sanitized protocol and persisted-record evidence (UTC log timestamps):

20:59:35.189  sent session/load for existing task
20:59:35.212  received successful result with configOptions
20:59:35.212  sent ACP session/prompt with sleep 60
20:59:39.736  eb927665 -> parent 14b5302a: ACP bash call
20:59:52.394  f79c88fc -> parent c964750c: desktop user prompt
20:59:53.700  85daa42d -> parent f79c88fc: desktop assistant reply
21:00:39.754  56169692 -> parent eb927665: ACP tool result
21:00:40.339  68fd48a9 -> parent 56169692: ACP assistant reply
21:00:40.340  ACP prompt result: stopReason=end_turn

The chain selected after relaunch excludes 85daa42d. An earlier, shorter attempt completed sequentially and is not counted as an overlapping reproduction. No Goose behavioral comparison was run for this scenario.

2. Returning from Settings during a completed turn can show stale chat state

Reproduction: start a long reply, open Settings while it streams, wait for the answer to finish persisting, then use Back.

Observed twice: the returned chat showed a partial answer and running indicator even though the complete answer was saved. Selecting another task and reselecting the original loaded the full answer. Subsequent prompts worked. One saved reply contained 1520 words; no corresponding runtime error appeared. This was an observed display-refresh condition, with the answer retained. No Goose comparison was run.

3. ACP session/delete succeeds while the session remains in session/list

Reproduction: create and prompt a session; close it; delete it; list sessions; reconnect with a fresh ACP process and list again.

Observed: deletion returned {}, and the same session remained listed immediately and after reconnect. Source inspection indicates ACP archives the task rather than using the desktop deletion path; the list includes it. Desktop deletion separately removed its task row, session file, and attachment directory.

20:24:41.216  sent id=16 session/delete for synthetic session
20:24:41.219  received {"jsonrpc":"2.0","id":16,"result":{}}
20:24:41.220  received id=17 session/list containing the same session

Old Goose source also used archive-like deletion. That is source evidence of possible inheritance, not a before/after behavioral comparison or a conclusion about the intended ACP contract.

4. ACP model-config responses wait behind an active prompt

Reproduction: while a session runs bash with sleep 15, send session/set_config_option for the model.

Observed: the config response arrived after prompt completion, 15.519 seconds after the request, then reported the existing model lock. A separate overlapping session/prompt was rejected promptly. The same wait-behind-prompt behavior was also observed during a cancellation test.

20:35:23.938  sent id=8 session/set_config_option
             {configId:"model", value:"gemma4-31b"}
20:35:39.457  received id=7 prompt completion
20:35:39.457  received id=8 error:
             "Maple tasks are model-locked after their first message"

Both current and old Goose source take an operation gate here. This may be inherited serialization behavior; no full Goose runtime comparison was made.

5. A missing MCP executable is named in logs and the desktop notice, but not in ACP notifications

Reproduction: configure an enabled account-level server named missing-validation-server with command /does-not-exist/pi-validation, alongside a working stdio fixture; run two ACP prompts.

Observed: working tools and both prompts completed, but neither ACP frame stream named the missing server. The first turn took about three seconds including live inference. The native desktop separately displayed a notice naming it.

[2026-10-09T20:26:54Z WARN maple_agent::agent::mcp::task]
MCP server "missing-validation-server" could not connect:
could not start /does-not-exist/pi-validation:
No such file or directory (os error 2)

No Goose behavioral comparison was run for this case. A hanging MCP handshake was not used to test the ten-second startup ceiling.

Model and backend observations

  • Gemma file-task behavior: all four tools executed, but the model first emitted a bash overwrite of input.txt that discarded an existing line. GLM and Llama preserved the requested contents. A Gemma follow-up stalled after a successful read and was cancelled at the harness's 120-second limit; other Gemma image/tool calls worked. The overwrite followed the model's arguments, and the stall's cause was not isolated.
  • Web extraction: open_url returned extraction_failed for Python and example.com. Direct OpenSecret SDK extraction, without the Agent, also failed for both. web_search worked; bash/curl retrieved the Python title as a fallback. This evidence does not isolate extraction behavior to the Agent rewrite.
  • Account denial: a Free local fixture received one generic 403 Maple rejected the inference request (status 403) in 80 ms, zero usage, no retry. This did not distinguish exhausted credits from plan/model restrictions. Expired/revoked credentials were not exercised.
  • MCP images on text-only models: returned a placeholder/note rather than a description, consistent with the PR's known open decision. Vision models described the synthetic image correctly.
  • Attachment references: text-only models sometimes supplied an invalid generated image reference first, then used the actual maple-attachment:// reference successfully.

Measurements and verification limits

  • GLM headless reply: 1499 words, first visible text at 7.903 s, total 45.95 s, RSS 29328 KiB (~28.6 MiB).
  • Desktop: observed window-open time 278 ms; RSS 140400 KiB (~137.1 MiB) after a saved 1520-word answer.
  • These are individual VM samples, not comparative benchmarks.
  • usage_update matched saved final context usage/window: GLM 7175/262144, Gemma 7078/262144, Llama 7464/131072.
  • Goose comparison was limited to actual profile migration using old debug binary d8d3002a, plus the source comparisons noted above. No general claim that all observed behavior is new.
  • No live automatic near-window compaction, external-agent execution, Maple CUA execution, Windows/Linux coverage, voice, background OS notifications, clipboard image paste, full Vim behavior, or retired-integration migration was established.
Detailed coverage against the handoff and scenario checklist

“Verified” applies to the stated scenario only; partial and untested cases are explicit. These labels describe test coverage, not release requirements.

Part A — ACP / headless

  • A1 VERIFIED — Protocol 2, capabilities, model selector, no modes.
  • A2 VERIFIED — Default GLM uses read/bash/edit/write; correct files, streaming text/thinking match saved content without duplication, generated and semantic titles, model locks after first message. ACP updates are patches: omitted kind on later updates preserves it. Initial bash announcement briefly uses the protocol default kind before an execute update.
  • A3 OBSERVED MODEL BEHAVIOR — GLM and Llama exact file outcomes pass; Gemma tool execution works but its unsolicited overwrite fails preservation. See provider observations; no confirmed runtime defect.
  • A4 VERIFIED — Vision/non-vision attachments; local and public HTTPS read_image; local PNG read with vision and fallback; MCP images. Non-vision MCP placeholder is the known limitation.
  • A5 PARTIAL / OBSERVATION — Long-command and immediate cancellation, child cleanup, recovery, and overlapping-prompt rejection pass. Config response waits for the turn; see observation 4.
  • A6 VERIFIED (partial) — Normal background job outlives its shell; 3000 output lines are saved whole, and the model receives the last 2000 (1000–2999). The ACP client preview has a separate smaller cap. No multi-minute test proved absence of every default timeout, and the 50KB threshold was not separately forced.
  • A7 VERIFIED (manual); NOT RUN (automatic) — Short history says “Nothing to compact yet.” Multiple long turns compact successfully in 15.19 s with completion notice. One large recent turn alone correctly offered nothing to compact. Automatic near-window compaction and its exact trigger size were not exercised.
  • A8 PARTIAL / OBSERVATION — Provisional sessions unlisted, close-before-prompt discards, load replays messages/tools, set_mode refused. Delete remains listed; see observation 3.
  • A9 VERIFIED — Explicit trust accept/deny, chooser once, trusted project SYSTEM/skills/templates included, denied ones excluded. Advertised /validation skill and template work. Chooser wording mentions project guidance including skills, but does not enumerate every file type.
  • A10 PARTIAL / OBSERVATION — HTTP MCP with header, account stdio tools, image results, close/process cleanup pass. Missing executable fails promptly, but ACP omits notice; see observation 5. A hanging handshake was not used to measure the ten-second ceiling.
  • A11 NOT RUN / EXCLUDED — Buzz removed from scope by owner; no further testing or findings requested.
  • A12 VERIFIED (partial) — Mid-stream backend outage and pre-request outage retry and recover; EOF during a long command stops ACP and its child processes. Free-account generic 403 is not retried. Exact credit category, expired/revoked sign-in, and every error-title variant remain unverified.
  • A13 VERIFIED — usage_update matches final saved context usage and catalog window: GLM 7175/262144, Gemma 7078/262144, Llama 7464/131072. Emitted at turn completion; no replay expectation.
  • A14 VERIFIED — Two independent sessions on one ACP connection return ALPHA and BETA without cross-talk.
  • A15 VERIFIED — Standalone OpenAI-compatible proxy, streaming and nonstreaming, returns HTTP 200 through local backend; run separately from desktop proxy owner.
  • A16 VERIFIED (bounded) — --version works. Audited 43 log/frame files against known fixture passwords, emails, tokens, and API key: zero matches. Expected missing-MCP warnings and resource-collision debug messages observed; no unexplained fatal error found. This is a bounded credential audit, not a proof against every possible secret pattern.
  • A17 VERIFIED — GLM 1499-word answer: first visible text 7.903 s, total 45.95 s; ACP RSS 29328 KiB (~28.6 MiB). Single sample.
  • A18 VERIFIED (partial) — Actual Goose binary d8d3002, debug, created a task on isolated local-test state. Pi starts on a copy, hides old task, runs a new task, and preserves default model/project, project trust and MCP config semantically. Integration choices and retired CuaDriver migration not exercised.

Part B — desktop checklist

  • T1 VERIFIED — Automatic and semantic titles observed.
  • T2 VERIFIED — Renamed “Pi Live Validation”; survived full app relaunch.
  • T3 VERIFIED — Settle, archive, restore preserve history.
  • T4 VERIFIED — Desktop delete removes task row, JSONL and attachment directory.
  • T5 VERIFIED — Pin and unpin.
  • T6 NOT RUN — Second-project/recent-folder UI flow; separate project roots were exercised through ACP only.
  • C1 VERIFIED (partial) — Queued follow-up chip becomes next turn; queue editing/removal not exercised.
  • C2 VERIFIED — Cmd+Enter steering during a shell wait reaches current workflow.
  • C3 VERIFIED — Stop during sleep 120 kills shell and child; stopped notice and successful follow-up. Stop while initial thinking/MCP connection setup was not separately exercised in the desktop.
  • C4 VERIFIED (picker); NOT RUN (paste) — Vision and non-vision file-picker attachments both work; clipboard image paste untested.
  • C5 NOT RUN — Voice dictation.
  • C6 VERIFIED — Draft model, Web and MCP choices visibly carry into task.
  • S1 VERIFIED (partial) — /new, /pin and /help exercised; model/Web controls used through their pickers, not both slash command forms.
  • S2 NOT RUN (desktop) — Manual /compact passed over ACP; desktop ring drop not observed.
  • S3 NOT RUN (bundled skill) — A synthetic project skill passed over ACP; bundled /handoff not invoked.
  • S4 VERIFIED (ACP coverage) — Trusted/untrusted project skill behavior verified; native trust chooser itself not separately driven.
  • S5 VERIFIED — Custom system marker and project AGENTS.md marker both reach new desktop task.
  • L1 VERIFIED — Four built-in file/shell tools, correct disk output.
  • L2 VERIFIED (ACP plus desktop attachments) — Local/public images and non-vision fallback; full matrix run over ACP.
  • L3 VERIFIED — Plan card advances 1/2 to 2/2.
  • L4 BLOCKED (partial) — web_search passes; open_url fails in backend's direct SDK too. Page-title fallback via bash works, the open_url request itself still returned an error.
  • L5 VERIFIED (answer); NOT RUN (skip) — Question card shown, Yellow selected, answer acknowledged. Escape-to-skip untested.
  • E1 NOT RUN — Codex agent execution; owner requested no subagents.
  • E2 NOT RUN — Claude Code execution; same scope restriction.
  • E3 NOT RUN — Background external agents.
  • E4 NOT RUN — External-agent question cards.
  • E5 NOT RUN — External-agent approvals.
  • U1 NOT RUN — Built-in CUA card reports Ready and existing macOS permissions On; no permission changes or CUA tool execution performed.
  • U2 NOT RUN — Retired CuaDriver choices/server migration.
  • M1 VERIFIED — Both stdio and HTTP fixture tools offered and used in desktop.
  • M2 VERIFIED (partial desktop) — First failure notice names missing server. ACP notice is absent as above; full desktop off/on retry sequence not exercised.
  • A1 VERIFIED (partial ACP) — See A1–A14 above; exceptions explicitly listed there.
  • A2 VERIFIED — Login, proxy and --version.
  • A3 EXCLUDED — Buzz at owner's request.
  • D1 VERIFIED (partial) — Human-readable tool summaries appear; setting toggled off. No exhaustive asynchronous side-model timing check.
  • D2 VERIFIED — Text-model attachment described via Gemma side model.
  • D3 NOT RUN — Automatic compaction and near-full desktop context ring; catalog usage and manual ACP compaction verified separately.
  • D4 VERIFIED — Pro Plan, percent used and reset date displayed.
  • D5 NOT RUN — Background OS notifications; logs show notifications intentionally skipped while window active.
  • D6 VERIFIED (partial) — Dark theme, Serif font, reduced motion and summaries settings applied. Vim preference toggled; editing semantics untested.
  • D7 VERIFIED (ACP migration coverage) — Actual old-profile/new-runtime test passed; no broad visual comparison of Goose versus Pi desktop.
  • Same task in two places OBSERVATION — Confirmed overlapping separate-process runs create divergent visible history; observation 1.
  • Settings/back refresh OBSERVATION — Observation 2, twice.
  • Desktop performance VERIFIED — Window opened in 278 ms on observed launch; RSS 140400 KiB (~137.1 MiB) after saved 1520-word reply. Single sample, local VM; not a release performance baseline.
  • Integration settings VERIFIED (inspection only) — Built-in CUA and installed signed-in Codex/Claude detected. External agents and CUA remained disabled; per-task MCP switch exercised.

Platform scope: macOS privacy grant/revoke flows were not exercised; the existing CUA permissions showed On. Linux/GNOME Wayland and Windows were unavailable on this host.

Evidence handling

Sanitized protocol excerpts are above; full synthetic ACP frame logs, saved-history snapshots, UI observations, and harness corrections are retained locally. A check of 43 log/frame files found no matches for known fixture credentials. Initial harness assumptions about patch-style tool updates, usage on replay, skill-command spelling, output-preview truncation, single-turn compaction, and default MCP-description fields were corrected before this report; they are not counted as product observations.

The test desktop, ACP clients, fixtures, and owned MDE services were stopped. The application checkout remained clean. Intended changes described in the PR, including hidden Goose-era tasks and absent MCP OAuth/composer ! commands, were not treated as regressions. This comment does not request fixes or assign acceptance decisions to any of the untested cases.

Copy link
Copy Markdown
Contributor Author

Following up on the live validation report.

Fixed: observation 2 (back from Settings shows a stale chat), in b994f08. The app passed the runtime's events only to the chat on screen, so a reply that finished while Settings was open never got its end. Master's app does the same. The fix still needs a CI run.

Postponed: the ACP observations (1, 3, 4 and 5). ACP is not the main path. For this PR the bar is that ACP keeps working on the new runtime as it did before. Rather than patch these one by one here, the ACP layer (Maple's own server code) will be reviewed as a whole later, including its process model: each ACP client starts its own copy of the runtime on the same account data as the desktop app.

  • 1. The desktop and a separate maple-agent acp process working on one task can split its history. Nothing is lost: both branches stay in the session file, but one is hidden after a relaunch. Until the review, use a task in one place at a time.
  • 3. session/delete archives the task, and session/list still lists it. Same as master.
  • 4. A model change sent during a running turn is answered only when the turn ends. Same lock as master. The answer is correct, just late.
  • 5. ACP clients are not told when an MCP server fails to start. The log and the desktop's notice name it. Master's ACP ignored these warnings too.

Generated by Claude Code

@AnthonyRonning

Copy link
Copy Markdown
Contributor Author

DeepSeek reasoning smoke rerun: all five levels passed on 98220ba625d537390d33365e842d261da5d52d8f.

At 2026-10-11 00:46:03–07 UTC (October 10, 5:46 p.m. Pacific), the existing smoke harness exercised Maple's provider adapter through the Rust SDK and local OpenSecret → Tinfoil, using the isolated Pro test account. Off (reasoning_effort: none), Low, High, Xhigh and Max each sent the expected field, returned HTTP 200, and completed normally with Stop. No application code or backend configuration changed for the rerun.

The earlier DeepSeek batch ran at 2026-10-10 23:37:31–32 UTC, with 502/503 results; local logs confirm Tinfoil returned the initial 502s. That timing falls inside the Tinfoil outage window reported by the owner. The successful rerun strongly supports the outage as the explanation, though it does not independently prove the incident's root cause.

This closes the previously unverified DeepSeek server-acceptance check: all 29 tested model/level choices now have a successful live result across the original run and this targeted rerun. This was a short acceptance/stream-completion smoke, not a reasoning-quality comparison.

Copy link
Copy Markdown
Contributor Author

Build macOS binary (dev) failed on b838666b in GPUI's build script. Its runner (blacksmith-6vcpu-macos-26-35wpuvbkh5tsvtky) has no Metal Toolchain, so gpui_macos could not compile its shaders: cannot execute tool 'metal' due to missing Metal Toolchain.

This failure is not caused by the PR. The stack pins the same GPUI revision as master (cc053a4a) and changes nothing under .github/. The same job passed on master's 9e49de87 an hour earlier, on the same runner pool. There is no code fix to port. I'll re-run the failed job once its workflow run finishes.


Generated by Claude Code

@AnthonyRonning

Copy link
Copy Markdown
Contributor Author

Master restack and live validation — 2026-10-11

Restacked the three phases onto master 9e49de8738612537b24d17d877d689da331c49fb, then pushed in order with explicit leases after all requested local checks passed.

PR Previous head New head
#1124 afddf99f 5782e61170a7f3719dc47a707ab0db61ba188710
#1104 2a334328 76309fcbfbd5d6b9730ddc503e43159493bb1f09
#1109 b4b41a5c b838666b1c7bc8b78e8b120f08be8a01a0a52ae2

Conflicts/fixes: none. Range-diff reports all 1 / 77 / 31 commits unchanged. Commit messages and trailers are unchanged. The Pi crate trees are identical to their corresponding old heads. The new top tree is the old top tree plus master's changes, with no extra application changes. Native stack #1125 still has #1124 → #1104 → #1109. Draft states were preserved; nothing was merged.

macOS checks before pushing

macOS 27.0.1, arm64. Full just ci passed at all three heads:

Head Workspace tests passed Ignored Additional headless app tests
#1124 5782e611 873 5 51 passed
#1104 76309fcb 1,278 5 51 passed
#1109 b838666b 1,121 3 51 passed

Every #1109 commit separately passed cargo fmt --all -- --check, cargo clippy --workspace --all-targets --locked -- -D warnings, and RUSTFLAGS='-D warnings' cargo test --workspace --locked, including the GUI app. The head's full gate also checked all app feature configurations and built GUI workspace targets with warnings denied.

Per-commit macOS checks

Every row passed formatting, workspace/all-target Clippy with warnings denied, and warning-denied workspace tests with --locked. Counts include the GUI app.

# Commit Tests passed Ignored
1 f07ec902 1279 5
2 bebdef1a 1279 5
3 d8684626 1369 5
4 b5511363 1367 5
5 2efc23b1 939 1
6 2d383aa8 951 1
7 ba68ffb3 957 1
8 8f6aaa0c 970 1
9 e65959db 977 1
10 bf9c0a17 982 1
11 89548b89 987 1
12 3c362314 987 1
13 7025d670 989 1
14 78dcfd9a 989 1
15 79c0debd 1011 1
16 396399c5 1011 1
17 5896357e 1041 3
18 cd07f1fd 1059 3
19 00a74c95 1107 3
20 6fd6ac60 1107 3
21 788e9a3e 1108 3
22 fcdcced2 1109 3
23 20bb422e 1109 3
24 19795c42 1111 3
25 5e1babaf 1111 3
26 6e3c859f 1113 3
27 bf2c06dd 1113 3
28 b2aa7191 1113 3
29 0314dcec 1114 3
30 411574f0 1118 3
31 b838666b 1121 3

Hosted Agent CI

All nine Agent jobs passed on the exact new heads: Linux, macOS and Windows for each PR. Runs: #1124, #1104, #1109.

Live retest of b838666b

Used a packaged GUI debug build and its matching CLI, isolated Local test-account state, password login, and real inference. The file-tool matrix used GLM 5.3, Gemma 4 31B and Llama 3.3 70B; the desktop and outage checks used GLM 5.3 Flash. This was behavior validation, not a performance comparison.

Area Result
Streaming and file tools Passed: read, bash, edit and write on all three model categories; expected files and replies.
ACP basics Passed: handshake, new/prompt, model locking, replay, usage windows, provisional-session close, mode refusal and independent sessions.
Cancellation Passed: bash and descendants ended; immediate cancellation, follow-up recovery and client-disconnect cleanup.
Images Passed: local, HTTPS and attached images, read/read_image, vision and text-only paths. MCP images for a text-only model remain the documented note.
MCP Passed: HTTP tools including an image result, account stdio tools and process cleanup. Startup-notice observation below remains.
Trust Passed: untrusted project instructions/commands excluded, trusted instructions effective and trusted slash commands execute. Initial command-list observation below remains.
Ignored skills Passed: the skill excluded by its folder's .gitignore was absent from / and from the model's reported skill list; the unignored skill remained. No discovery tool calls were used.
Nested linked worktree Passed: the worktree's AGENTS.md marker was present and the main checkout's marker absent, without discovery tool calls.
Compaction Passed: short-history response, long manual compaction, and automatic compaction with notices at 56,856 tokens using an explicit 64,000-token window. This is a bounded trigger/recovery check, not a production-window stress test.
Bash background/output Passed: ordinary background child survived its command; the model received the last 2,000 of 3,000 output lines, and the full output file retained all 3,000.
Desktop Passed: live Low and High thinking turns, changing the existing task's level, queue delivery after a running bash call, and steering a queued instruction into the current run.
Brief backend interruption Passed: stopped/restarted the local backend mid-reply; the reply completed. This confirms this recovery scenario, not every retry condition.
Retry exhaustion Passed over ACP after 181.09 seconds: Network error: could not connect to Maple, not Request timed out.. The desktop run also settled to one row with that same message after retries ended. Both this build and 98220ba6 displayed multiple error rows while retries were in progress, then one row when the run settled.

Observations reproduced on the previous 98220ba6 build

Reran the affected cases on that exact old binary, with separate state and the same local backend:

  • A config change waits for the active ACP prompt to settle/cancel. An overlapping prompt is refused.
  • session/delete leaves an archived entry visible in session/list.
  • A failed MCP-server startup is not announced to the ACP client.
  • The first available-command notification after newly trusting a project omits its newly enabled skill, although invoking the skill/template succeeds.

These observations also occur on 98220ba6; no fixes or decisions about their intended behavior were made here.

One initial long-output harness flag was a measurement error: it inspected the separately capped ACP display, not the tool result delivered to the model. Reading each build's persisted Pi session and saved output confirmed the correct 2,000-line tail and 3,000-line full file on both builds.

Not run in this retest

  • Hosted OAuth return-to-app: only Local credentials were available; no authorized Dev account was used.
  • System One: Maple has no live caller yet.
  • Buzz, as requested.
  • No fresh live external-agent, built-in CUA, voice, notification, proxy, exhausted-credit/revoked-login or Goose-profile migration run; earlier results are not presented as newly retested.
  • Live Linux/Windows execution; coverage there is hosted Agent CI.

The local backend was restored after the interruption tests. No application code was changed during validation.

@AnthonyRonning
AnthonyRonning marked this pull request as ready for review October 13, 2026 00:02
@AnthonyRonning
AnthonyRonning removed this pull request from stack #1125 October 13, 2026 00:04
now_ms returns a later time on every call, a millisecond ahead of the
clock if need be, so a message's timestamp tells it apart from every
other message the process made. Maple keys its timeline rows by it.

Pi has no equivalent: its messages take the plain clock. This is a
Maple-driven adaptation of the Pi crates, so it stays with the Maple
integration until the owner reviews it; Maple could key its rows
another way instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
The Goose runtime moves to crates/maple-agent-goose, as the package
maple-agent-goose, so the runtime on the Pi crates can take
crates/maple-agent. The code is unchanged and stays compiled for
side-by-side checks until it is deleted; the app keeps using it under
its old name until it switches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
The runtime crate is now built on pi-ai, pi-agent-core and
pi-coding-agent. The Goose runtime moves to crates/maple-agent-goose,
unchanged and still compiled, for side-by-side checks until it is
deleted.

What runs on Pi:
- Maple's provider: Pi's Chat Completions provider over OpenSecret's
  encrypted transport, with Maple's catalog as Pi models. Every failure
  becomes one fixed Maple message per category, worded so Pi's retry
  and context-overflow checks classify it. The transport times out a
  response that does not start or stalls, so Pi's own request timeout
  is off.
- Tasks: one JSONL session file per task in the account's owner-only
  folder, and a SQLite index of titles, state, web switch and settings
  that lists tasks while the runtime is stopped.
- Runs: one per task, through one session factory. Pi's events become
  Maple's timeline rows, with ids that stay the same from the live rows
  to the reloaded ones. Stop keeps the reply so far and adds "Stopped
  by user". Messages sent during a run wait as chips and follow as one
  turn; steering joins the running turn.
- Maple's read, edit and write tools as Pi tools.

The account's MCP server list is checked and saved as before: names,
commands, endpoints, environment variables and headers, and no new
server under the computer use integration's name. Reads and saves of
the account's settings take one lock, as they took Goose's runtime
lock, so a save cannot overwrite a change made since its read.

Features that have not moved yet answer from placeholders: running MCP
servers, integrations, external agents, skills and slash commands, side
models, image attachments and ACP.

The app imports the new crate. Its Goose wording for compaction notices
goes, since the Pi runtime never sends those strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Tasks now get Pi's read, bash, edit and write from the Pi session, or
powershell in place of bash on Windows without Git Bash. Maple's own
read, edit and write go; the timeline shows bash and powershell calls
as Terminal rows.

Maple sets up how their commands start, through bash's spawn hook:
- the login shell's PATH, so an app opened from the Dock or a desktop
  launcher finds the tools the user's terminal finds. It is read once
  per process, from when the runtime starts, behind a marker so profile
  banners cannot be mistaken for it, bounded in time and output, and
  everything the probe starts is stopped;
- the task's id as AGENT_SESSION_ID;
- the task's tool context, so the ACP bridge's variables stay out.

MAPLE_SHELL names the bash to run and replaces GOOSE_SHELL; the README
says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
claude and others added 27 commits October 13, 2026 00:05
Settings and the composer's integrations chip list Maple's curated
integrations again, as Goose's runtime did: built-in computer use (Cua),
and the Codex and Claude Code command lines.

- A card joins what Maple finds on the device with the choice saved for
  new tasks in the account's device-local integrations.json, version 2.
  A file from a newer build is reported and left alone, and a choice of
  the retired standalone CuaDriver is dropped.
- Codex and Claude Code are looked for on the login shell's PATH once it
  has been read, else on the app's own. Each version is read with a
  probe of three seconds and bounded output; a Codex older than 0.143.0
  needs an update. Sign-in is reported but does not gate the switch.
  A probe runs in its own process session, which is ended afterwards,
  and on the PATH it was found on, so a script finds its interpreter
  there (Goose ran the version probe on the app's PATH).
- An external agent can be switched on only while its CLI can run, and
  off at any time. Built-in CUA reads as not available until embedded
  CUA returns: it cannot be switched on, and its setup finishes nothing.
  Switching it on is refused while a custom MCP server has its name.
- The choices are read and saved under the settings lock; detection
  runs before it is taken.

What a choice does in a task moves with external agents and embedded
CUA, and so do the skills Goose's runtime installed for external agents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Desktop tasks get Maple's todo_write and request_user_input again, with
Goose's schemas, descriptions and results:

- todo_write records the plan the user watches. The interface draws the
  list from the call, and the model gets it back, confirmed.
- request_user_input asks one to three questions on a card and waits for
  the answers. A stopped run takes the card back, and an empty answer
  reads "(no answer provided)". As in Goose, a near miss does not
  dead-end the model: `questions` as one object, one question at the top
  level, missing or extra fields. Blank questions and options are
  dropped, ids made unique, and a card holds at most three questions of
  five options.

The questions go through the broker of the service that started the
runtime, which the interface answers, instead of the process-wide one
Goose's tool clients had to reach for: a service built later, or a test
running alongside, no longer takes another runtime's questions.

ACP tasks get neither, as before. Errors read as Pi's tools' do, without
Goose's "Error: " prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Tasks get Maple's web_search and open_url again, with Goose's schemas,
descriptions, limits and checks, through Maple's privacy-preserving web
provider:

- web_search returns links, titles and short snippets as JSON marked as
  untrusted evidence, dropping results from the end until it fits 64,000
  characters, so it stays valid JSON.
- open_url fetches one public HTTPS page as sanitized text, bounded to
  32,000 characters without cutting a code delimiter, which would turn
  inert image code back into an image. The URL must use HTTPS, carry no
  credentials and name a public host: loopback, private, link-local,
  documentation and internal names and addresses are refused, embedded
  IPv4 included.
- Errors are bounded to the same limits, and read as Pi's tools' do,
  without Goose's "Error: " prefix.

As in Goose, a task's web switch decides whether the tools are offered,
and a change takes effect from the task's next run. Desktop and ACP
tasks both have one.

New dependency: pulldown-cmark, already in the lockfile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
The small requests Goose's runtime made beside a task move to the Pi
runtime, with Goose's models, prompts, limits and timeouts:

- A new task's title: the first prompt still names the task at once,
  and llama3-3-70b is asked for a three-to-five-word title, which
  replaces it unless the title changed meanwhile, by the user or
  anything else. The answer is cleaned (reasoning blocks, quotes,
  control characters) and bounded like any title.
- One-line summaries of a finished tool call or thinking block for the
  activity feed, from the same model, at most 100 characters.
- /btw side questions: the task's model gets the task's context, then
  the side thread's earlier turns and the framed new question. The
  task's tools stay in the request, so the provider can reuse its
  cached prefix, but tool_choice is none. The answer streams as
  SideQuestion events and is not saved to the task.

Titles and side questions end when the runtime stops; a request that
times out is cancelled and drained, as Goose's were. The requests go
through the runtime's model registry, so they take the same transport
as the task's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Every task gets Maple's read_image again, with Goose's schema, limits
and messages:

- An image from a Maple attachment of the task, a local file (relative
  to the task's folder, or a file URL), or an http(s) URL. A URL must
  name a public host, carry no credentials, and answer within 30
  seconds without a redirect, which could send the fetch to a loopback
  or cloud-metadata address. Files must be regular files.
- At most 20MB, PNG, JPEG, GIF or WebP. An optional crop comes back as
  a PNG of the cropped rectangle. The result is a one-line summary and
  the image, which Pi then fits for the model as it fits every tool's
  images.
- A task whose model cannot see images gets the image described
  instead: the tool takes a required context, and gemma4-31b describes
  the image for it, with thinking turned off in the request, as Goose
  sent it. A failed description says what was loaded and why it
  failed.

Goose staged the image in a temporary file for its own image tool; the
port decodes the bytes it read directly. The description's token usage
is not recorded yet: that moves with the usage ledger.

New dependency: image, already in the lockfile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Tasks get Pi's resources: instruction files, skills, prompt templates,
and SYSTEM.md and APPEND_SYSTEM.md, as Pi loads them, from the
account's own folder and a project's .maple folder where Pi has
~/.pi/agent and .pi.

- Instruction files: AGENTS.md (or CLAUDE.md) from the project folder
  and every folder above it, the account's, and ~/.agents/AGENTS.md,
  which Maple's Goose runtime read too. They load in every project, as
  in Pi. Goose's .goosehints is not read.
- Skills: the account's, ~/.agents/skills, and in a trusted project
  .maple/skills and .agents/skills up to the repository root, as Pi
  reads them. The folders Goose's runtime read besides, ~/.claude/skills
  and ~/.config/agents/skills, and in a trusted project .claude/skills
  and .goose/skills, are Pi's extra skill paths, so they come after the
  user's own. The model sees each skill's name, description and file in
  the system prompt and reads the file with the read tool, as in Pi;
  Goose's load_skill tool is gone.
- Prompt templates: the account's prompts folder and a trusted
  project's .maple/prompts.
- Resources are read again at each run and a loaded session takes them
  through set_resources, so a skill or instruction file changed since
  shows from the next run, as Goose's skills did. Only the prompt
  sections that changed go out again.

The composer's / list holds the skills, then the prompt templates whose
names no skill has, with argument hints from a skill's frontmatter
(top level or metadata, as Maple's bundled skills give them). A
command expands into the skill's instructions as Pi's /skill:name does,
with the arguments after them, or into the filled template; names
match regardless of case. Resolving a command now takes the account, so
the app passes it.

Project trust covers everything a project adds besides its instruction
files. The trust prompt asks when trusting would add a skill or
template that does not load now, or a .maple/SYSTEM.md or
APPEND_SYSTEM.md, and names which (two new protected features). Before,
it looked only for .agents/skills/**/SKILL.md, and the / list offered
and ran an untrusted project's skills, which the model's load_skill
refused; now an untrusted project's skills are neither listed nor run.

The README describes where instructions, skills and templates come
from, and its file table drops the Goose rows: task sessions and their
index are in the account's sessions folder, and the account's skills
folder replaces the Goose one, where the delegation skills will go
when external agents return.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Maple always appends its opening instructions, and the host's appended
text takes the place of APPEND_SYSTEM.md, as Pi's own flag does. So a
project's APPEND_SYSTEM.md never reaches a task, and trusting the
project for it would add nothing: only its SYSTEM.md now counts for the
trust prompt. The README no longer lists APPEND_SYSTEM.md among the
account's files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Images attached in the composer reach the model again. They are stored
with the task as before: owner-only, PNG, JPEG or WebP, at most ten per
message and 10MB each.

- The model gets what the user typed, then each image's name and
  maple-attachment source, as Goose's runtime wrote them. A model
  without vision is told to look with read_image, which reads the
  task's attachments. A model with vision gets the images beside the
  text; unlike Goose's runtime, its text names their sources too, so
  read_image can crop one.
- A message sent during a run waits as a chip with its images, steering
  carries them, and a stopped run puts its unsent messages back with
  theirs. A message Pi had already taken for steering comes back with
  its images by source only.
- The timeline shows what the user typed with the images, live and
  after a reload, read back from the message itself, so no other record
  is kept. The interface draws them from the task's attachments.
- A task is named from its first message with text; images alone do not
  name it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
A task's MCP servers run on rmcp's client, by the conventions of Pi's
MCP support. Maple starts a stdio server itself, so it can stop it as
Pi does; a Streamable HTTP server is reached through reqwest.

Which servers a task has is saved with the task, by name: a new task
gets those switched on in Settings, or exactly the ones the composer
names, and the composer's menu switches them per task. A task runs the
servers' current settings, so an edit applies from its next run, and
no secret is copied into the task. A chosen server that is no longer
configured stays listed, switched on and unavailable, and can only be
switched off.

The servers start with the task's run, in the background, and the run
waits up to ten seconds for them before its first prompt, as Pi's first
prompt does; one that connects later joins from the next prompt. Their
tools reach the model as mcp__<server>__<tool>, with the server's
descriptions and schemas, and their results as text and images, long
text cut in the middle with the whole saved to a file. Tool list changes
are read again, and a dropped connection opens again on the next call.
As in Pi, a tool is never taken from the session: one the server
withdrew, or of a server switched off or stopped, stays, and calling it
fails with Pi's words until the server offers it again. The connected
servers' instructions join the system prompt. A server that fails to
connect is named in the run's notice with Maple's wording, once, and is
tried again at later runs without holding them up. Switching a server on
for a loaded task connects it at once and keeps it off with the reason
when it fails.

A stdio server runs in the task's folder with the login shell's PATH
and its environment variables, in its own process group, and stops by
its input closing, then SIGTERM, then SIGKILL to the group, which also
ends what it started; a failed start shows the end of its error
output. An HTTP server's URL and header values take $NAME and ${NAME}
from its environment variables, and redirects are not followed. Calls
time out by the server's timeout, which progress resets, and Stop
tells the server the call was cancelled.

The servers live with the task's loaded session and stop when it shuts
down; the four idle tasks used last keep theirs. MCP tool rows read as
"server: tool".

A fake Streamable HTTP server in the tests drives rmcp's client for
real: the handshake, calls, progress, cancellation, timeouts and tool
list changes, and three runs end to end, among them a withdrawn tool
and a server switched off, whose tools stay and fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
The composer's context ring read the latest row of Goose's usage
ledger in its sessions.db, which nothing writes now that Goose is
gone, so the ring stayed empty. It now asks the runtime, which counts
the task's context as Pi does: the last reply's reported usage, plus
an estimate of what came after it, which also covers a compaction.
A loaded task counts its live session; any other task, its session
file, which a test checks gives the same count. A task with nothing in
it yet has no count, as before. The limit still comes from the model
catalog or MAPLE_CONTEXT_LIMIT.

The app drops its read-only handle on Goose's sessions database. The
ledger had no other reader: side-model and image-description usage was
recorded there only for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
A desktop task can hand work to Codex or Claude Code again, with the
Goose runtime's agent_start, agent_send, agent_status, agent_cancel and
list_agent_providers tools. The Codex app-server client, the Claude Code
stream-json client, and the host that runs them (activity, approvals,
questions, Stop, process groups) are the Goose runtime's, with its Goose
types replaced:

- The tools are Pi tools every desktop task has. A run declares them
  only while the task may use a provider: one switched on in Settings
  and for the task. The task's choice is saved with the task, as
  `externalAgents` in its settings, and its MCP menu lists the agents
  switched on in Settings, as before.
- A turn's progress repaints the row of the tool call that started it.
  The tool finds that row from the reply Pi stored before running it.
- A background turn's result reaches the model as a custom message the
  user does not see. Maple keeps it out of sight in the task's queue
  behind a running turn, sends it as a turn of its own, or starts a run
  for it. A run stopped before it went leaves it in the task's history
  for the next message, as Goose wrote it to history on Stop.
- The turn's end is a session entry the transcript projects back onto
  the call's row, so a reopened task shows what the agent did, followed
  by a notice. A task reopened while a turn runs shows the row as it
  stands, and the row above the composer and its Stop button come back.
- Deleting a task, stopping the runtime or signing out ends its agents
  without a report.
- The /handoff, /committee and /advisor skills go into the account's
  skills folder, which Pi reads, while an agent is on in Settings.

One change from Goose: an agent whose command could not start no longer
keeps one of a task's four places.

The driver tests run the Goose runtime's fake Codex and Claude Code
CLIs. New end-to-end tests run them under the runtime with a scripted
model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Computer use runs in desktop tasks again, with the Cua Driver SDK in
Maple's process as before: the SDK dependency at the same pin returns
(Linux with portal input, and zbus for the GNOME helper check), with its
Nix hash and the vendored GNOME Shell helper, which is byte-identical at
the pin, so the helper's recorded revision is now the pin.

What the Goose runtime did, on Pi:

- Each run binds a task that has CUA on to its own account-and-task
  scoped CUA session anew, which renews its authorization, and registers
  the catalog's tools on the task's Pi session as cua-driver__<tool>,
  with the instructions in a prompt section. The lifecycle tools and
  every session argument stay hidden. The binding happens beside the MCP
  servers' start, and a failure is named in the same notice, as
  "Computer use (CUA)". The binding closes with the task's session,
  or when CUA is switched off for it; the tools stay, as in Pi a tool
  is never taken from a session, and calling one fails until the task
  is bound again.
- Every model gets the observation's structured grounding as bounded,
  image-free text; a model without vision, read from the run's model,
  gets each screenshot described by the side model in its place.
- A task's choice is saved with it, as computerUse in its settings. A
  new desktop task records one once CUA is set up on the device: on when
  the composer names it, or else the default for new tasks. The MCP menu
  ends with CUA's row, and switching it on binds a loaded task at once.
- The Integrations card reads the device's permissions, the GNOME
  helper's state and the SDK version; setup asks for the permissions on
  the click's thread and installs the helper where it is missing.

Pi fits a screenshot like every tool image (2000 pixels, 4.5MB), with
its note on mapping coordinates back; Goose sent screenshots as
captured. Helper usage is not recorded.

The unit tests are Goose's, on the new types; the SDK's own runtime runs
the lifecycle test. An end-to-end test covers the switch, the row and
new tasks' choice, and, where CUA can run, a real binding's tools, which
fail once CUA is switched off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
`maple-agent acp` serves the Agent Client Protocol again, now on the Pi
runtime: the agent-client-protocol dependency returns at the same pin,
and the server's transport, handler, conversions and session state are
ported from the Goose runtime's, with its protocol surface unchanged
(protocol 2, model selector only, no modes, list, load, close, delete,
/compact and the skills' commands, images, usage, Buzz's systemPrompt
and session title).

The runtime gains what a calling surface needs, in agent::surface:

- A lease holds each task a connection drives. While it lasts, the
  task's session is built with the lease's tool context (the bridge's
  allowlisted variables), the desktop's own tools stay out of it (the
  plan, questions, external agents and computer use), and the client's
  HTTP MCP servers run beside the task's, in place of a saved one of the
  same name. The desktop cannot send to a held task. Releasing revokes
  the context, stops the task's run and unloads its session.
- `send_surface_message` runs a prompt as a run of its own, after any
  run the surface stopped has ended, with events for the caller alone;
  the prompt's cancellation stops the run from its setup on.
- A task created over ACP is provisional until its first prompt: lists
  leave it out, and closing it untouched deletes it. A runtime start
  removes provisional tasks a crash left behind for more than ten
  minutes.
- The caller's system prompt follows Maple's own for the tasks it
  creates, for as long as the runtime runs.
- Trust asked over ACP is saved for the session's project, and the
  task's session is built again with it.

How a turn reaches the client on Pi: text and thinking stream as they
come, and the row that settles a message sends only what did not stream;
a tool call is announced with its kind, which result updates keep. A
failed reply is told when the run ends failed, since Pi may still retry
it. Runs now publish `Compacting` and `Compacted`, which the client sees
as the reference adapters' notices; the desktop ignores them and reloads
on `HistoryReplaced` as before. `usage_update` counts the task's context
as Pi does rather than the turn's tokens. `/compact` on a history too
short to summarize says so instead of failing.

The dispatcher no longer waits on slow requests: prompts are admitted in
order, so a `session/cancel` right behind one finds it, and everything
else about them, close, load and config changes run in the background.

The ported unit tests run unchanged where the shapes did; stdio tests on
the scripted provider cover a full turn and its replay, cancel, a failed
turn, /compact, images through read_image, the trust prompt, and Buzz's
fields; runtime tests cover leases, provisional tasks, the bridge
context reaching a command, a client's MCP server, and the startup
sweep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Cargo resolved the packages that built-in CUA and ACP brought back at
the latest compatible versions, so twelve of them came back newer than
the lockfile Maple shipped with: async-compat, camino,
crossbeam-channel, ctutils, hybrid-array, lru, textwrap, wl-clipboard-rs
and zstd-safe for the Cua Driver SDK, and jiff-tzdb and serde_with (with
its macros and darling) for agent-client-protocol. They are pinned back,
so every package those two commits added is one the shipped lockfile
had, at its version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
With every feature on the Pi runtime, the crate's temporary
`allow(dead_code)` goes, with what the Goose runtime left unused: the
transient MCP server types ACP's servers no longer need, a usage delta,
a permissions helper for Goose's config file, three tool context methods
(the launch fence's test now covers revocation), and the tool context's
ephemeral flag, which nothing reads. Renaming a task refuses a
signed-out session again, as it did before the port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Three rows named Goose-era tests that are gone: the queued follow-up now
names the Pi runtime's test, and the old computer-use row checks
settings an older build saved, as its tests do. The row for an old
task's `delegate` history checks instead that the app starts on an
account with Goose-era tasks, which the Pi runtime does not read. The
tools row says `bash`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Pi's `read` opens images, and gives a model that cannot see them Pi's
note that the image is left out. Goose's runtime answered such a read
with the way to a description: the file is an image, to inspect with
`read_image`. A Pi extension of each task's session answers so again,
through Pi's `tool_result` rewrite, while the session's model cannot see
images; a model with vision still gets the image, as in Pi. The prompt
of a model without vision carries Goose's line again: "Use read_image
when you need to inspect an image."

`read_image` was set up for the model its session was built with, and a
task loaded for a side question or `/compact` is built without vision,
so a run on a model with vision could get descriptions in place of
images, and the reverse images it cannot see. The same extension sets it
up again on Pi's `model_select` when the model's vision changes. It now
resolves a local path as `read` does, macOS screenshot names included,
so the path `read` answers with opens.

Images in MCP servers' results still reach a model without vision as the
provider's note that it does not accept images.

Both run end to end on the scripted model: `read` on an image for a
model without and with vision, and `read_image`'s schema for runs that
can and cannot see after a load without vision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Windows builds the runtime without built-in CUA, so the screenshot
descriptions only CUA's driver asks for, and the fake agents' search
path only the Unix end-to-end tests set, went unused there once the
crate's `allow(dead_code)` went. They build where their callers do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
A run's model is built from what its host sends: the model's vision and
context window. Neither host sent the window, so every run compacted at
the 128,000-token default, whatever the catalog gives the model. ACP
also looked up vision only for a prompt with images, as it did under
Goose, so a vision model ran as one without vision on every other turn:
`read` sent it to `read_image`, which described images it could see.

The runtime now answers one catalog lookup with both,
`model_catalog_entry`. ACP looks it up on a session's first turn and
keeps it until the model changes, and sends both with every turn. Its
`usage_update` reports the same window. The desktop keeps each model's
entry where it kept vision and sends the window with each message. The
context ring already showed the catalog's window; compaction now agrees
with it.

A model the catalog does not describe, or a catalog that cannot be read,
still runs without vision and on the default window. ACP asks again on
the next turn.

The ACP server runs a turn without images on a catalog that marks the
model with vision and a 64,000-token window. The run's model sees images
and has that window, `read_image` needs no context, and `usage_update`
reports 64,000. The runtime's lookup gives a listed model's entry, an
empty one for a model the catalog does not list, and none while the
catalog cannot be read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
The app passed the runtime's events to the chat only while the chat was
on screen. Settings parks the chat, so a reply that went on streaming,
or ended, while Settings was open lost its last rows and its end: back
from Settings, the chat showed the partial reply with its run still
going until another task was selected and this one again. Master's app
does the same.

The parked chat now gets the events too, as it already gets the update
banner. It only updates its own state from them.

Found in live validation. The window code has no test harness; Maple
Agent CI builds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
The scenario checklist records the live run of 2026-10-09 on 680ca68:
what passed, what was not run, and what it found, with the ACP findings
deferred to a later review of the ACP layer.

The README says what MAPLE_CONTEXT_LIMIT does: it replaces the
catalog's window for compaction and the context ring, rather than
filling in a missing one. Its ACP section asks for a task to be used in
one place at a time, since the desktop app and `maple-agent acp` are
separate processes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Check that ordered steering survives session setup and an early stop.
Keep the first prompt and accepted steers in their original order.

Co-Authored-By: Codex <noreply@openai.com>
Codex-Session: 01a1192f-4f37-7821-af8a-76570a6f5f1f
Map catalog reasoning controls to Pi and clamp its default per model.
Persist task picks through Pi and save the choice for new tasks.
Keep side requests independent of the composer selection.

Co-Authored-By: Codex <noreply@openai.com>
Codex-Session: 01a1192f-4f37-7821-af8a-76570a6f5f1f
Maple's model registry now serves Pi's System One classifier as well
as its chat models: the classifier API over the account's encrypted
transport, and System One's model (glm-5-3-flash, the only one
Maple's API serves), which judges text and images. Any Maple code or
extension with the registry can classify with it. As Pi's classifier
requests do, a request the server fails or asks to wait is sent up to
two more times.

The transport sends Pi's System One requests to /v1/systemone, where
it sent only chat completions before, and asks for a JSON reply
there. A failed reply keeps Maple's fixed message, as for chat, so
System One's error carries no part of the server's body.

Responses now carry their headers, names in lowercase, as Pi's
transports give them: Pi's response hook sees them, and a classifier
retry follows the server's retry-after or x-should-retry. Chat
requests still send nothing again themselves, so headers change no
retry there.

Nothing in Maple classifies yet. A result carries its own usage, for
the feature that asks to report: the server counts what Maple bills,
and a run's usage counts its replies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Buzz is out of scope, so the ACP server keeps what any editor uses and
loses what only Buzz's bridge needed:

- `_maple/bridge/hello`, and the bridge's variables (Buzz's credentials)
  read from the process or from a `buzz-dev-mcp` server definition and
  handed to a session's commands. A client's stdio MCP server is now
  always refused.
- The tool context those variables travelled in, with its scrubbing of
  `BUZZ_*` from every task's commands and external agents, its
  revocation and its launch fence. A lease still ends its hold at once
  when it is released, and an ACP task's commands keep the PATH the
  client started Maple with, as they did through the context.
- `session/new`'s `systemPrompt` and `_meta.sessionTitle`, and the
  runtime's per-task system prompts. ACP tasks are named from their
  first prompt, as desktop tasks are.
- Protocol version 2, which told Buzz that `session/new` takes its
  `systemPrompt`: `initialize` answers with the latest stable version,
  1.
- The count of connections that carried Buzz's credentials.

The README's ACP section, the external agents doc and the scenario
checklist drop Buzz, and two errors no longer name a tool context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
Maple Agent's ACP has no users yet, so its configuration needs no
migration: a missing file means the defaults, and Maple no longer looks
for one in the proof of concept's account folder to copy over. Copying
it was all that wrote the file, so saving goes too: Maple only reads
it. The note and test for fields that older builds saved go, and a test
of loading the file replaces the one of saving it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STEaSNTNUiiNNLXcKidE2d
@AnthonyRonning
AnthonyRonning changed the base branch from claude/kind-turing-gzv8nl to master October 13, 2026 00:05
@AnthonyRonning
AnthonyRonning merged commit fe52d9e into master Oct 13, 2026
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