# How VulnClaw's Persistent Pentesting Mode Handles State Preservation Across Cycles

> Discover how VulnClaw's persistent pentesting mode preserves state. Learn how it serializes session data to disk, ensuring findings and target information survive interruptions and accumulate across cycles.

- Repository: [Unclecheng/VulnClaw](https://github.com/Unclecheng-li/VulnClaw)
- Tags: internals
- Published: 2026-06-30

---

**VulnClaw's persistent pentesting mode preserves state by continuously serializing the `SessionState` object to disk after every autonomous round and cycle, ensuring findings, target data, and reflexion memories survive interruptions and accumulate across multiple bounded execution cycles.**

The `Unclecheng-li/VulnClaw` repository implements a robust state preservation architecture that transforms autonomous security testing from a single-shot operation into a long-running, resumable process. Unlike standard pentesting loops that lose context upon termination, persistent mode treats the `SessionState` as the single source of truth, incrementally writing snapshots to JSON files after each execution round. This design enables assessments to pause, resume, or iterate continuously while building a cumulative vulnerability knowledge base.

## How Persistent Mode Maintains Session Continuity

At the core of VulnClaw's persistence mechanism lies the `SessionState` object, which acts as the container for all discovered intelligence, execution history, and target metadata.

### The SessionState Object as the Single Source of Truth

The `SessionState` class holds the current target definition, discovered findings, operational notes, and a complete list of executed steps. Within the `auto_pentest` loop in [`vulnclaw/agent/loop_controller.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/agent/loop_controller.py), every round concludes with an explicit call to persist this state. According to the source code at lines 61-62, the method invokes `agent.context.state.save()` to write the entire state object to a JSON file under the `sessions/` directory:

```python

# Inside auto_pentest → after each round

agent.context.state.save()

```

This operation guarantees that later rounds or entirely new cycles start from the exact same state, eliminating data loss between bounded execution windows.

### Continuous Persistence After Each Round

The persistence mechanism operates synchronously with the execution loop. After the agent completes a round of autonomous testing, the system immediately serializes the updated context. This approach ensures that even if the process terminates unexpectedly, the last completed round's state remains intact on disk.

### Reflexion Memory Integration at Cycle Boundaries

When a cycle completes, the agent may have accumulated *reflexion* data—statistics about failed paths and constraint observations that inform future decision-making. As implemented in [`vulnclaw/agent/loop_controller.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/agent/loop_controller.py) at lines 80-84, the system checks for the presence of `_save_reflexion_snapshot` and merges this memory back into the session before writing the final state:

```python
if hasattr(agent, "_save_reflexion_snapshot"):
    agent._save_reflexion_snapshot()
    agent.context.state.save()

```

This step ensures that learned behaviors and historical failure patterns persist across cycle boundaries, improving the efficiency of subsequent iterations.

## Cycle Architecture and Context Reuse

The `persistent_pentest` method orchestrates multiple autonomous runs while maintaining a single, shared `AgentContext` instance.

### Bounded Cycles with Shared State

Rather than recreating the execution context for each iteration, the method initializes `AgentContext` once and reuses it across all cycles. As shown in lines 30-46 of [`vulnclaw/agent/loop_controller.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/agent/loop_controller.py), the loop repeatedly calls `auto_pentest` with the same state object:

```python
results = await agent.auto_pentest(
    user_input=...,  # Include previous findings

    target=agent.context.state.target,
    max_rounds=rounds_per_cycle,
    on_step=_make_step_callback(cycle_num),
    stream_sink=stream_sink,
)

```

Because the `agent.context.state` object is never recreated, findings discovered in earlier cycles remain immediately available for reference in later cycles, enabling compound discoveries where each cycle builds upon the last.

## Configuration-Driven Resource Limits

To prevent runaway resource consumption during long-running assessments, VulnClaw exposes configurable limits through its schema definition in [`vulnclaw/config/schema.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/config/schema.py) at lines 280-284:

```python
persistent_rounds_per_cycle: int = Field(
    default=100, description="Rounds per persistent pentest cycle"
)
persistent_max_cycles: int = Field(
    default=10, description="Max cycles for persistent pentest (0=unlimited)"
)

```

These settings allow operators to cap CPU usage and API calls while still preserving progress between cycles. When `persistent_max_cycles` is set to 0, the system runs indefinitely until manually interrupted, with state preservation occurring at every step.

## Automatic Cycle Reporting and Artifact Generation

At the conclusion of each cycle, the system generates persistent cycle reports that complement the JSON state files. The `generate_persistent_cycle_report` function in [`vulnclaw/report/generator.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/report/generator.py) (starting at line 589) creates markdown artifacts containing cycle numbers, timestamps, and snapshots of the current `SessionState`:

```python
from vulnclaw.report.generator import generate_persistent_cycle_report

report_path = generatepersistent_cycle_report(
    session=agent.context.state,
    cycle_num=cycle_num,
    ...
)

```

These reports serve as human-readable checkpoints alongside the machine-readable JSON state files, facilitating audit trails and manual review of intermediate findings.

## Practical Implementation Examples

### CLI Usage

Launch a persistent pentest against a target using default configuration values:

```bash
vulnclaw persistent example.com

```

### Programmatic Control with Custom Limits

Run a persistent pentest from Python with explicit cycle and round limits:

```python
from vulnclaw.agent.core import AgentCore
from vulnclaw.config import load_config

cfg = load_config()  # Loads config including persistent settings

agent = AgentCore(cfg)

# Launch with 200 rounds per cycle, max 5 cycles

cycle_results = await agent.persistent_pentest(
    user_input="Perform an authorized persistent penetration test against example.com.",
    target="example.com",
    rounds_per_cycle=200,
    max_cycles=5,
    auto_report=True,
)

print(f"Completed {len(cycle_results)} cycles – final report at {cycle_results[-1].report_path}")

```

### Recovering and Inspecting Saved State

To resume a session or inspect accumulated findings offline, load the JSON state file directly:

```python
from vulnclaw.target_state.store import load_session_state

state = load_session_state("sessions/example.com.json")
print(state.findings)  # Shows all vulnerabilities discovered across cycles

```

## Key Files and Responsibilities

| File | Purpose | Direct Link |
|------|---------|-------------|
| [`vulnclaw/agent/loop_controller.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/agent/loop_controller.py) | Contains `auto_pentest` loop and `persistent_pentest` wrapper logic | [loop_controller.py](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/agent/loop_controller.py) |
| [`vulnclaw/config/schema.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/config/schema.py) | Defines `persistent_rounds_per_cycle` and `persistent_max_cycles` configuration fields | [schema.py](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/config/schema.py) |
| [`vulnclaw/report/generator.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/report/generator.py) | Implements `generate_persistent_cycle_report` for markdown artifact generation | [generator.py](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/report/generator.py) |
| [`vulnclaw/target_state/store.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/target_state/store.py) | Handles JSON serialization/deserialization of `SessionState` objects | [store.py](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/target_state/store.py) |
| [`vulnclaw/cli/main.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/cli/main.py) | CLI entry point that routes `persistent` commands to `AgentCore` | [cli/main.py](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/cli/main.py) |

## Summary

- **SessionState is the single source of truth**: The `agent.context.state` object captures target data, findings, notes, and execution history, with `save()` called after every round in [`loop_controller.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/loop_controller.py) lines 61-62.
- **Reflexion data persists across cycles**: The system merges failure-pattern statistics back into the session state at cycle boundaries (lines 80-84) before writing the final snapshot.
- **Context reuse enables accumulation**: The `persistent_pentest` method reuses the same `AgentContext` across all cycles (lines 30-46), ensuring findings compound rather than reset.
- **Configuration controls resource usage**: `persistent_rounds_per_cycle` and `persistent_max_cycles` in [`config/schema.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/config/schema.py) limit execution while preserving intermediate state.
- **Dual-format persistence**: JSON state files enable programmatic resumption, while markdown cycle reports from [`generator.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/generator.py) provide human-readable audit trails.

## Frequently Asked Questions

### What happens if a persistent pentest is interrupted mid-cycle?

If the process terminates during a cycle, the state remains preserved up to the last completed round. Since `agent.context.state.save()` executes after every round in [`loop_controller.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/loop_controller.py), resuming the session reloads the most recent snapshot from the `sessions/` directory, losing only the in-progress round's partial data.

### How does reflexion memory improve state preservation?

Reflexion memory stores statistics about failed exploitation paths and environmental constraints observed during previous cycles. By calling `_save_reflexion_snapshot` and re-saving state at cycle boundaries (lines 80-84), VulnClaw ensures that the agent avoids previously failed approaches in subsequent cycles, making the persistent mode increasingly efficient over time.

### Can I resume a pentest from a saved session file on a different machine?

Yes. The `load_session_state` function in [`vulnclaw/target_state/store.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/target_state/store.py) deserializes the JSON state file independently of the original execution environment. Transferring the [`sessions/target.json`](https://github.com/Unclecheng-li/VulnClaw/blob/main/sessions/target.json) file to another system with VulnClaw installed allows immediate resumption, provided the target network configuration remains accessible.

### What are the default limits for persistent pentesting cycles?

According to [`vulnclaw/config/schema.py`](https://github.com/Unclecheng-li/VulnClaw/blob/main/vulnclaw/config/schema.py) lines 280-284, the default configuration allows 100 rounds per cycle (`persistent_rounds_per_cycle`) and a maximum of 10 cycles (`persistent_max_cycles`). Setting `persistent_max_cycles` to 0 removes the upper bound, enabling indefinite execution with continuous state preservation.