Skip to content
Merged
Show file tree
Hide file tree
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 Aug 2, 2026
600cd9b
feat: add ACP coding agent package
scald Aug 2, 2026
14714cc
fix: update ACP default model
scald Aug 2, 2026
e2f5bf4
fix: use NVIDIA model by default
scald Aug 2, 2026
c5674c3
feat(cli): add shared coding agent host layer
furgalep Aug 6, 2026
609841c
feat(acp): host shared durable coding sessions
furgalep Aug 6, 2026
35fd067
fix(cli): preserve readable bounded file diffs
furgalep Aug 7, 2026
cbb35fa
refactor(acp): keep live runtime ownership private
furgalep Aug 10, 2026
f032d10
fix(acp): import sessions from cli package
furgalep Aug 12, 2026
dac19b5
chore(release): build and publish nooa-acp
furgalep Aug 14, 2026
fd90769
docs(acp): document the async factory, drop the default model, add Ze…
furgalep Aug 14, 2026
f7ecb5b
test(acp): cover the console script and stop timeouts from flaking
furgalep Aug 14, 2026
bab4848
fix(acp): make session/close reachable and survivable
furgalep Aug 14, 2026
b87a1e9
fix(acp): let a session recover from a failed update
furgalep Aug 14, 2026
69645cb
fix(acp): scope libs per workspace and report cancellation as cancell…
furgalep Aug 14, 2026
c1d4526
fix(acp): render Python source in tool calls
furgalep Aug 12, 2026
f782f4a
fix(acp): render Python source as markdown
furgalep Aug 12, 2026
9596f27
feat(acp): expose workspace skill commands
furgalep Aug 12, 2026
74279db
fix(acp): accept forwarded remote MCP servers
furgalep Aug 12, 2026
ddf5eda
fix(acp): tolerate unavailable MCP servers
furgalep Aug 12, 2026
43ae557
test: isolate the coding-settings suite and mark deferred behaviour
furgalep Aug 16, 2026
03577ee
fix(acp): stop skills and MCP servers displacing the agent's own tools
furgalep Aug 16, 2026
d627562
fix(acp): say so when a turn is cancelled
furgalep Aug 17, 2026
5192572
fix(acp): title a cancelled tool card "Cancelled"
furgalep Aug 17, 2026
c7871fb
fix(acp): give each replayed turn its own boundary
furgalep Aug 19, 2026
e5456b7
fix(acp): advertise the MCP transports the adapter actually connects
furgalep Aug 19, 2026
511ac33
docs(acp): state that opening a repository runs code from it
furgalep Aug 19, 2026
7f9e13c
fix(acp): close cards, keep stop reasons, and order replay
furgalep Aug 19, 2026
2e9e00d
fix: repair the event pump, diff generation, settings fallback and re…
furgalep Aug 19, 2026
0b7179f
fix: close the remaining CodeRabbit findings
furgalep Aug 19, 2026
54fc2b5
docs: list ACP among the optional sub-packages
furgalep Aug 19, 2026
23914b4
fix(coding): bound the instruction read itself, not just what is kept
furgalep Aug 19, 2026
d21bd3b
docs: invite people to try NOOA in Zed
furgalep Aug 19, 2026
12e7290
docs: stop implying nooa-acp is usable from a terminal
furgalep Aug 19, 2026
325c6c6
test(mcp): cover the tool_call_timeout carried across an OAuth refresh
furgalep Aug 19, 2026
965c875
docs(acp): move ACP docs out of the root README into the package
furgalep Aug 19, 2026
b69bd81
fix: close the protection hole and the test that could not fail
furgalep Aug 20, 2026
d3de552
test(acp): prove the bridge is live before asserting an absence
furgalep Aug 20, 2026
e69c11c
test: harden the suite against tests that cannot fail
furgalep Aug 20, 2026
da4d892
test: fail on a hang instead of stalling the run
furgalep Aug 20, 2026
24c5855
fix(acp): close remaining review gaps
furgalep Aug 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- name: Build all packages
run: |
rm -rf dist
for pkg in nooa nooa-cli nooa-memory nooa-bench; do
for pkg in nooa nooa-cli nooa-acp nooa-memory nooa-bench; do
uv build --no-sources --package "$pkg" --out-dir dist
done
- name: Upload wheels
Expand Down
21 changes: 12 additions & 9 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Publishes the four workspace packages to PyPI.
# Publishes the five workspace packages to PyPI.
#
# Trigger: a GitHub Release is *published* (the release's tag `vX.Y.Z` is what
# uv-dynamic-versioning turns into the package version — see RELEASING.md).
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Build all packages
run: |
rm -rf dist
for pkg in nooa nooa-cli nooa-memory nooa-bench; do
for pkg in nooa nooa-cli nooa-acp nooa-memory nooa-bench; do

