Skip to main content

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_concurrency Work 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_answer archive 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

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

agentcyberrange-0.1.0.tar.gz (53.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentcyberrange-0.1.0-py3-none-any.whl (24.5 kB view details)

Uploaded Python 3

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

Hashes for agentcyberrange-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5047ad2c9679585010c04cbd4e7529dac515a83c37701cdef73a77ee8625e8bf
MD5 2b8413a44d632fd9c1a9e741e124e1f8
BLAKE2b-256 910ed2d65fa535c77adb553d1a1d1f3e2ee262da21e5a690f1a39beb49039f17

See more details on using hashes here.

File details

Details for the file agentcyberrange-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for agentcyberrange-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 786e184625bd2be0191b849f6aa67123dd2b604a26352d4adffc2d4bca870f38
MD5 afafcdf3b669fe72e6d99feeb3885abd
BLAKE2b-256 6ddcf73636fc27059d7f89a695bdcb0bbd35e706350d6a1e15f64311d5cb9471

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page