Agent Cyber Range SDK
agentcyberrange is the Python SDK for running autonomous Agents against the
Agent Cyber Range evaluation platform. It discovers the available Challenges,
keeps a bounded number of Arenas running, waits for capacity, and submits each
Arena when the Agent finishes or reaches its deadline.
Access to the evaluation service requires a Task Token issued by Agent Cyber Range.
Python 3.11 or newer is required.
Install
Install the latest release from PyPI:
python -m pip install agentcyberrange
For local development from a repository checkout:
uv sync --extra dev
Quick start
Set the Task Token issued by Agent Cyber Range:
export AGENTCYBERRANGE_TASK_TOKEN='replace-with-secret'
Then provide one asynchronous Agent function:
import asyncio
import os
from agentcyberrange import (
AgentCyberRangeClient,
AgentOutputPath,
ArenaHandle,
run_agent,
)
async def run_your_agent(arena: ArenaHandle) -> AgentOutputPath | None:
# Replace this body with your Agent implementation.
print(arena.task_prompt)
print(arena.entry_urls)
agent_output: AgentOutputPath | None = None
return agent_output
async def main() -> None:
async with AgentCyberRangeClient(
base_url=os.environ.get(
"AGENTCYBERRANGE_BASE_URL", "https://eval.agentcyberrange.io/"
),
task_token=os.environ["AGENTCYBERRANGE_TASK_TOKEN"],
) as client:
challenges = await client.list_challenges()
# Replace these with one or more specific Challenge IDs if needed.
challenge_ids = [challenge.challenge_id for challenge in challenges[:5]]
result = await run_agent(
run_your_agent,
max_concurrency=2,
challenge_ids=challenge_ids,
client=client,
)
print(result.model_dump_json(indent=2))
asyncio.run(main())
The Quick Start keeps the main customization points visible: the Agent callback, the
server-provided Prompt, and two concurrent Agents. The SDK returns the available
Challenge list, and the example selects the first five for this run. It leaves
agent_timeout unset, so Web Challenges use the 30-minute default and post Challenges use
the 2-hour default. Pass agent_timeout directly to apply one exact limit across the
selected Challenges.
Environment variables are reserved for connection configuration
(AGENTCYBERRANGE_TASK_TOKEN and, optionally, AGENTCYBERRANGE_BASE_URL). Per-run
policy such as concurrency, Agent timeout, model, and permission mode stays explicit in
Python arguments or CLI flags instead of hidden process-global configuration.
When agent_timeout is omitted, the SDK uses a 30-minute Web (non-post) default or a
2-hour post default. These are defaults, not maximums; the server-provided Arena lease is
the final resource deadline.
agent_timeout is an optional local limit in seconds. run_agent() applies an explicit
value instead of the default. For example, agent_timeout=3600 runs
both Web and post Agents for up to 60 minutes. The effective deadline is the earlier of
this local limit and the Arena's lease_expires_at. When the local timeout is reached,
the SDK signals arena.cancel_event and cancels the Agent callback. It then allows
cancellation cleanup and Web output finalization for up to two minutes before submitting;
submission happens sooner when cleanup finishes sooner. A post Challenge is submitted
with an empty body. A non-post callback may return its output archive during cancellation;
if no archive is available by the finalization deadline, the SDK submits a minimal empty
final_answer archive instead. The backend closes the Arena after this terminal
submission. The returned task outcome records the timeout in error_code and
error_message.
on_event receives lifecycle and retry events. Capacity waits include the HTTP status,
error code, request ID, Retry-After, and attempt number in SchedulerEvent, so callers
can report an automatic wait without disabling it. Pass a RetryPolicy to run_agent()
when a finite capacity_wait_timeout is required.
arena_ready_delay adds a cancellable settling period after Fleet reports an Arena as
running and before the Agent callback starts. This is useful when an exposed service needs
a few extra seconds to begin accepting connections.
The SDK automatically:
- discovers all currently available Challenges;
- keeps at most
max_concurrencyWork Slots active; - waits and retries when the control plane has no free capacity;
- refills a Work Slot as soon as an Arena is submitted;
- submits a post Challenge when the Agent returns, raises, times out, or reaches the server deadline;
- submits a non-post Challenge with the returned output archive, or with a minimal empty
final_answerarchive when the Agent returns no path or raises.
Run Codex
Install and authenticate the Codex CLI, set the Agent Cyber Range Task Token, then run:
uv run python examples/codex_cli.py
The example uses the default SDK settings and Codex model. It starts a non-interactive,
ephemeral codex exec session in a temporary workspace and enables network access inside
the workspace-write sandbox. See the
Codex non-interactive mode documentation.
The Codex example passes the server-provided arena.task_prompt to Codex unchanged. It
removes AGENTCYBERRANGE_TASK_TOKEN and AGENTCYBERRANGE_BASE_URL from the child
environment, so Codex cannot directly operate the control plane. If Codex creates a
final_answer/ directory for a non-post Challenge, the callback archives it under
.agentcyberrange/agent-output/ and returns that archive path to the Scheduler.
When an SDK timeout or Arena deadline cancels the callback, the example first stops Codex
and then archives any existing final_answer/ before the temporary workspace is removed.
The Scheduler submits that archive immediately. If the directory does not exist, the
Scheduler submits a minimal empty final_answer archive instead. A post Challenge returns
None and is submitted with an empty body.
Low-level client
Use AgentCyberRangeClient when you need to manage one Arena manually:
import asyncio
import os
from agentcyberrange import AgentCyberRangeClient
async def main() -> None:
async with AgentCyberRangeClient(
base_url=os.getenv(
"AGENTCYBERRANGE_BASE_URL",
"https://eval.agentcyberrange.io/",
),
task_token=os.environ["AGENTCYBERRANGE_TASK_TOKEN"],
) as client:
challenges = await client.list_challenges()
if not challenges:
raise RuntimeError("Agent Cyber Range has no available Challenges")
arena = await client.create_arena(challenges[0].challenge_id)
print(arena.task_prompt)
print(arena.entry_urls)
result = await client.submit(arena.arena_id)
print(result.verdict)
asyncio.run(main())
create_arena() automatically waits on capacity_exhausted and reuses the same
Idempotency-Key across retries. The client honors standard proxy environment variables;
set trust_env=False when a localhost backend must bypass them.
submit() also accepts a .tar.gz or .zip path for a non-post Challenge:
result = await client.submit(
arena.arena_id,
agent_output="outputs/agent-output.zip",
)
The SDK resolves a relative output path against the process working directory when
submit() starts, then reads the file once so an ambiguous HTTP result can safely retry
the same archive. The file must be non-empty and no larger than 10 MiB.
With run_agent(), return the archive path when a non-post Agent produced one. Returning
None is valid for both Challenge types: the Scheduler uses an empty request body for a
post Challenge and a minimal empty final_answer archive for a non-post Challenge.
from agentcyberrange import AgentOutputPath, ArenaHandle
async def run_your_agent(arena: ArenaHandle) -> AgentOutputPath | None:
agent_output = await your_agent(arena)
return agent_output
Examples
examples/quickstart.py: minimal custom Agent callbackexamples/codex_cli.py: Codex CLI integrationexamples/manual_client.py: low-level client flow
Development verification
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run mypy src
uv build
scripts/live_backend_smoke.py exercises every low-level client method.
scripts/live_scheduler_smoke.py verifies rolling refill and capacity fallback.
scripts/run_agent_test.py runs mock or external Agent processes against the deployed
control plane and asserts that no active Arena is left behind.
For a repository-only live Agent test:
uv run python scripts/run_agent_test.py
To replace the mock Agent with another process:
uv run python scripts/run_agent_test.py \
--agent-command 'codex exec --ephemeral --skip-git-repo-check --sandbox workspace-write -c sandbox_workspace_write.network_access=true -'
The default base URL is https://eval.agentcyberrange.io/. Override it with
AGENTCYBERRANGE_BASE_URL when testing another deployment.
Metadata
Release files for agentcyberrange 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| agentcyberrange-0.1.3.tar.gz | 53.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentcyberrange-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 78.5 kB
Release files / agentcyberrange-0.1.3.tar.gz
| Download URL | agentcyberrange-0.1.3.tar.gz |
|---|---|
| Size | 53.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4dda8dcdd6c7f5d6d757ed46419c3575a2d74f8ce2d57f35f3f4ad534f78c119
|
|
BLAKE2b-256 checksum How to use checksums |
6fd2e4848d19322b7dd829b7ec3978e83173b702c0f37c3b2a0094d42e560eb3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / agentcyberrange-0.1.3-py3-none-any.whl
| Download URL | agentcyberrange-0.1.3-py3-none-any.whl |
|---|---|
| Size | 24.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e72ebc82e2bba4661c8250f55a36dc57d2bdf62ce220f6e30c39f7eea30d2ce8
|
|
BLAKE2b-256 checksum How to use checksums |
fb5a1a722d0ddda452b8ebfb891916ff1225856cf25ee9ea026d77bc096d8c20
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for agentcyberrange 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.