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.
The evaluation system is partially open source. You can inspect and test the SDK locally from this GitHub repository before requesting an official evaluation.
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 shorter 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.
Fleet and the SDK both limit a Web (non-post) Arena to 30 minutes and a post Arena to 2
hours. These values are the SDK's defaults and hard maximums. When agent_timeout is
omitted, the SDK selects the limit from the Challenge type.
agent_timeout is an optional local limit in seconds. run_agent() applies the same
requested value to every selected Challenge, then caps it at the type-specific maximum.
For example, one hour becomes 30 minutes for Web and remains one hour for post; three
hours becomes 30 minutes for Web and 2 hours for post. The effective deadline is the
earlier of this local limit and the individual Arena's authoritative server deadline.
When it is reached, the SDK signals arena.cancel_event and cancels the Agent callback. A
post Challenge is submitted with an empty body. A non-post callback may finish its
cancellation cleanup by returning an output archive, which the SDK submits immediately;
if cancellation produces no archive, the SDK submits a minimal empty final_answer
archive instead. A timeout or deadline therefore always triggers a terminal submit rather
than abandoning the Arena with a close request. The returned task outcome records the
timeout or deadline 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. 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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentcyberrange-0.1.0.tar.gz.
File metadata
- Download URL: agentcyberrange-0.1.0.tar.gz
- Upload date:
- Size: 53.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5047ad2c9679585010c04cbd4e7529dac515a83c37701cdef73a77ee8625e8bf
|
|
| MD5 |
2b8413a44d632fd9c1a9e741e124e1f8
|
|
| BLAKE2b-256 |
910ed2d65fa535c77adb553d1a1d1f3e2ee262da21e5a690f1a39beb49039f17
|
File details
Details for the file agentcyberrange-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agentcyberrange-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
786e184625bd2be0191b849f6aa67123dd2b604a26352d4adffc2d4bca870f38
|
|
| MD5 |
afafcdf3b669fe72e6d99feeb3885abd
|
|
| BLAKE2b-256 |
6ddcf73636fc27059d7f89a695bdcb0bbd35e706350d6a1e15f64311d5cb9471
|