Skip to main content
Glama
README.md
# Async Hero

Async Hero is a small local MCP server that lets a primary Codex agent queue independent,
read-only research jobs backed by any Codex-compatible OpenAI Responses-style inference
provider while continuing its own work. DeepSeek is the default deployment, not an executor
hard dependency. The primary agent remains the orchestrator and authority. Worker results
are untrusted evidence bundles, not security verdicts.

```text
User -> Codex VS Code / Sol -> Async Hero MCP -> async job manager
                                            -> codex exec -> Responses-compatible provider
```

The MVP is intentionally one STDIO MCP process, SQLite, one worker at a time by default,
and three declarative profiles. It does not implement a custom LLM loop, arbitrary shell
execution, automatic exploitation, or autonomous finding promotion.

## Requirements and install

- Python 3.11+ (3.12+ recommended)
- Codex CLI with the Phase 0 flags described in
  [the compatibility report](docs/compatibility-report.md)
- An inference provider implementing the OpenAI Responses wire format supported by Codex
- The provider API key named by the isolated Codex configuration
- `uv` (recommended)

```powershell
uv venv --python 3.12
uv sync --extra dev
```

Plain `pip install -e ".[dev]"` also works.

## Why DeepSeek is the default

The project currently defaults to `deepseek-flash` because DeepSeek released V4.1-Flash on
2026-09-10 as a 552B-parameter MoE with only 8B active parameters for input and 16B for
output. DeepSeek reports that it exceeds V4-Pro across performance, cost, speed, and total
runtime, while its published API prices are unusually low. That makes its cost-to-accuracy
ratio attractive for parallel research workers, where usage multiplies quickly. These are
provider-published claims and should be validated with this project's benchmark procedure.
See the [official V4.1-Flash announcement](https://api-docs.deepseek.com/news/news260910/)
and [current pricing](https://api-docs.deepseek.com/quick_start/pricing/).

## Isolated provider configuration

Create a separate worker home. Do not copy the normal `~/.codex` directory and do not edit
the primary Codex provider configuration.

```powershell
New-Item -ItemType Directory -Force "$env:USERPROFILE\.async-hero\codex"
Copy-Item examples\codex-home\config.toml.example `
  "$env:USERPROFILE\.async-hero\codex\config.toml"
$env:DEEPSEEK_API_KEY = "<set this securely in your shell or secret manager>"
$env:ASYNC_HERO_CODEX_HOME = "$env:USERPROFILE\.async-hero\codex"
python -m async_hero.smoke
```

The key is never stored by Async Hero. The executor passes only a small environment allow
list to Codex, and the isolated config gives model-generated commands only process essentials
while explicitly excluding the provider key. `CODEX_SQLITE_HOME`, `TEMP`, and `TMP` are
worker-local scratch directories. Sessions are ephemeral.

To use another provider, copy
[`openai-compatible.config.toml.example`](examples/codex-home/openai-compatible.config.toml.example)
instead and set its model ID, HTTPS base URL, provider ID, and `env_key`. Codex currently
requires the Responses wire API for custom providers; a Chat Completions-only endpoint is
not sufficient. Loopback HTTP is accepted for a local inference server, while remote
providers must use HTTPS. Set `ASYNC_HERO_MODEL` to override the configured default model
for all profiles, or set `model` in one worker profile for a profile-specific override.

The smoke test must print `PASS` before relying on real jobs. It validates the local CLI,
isolated provider, read-only execution, JSONL events, schema-constrained output, and clean
exit. Missing configuration or credentials fails loudly.

On native Windows, the portable template selects the `unelevated` sandbox backend. Change
`[windows].sandbox` to `"elevated"` after its administrator-approved one-time provisioning;
Async Hero accepts either but does not accept a disabled backend.

## Run as a local MCP server

Set the authorized research workspace and start the STDIO server:

```powershell
$env:ASYNC_HERO_WORKSPACE = "C:\absolute\path\to\research"
$env:ASYNC_HERO_CODEX_HOME = "$env:USERPROFILE\.async-hero\codex"
python -m async_hero.server
```

For Codex VS Code, adapt [the MCP configuration example](examples/codex-mcp-config.toml),
or add the same STDIO command in **Settings > MCP servers**, then restart the extension.
Official Codex documentation confirms that the CLI and IDE share MCP configuration. Keep
the provider key in the environment; the example's `env_vars = ["DEEPSEEK_API_KEY"]`
forwards the DeepSeek default by name without writing its value to TOML. For another
provider, replace that name with its configured `env_key`. Add worker-MCP environment names
only when a profile is intended to use those MCPs.

Useful server settings:

| Environment variable | Default | Meaning |
|---|---:|---|
| `ASYNC_HERO_WORKSPACE` | current directory | Only research root workers may inspect |
| `ASYNC_HERO_STATE_DIR` | `./state` | SQLite and immutable job artifacts |
| `ASYNC_HERO_CODEX_HOME` | `~/.async-hero/codex` | Isolated worker Codex configuration |
| `ASYNC_HERO_CODEX_BIN` | `codex` | Codex executable path |
| `ASYNC_HERO_MODEL` | provider config default | Optional model override for every worker profile |
| `ASYNC_HERO_MAX_WORKERS` | `1` | Global concurrent workers; overflow queues |
| `ASYNC_HERO_MAX_TIMEOUT` | `3600` | Server hard timeout ceiling |
| `ASYNC_HERO_MAX_EVENT_BYTES` | `8388608` | Per-job JSONL ceiling |
| `ASYNC_HERO_MAX_RESULT_BYTES` | `2097152` | Final result ceiling |
| `ASYNC_HERO_MAX_STDERR_BYTES` | `2097152` | Per-job stderr ceiling |

## MCP workflow

The generic tools are `start_job`, `start_jobs`, `get_job`, `get_jobs`, `get_result`,
`get_results`, `list_jobs`, `cancel_job`, `cancel_jobs`, and `list_worker_profiles`.
There are no worker-specific methods and no command/environment parameter.

```json
{
  "worker_profile": "tracer",
  "task": "Trace attacker-controlled PluginIdentifier values into persistent plugin configuration.",
  "scope_paths": ["server/plugin", "server/config"],
  "timeout": 900,
  "evidence_budget": "medium"
}
```

`start_job` validates and persists the request, then returns a queued job ID immediately.
The dispatcher starts it when global and profile limits permit. Poll with `get_job`; retrieve
the evidence with `get_result` after `completed`. Failed, cancelled, timed-out, interrupted,
and schema-invalid jobs remain explicit terminal states.

All scope paths must be relative, traversal-free, exist below the configured workspace, and
survive canonical-path containment checks. A request may select only a schema under the
configured schema directory. Scope paths constrain the assignment and evidence expectations;
the MVP read sandbox is workspace-wide. The workspace itself is server-owned configuration,
not an MCP request parameter, so a caller cannot redirect a worker to an arbitrary path.
Async Hero constructs every `codex exec` argument itself.

## Profiles and results

Profiles live in `workers/*.toml`; their JSON schemas live under `schemas/`. On restart (or
`list_worker_profiles(reload=true)`), adding one valid TOML file and optionally one schema
makes a new profile available without server code changes. A profile is a reusable policy;
each submitted job creates an independent worker instance/process.

`model` and `mcp_servers` are optional profile fields. Omitting `model` uses the isolated
provider's configured model. `mcp_servers = ["re_exports"]` grants only that administratively
configured MCP to instances of the profile.

The initial profiles are:

- `mapper`: components, entry points, boundaries, checks, sensitive consumers, symbols.
- `tracer`: source-to-guards-to-consumer data/control/state paths and unresolved links.
- `falsifier`: counterevidence, guards, mitigations, reachability, attacker assumptions, and
  alternative explanations. It cannot return a confirmation verdict.

Schemas preserve observations, static conclusions, hypotheses, assumptions, contradictory
evidence, uncertainty, and proposed validation. Evidence locations support file/line,
symbol/class/function, address/RVA, and artifact hash.

## Worker workspace and MCP access

Every worker starts with the configured research workspace as its working directory and a
job-specific state/scratch directory. Shell access to the research workspace remains
read-only. The job request can narrow the declared scope but cannot replace the workspace.

Worker MCPs are configured separately under `mcp_servers` in the isolated worker
`config.toml`; Async Hero never imports MCPs, plugins, credentials, or settings from the
primary Codex home. Each server must:

- be disabled at rest with `enabled = false`;
- declare a non-empty `enabled_tools` allowlist;
- use `default_tools_approval_mode = "writes"`;
- use HTTPS, or loopback HTTP for a local server;
- source credentials from named environment variables whose values are never persisted;
- be granted explicitly by ID in a worker profile's `mcp_servers` list.

At launch, Async Hero enables the profile's selected servers and explicitly disables every
other configured server. The orchestration MCP itself cannot be granted, preventing recursive
delegation. MCP implementations run outside the filesystem semantics of ordinary shell
commands, so configure only trusted servers and allowlist genuinely read-only tools. Do not
give workers ticket-writing, report-editing, database-mutating, runtime-exploitation, or
secret-retrieval tools.

Example isolated configuration:

```toml
[mcp_servers.re_exports]
url = "http://127.0.0.1:8765/mcp"
enabled = false
enabled_tools = ["search_symbols", "read_function"]
default_tools_approval_mode = "writes"
```

Then opt a profile in:

```toml
mcp_servers = ["re_exports"]
```

For an authenticated MCP, use `bearer_token_env_var`, `env_vars`, or `env_http_headers` and
add each referenced variable to `shell_environment_policy.filters` with value `"exclude"`.
This lets Codex authenticate the MCP without exposing that credential to worker shell
commands. Codex supports per-server tool allowlists and environment forwarding in its
[official MCP configuration](https://learn.chatgpt.com/docs/extend/mcp?surface=cli).

## Persistence, shutdown, and limits

SQLite uses WAL mode. Job artifacts are stored under `state/jobs/<job-id>/` as
`request.json`, `events.jsonl`, `result.json`, `stderr.log`, and `metadata.json`. Results are
retrievable after restart. Any job left `running` across a crash becomes `interrupted`, never
`completed`; queued jobs resume dispatch.

Timeout and cancellation terminate the full process tree. Windows uses `taskkill /T` with a
graceful attempt followed by `/F`; POSIX uses a dedicated process group with TERM then KILL.
One bad worker is contained to its job and size/runtime limits.

Workers see the research workspace through Codex's `read-only` sandbox. The scratch directory
is reserved for Codex process state and logs; model-generated commands cannot write there in
this MVP. Scope paths are not secret-hiding boundaries: point `ASYNC_HERO_WORKSPACE` at an
authorized, sanitized research tree that contains no credentials or unrelated private data.
Immutable workspace snapshots are the next isolation extension point.

## Development and benchmark

```powershell
python -m pytest
python -m async_hero.smoke  # requires isolated config and key
python scripts/live_mcp_test.py  # starts mapper, tracer, and falsifier via real STDIO MCP
python scripts/live_mcp_test.py --profiles tracer  # rerun selected profiles
```

The live driver temporarily raises the server concurrency limit to three, verifies that
`start_jobs` returns before workers finish, polls terminal states, and retrieves results.
It can consume meaningful API tokens; use it deliberately. See the recorded local outcome
in [the compatibility report](docs/compatibility-report.md).

See [the benchmark procedure](docs/benchmark.md). Evaluate facts, exact references, call
paths, missed guards, contradictions, false positives, cost, time, and Sol review effort—not
the amount of generated prose.

TDQS

A3.6/5.0

Scored across 10 tools

Disambiguation4/5

The singular/plural pairs (start_job/start_jobs, get_job/get_jobs, get_result/get_results, cancel_job/cancel_jobs) are clearly distinguished by cardinality, and list_jobs/list_worker_profiles target different resources. The only minor ambiguity is between get_job and get_result, but descriptions clarify that get_result is for completed results.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern: start, get, list, cancel, and list_worker_profiles is the only compound noun but still fits the pattern. Singular and plural forms are used systematically.

Tool Count5/5

10 tools is well-scoped for a job queueing and management server. Each tool serves a distinct purpose, and the singular/plural pairs cover both single-item and batch operations without unnecessary bloat.

Completeness4/5

The surface covers the full job lifecycle: create, read, list, cancel, and retrieve results. A minor gap is the lack of an explicit retry or delete operation, but the core workflow is complete.

Maintenance

ActivityMaintained
ResponsivenessNo issues