Copy link
Copy Markdown
Collaborator

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.

Copy link
Copy Markdown
Collaborator Author

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-acp currently returns 404 on both PyPI and TestPyPI, and the repository has no pypi-nooa-acp environment yet. The source side is wired and RELEASING.md names the required Trusted Publisher identities, but a PyPI owner must register nooa-acp for NVIDIA-NeMo/labs-OO-Agents, workflow publish.yml, environment pypi-nooa-acp (and the analogous TestPyPI publisher for rehearsals) before release. I also fixed the remaining stale “four packages” wording in 24c5855.

uv build --no-sources --package "$pkg" --out-dir dist
done
ls -l dist
Expand All @@ -71,24 +71,27 @@ jobs:

expected = Version(os.environ["TAG"].removeprefix("v"))
wheels = sorted(pathlib.Path("dist").glob("*.whl"))
assert len(wheels) == 4, f"expected 4 wheels, got {[w.name for w in wheels]}"
assert len(wheels) == 5, f"expected 5 wheels, got {[w.name for w in wheels]}"
for whl in wheels:
_, version, _, _ = parse_wheel_filename(whl.name)
if version.is_devrelease:
sys.exit(f"{whl.name}: dev version — the tag is not reachable from HEAD")
if version != expected:
sys.exit(f"{whl.name}: built {version}, but the tag says {expected}")
print(f"OK — all four packages built as {expected}")
print(f"OK — all five packages built as {expected}")
PY

# Catches a broken wheel before it is on PyPI forever.
- name: Smoke-test the wheels in a clean environment
run: |
uv venv /tmp/smoke --python 3.12
VIRTUAL_ENV=/tmp/smoke uv pip install \
dist/nooa-*.whl dist/nooa_cli-*.whl dist/nooa_memory-*.whl dist/nooa_bench-*.whl
/tmp/smoke/bin/python -c "import nooa, nooa_cli, nooa_memory, nooa_bench; print(nooa.__version__)"
dist/nooa-*.whl dist/nooa_cli-*.whl dist/nooa_acp-*.whl \
dist/nooa_memory-*.whl dist/nooa_bench-*.whl
/tmp/smoke/bin/python -c "import nooa, nooa_cli, nooa_acp, nooa_memory, nooa_bench; print(nooa.__version__)"
/tmp/smoke/bin/nooa --version
/tmp/smoke/bin/nooa --help
/tmp/smoke/bin/nooa-acp --help

- uses: actions/upload-artifact@v4
with:
Expand All @@ -98,7 +101,7 @@ jobs:
# One job per package, each in its OWN environment (`pypi-<package>`).
#
# PyPI keys a *pending* trusted publisher on
# (owner, repo, workflow filename, environment). Four packages sharing one
# (owner, repo, workflow filename, environment). Five packages sharing one
# environment collide: PyPI rejects the 2nd registration with "a pending
# trusted publisher matching this configuration has already been registered
# for a different project name", because it cannot tell which project to
Expand All @@ -112,7 +115,7 @@ jobs:
strategy:
fail-fast: false # a partial publish is recoverable; a cancelled one is messier
matrix:
package: [nooa, nooa-cli, nooa-memory, nooa-bench]
package: [nooa, nooa-cli, nooa-acp, nooa-memory, nooa-bench]
environment:
name: testpypi-${{ matrix.package }}
url: https://test.pypi.org/p/${{ matrix.package }}
Expand Down Expand Up @@ -158,7 +161,7 @@ jobs:
strategy:
fail-fast: false
matrix:
package: [nooa, nooa-cli, nooa-memory, nooa-bench]
package: [nooa, nooa-cli, nooa-acp, nooa-memory, nooa-bench]
environment:
name: pypi-${{ matrix.package }}
url: https://pypi.org/p/${{ matrix.package }}
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,16 @@ uv add nooa
Or with pip: `pip install nooa`.

<details>
<summary><b>Optional sub-packages</b> — CLI, memory, benchmarks, evaluation pipeline</summary>
<summary><b>Optional sub-packages</b> — CLI, ACP, memory, benchmarks, evaluation pipeline</summary>

<br />

The CLI, memory, and benchmark packages are separate distributions. Install
The CLI, ACP, memory, and benchmark packages are separate distributions. Install
them by name, or pull them in as extras of the core package:

```bash
uv add nooa-cli # or: uv add "nooa[cli]"
uv add nooa-acp # or: uv add "nooa[acp]"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
uv add nooa-memory # or: uv add "nooa[memory]"
uv add nooa-bench # or: uv add "nooa[bench]"

Expand All @@ -98,6 +99,7 @@ uv add "nooa[cli,memory]" # several at once
| Package | Extra | What it adds |
|---|---|---|
| `nooa-cli` | `nooa[cli]` | the `nooa` command, trace viewer, eval runner |
| `nooa-acp` | `nooa[acp]` | coding agent for Agent Client Protocol hosts such as Zed — [setup](packages/nooa-acp/README.md) |
| `nooa-memory` | `nooa[memory]` | long-term memory subsystem (`MemoryManager`) |
| `nooa-bench` | `nooa[bench]` | `BenchAgent` and the Harbor benchmark runner |

Expand Down
9 changes: 5 additions & 4 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Releasing

`nooa`, `nooa-cli`, `nooa-memory`, and `nooa-bench` release together from one
`nooa`, `nooa-cli`, `nooa-acp`, `nooa-memory`, and `nooa-bench` release together from one
commit. The version comes from the Git tag: on `v0.0.10` the distributions are
`0.0.10`; between tags they are development versions.

Expand All @@ -23,7 +23,7 @@ The strict gate performs:

1. Ruff lint and formatting, SPDX checks, unit tests, and explicit OS sandbox
containment tests.
2. Builds all four wheels and source distributions under a temporary local tag,
2. Builds all five wheels and source distributions under a temporary local tag,
verifies their versions, and smoke-tests imports and `nooa --version` in a
clean environment.
3. Runs the full capability suite for the candidate and previous release, fresh
Expand Down Expand Up @@ -57,7 +57,7 @@ release or uploads to PyPI.

Publishing is the single human approval. `.github/workflows/publish.yml` listens
for `release: published` and automatically rebuilds, smoke-tests, and uploads
all four packages to PyPI using Trusted Publishing. Despite their names, the
all five packages to PyPI using Trusted Publishing. Despite their names, the
current `pypi-*` GitHub Environments have no configured reviewer protection, so
there is no second approval after **Publish release**.

Expand Down Expand Up @@ -121,7 +121,8 @@ GitHub draft UI.

Each project needs a publisher configured for owner `NVIDIA-NeMo`, repository
`labs-OO-Agents`, workflow `publish.yml`, and its distinct environment:
`pypi-nooa`, `pypi-nooa-cli`, `pypi-nooa-memory`, or `pypi-nooa-bench`. Repeat
`pypi-nooa`, `pypi-nooa-cli`, `pypi-nooa-acp`, `pypi-nooa-memory`, or
`pypi-nooa-bench`. Repeat
with `testpypi-*` environments on TestPyPI.

Every `uses:` entry in `publish.yml` must remain compatible with the NVIDIA
Expand Down
29 changes: 29 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,35 @@ Source/Homepage: https://github.com/yaml/pyyaml

---

## `nooa-acp`

agent-client-protocol - Apache License 2.0
Attribution Statements: NVIDIA includes `agent-client-protocol` under the Apache License 2.0 for use with NVIDIA OO Agents.
License Text(https://spdx.org/licenses/Apache-2.0.html)
Source/Homepage: https://github.com/agentclientprotocol/python-sdk

click - BSD 3-Clause License
Attribution Statements: NVIDIA includes `click` under the BSD 3-Clause License for use with NVIDIA OO Agents.
License Text(https://spdx.org/licenses/BSD-3-Clause.html)
Source/Homepage: https://github.com/pallets/click

litellm - MIT License
Attribution Statements: NVIDIA includes `litellm` under the MIT License for use with NVIDIA OO Agents.
License Text(https://spdx.org/licenses/MIT.html)
Source/Homepage: https://github.com/BerriAI/litellm

nooa - Apache License 2.0
Attribution Statements: NVIDIA includes `nooa` under the Apache License 2.0 for use with NVIDIA OO Agents.
License Text(https://spdx.org/licenses/Apache-2.0.html)
Source/Homepage: https://github.com/NVIDIA-NeMo/labs-OO-Agents

nooa-cli - Apache License 2.0
Attribution Statements: NVIDIA includes `nooa-cli` under the Apache License 2.0 for use with NVIDIA OO Agents.
License Text(https://spdx.org/licenses/Apache-2.0.html)
Source/Homepage: https://github.com/NVIDIA-NeMo/labs-OO-Agents

---

## `nemo-oo-agents-benchmarks`

click - BSD 3-Clause License
Expand Down
185 changes: 185 additions & 0 deletions packages/nooa-acp/README.md
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.
Loading
Loading