RedAmon Inside RedAmon Agent · how the agent harness works Source
RedAmon · agent internals

Inside RedAmon Agent

RedAmon Agent is an autonomous penetration tester. You give it an objective and an approved scope. It plans the engagement, drives real offensive tools inside an isolated Kali sandbox, reads every result, changes course when the evidence changes, and climbs from reconnaissance to exploitation to post-exploitation, with you approving each step up. It works from the knowledge graph your recon built and from the lessons of earlier sessions, and it closes with a report of what it proved. Its judgment comes from a reasoning model. Its reliability comes from the deterministic harness around that model, which turns every decision into an action that is checked, logged and resumable.

14 LangGraph nodes 1 JSON decision per step state saved after every node 100-iteration budget human approval before exploitation

How it is designed

1The model thinks, the harness acts

At every step one model call weighs the whole mission and commits to a single structured decision. Everything around it is plain code: it routes the decision, checks it against the phase and the scope, runs the tools and saves the state.

2You stay in command

The agent starts in the informational phase. Exploitation and post-exploitation each wait for your approval, dangerous tools can be held for confirmation one by one, and you can send guidance or stop the run while it works.

3Nothing is trusted alone

Scope checks run before the first step and refuse when in doubt. Tool output is fenced as hostile input, and every graph query is confined to your project. Wherever code can enforce a limit, it does.

4It learns from every step

It grades each result against what actually changed and escalates when it stalls, up to a Deep Think that rewrites the plan. Each attack chain is saved to a graph, so the next session starts from what this one found and what failed.

Core concepts

Watch one engagement run below, then take the agent apart chapter by chapter.

Mission replay · one engagement, step by step
Where the graph isno checkpoint yet
What the operator seesblog.lab.local
0 / 0

Press Play. The replay follows one engagement against a lab web server, from the operator's first message to the final report, and stops whenever the real agent would wait for a human.

Fictional lab target. The node routing, the event order and the gates follow the real code. Tool outputs are shortened. Productivity scores use the real formula with illustrative signal counts.

01

A state machine around one decision

The agent is a LangGraph StateGraph: 14 nodes joined by labelled edges. Nodes are async Python functions, edges are routing rules, and one typed state object travels between them.

Three families of nodes do the work. Reasoning nodes decide: initialize, think and generate_response. Action nodes run tools, one at a time, in parallel waves, or through a fireteam of sub-agents. Human-in-the-loop nodes come in pairs: the await_* nodes stop the run, and the process_* nodes handle your answer when the next run starts.

The model never chooses the next node. It returns a decision, and a plain Python router reads it and picks the edge. After every node, LangGraph saves the whole state to a checkpoint, which lives in PostgreSQL by default. That one fact explains stop and resume, a browser closed mid-run, a backend restart, and approvals that can wait for hours.

InteractiveGraph explorer · click a node, or pick a scenario
reasoningactionfireteamhuman in the loopstart / end

Pick a node or a scenario

Each node shows what it does and where it can route. Each scenario lights the exact path the router takes, with the rule it applies.

!

Pausing ends the run. await_tool_confirmation, await_approval and await_question lead straight to END. The graph does not sit and wait inside a node. Your answer starts a new run on the same thread: it enters at START, and initialize sees the pending answer and routes to the matching process_* node.

MAX_ITERATIONS = 100checkpointer: AsyncPostgresSaverstate: AgentState (TypedDict)
i

Typed state, on purpose. A node can only write fields that are declared on AgentState. LangGraph drops anything else when it merges the update. The only way into the agent's memory is through a declared channel.

02

Before the first thought: initialize

Every operator message enters at START and passes through initialize. Before any reasoning happens, it decides whether the run is a resume, a new objective or a blocked target.

What initialize does, in order
1
Scope guardrails at session start

The hard guardrail checks every in-scope hostname against a fixed list of government, military, education and intergovernmental domains. It cannot be switched off; IP targets are not on any list. Then the soft guardrail, on by default, asks an LLM whether the target is a well-known public site (tech giants, banks, social media, news). Private IPs pass automatically. If the soft check itself fails, the agent refuses: it fails closed. The checks run until the first step is recorded and again whenever the scope changes; a refusal sticks for the rest of the session.

2
Is this a resume?

If an approval, an answer or a tool decision is waiting in the state, the run is routed to process_approval, process_answer or process_tool_confirmation instead of a fresh think.

3
New objective?

A message that arrives after a completed task, or that differs from the current one, becomes a new objective. The finished objective is archived with its findings. One session can hold a sequence of objectives, and they share the session's 100-iteration budget.

4
Classify the request

An LLM classifier picks the attack skill: one of the enabled built-in skills, a user skill (user_skill:<id>) or a <term>-unclassified placeholder. It also extracts the target host, port and CVEs. It has 3 attempts with backoff; if all fail, it falls back to cve_exploit. When the classifier says a new objective needs exploitation, the phase is not raised for it: unless the session is already there, the objective starts in informational and the agent has to ask.

5
Remember earlier sessions

The five most recent finished attack chains of this project are read from the attack-chain graph (EvoGraph), keeping their critical and high findings and their failure lessons. They become the Prior Attack Chain History section of every prompt in this session.

LiveThe hard guardrail · the real rules, running in your browser

The 14 suffix patterns and the 196 protected domains this checker uses are copied from the real list. The Python agent, the recon orchestrator and the web app share one set of test vectors, so all three copies must agree.

What the classifier returns

The classifier answers with a small typed object. Only attack_path_type, required_phase and the target hints are kept; attack_path_type decides which playbook goes into the prompt (chapter 13).

# example: "Exploit CVE-2021-41773 on 10.10.11.42"
{
  "required_phase": "exploitation",
  "attack_path_type": "cve_exploit",
  "secondary_attack_path": "path_traversal",
  "confidence": 0.9,
  "reasoning": "A named CVE against a network service: the Metasploit workflow fits.",
  "detected_service": "http",
  "target_host": "10.10.11.42",  "target_port": null,  "target_cves": ["CVE-2021-41773"]
}
03

Anatomy of a think step

think is the only place where the model decides anything. Each step sends one fresh pair of messages: a large system prompt rebuilt from the state, and a short human message that never changes. The model answers with exactly one JSON object.

There is no chat history. The model never sees its earlier replies as a conversation. Each step, the past is rewritten into the prompt, already summarised. Every step is a self-contained (situation → decision) pair: easy to audit, and cheap to cache.

The system prompt is ordered from the most static text to the most volatile, in four zones. Three hidden markers split it. When the model is called through the Anthropic API client with prompt caching on (the default), each of the first three zones gets its own cache breakpoint. Every step still sends the whole prompt, but on a normal step only the live suffix is processed at full price; the zones above it are read from the cache.

InteractivePrompt assembler · click a section, then trigger an event
EVENT

The live suffix: what gets appended this step

After the state sections, the code appends up to 15 blocks (a couple with a sub-block), most of them conditional, in a fixed order. The last block is always the skill-fit check: the most recent text gets the most attention, so the question "is this still the right attack class?" comes last on purpose.

