Catalog PyPI
Back to packageRELEASE DETAILS

0.1.3

PyPI

aicraft

Run coding agents (Claude Code, Codex, Aider, OpenHands, …) against prompts in container sandboxes via Harbor

agentcoding-agentharborllmsandbox
Published12 Jul 2026
Direct dependencies7
Transitive dependenciesNot calculated

README

Version 0.1.3 documentation

aicraft

Run coding agents (Claude Code, Codex, Aider, OpenHands, …) against prompts in container sandboxes. A thin Python library + CLI on top of the Harbor framework, focused on ad-hoc single-prompt runs rather than full benchmark evaluation.

Status: alpha. API and CLI may shift between 0.1.x releases. Pin minor versions if you depend on it.

Why this exists

Harbor itself is built around full benchmark lifecycles: scaffolding a task directory, running an agent over a dataset, scoring with a verifier, publishing trajectories. That's heavy when all you want is "run agent X on prompt Y, give me the answer."

aicraft flips the abstraction:

  • Inline prompt or --prompt-file instead of a hand-built task directory
  • Verifier disabled by default
  • Auto-synthesizes the minimal Harbor task structure under the hood
  • ATIF-aware final-text extraction (works across any agent that emits ATIF)
  • Mount allowlist enforcement (refuses to mount paths outside configured roots)
  • Provider presets (--provider openrouter etc.) for routing OpenAI-protocol agents through aggregator gateways

It's the opinionated, ad-hoc-friendly subset of Harbor — like gh is to a git push origin <branch> && curl github.com/... chain.

Install

pip install aicraft

aicraft requires Python 3.12+ and a working Docker daemon (or Podman with the Docker compatibility socket — set DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock). Harbor itself spawns each agent in a Docker container.

Usage

Library

import asyncio
from pathlib import Path
from aicraft import AgentRunner, AgentConfig, MountSpec

async def main():
    runner = AgentRunner()
    result = await runner.run(AgentConfig(
        prompt="Summarize the structure of /workspace/code in one paragraph.",
        agent="claude-code",
        mounts=[MountSpec(host=Path("/data/repo"), container=Path("/workspace/code"))],
        timeout_s=600,
    ))
    print(result.status)         # "completed" | "timeout" | "error"
    print(result.final_text)     # agent's textual reply (extracted from ATIF)
    print(result.trajectory_path)  # full trajectory on disk

asyncio.run(main())

CLI

# Inline prompt
aicraft run -a claude-code "Find dead code in this repo"

# Prompt from a file
aicraft run -a codex -M gpt-5 -f ./prompt.md

# With a mounted code directory
AICRAFT_MOUNT_ROOTS=/data/repos aicraft run -a claude-code \
  "Refactor the parser for readability" \
  --mount /data/repos/foo:/workspace/code:ro

# Specify model and a higher timeout, write structured result to file
aicraft run -a claude-code "..." \
  --model claude-sonnet-4-7 \
  --timeout 1800 \
  --output ./result.json

# Bump the container memory limit for memory-hungry agents/models
aicraft run -a claude-code "..." --memory-mb 8192

# Route an OpenAI-protocol agent through OpenRouter for open-weight models
OPENROUTER_API_KEY=sk-or-... aicraft run \
  -a codex -M deepseek/deepseek-chat --provider openrouter \
  "Describe the bug in /workspace/buggy.py and fix it" \
  --mount /tmp/scratch:/workspace:rw

# See installed agents
aicraft list-agents

# Locate a previously-run trajectory on disk
aicraft trajectory aicraft-3a2b1c0d9e8f

Mount syntax: host:container[:ro|rw] — default is ro. Pass :rw explicitly when the agent needs to write back.

Output

aicraft run prints a JSON document to stdout for pipeline use:

{
  "status": "completed",
  "final_text": "def reverse_string(s: str) -> str:\n    return s[::-1]",
  "trajectory_path": "/path/to/trajectories/aicraft-7dad38f46662",
  "duration_s": 65.1,
  "trial_id": "aicraft-7dad38f46662",
  "error": null
}

…and a human-readable banner to stderr so the agent's reply is easy to spot amid debug logs:

════════════════════════════════════════════════════════════════════════
 AGENT OUTPUT  [OK]  trial=aicraft-7dad38f46662  65.1s
════════════════════════════════════════════════════════════════════════
def reverse_string(s: str) -> str:
    return s[::-1]
════════════════════════════════════════════════════════════════════════

Exit codes: 0 on completed, 1 on timeout / error, 2 on a pre-flight config issue (mount not allowed, missing required model, unknown provider).

The trajectory directory contains everything Harbor captured: the ATIF trajectory.json, the agent's raw session logs, and any verifier artifacts. For an aicraft run there's no verifier output, but the ATIF trajectory has the full tool-call history.

Configuration

VariablePurposeDefault
AICRAFT_MOUNT_ROOTSColon-separated absolute host paths permitted as mount sources. Empty means no mounts allowed.empty
AICRAFT_TRAJECTORY_DIRWhere captured trajectories are stored. Override per-call with --trajectory-dir../trajectories/ (cwd)
OPENAI_API_KEY, OPENAI_BASE_URL, ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, GEMINI_API_KEY, GOOGLE_API_KEY, CLAUDE_CODE_OAUTH_TOKENStandard provider env vars consumed by Harbor agents.—
OPENROUTER_API_KEY, FIREWORKS_API_KEY, TOGETHER_API_KEY, GROQ_API_KEYProvider-specific keys consumed by --provider <name>.—

Provider presets

--provider <name> rewrites OPENAI_BASE_URL and OPENAI_API_KEY from a friendly name plus a provider-specific env var, so OpenAI-protocol agents (codex, copilot-cli) can route through aggregator gateways without manual env juggling. Anthropic-protocol agents (claude-code) read ANTHROPIC_* directly and need separate setup.

PresetBase URLKey env var
openrouterhttps://openrouter.ai/api/v1OPENROUTER_API_KEY
fireworkshttps://api.fireworks.ai/inference/v1FIREWORKS_API_KEY
togetherhttps://api.together.xyz/v1TOGETHER_API_KEY
groqhttps://api.groq.com/openai/v1GROQ_API_KEY

Limitations

See GOTCHAS.md for the full list with reproduction steps and workarounds. Highlights:

  • First run per agent is slow — Harbor re-installs the agent CLI in a fresh container on every trial (~45–55s for claude-code/codex). Image build is cached; container is fresh.
  • Rootless Podman + claude-code — the agent runs fine, but trajectory ingestion fails reading session JSONL files (claude-code writes them at 0600, and rootless Podman's userns mapping makes them unreadable from the host). Use Docker for claude-code, or other agents (codex, aider, nop) on Podman — they're unaffected.
  • final_text for non-ATIF agents — may be empty; the trajectory directory still has the full record.
  • Codex requires an explicit --model — Harbor's codex agent has no default. aicraft pre-validates this in <1s.
  • --provider is OpenAI-protocol only — claude-code (Anthropic protocol) needs ANTHROPIC_BASE_URL + ANTHROPIC_API_KEY set manually for gateways like OpenRouter.
  • harbor#1514 — Defer litellm/aiohttp imports to avoid ~25s startup overhead

License

MIT — see LICENSE.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 7

Native requirements
7 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
harbor
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.12",
  "evaluation": "NOT_EVALUATED"
}
harbor<0.19,>=0.5runtime
cyclopts
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.12",
  "evaluation": "NOT_EVALUATED"
}
cyclopts>=4.0.0runtime
pydantic
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.12",
  "evaluation": "NOT_EVALUATED"
}
pydantic>=2.10.0runtime
litellm
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.12",
  "evaluation": "NOT_EVALUATED"
}
litellm>=1.83.0runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.12",
  "evaluation": "NOT_EVALUATED"
}
pytest>=8.0.0; extra == "dev"runtime
pytest-asyncio
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.12",
  "evaluation": "NOT_EVALUATED"
}
pytest-asyncio>=0.24.0; extra == "dev"runtime
ruff
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.12",
  "evaluation": "NOT_EVALUATED"
}
ruff>=0.8.0; extra == "dev"runtime

Distribution files

2 collected
aicraft-0.1.3-py3-none-any.whl

bdist_wheel · 16.7 KB · Python py3

Source file
aicraft-0.1.3.tar.gz

sdist · 20.74 KB · Python source

Source file

Repository

PENDING
kashifpk/aicraft

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 10:20 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:9a3e093468fb685282fd9f976620ee4d5b5cd42fa4d5ef1b90794745aec6880f

Other collected versions

View all collected versions →