# Agents

> Define an agent on Claude from Python, submit runs, run many in parallel, and read each run's answer and steps.

Source: https://nodus-platform-site.pages.dev/docs/guides/python/agents/
Build revision: 211ad9f836655b1c3a2668c4693e442471f28614

**Beta:** this feature may change.

An Agent is a definition: a system prompt, the Claude access it runs on and a cost cap. Each run is one conversation in its own sandbox. The [Agents guide](https://nodus-platform-site.pages.dev/docs/guides/agents/) covers the model, the cost cap and keeping a run open for follow-ups; this page is the Python side.

```python
import nodus

agent = nodus.ClaudeAgent("helper", system="You are careful.", families=["haiku", "sonnet"])
print(agent.remote("Use the shell to print the Python version"))   # runs to the end, returns the answer

run = agent.submit("Summarize the logs", keep_alive=True)
run.send("message", "Now the staging logs")
print(run.answer())
print(run.steps())
run.cancel()
```

`ClaudeAgent` creates the agent on first use. Pass `api_key_secret="anthropic-key"` to use your own Anthropic key, or `ClaudeAgent.from_name("claude-assistant", project="nodus")` to run the ready-made template.

## Run agents in parallel

An AgentGroup runs many runs of one agent at once, with a limit on how many run together, a cost cap for the whole group and dependencies between runs. Parallel agents are in Beta, like the rest of Agents. The [Agents guide](https://nodus-platform-site.pages.dev/docs/guides/agents/#run-agents-in-parallel) explains each limit and the YAML and CLI side.

examples/agents/parallel-agents/main.py

```python
"""Run questions in parallel under one AgentGroup, then fan a list out with agent.map.

Run it with `python examples/agents/parallel-agents/main.py`.
"""

import nodus


def main() -> None:
    agent = nodus.ClaudeAgent(
        "parallel-agents-py",
        system="You answer every question in one short sentence.",
        families=["haiku"],
        max_tokens=1024,
        per_run_max_cost="0.10",
    )
    agent.deploy()

    group = nodus.AgentGroup.create("parallel-agents-py", agent, max_active=2, max_cost="0.60")
    try:
        group.submit_many(
            [
                {"key": "a", "input": "What is the capital of France?"},
                {"key": "b", "input": "What is the capital of Japan?"},
                # c starts only after a and b have succeeded.
                {"key": "c", "input": "Say that both questions are answered.", "depends_on": ["a", "b"]},
            ]
        )
        group.seal()
        status = group.wait()
        print(f"{status.phase}: {status.counts.succeeded} of {status.counts.total} runs succeeded")
        for run in group.runs():
            print(run.name, "->", run.answer())
    finally:
        group.delete()

    # For a plain list of inputs, map does the same in one call and returns the answers in order.
    for answer in agent.map(["What is 2 + 2?", "What is 3 + 3?"], max_active=2):
        print(answer)


if __name__ == "__main__":
    main()
```

* `nodus.AgentGroup.create(name, agent, max_active=, max_pending=, max_cost=)` creates the group over a deployed agent. `max_cost` caps the runs’ Claude usage together; their sandboxes are billed on their own. `submit_many` takes tasks `{key, input, depends_on}`, puts every task after the tasks it depends on, sends them in batches of up to 100 runs and returns the runs in your order. A cycle or a repeated key raises `nodus.errors.Invalid` before anything is sent.
* `group.seal()` says that no more runs are coming. A group finishes only once it is sealed, and `group.wait()` returns its status when it has. `group.runs()` returns the member runs, and each run’s `answer()` is its text.
* `group.cancel()` cancels the runs that have not finished. `group.delete()` removes the group and its runs.
* `agent.map(inputs, max_active=, order_outputs=True, return_exceptions=False)` creates a group, runs one task for each input, yields each answer text and deletes the group at the end. A run that does not succeed raises `nodus.errors.AgentRunFailed`, or is yielded as the exception when you pass `return_exceptions=True`.

## Not available yet

`nodus.Agent` is the definition of a durable Python program (`@agent.entrypoint`, `@agent.step`, `ctx.step`). Runs execute on Claude, not as your code, so a definition that names `source`, an entrypoint, `setup`, `secrets`, `env`, `network`, `models`, `cpu`, `memory`, `max_cost` or worker settings raises `nodus.errors.Unsupported` when it is deployed. Submitting a run with `session_key=` or `group=` (use `AgentGroup.submit_many` for group runs), `AgentGroup.create(max_held=, evaluation=)`, `group.results()`, and a run’s `outputs`, `resolve()`, `retry()`, `suspend()`, `resume()` and `children()` raise it too. `nodus.Agent("name", image=..., per_run_max_cost=...)` and `agent.submit(input, deadline=...)` work as written, and `run.result()` and `agent.remote(input)` return the run’s answer text, the same as `run.answer()`.
