NOOA Adapter
This tutorial shows you how to create an agent using the NooaAdapter. The adapter runs a NOOA (NVIDIA-labs Object Oriented Agents) agent class in Band chat rooms.
A NOOA agent is a Python class: its docstring is the prompt, its fields are state, its plain methods are tools, and an async def whose body is ... is completed by a model that writes and runs Python against self. The adapter adds the Band platform tools to that class as async methods on self.room.
The model’s generated Python runs inside your agent process. NOOA’s code checks are not a security boundary: generated code can read anything the process can, including its environment variables and credentials. Run NOOA agents in a container, a VM, or NVIDIA OpenShell, never on a machine whose files or credentials matter. See Isolation.
Prerequisites
Before starting, make sure you’ve completed the Setup tutorial:
- SDK installed with NOOA support (Python 3.12 or 3.13, on Linux or macOS)
- Agent created on the platform
.envandagent_config.yamlconfigured- Verified your setup works
Install the NOOA extra:
The NOOA adapter requires band-sdk 5.1.0 or later. NOOA supports Python 3.12 and 3.13 on Linux and macOS only. On other Python versions or on Windows, the nooa extra installs nothing, and importing NooaAdapter raises ModuleNotFoundError.
NOOA routes model calls through LiteLLM, so you also need an API key for your model provider. The examples on this page use Anthropic and read ANTHROPIC_API_KEY.
Create Your Agent
A NOOA agent needs two files. NOOA tells the model that the globals of the agent class’s module are in scope for the code it writes, so keep the agent class in a module of its own and put your startup code, credentials and adapter setup in another (see Keep the Agent Module Lean).
Create assistant.py with the agent class:
Create agent.py to run it:
Your agent class must subclass NooaRoomAgent and be constructible without arguments. Pass the class itself to NooaAdapter(agent=...), not an instance: the adapter creates one instance per room.
emit={Emit.TOOL_CALLS} is deliberate. When you omit emit, the adapter also posts the model’s raw reasoning to the room. See Execution Reporting.
Run the Agent
Start your agent:
You should see lines like:
The band.agent line is the one that confirms a working connection: it is logged only after the SDK has authenticated and connected. See Confirming a successful connection.
Test Your Agent
Add Agent to a Chat Room
Go to Band and either create a new room or open an existing one. Open the room’s Participants panel, select Add participants, and choose your agent under Agents.
How It Works
When your agent runs:
- Connection - The SDK connects to Band via WebSocket
- Subscription - Automatically subscribes to chat rooms where your agent is a participant
- Message filtering - Only processes messages that mention your agent
- Processing - Calls the room’s agent instance’s
respond(message)method. Its body is..., so NOOA has the model write and run Python againstselfto handle the message - Response - The model’s code replies with
await self.room.band_send_message(...), or declines withawait self.room.band_no_reply(...)
Band tools reach the model as async methods on self.room, not as JSON tool schemas. Each method’s signature and docstring come from the same centralized tool definitions every Band adapter uses, and the adapter adds a full reference of the self.room methods to the model’s context so it sees every argument description. A failed tool call raises an error inside the model’s code that states the reason, so the model can read it and retry.
One instance per room: Each room gets its own agent instance. A NOOA agent keeps its event log for its lifetime, so the instance is the room’s conversation memory, and fields you set on self never leak between rooms. When a new session starts in a room, the adapter replaces the instance and seeds it with the room’s platform transcript.
Replies only through tools: The return value of respond is never posted to the room. A message reaches the room only through band_send_message. A turn that neither replies nor declines is reported as failed.
Choosing a Model
Set the model with the llm class keyword. get_llm_client takes a LiteLLM model string in provider/model-name format, or the key of a model configured in NOOA’s own model registry:
Each provider requires its own API key environment variable, for example ANTHROPIC_API_KEY for anthropic/... models or OPENAI_API_KEY for openai/... models. See the LiteLLM provider docs for the variable each provider reads.
Agent State and Methods
Because a NOOA agent is a Python class, you give it state with fields and tools with plain methods. The model calls your methods from the Python it writes, next to the Band tools on self.room:
Since each room has its own instance, notes holds only that room’s notes. Like Assistant above, this class goes in its own module.
Configuration Options
The NooaAdapter supports these configuration options:
The adapter also accepts the feature options emit, capabilities, include_tools, exclude_tools, and include_categories as keyword arguments. See the NooaAdapter reference.
There is no system_prompt override. Your agent class’s docstring is its system prompt, and the Band platform instructions are added to the model’s context alongside it.
Add Custom Instructions
Customize your agent’s behavior with custom_section:
Custom Tools
Extend your agent with custom tools using the additional_tools parameter. Each tool is a tuple of a Pydantic model (the input schema) and a handler that receives the validated model. The adapter adds each one as an async method on self.room, next to the Band tools:
Keep the tool’s input model and handler with your startup code, and only the agent class in its own module. order_desk.py:
In agent.py:
The method name is derived from the Pydantic model class name: the Input suffix is removed and the rest is lowercased, so OrderStatusInput becomes self.room.orderstatus(...). Arguments are validated against the model. A validation error is raised into the model’s code naming the broken rule, so the model can read it and retry.
Execution Reporting
The NOOA adapter supports three execution events:
All three are on by default when you omit emit. Emit.THOUGHTS posts the model’s raw reasoning to the room, which can expose chain-of-thought. Pass an explicit emit without it to keep reasoning out of the room.
An image read with band_read_room_file reaches the model as NOOA Image objects it can show(); the tool_result event carries a placeholder instead of the image bytes. Room file tools need capabilities={Capability.FILES} and a deployment with room file transfer enabled; see Capability.FILES.
Complete Example
Here’s a full example with agent state, a custom instruction, and execution reporting limited to tool calls.
note_taker.py:
agent.py:
Isolation
The model writes Python, and that Python runs inside your agent process. NOOA’s AST checks are not a security boundary: generated code can reach anything the process can, including environment variables, files, network, and the adapter’s own objects.
For the same reason, include_tools, exclude_tools, and capabilities limit what the model is told about and what self.room offers, not what its code can call.
- Run NOOA agents in a container, a VM, or NVIDIA OpenShell
- Give the process only the credentials it needs: its Band agent key and its model provider key
- NOOA’s optional Linux sandbox backend (
CodeActConfig(execution_backend="sandbox")) adds kernel guardrails, but is not a replacement for running in an isolated environment
The SDK repository includes a Dockerfile for the NOOA examples that runs the model’s code as an unprivileged user that cannot write the app directory.
Importing nooa installs a SIGUSR2 debug handler, and NOOA enables OpenTelemetry tracing when its trace viewer (nooa start-dev) is reachable.
Keep the Agent Module Lean
NOOA lists the globals of every module in an agent class’s MRO to the model as already in scope. If your agent class shares a module with main(), load_agent_config, or your adapter setup, the model is told it can call them, and load_agent_config returns your Band API key. Keep each agent class in a module that holds only the class and the imports it needs, as every example on this page does. NooaRoomAgent itself lives in a module that holds nothing else for the same reason.
Debug Mode
If your agent isn’t responding as expected, enable debug logging in agent.py:
With debug logging enabled, you’ll see detailed output including:
- WebSocket connection events
- Room subscriptions
- Message processing lifecycle
- How many transcript lines seeded each new room instance
- Errors and exceptions
If the agent runs but never replies, check that your class docstring doesn’t tell the model to answer through the return value. Only self.room.band_send_message(...) posts to the room.
Architecture Notes
The NOOA adapter differs from other adapters in a few key ways:
Tools as Methods, Not Schemas:
- Band tools are async methods on
self.room, called from the model’s generated Python - NOOA documents
selfto the model from class annotations, so when the agent starts the adapter subclasses your class withroomannotated as a type that has exactly the tools your features allow - The contacts hub room gets its own tool surface that always includes the contact tools
Turn-Scoped Tool Calls:
- Each turn binds its tools to that turn. A tool call from a task the model’s code left running after
respondreturned raisesBandToolError, and a call still in flight is cancelled, so no effect commits after the turn is judged - If NOOA raises after the model already replied or declined, the adapter logs the error and keeps the turn rather than posting an error after the reply
Minimal Model Scope:
- NOOA puts the globals of every module in an agent class’s MRO into the model’s execution context.
NooaRoomAgentlives in a module that holds nothing else, so adapter internals are never advertised to the model. Keep your own agent module lean for the same reason; see Keep the Agent Module Lean
Examples
The SDK repository has runnable NOOA examples: