-
Notifications
You must be signed in to change notification settings - Fork 262
feat(acp): serve durable coding sessions over ACP #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
0a606cd
feat(mcp): add async stdio tool factory
scald 600cd9b
feat: add ACP coding agent package
scald 14714cc
fix: update ACP default model
scald e2f5bf4
fix: use NVIDIA model by default
scald c5674c3
feat(cli): add shared coding agent host layer
furgalep 609841c
feat(acp): host shared durable coding sessions
furgalep 35fd067
fix(cli): preserve readable bounded file diffs
furgalep cbb35fa
refactor(acp): keep live runtime ownership private
furgalep f032d10
fix(acp): import sessions from cli package
furgalep dac19b5
chore(release): build and publish nooa-acp
furgalep fd90769
docs(acp): document the async factory, drop the default model, add Ze…
furgalep f7ecb5b
test(acp): cover the console script and stop timeouts from flaking
furgalep bab4848
fix(acp): make session/close reachable and survivable
furgalep b87a1e9
fix(acp): let a session recover from a failed update
furgalep 69645cb
fix(acp): scope libs per workspace and report cancellation as cancell…
furgalep c1d4526
fix(acp): render Python source in tool calls
furgalep f782f4a
fix(acp): render Python source as markdown
furgalep 9596f27
feat(acp): expose workspace skill commands
furgalep 74279db
fix(acp): accept forwarded remote MCP servers
furgalep ddf5eda
fix(acp): tolerate unavailable MCP servers
furgalep 43ae557
test: isolate the coding-settings suite and mark deferred behaviour
furgalep 03577ee
fix(acp): stop skills and MCP servers displacing the agent's own tools
furgalep d627562
fix(acp): say so when a turn is cancelled
furgalep 5192572
fix(acp): title a cancelled tool card "Cancelled"
furgalep c7871fb
fix(acp): give each replayed turn its own boundary
furgalep e5456b7
fix(acp): advertise the MCP transports the adapter actually connects
furgalep 511ac33
docs(acp): state that opening a repository runs code from it
furgalep 7f9e13c
fix(acp): close cards, keep stop reasons, and order replay
furgalep 2e9e00d
fix: repair the event pump, diff generation, settings fallback and re…
furgalep 0b7179f
fix: close the remaining CodeRabbit findings
furgalep 54fc2b5
docs: list ACP among the optional sub-packages
furgalep 23914b4
fix(coding): bound the instruction read itself, not just what is kept
furgalep d21bd3b
docs: invite people to try NOOA in Zed
furgalep 12e7290
docs: stop implying nooa-acp is usable from a terminal
furgalep 325c6c6
test(mcp): cover the tool_call_timeout carried across an OAuth refresh
furgalep 965c875
docs(acp): move ACP docs out of the root README into the package
furgalep b69bd81
fix: close the protection hole and the test that could not fail
furgalep d3de552
test(acp): prove the bridge is live before asserting an absence
furgalep e69c11c
test: harden the suite against tests that cannot fail
furgalep da4d892
test: fail on a hang instead of stalling the run
furgalep 24c5855
fix(acp): close remaining review gaps
furgalep File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| # nooa-acp | ||
|
|
||
| **Run the NOOA coding agent inside your editor.** `nooa-acp` is an | ||
| [Agent Client Protocol](https://agentclientprotocol.com) server, so any | ||
| ACP-speaking client — Zed today — can drive the same agent the terminal host | ||
| uses: CodeAct, repository tools, a persistent shell, installed skills, workspace | ||
| slash commands and durable sessions, with file edits and terminal commands | ||
| surfaced as structured activity. | ||
|
|
||
| It hosts `nooa_cli.coding.CodingAgent` directly. Repository instructions | ||
| (`AGENTS.md`), coding tools, summarization, installed `nooa.skills` entry points | ||
| and semantic file and terminal activity therefore have no separate ACP | ||
| implementations — fix something here and the terminal host gets it too. | ||
|
|
||
| This is new and we would like it exercised. If something breaks, please say so. | ||
|
|
||
| ## Install | ||
|
|
||
| ```bash | ||
| uv add nooa-acp # or: uv add "nooa[acp]" | ||
| ``` | ||
|
|
||
| There is no default model. Set `NOOA_MODEL` or pass `--model`, or the command | ||
| exits with a usage error. | ||
|
|
||
| ## Quick start: Zed | ||
|
|
||
| Zed launches ACP agents as "external agents". Add NOOA to `settings.json` | ||
| (`cmd-,`): | ||
|
|
||
| ```json | ||
| { | ||
| "agent_servers": { | ||
| "NOOA": { | ||
| "type": "custom", | ||
| "command": "uvx", | ||
| "args": ["nooa-acp"], | ||
| "env": { | ||
| "NOOA_MODEL": "nvidia_nim/nvidia/nemotron-3-super-120b-a12b", | ||
| "NVIDIA_API_KEY": "nvapi-..." | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Open a repository, then pick **NOOA** from the `+` menu in the agent panel. Zed | ||
| runs the command with your worktree as its working directory, so repository | ||
| instructions, project skills and sessions resolve against the open project. | ||
|
|
||
| Credentials go in `env` here rather than in Zed's own settings: the agent is a | ||
| separate process and inherits only what Zed passes it. Use a secret-manager | ||
| wrapper as the `command` if you would rather not put a key in `settings.json`. | ||
|
|
||
| From a checkout of this repository, point the client at the workspace package | ||
| instead: | ||
|
|
||
| ```bash | ||
| uv run --project "$PWD" --package nooa-acp -- nooa-acp | ||
| ``` | ||
|
|
||
| ### MCP servers do not carry over from Zed | ||
|
|
||
| **Remote MCP servers you authenticated inside Zed are not usable from an ACP | ||
| agent.** Zed holds those OAuth tokens itself and does not pass them down, so a | ||
| server showing a green indicator in Zed's own UI arrives at the agent either | ||
| with no tools at all or with nothing but its `authenticate` / | ||
| `__complete_authentication` stubs. Local stdio MCP servers are unaffected. | ||
|
|
||
| This is a known Zed limitation, tracked in | ||
| [zed-industries/zed#54410](https://github.com/zed-industries/zed/issues/54410) | ||
| (open, labelled `area:ai/mcp` + `area:ai/acp`). A maintainer has said the | ||
| plumbing largely exists and the work is queued, but as of this writing it is | ||
| unresolved. | ||
|
|
||
| Configure the MCP server directly for NOOA instead — through NOOA's own | ||
| `.mcp.json` — and it works normally, because the agent then owns the | ||
| connection and its credentials rather than borrowing Zed's. | ||
|
|
||
| ## Launching the server yourself | ||
|
|
||
| ```bash | ||
| nooa-acp --model nvidia_nim/nvidia/nemotron-3-super-120b-a12b | ||
| ``` | ||
|
|
||
| This is a JSON-RPC server, not an interactive program: it speaks ACP on | ||
| stdin/stdout and exits when its input closes, so running it in a terminal | ||
| without a client does nothing. Launch it this way to wire up an ACP client | ||
| other than Zed, or to watch the diagnostics it writes to stderr while a client | ||
| drives it. `--model` accepts any LiteLLM model name or configured NOOA alias. | ||
|
|
||
| ## Opening a repository runs code from it | ||
|
|
||
| **Creating a session imports Python from the workspace, before you send a | ||
| prompt.** This is deliberate — it is how workspace skills work — but it means | ||
| opening a folder is enough to execute code it contains. Treat opening a | ||
| repository with NOOA as equivalent to running its build. | ||
|
|
||
| Three paths load workspace code at `session/new` and `session/load`: | ||
|
|
||
| - **Skill roots.** Every `.py` file under `.agents/skills`, `.cursor/skills`, | ||
| `.claude/skills`, or `.claude/commands` is imported. Module-level code runs | ||
| during import, before anything checks whether the file defines a skill, so the | ||
| contents are irrelevant. | ||
| - **Workspace settings.** `<workspace>/.nooa/settings.yaml` and the legacy | ||
| `.nooa/config.toml` may name *additional* skill roots. Those paths are not | ||
| confined to the workspace: a relative path escaping it, an absolute path, or a | ||
| symlink is accepted as written. | ||
| - **Libraries.** `<workspace>/.nooa/libs/<package>/` is imported and its | ||
| directory is prepended to `sys.path` for the life of the process. One ACP | ||
| server serves several workspaces, so a package name there can shadow the same | ||
| import for later sessions on other workspaces. | ||
|
|
||
| The agent runs as you, in a process holding your model credentials. There is no | ||
| consent prompt on these paths. | ||
|
|
||
| **Open repositories you would run.** For anything else, use an OS-level sandbox, | ||
| or start a separate server per workspace with credentials scoped to that task. | ||
|
|
||
| ## How it behaves | ||
|
|
||
| ACP uses standard input and output for JSON-RPC. Diagnostics are written to | ||
| standard error. The agent can execute generated Python and shell commands, so | ||
| use an OS-level sandbox for untrusted tasks. Generated code shares the agent's | ||
| process environment, including model credentials; launch it with only the | ||
| credentials and network access that the session may use. | ||
| Cancellation stops cooperative local work immediately. An in-flight provider | ||
| request may finish in the background when its client does not support | ||
| transport-level aborts. Slash commands run on the agent's event loop so they | ||
| have the same semantics as the native TUI and can safely start agent jobs. An | ||
| async command is cooperatively cancellable; a synchronous command that blocks | ||
| that loop cannot be preempted by the current in-process adapter. The planned | ||
| one-process-per-agent boundary is the safe kill mechanism for that case. | ||
|
|
||
| ## Sessions and skills | ||
|
|
||
| Each ACP session has an independent live agent and allows one foreground prompt | ||
| at a time. Sessions are stored in `<workspace>/.nooa/sessions`, where the TUI | ||
| and ACP adapter can share list and replay metadata. These files are inside the | ||
| workspace trust boundary: a repository can supply session records that appear | ||
| in `session/list` and are replayed as conversation history by `session/load`. | ||
| Open only repositories whose code and conversation history you trust. The | ||
| adapter also advertises session close; closing a live session preserves its | ||
| durable history. | ||
|
|
||
| The current stdio adapter hosts those live agents in its own process. That is | ||
| an adapter-private implementation detail rather than part of the durable | ||
| session API: the live-session registry is isolated inside `nooa-acp` so it can | ||
| later be replaced by handles to an agent daemon without changing stored | ||
| sessions, the shared coding agent, or the ACP protocol surface. | ||
|
|
||
| Python skill packages use the interpreter's normal import machinery. Multiple | ||
| sessions may use distinct skill package names, but two workspaces must not load | ||
| different checkouts under the same top-level Python package name in one ACP | ||
| server process. Launch a separate stdio server for those workspaces. A future | ||
| one-process-per-agent daemon will make that isolation an OS process boundary. | ||
|
|
||
| Installed `nooa.skills` entry points are loaded into the shared skill registry | ||
| but remain opt-in. The agent can activate a relevant skill with | ||
| `self.skills.activate(["name"])`. Stdio MCP servers supplied by an ACP client | ||
| are registered and activated as `mcp.<name>` skills for that session. | ||
|
|
||
| Workspace and user skill roots are shared with the terminal host through | ||
| layered `settings.yaml`. New configuration should use: | ||
|
|
||
| ```yaml | ||
| coding: | ||
| additional_skills_dirs: | ||
| - ../nemo-oo-skills | ||
| ``` | ||
|
|
||
| The existing `tui.additional_skills_dirs` key remains supported during the | ||
| migration, as does the older project-local `.nooa/config.toml` key | ||
| `[tui].libs_dirs`. Packaged libraries declared through `nooa.skills`, `SKILL.md` | ||
| skills, and standalone Python skills are discovered from each configured root. | ||
| Loaded `@slash_command` methods are advertised through ACP and matching | ||
| `/command arguments` prompts are dispatched through the shared typed command | ||
| router. Command discovery is refreshed when loaded skills change. | ||
|
|
||
| The current adapter accepts text and resource-link prompts plus stdio, HTTP, | ||
| and SSE MCP servers forwarded by an ACP client. ACP-transport MCP proxies, | ||
| additional workspace directories, images, and embedded resources are not | ||
| advertised yet. An unavailable, duplicate, or unsupported MCP server is skipped | ||
| with a session warning so it cannot prevent a new or restored NOOA session from | ||
| opening. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought: if not done already you also need to register the new package in pypi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed — this still needs external release setup.
nooa-acpcurrently returns 404 on both PyPI and TestPyPI, and the repository has nopypi-nooa-acpenvironment yet. The source side is wired andRELEASING.mdnames the required Trusted Publisher identities, but a PyPI owner must registernooa-acpforNVIDIA-NeMo/labs-OO-Agents, workflowpublish.yml, environmentpypi-nooa-acp(and the analogous TestPyPI publisher for rehearsals) before release. I also fixed the remaining stale “four packages” wording in24c5855.