InteractiveSet the situation, see the blocks

    One reply, two directions

    The answer does two jobs at once. output_analysis looks back: it analyses the output of the tool that ran in the previous step. action, tool_name and tool_args look forward to the next tool. The analysis of step N therefore lives in the JSON of step N+1.

    InteractiveA representative decision, iteration 3 · pick a half

    ↻

    When the JSON is broken. The bad reply and the validation error go back to the model with "fix the error and output EXACTLY ONE valid JSON object". It gets 3 attempts in total (LLM_PARSE_MAX_RETRIES). After that the step ends with a synthetic complete, and so does an LLM call that keeps failing after its transient-error retries, so a broken model can never spin forever.

    +

    A think step can make more than one LLM call. Deep Think (chapter 9) may run before the decision, and the exploit-path search (chapter 10) may run after it and rewrite the chosen action. Both add their token counts to the same step.

    04

    Seven actions, three ways to act

    The decision's action field is the router's input. It has seven possible values. Three of them run tools, each in a different shape.

    AnimatedSame three jobs, three shapes
    Purple = an LLM think · amber = a tool running · teal = merge

    Use a wave when the jobs are single tool calls that share one analysis, such as reading the graph and searching the web at the same time. A wave runs up to 10 tools concurrently (PLAN_MAX_PARALLEL_TOOLS), and the next think analyses every output together.

    Use a fireteam when each angle needs its own chain of reasoning, such as an auth specialist and a header-policy specialist that each iterate. A fireteam uses more LLM calls, and in exchange each angle gets its own focused loop (chapter 11).

    ⛓

    Mutex groups. Some tools share state inside the sandbox: metasploit_console and msf_restart share one long-lived msfconsole. A wave that contains two tools from the same group is rejected whole, before anything runs, rather than racing on shared state.

    05

    The journey of one tool call

    Between the model naming a tool and the tool running, the call passes a chain of checks. Some live in the prompt, some in the graph, some in the executor. Pick a tool and a situation, then send it through.

    InteractiveThe gate gauntlet
    Current phase
    Your answer if it pauses
    06

    Tool output is hostile

    Everything a tool returns comes from a target that may be hostile. A page can carry text that imitates the prompt: a fake heading, a fake system line, a fake end-of-output marker. RedAmon fences every output with a marker whose id is chosen after the output exists.

    Try itWrite a malicious response, then let the agent wrap it
    What the model reads
    Each click draws a fresh 16-hex nonce with the browser's crypto, as secrets.token_hex(8) does.
    1 · The nonce comes last

    The id is generated after the output exists, and the tool never sees it. A hostile page cannot print a closing marker it does not know.

    2 · Forgeries are defanged

    Anything that looks like the marker prefix gets zero-width spaces inserted inside its <<<, shown here as , so it can never match a real marker.

    3 · A standing rule

    Near the top of the agent's prompt, in tier 0, a standing rule tells the model to treat fenced text as data, never as instructions, and to ignore markers it did not see opened.

    The text inside the fence stays verbatim, so the agent still reads payloads exactly. The same fence wraps the older outputs in the step history, the Deep Think inputs, graph data that touches scanner text, and crawled knowledge pages. The single-line form (UNTRUSTED_PREVIEW) is used inside step summaries.

    In the codeprompt_safety.py
    07

    What the agent remembers

    The agent's memory is the state object. Each step it is rendered back into the prompt as Attack Chain Progress. In parallel, it is written to EvoGraph, an attack-chain graph in Neo4j that the next session reads.

    The prompt never carries a raw log. format_chain_context builds it in a fixed order:

    • Findings, de-duplicated and sorted by severity.
    • Failed attempts, each with its lesson.
    • Decisions and fireteam waves.
    • The steps: recent ones in detail, older ones as one-liners.

    Facts the model extracts (ports, services, technologies, vulnerabilities, credentials, sessions) merge into target_info. Lists are merged without duplicates, and the primary target is replaced whenever the model reports a new one.

    InteractiveHow the history degrades with age
    Session length: iteration 35
    InteractiveEvoGraph: the attack chain that outlives the session
    last 20 iterations in detailup to 50 older as one-linersfindings always kept5 chain node typesgraph writes are time-bounded
    08

    Knowing when it is stuck

    An agent that repeats itself is the classic failure. RedAmon asks the model to grade every result, checks the grade against what actually changed, and adds signals computed from the state rather than self-reported. It then turns everything into one score with escalating responses.

    Six verdicts

    Every output_analysis carries a productivity verdict and a rationale. new_info and diagnostic_progress must also cite what was new.

    InteractiveThe honesty audit · the model's claim versus what actually changed
    The model claims
    What the orchestrator observed this step

    One score, five tiers

    Every think step computes a continuous productivity score from seven signals. Most are computed by the orchestrator from the state rather than self-reported, such as how many iterations have passed since the engagement state last grew, so the model cannot simply claim them. The weights shift with session age: early sessions tolerate exploration, late ones punish stalling. Exploitation punishes repeated attempts harder. This calculator runs the real formula.

    Real formulaProductivity score lab
    0.00green
    03479
    
          

    Axis lock-in: the same dial, turned again

    For expensive attempts (credential brute force, directory fuzzing, automated SQLi), an axis extractor reduces each call to the dimensions it holds constant. Three Hydra runs against user admin with three different wordlists collapse onto one axis. The ledger therefore sees the loop even when it is spread over twenty iterations. Each unproductive attempt on the same axis adds the axis weight to the score: at iteration 20 in exploitation, three attempts alone reach 10.2, the critical tier. From red upwards the prompt demands a different hypothesis class, endpoint or credential, not a bigger wordlist.

    InteractiveThe axis ledger · uses the real Hydra extractor
    ≈

    Uniform responses are inconclusive, not negative. When at least 3 of the last 25 steps share the same failure class and the same body-size bucket (32 bytes), and each finished in under 200 ms, a warning is added to the next prompt. The probes most likely never reached the layer under test: a shell-quoting error, a network error, or a 5xx returned almost instantly. Ordinary 4xx answers do not count. The vector must not be marked as tested on that evidence.

    09

    Deep Think: stopping to re-plan

    Deep Think is a second LLM call that runs before the decision, when the situation calls for a new strategy. Its only job is strategy, so it never picks a tool. The result is rendered into every following prompt until a new Deep Think replaces it.

    Situation

    What is known, in a few sentences.

    Competing hypotheses

    Explanations for the evidence, each with its supporting evidence and one probe that tells them apart. At least two are required once a finding is 60% confident or more. The prompt then demands that the next action be one of those probes: a list of guesses becomes an experiment.

    Vectors · approach · priority · risks

    The attack vectors seen, the recommended approach, an ordered priority list, and the risks with their mitigations.

    InteractiveWhen does it fire? Click cells to add events
    phase changetier orange / redtier criticalmodel asks (fires next step)
    Real tokenizerThe novelty check · is the new plan just the old one reworded?
    DEEP_THINK_COOLDOWN_ITERATIONS = 5DEEP_THINK_NOVELTY_JACCARD_MAX = 0.6STATE_GROWTH_HARD_THRESHOLD = 10
    10

    Exploit-path search (LATS)

    The normal loop follows one idea at a time. Exploit-path search turns exploitation into a tree search inspired by Monte Carlo Tree Search (the LATS research pattern). It proposes several probes, fires them together as one parallel wave, scores each real response, pushes the scores up the branch, and sends the next wave to whichever branch is warming up.

    It is not a new node. It is one hook inside think, after the model's reply. On an active turn it evaluates last turn's probes using the model's own response_class labels. It then checks for an exit, selects the best place to search with UCT and expands it into concrete probes. Finally it overrides the action with the next probes: one tool call, or a plan_tools wave with at most one probe per mutex group. They flow through the same router, approval gate and executor as any other tool call. Selection, scoring and backpropagation are pure arithmetic; only expansion calls the LLM.

    PlaygroundSearch a login page · you play the target
    Exploration vs focus (UCT c)1.4
    Prune floor0.15
    The response class for the probe in flight
    ⇄

    Shadow mode versus drive mode. When the feature is on, it drives by default: its proposals become the real probes, and the finished tree (its 40 highest-value nodes, with their lessons) is carried into the agent's context. In shadow mode it still builds and expands its own tree, and those expansion calls still cost tokens, but it only scores its proposals against what the agent actually ran with the same tool. It never changes the action and never feeds the tree back; the agent cannot see it.

    off by default · drives when onexploitation phaseactivates with ≥ 2 credible probesbudget 50 rollouts (one per wave)depth 6 · width 6 · 120 nodesre-triggers at score 3.0, below Deep Think's 4.0a foothold or a spent budget ends the runDeep Think muted while it drives
    11

    Fireteam: parallel specialists

    When an objective splits into independent angles that each need several steps of reasoning, the root agent can deploy a fireteam: a few specialist sub-agents (five at most by default) running in parallel inside the same process. Each runs its own small ReAct graph.

    deploy_fireteam builds one isolated state per member: a snapshot of the parent's target info, phase and skill, a read-only copy of its findings, failures, decisions and recent steps, the sibling missions (marked out of scope), and the member's own mission and toolbox. The deploy node starts one task per member under a semaphore. The members interleave on one event loop, with no message bus and no talking to each other.

    fireteam_collect merges every member's target-info delta into the parent and appends their findings, each tagged with the specialist that found it. The root then thinks again with the merged picture.

    The member graph · 5 nodes
    InteractiveA wave of specialists · answer each one in any order
    Max concurrent members5
    Bounded

    At most 5 members and 5 running at once by default, 10 iterations per member and a wall-clock limit per wave. A member that runs out of iterations ends as partial and keeps its findings.

    No recursion, frozen phase

    A member cannot deploy its own fireteam, change phase, ask you a question or switch skill; any of those is turned into complete. Its tools are scoped to its mission: a tool outside that toolbox is challenged once for a stated reason, then allowed.

    Same rails

    Members use the same executor, so phase gates, Rules of Engagement and dangerous-tool approvals all apply. Each member parks on its own approval card: the members already running carry on, the parked one keeps its concurrency slot, and an unanswered card is rejected after 10 minutes.

    12

    The human in the loop

    The operator can steer at any point. Some controls are pauses built into the graph. Others act on a running graph without stopping it.

    InteractiveThe three phases · click a transition
    InteractiveGuidance while it works
    Guidance queue (per session)
    Prompt suffix · block 14
    13

    Skills: swappable expertise

    A skill is a playbook injected into the prompt. Choosing one changes the playbook inside the tools section and the attack-skill block, both of which live in tier 2 of the prompt. The code stays the same.

    Built-in attack skills · 14

    Written in code, each with a step-by-step workflow, many with their own settings. Each one can be switched on or off per project. The classifier picks from the enabled ones, and they drive the tool guidance.

    User attack skills · up to 20 on

    Markdown playbooks a user uploads, or imports from the community catalogue. They are off until a project turns them on, and they compete in the classifier as user_skill:<id>.

    Chat skills · on demand

    Reference docs (frameworks, protocols, tools) injected with /skill <name>. During a run they travel through the guidance queue as [CHAT SKILL: name] and leave the attack class alone. Sent with a new message, they are prepended to the objective that the classifier reads.

    ExploreThe 14 built-in skills and their project defaults

    Changing class mid-run

    The skill-fit check closes every prompt. If the live evidence fits a different enabled class better, the action this turn must be switch_skill. The new class is validated: it must be an enabled built-in skill, an enabled user_skill:<id> or a <term>-unclassified. A rejected switch is written into the step history so the model sees it. The router then sends the run straight back to think, and the next prompt re-renders tier 2 with the new playbook.

    When a switch_skill lands on a concrete class while the agent is still in informational, the phase flips to exploitation on its own, but only if the project does not require approval for exploitation. With the default settings nothing flips: the agent must request transition_phase itself, and that waits for your approval.

    🔌

    Plugin rewrites of skill text. A plugin can propose a new version of a built-in skill's static text. It reaches the prompt only after a person approves it, and only while it still matches the factory text it was based on (checked by hash). Every part of the skill must pass validation: no hidden characters, no fake prompt headings, no untrusted-output fences or cache markers, and a size cap. If any part fails validation, the whole skill falls back to the factory text.

    14

    Workspace and long jobs

    Every project has a sandboxed folder that the agent and every fireteam member work in. It is how big outputs stay out of the prompt and how slow scans avoid blocking a step.

    /workspace/<projectId>/
    ├── notes/          the agent's scratch (fs_write, fs_edit)
    ├── tool-outputs/   big outputs land here automatically
    │     2026-10-23T09-41-12Z-execute_nuclei.txt
    ├── jobs/           background runs: <id>.log + .meta.json
    └── uploads/        your inbox: listed in the prompt

    24 fs_* tools read, write, edit with undo, search and archive inside that root. Every path is resolved and rejected if it escapes the root. Archive extraction refuses zip-slip, escaping links and zip bombs.

    5 job_* tools run any tool as a background task, so a long ffuf or nuclei run does not hold up a wave that waits for its slowest step. job_spawn checks the phase of the tool it wraps, so it cannot be used to run a tool the phase forbids. Up to 16 jobs run at once across the agent, fewer when memory is tight.

    InteractiveAuto-offload
    Tool output size
    15

    Safety, layer by layer

    No single check is trusted alone. The layers overlap on purpose: code catches what the prompt cannot enforce, and the prompt explains what the code would otherwise only refuse. Some limits live only in the prompt, and each layer says where it is enforced.

    ExampleEvery graph query is rewritten to your project

    For query_graph, a translator LLM writes Cypher from the agent's question. The tenant filter then rewrites the query before it runs. Every node pattern gets the project's ids and excludes muted findings. A write is refused outright and the error goes straight back to the agent. A pattern that cannot be scoped, a dangerous procedure, a variable-length path or a query that fails to run is retried, with the error sent back to the translator, up to 3 attempts in total.

    The translator writes
    What runs on Neo4j
    16

    Be the harness

    Twelve situations from real runs. You play the orchestrator: decide what the code does next.

    0 / 12 answered