File: docs/architecture.md
PentestGPT architecture
Status: 2026-07-12
This is the internal source of truth for the current repository shape. The root README.md is the
public project page and is intentionally being revised separately.
Repository family
PentestGPT_Project/
โโโ PentestGPT/ # framework, legacy interactive client, and tool image
โโโ UnifedAgentWrapper/ # canonical unified-agent package
โโโ xbow-benchmark/ # reference-only benchmark harness and historical resultsThe three directories are independent Git repositories. The XBOW checkout is retained as a
reference corpus only; the product CLI, runtime, and CI do not depend on it. Benchmark logic and
result archives must not be added here.
Maintained runtime
pentestgpt_agent/ is the autonomous framework. It is a nested uv project with its own lockfile and
environment. The loop deliberately has only two LLM roles:
RunSnapshot -> Supervisor -> compile_plan -> one TaskLease
|
TraceStore <- EpisodeRunner <- Executor <----+
| |
+---- compile_execution -------+
|
MemoryKernel- The Supervisor chooses one task or proposes completion.
- The Executor performs one leased task and returns a typed result.
- Both roles use fresh provider sessions and
FULL_ACCESS. The deployment environment is the
isolation boundary; PentestGPT does not maintain a second tool or filesystem sandbox. - Deterministic code owns scope validation, leases, evidence provenance, retries, revisions, and
canonical state. - SQLite is canonical memory. Provider transcripts are diagnostic traces, not memory.
- There is no always-on judge, RAG service, speculative backlog, or parallel scheduler.
pentestgpt_legacy/ is the maintained human-driven implementation of the USENIX 2024 workflow. It
has its own lightweight provider clients and does not use unified_agent.
Why unified-agent remains useful
The external UnifedAgentWrapper is a real seam with two production adapters: Claude Code and
Codex. PentestGPT depends on its small interface for:
- provider selection, model, effort, workspace, and permission configuration;
- structured-output invocation;
- normalized command, tool, file, session, usage, cost, and terminal events;
- shared task rendering and provider error handling.
Removing that module would duplicate provider SDK churn inside pentestgpt_agent.trace andpentestgpt_agent.trial. It therefore earns its place as a deep module. PentestGPT policy must stay
outside it: task kinds, memory, evidence, scope, scheduling, and completion belong to this repo.
The dependency is pinned to a commit of the public package in pentestgpt_agent/pyproject.toml, andtests/test_dependency.py verifies that the nested project imports the installed dependency rather
than the repository-root copy.
Root unified_agent copy
The root unified_agent/ is an older, drifted compatibility copy. No maintained PentestGPT runtime
imports it:
pentestgpt_agentuses the external package;pentestgpt_legacyuses its own provider clients;- only root packaging, its duplicate tests, and the current tool-image health check retain it.
Do not develop features in this copy. Removing it is desirable, but it is a separate public-package
cleanup because the root wheel currently exports the package and the Docker image copies it. That
cleanup must update the root package metadata, Docker health tests, lockfile, and public README in
one deliberate change.
Deep modules
| Module | Interface | Hidden implementation |
|---|---|---|
PentestLoop |
run(RunSpec) -> RunSnapshot |
recovery ordering, retries, episode identity, failure settlement |
MemoryKernel |
create/open/snapshot and atomic commits | SQLite schema, transactions, revisions, leases, dependency liveness |
compile_plan |
decision + snapshot -> valid plan | scope, dependency, phase, completion, and size validation |
compile_execution |
result + lease + trace -> valid execution | exact receipt matching, evidence fallback, identity, recovery rules |
EpisodeRunner |
one typed episode -> normalized result | provider invocation and durable append-only trace files |
external UnifiedAgent |
one task over Claude or Codex | SDK differences, native options, event normalization |
These interfaces are the preferred test surfaces. New provider behavior belongs behindUnifiedAgent; new canonical-state behavior belongs behind the Memory Kernel or compilers.
Memory and retrieval
Stored state is complete and retrieval is bounded. The Supervisor receives the open working set,
recent closed work, required basis/dependency context, selected observations, history counts, and
recent diagnostics. The Executor receives one task, explicit basis, same-task evidence, and a retry
diagnostic. A future retriever may select canonical IDs, but it must not replace SQLite or turn
summaries into evidence.
The current controller weakness is convergence, not database capacity: long runs can lose compact
coverage information and revisit completed surfaces. The next design slice should improve the
deterministic strategy projection and duplicate/branch policy before adding another agent or RAG.
Runtime and benchmark ownership
- Local framework development runs from
pentestgpt_agent/. - The root Docker image supplies pentest tools, provider CLIs, legacy code, and persisted auth. It
does not currently bake in the maintained framework. - The sibling
xbow-benchmarkcheckout is a historical/reference artifact, not a supported runtime
or verification path. PentestGPT owns no XBOW runner. - HTB execution happens only on the authorized remote attack box described in the parent project
guide, never directly from the development Mac.
Current design priorities
- Preserve the two-role loop and deterministic memory authority.
- Improve Supervisor coverage retrieval and convergence using saved-trace replay tests.
- Keep both roles fully enabled inside a restricted deployment environment.
- Remove the root
unified_agentcompatibility copy in a coordinated public-package cleanup. - Add a goal-specific verifier only when non-CTF completion semantics require it.
File: docs/docker-dev-plan.md
Docker runtime status
Status: 2026-07-12
This document describes the repository as it exists now. It replaces the original implementation
plan, whose phase matrix and in-repo benchmark paths are obsolete.
Current image responsibility
pentestgpt:latest is a disposable pentest-tool and provider-CLI environment. It contains:
- Ubuntu 24.04, Python 3.12, Node 20,
uv, Claude Code, and Codex; - common network/pentest tools such as nmap, gobuster, dirb, netcat, curl, DNS utilities, jq, and
ripgrep; - the root
pentestgpt_legacypackage; - the old root
unified_agentcompatibility copy; - persistent Claude and Codex authentication helpers.
It deliberately excludes benchmark fixtures/results, credentials, workspaces, and run artifacts.
The maintained pentestgpt_agent nested project is not baked into this image. Consequently,make docker-run deliberately fails fast with a wiring diagnostic instead of invoking an absent
CLI. There is no product-owned benchmark workaround; framework-image wiring remains an independent
deployment task.
Repository ownership
PentestGPT/ image, auth helpers, framework source, legacy client
UnifedAgentWrapper/ canonical provider-wrapper package
xbow-benchmark/ reference-only benchmark harness and historical resultsThe product does not support or invoke the sibling benchmark harness. Keep result JSONL, target
orchestration, and benchmark-specific adapters out of this repository.
Persistent provider login
Authentication state lives in named volumes and is never copied into image layers:
pentestgpt-claude -> /home/pentester/.claude
pentestgpt-codex -> /home/pentester/.codexThe providers require different setup paths:
- Claude uses a long-lived
setup-token, stored as.claude/oauth_tokenand exported asCLAUDE_CODE_OAUTH_TOKENby the entrypoint. - Codex performs its own in-container OAuth login. Its callback is forwarded through a
socathop;
hostauth.jsonmust not be copied because ChatGPT refresh tokens rotate.
Useful commands:
make docker-build
make docker-login
make docker-auth-status
ROUNDTRIP=1 make docker-auth-status # spends a minimal provider call
make docker-shell
make docker-down # keeps auth volumes
make docker-nuke # deliberately removes auth volumesThe auth-status check is advisory by default. Named volumes are credentials and must be protected
like a logged-in workstation.
Isolation contract
Both PentestGPT roles use provider FULL_ACCESS. The container or dedicated attack box is therefore
the blast radius and security boundary. A deployment must:
- contain only authorized target routes;
- avoid mounting unrelated source, home directories, tokens, or host sockets;
- mount run state only when persistence is required;
- treat traces and SQLite state as sensitive;
- tear down the environment after the assessment.
The tool image runs as pentester, which has passwordless sudo. It is isolation from the developer
host only when mounts, capabilities, devices, and networking are deliberately constrained.
Framework-image decision still open
There are two reasonable future shapes:
- Build framework and
unified-agentwheels outside Docker, then copy them into a dedicated runtime
image. This matches the proven qualification runner and preserves exact package hashes. - Publish both packages and install pinned releases during the Docker build.
Do not copy the root unified_agent/ directory into the maintained framework. It is version 0.1-era
compatibility code; the agent requires the pinned external 0.3 package.
Whichever shape is selected must make these checks true in a fresh container:
pentestgpt-agent --help
python -c "import pentestgpt_agent, unified_agent; print(unified_agent.__version__)"Only after that should make docker-run be advertised as supported.
Build cleanup opportunities
These are independent of framework design:
- remove
apt-get upgradefor faster, more reproducible builds; - install
socatin the main apt layer; - combine global npm installs;
- use BuildKit cache mounts for apt, npm, and uv;
- install from lockfiles/wheels before copying frequently changing source;
- remove the root
unified_agentcopy and its SDK dependencies when the public-package cleanup is
performed.
External benchmark reference
The sibling xbow-benchmark repository is retained only for historical reference. The product
Makefile, CLI, CI, and Docker runtime do not invoke it. If a future evaluation reuses that corpus,
design the adapter in an external evaluation repository rather than restoring a benchmark runner
inside PentestGPT.
File: pentestgpt_agent/README.md
PentestGPT Agent
Small, durable penetration-testing loop built on the externalunified-agent package. It is the
maintained autonomous framework; the root pentestgpt_legacy package remains the human-driven
USEN-2024 workflow.
The supported deployment boundary is an isolated, disposable environment containing only
authorized targets and the required provider credentials.
Runtime shape
SQLite snapshot
-> recover an active lease or invoke a fresh Supervisor
-> validate one typed decision and atomically lease one task
-> recover its trace or invoke a fresh Executor
-> validate trace identity and exact receipt evidence
-> atomically commit attempt, observation, and transition
-> repeat, finish, safely retry, or fail closed- The Supervisor and Executor both receive all provider tools and
FULL_ACCESSfilesystem/process
permissions. PentestGPT relies on deployment isolation rather than a second in-process sandbox. - The Supervisor proposes at most one new task and selects exactly one ready task or completion.
- The Executor receives one leased task with an explicit task kind and bounded provider turn budget.
- Every episode is fresh (
resume = null); provider conversation history is not memory. - Deterministic code owns scope, dependencies, leases, receipt provenance, retries, completion
bases, and canonical state transitions. - Provider actions and file writes are retained in traces and audit totals, but are allowed.
- There is no speculative backlog, parallel scheduler, RAG service, or always-on judge.
See CONTEXT.md for the exact domain language and invariants, and../docs/architecture.md for repository-level decisions.
Memory and traces
SQLite stores runs, typed tasks, attempts, bounded exact observations, and one transition per
revision. An observation must resolve to one exact contiguous slice of one eligible command or tool
receipt. Completed nonzero commands may ground negative findings; provider/tool transport failures
cannot. CRLF/LF normalization is the only accepted textual transport normalization.
An oversized receipt contributes at most an exact 4,000-character suffix and can commit onlyprogress. A no-action attempt may reuse exact evidence from an earlier attempt of the same task;
unsupported paraphrases degrade to progress and never create evidence.
The Supervisor receives a bounded projection: open work, four recent closed tasks, required
dependency/basis context, selected observations, aggregate history, and recent diagnostics. The
Executor receives one task, its explicit basis, up to two same-task observations, and one retry
diagnostic. Future retrieval may select canonical IDs, but SQLite remains authoritative.
Each episode directory contains:
input.json exact role input, prompt/schema hashes, provider policy
events.jsonl chronological normalized tool, command, file, and terminal events
output.json normalized result, usage, cost, duration, and failureTraces contain sensitive target output and provider session identifiers. They are mode-restricted,
not encrypted or tamper-evident.
Failure and restart behavior
- Operational failures retry only when no external-action receipt exists.
- Actionful failures are never replayed automatically.
- Existing terminal traces are compiled and committed before any provider reinvocation.
- A terminal event can reconstruct a missing
output.json. - Supervisor decisions are recovered by state revision; invalid plans receive one bounded retry.
- Attempt and decision budgets survive restart.
--resumerequires an exacttrial-config.jsonmatch, including goal, targets, provider, model,
effort, prompt/schema hashes, budgets, both full-access role policies, environment, andunified-agentversion.
Install and verify
Run from this directory so the repository-root compatibility copy cannot shadow the dependency.
uv sync --extra claude # or: codex / all
uv run python -m pytest -q
uv run ruff check src tests
uv run ruff format --check src tests
uv run mypy src
uv lock --check
uv buildThe normal suite uses scripted provider adapters and a loopback target. The opt-in live smoke spends
provider tokens:
PENTESTGPT_AGENT_LIVE=1 \
PENTESTGPT_AGENT_BACKEND=claude \
PENTESTGPT_AGENT_MODEL=claude-opus-4-8 \
uv run python -m pytest tests/live/test_local_model.py -q -sRun and resume
uv run pentestgpt-agent \
--goal "Capture the flag from this authorized target." \
--target http://127.0.0.1:8080 \
--backend claude \
--model claude-opus-4-8 \
--effort xhigh \
--run-id example
uv run pentestgpt-agent \
--goal "Capture the flag from this authorized target." \
--target http://127.0.0.1:8080 \
--backend claude \
--model claude-opus-4-8 \
--effort xhigh \
--run-id example \
--resumeAudit a held-out oracle:
uv run pentestgpt-agent-audit \
--run-dir runs/example \
--expected-flag 'FLAG{...}' \
--output runs/example-audit.jsonAudit schema 2 checks structural completion, canonical oracle evidence, settled leases/episodes,
exact receipt grounding, identity, dependency/basis integrity, current TEST provenance for EXPLOIT,
completion basis, revision continuity, and settled failures. Supervisor actions and provider-memory
writes are reported as diagnostics rather than failures.
UnifiedAgent dependency
unified-agent==0.3.0 is pinned to the merged inspector commit in pyproject.toml. It is the provider seam
for Claude Code and Codex: task invocation, model/effort configuration, structured output, and
normalized events. PentestGPT keeps task policy, memory, evidence, and completion outside that
dependency.
tests/test_dependency.py verifies that imports resolve to the installed external package, not the
obsolete repository-root unified_agent/ copy.
Live process inspector
UnifiedAgent 0.3 adds a read-only local web inspector. PentestGPT gives every monitored provider
episode a stable --<episode-id> identity and attaches the role, state revision,
task, and attempt as metadata. The framework's trace journal remains canonical; the inspector
database is an additional real-time diagnostic projection.
To inspect local wrapper changes before publishing a new immutable dependency revision, explicitly
opt into the sibling checkout:
export PYTHONPATH="$(cd ../../UnifedAgentWrapper && pwd)"
export UNIFIED_AGENT_MONITOR_DB="$(pwd)/runs/inspector.sqlite3"
uv run --no-sync pentestgpt-agent \
--goal "Assess this explicitly authorized target." \
--target http://127.0.0.1:8080 \
--backend codex \
--model gpt-5.5 \
--run-id inspector-example
# In another terminal, from ../../UnifedAgentWrapper:
PYTHONPATH=. .venv/bin/python -m unified_agent.monitor_web \
--database "$(cd ../PentestGPT/pentestgpt_agent && pwd)/runs/inspector.sqlite3" \
--openThe pinned 0.3 dependency provides the same monitor behavior in normal synchronized environments;
the sibling override is only for wrapper development.
External evaluation references
The sibling ../../xbow-benchmark checkout is retained only as a historical/reference corpus.
There is no supported XBOW command, runner, or CI path in pentestgpt-agent, and benchmark-specific
orchestration must stay outside this product repository. Historical engineering records may still
name earlier XBEN qualifications; they are not current regression guarantees.
The later HTB Enigma qualification did not solve the target; seeHTB_ENIGMA_QUALIFICATION_20260712.md.
Current limits
FULL_ACCESSis intentional. Either role may read or alter mounted state and credentials; the
surrounding container/VM must be treated as the blast radius.- Provider
max_turnsis not a portable command/action limit, especially on Codex. It bounds the
provider interaction where supported, not external side effects. - Controller convergence is the main demonstrated performance problem: older coverage can fall out
of the Supervisor projection and trigger repeated discovery. COMPLETEDproves structural evidence citation, not arbitrary natural-language entailment. CTF
audits use a held-out oracle; general goals need a goal-specific verifier.- SQLite has no versioned migration framework. Trace storage is sensitive and not power-loss or
tamper hardened. - Structured findings, report generation, and a PentestGPT product UI remain future work. The
wrapper-level process inspector is available for live diagnostics.