Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ReAgent

For a decoupled local progress window with optional worker controls, see the [live monitor guide](docs/live-monitor.md).

[![PyPI](https://img.shields.io/pypi/v/auto-re-agent)](https://pypi.org/project/auto-re-agent/)
[![Python](https://img.shields.io/pypi/pyversions/auto-re-agent)](https://pypi.org/project/auto-re-agent/)
[![CI](https://github.com/Dryxio/reagent/actions/workflows/ci.yml/badge.svg)](https://github.com/Dryxio/reagent/actions/workflows/ci.yml)
Expand Down
44 changes: 44 additions & 0 deletions docs/live-monitor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Live progress monitor

`re-agent monitor` serves a local dashboard independently of the reversal worker. It reads existing session JSON files and logs; it does not import model providers or start work automatically.

```sh
re-agent monitor --work-dir /path/to/project --session-glob re-agent-progress.json --total 500
```

Open the printed `http://127.0.0.1:8765` address. The page refreshes every two seconds and shows saved function results, review passes, failures, rounds, and recent log output. Omit `--total` when the planned count is unknown. A review pass is not a proof of compilation or binary equivalence.

Session patterns are relative to `--work-dir` and can be repeated. For a batch runner:

```sh
re-agent monitor --work-dir /path/to/project --session-glob 'reports/batch-*/progress.json' --log-glob 'reports/batch-*/stderr.log' --total 500
```

The monitor deduplicates function addresses across matching files using their latest result timestamps. Select sessions for the same binary/version; unrelated projects can reuse addresses. Unchanged files are cached, partial JSON writes retain the last readable snapshot, and log output is bounded to the last 7,000 bytes. Counters describe saved final results, not in-flight rounds.

## Optional worker controls

Install the optional process-management dependency:

```sh
pip install 'auto-re-agent[monitor]'
```

Supply an explicit argument array after `--worker`, which must be the last monitor option:

```sh
re-agent monitor --work-dir /path/to/project --session-glob re-agent-progress.json --total 500 --worker re-agent --config re-agent.yaml reverse --manifest targets.json --max-functions 500
```

On Windows, quote paths containing spaces and use an executable such as `python.exe` or `re-agent.exe`, rather than a shell script. Worker arguments are passed directly, without shell interpretation.

- **Start / resume** launches the configured command. Resuming completed work depends on that command's checkpoint behavior. ReAgent uses its configured session and attempt limits; the monitor does not reset them.
- **Stop run** terminates the owned worker process tree. Saved session files remain intact. An interrupted model call may need to be repeated.
- Closing the browser or monitor host leaves the worker running. Restart the monitor with the same work directory, state directory and worker command to reconnect.
- Duplicate starts sharing a state directory are protected by a process lock. Adoption checks the command, working directory record, PID and process creation time, so stale PID records do not attach to unrelated processes.

Control records and worker stdout/stderr live in `reports/monitor` by default; override with `--state-dir`. Treat these as local run artifacts, not source files. `--port` changes the port; zero selects an available port.

## Local access

The server binds only to IPv4 loopback. Host-header checks reject DNS-rebinding requests. Control endpoints require a random per-host token and reject cross-origin requests. HTTP clients cannot supply a different worker command or arbitrary file path. No external scripts, fonts, telemetry or services are used by the dashboard. This is a local tool, not a multi-user network service.
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ dependencies = [
]

[project.optional-dependencies]
monitor = ["psutil>=7.0"]
ghidra-bridge = ["ghidra-ai-bridge>=0.2.0"]
headless = ["ghidra-ai-bridge[headless]>=0.2.0"]
benchmark = ["pefile>=2024.8.26", "unicorn>=2.1"]
dev = ["pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.8", "mypy>=1.13"]
dev = ["pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.8", "mypy>=1.13", "psutil>=7.0"]

[project.scripts]
re-agent = "re_agent.cli.main:main"
Expand Down Expand Up @@ -74,7 +75,7 @@ warn_return_any = true
warn_unused_configs = true

[[tool.mypy.overrides]]
module = ["anthropic.*", "openai.*"]
module = ["anthropic.*", "openai.*", "psutil.*"]
ignore_missing_imports = true

[tool.pytest.ini_options]
Expand Down
23 changes: 21 additions & 2 deletions src/re_agent/agents/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from re_agent.llm.observed import CallBudget, ObservedProvider
from re_agent.llm.protocol import LLMProvider
from re_agent.parity.source_indexer import SourceIndexer
from re_agent.verification.candidate import extract_candidate_body
from re_agent.verification.objective import verify_candidate


Expand Down Expand Up @@ -128,8 +129,25 @@ def run_fix_loop(
log_path = log_dir / f"round{round_num}-{timestamp}-reverser.json"
log_path.write_text(json.dumps(log_entry, indent=2), encoding="utf-8")

# Check
verdict = checker.check(code, target)
# Avoid spending a reviewer call on output the configured candidate gate
# cannot consume. Keep the same repair/checkpoint path as other failures.
preflight_error = None
if candidate_gate is not None:
try:
extract_candidate_body(code)
except ValueError as exc:
preflight_error = str(exc)
if preflight_error:
checker.last_prompt = ""
checker.last_response = ""
verdict = CheckerVerdict(
verdict=Verdict.FAIL,
summary="Local candidate preflight failed; model review skipped",
issues=[preflight_error],
fix_instructions=["Return exactly one complete function definition without helper definitions."],
)
else:
verdict = checker.check(code, target)
last_verdict = verdict

objective_verdict: ObjectiveVerdict | None = None
Expand All @@ -148,6 +166,7 @@ def run_fix_loop(
"round": round_num,
"timestamp": timestamp,
"phase": "check",
"checker_skipped": preflight_error is not None,
"prompt": checker.last_prompt,
"response": checker.last_response,
"verdict": verdict.verdict.value,
Expand Down
2 changes: 1 addition & 1 deletion src/re_agent/agents/prompts/checker_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ You are a reverse engineering quality checker. Your job is to verify that revers

Verification standards:
- Every line of Ghidra logic must have corresponding source code
- Every struct offset must map to a named member
- Use supplied type evidence to verify named members. When no layout is available, accept explicit byte offsets that preserve access widths and semantics; do not fail code solely for lacking member names or demand invented structs.
- Every function call must be identified and matched
- Expression order must match exactly (floating point is order-sensitive)
- No missing branches, conditions, or edge cases
Expand Down
4 changes: 2 additions & 2 deletions src/re_agent/agents/prompts/reverser_task.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ ${project_rules}

Requirements:
1. Match every branch and call from the decompile
2. Map all offsets (e.g. param_1 + 0x88) to real member names
2. Use member names only when supported by supplied type evidence. Otherwise preserve the exact byte offset and access width, and document the unresolved layout; do not invent structs or member names.
3. Preserve exact expression/operand order
4. Use existing project patterns and naming conventions
5. Output the complete function implementation in a ```cpp block
5. Output exactly one complete function implementation in a ```cpp block. Do not wrap it in namespace/class definitions or add helper type/function definitions; report missing declarations outside the code block.
6. End with: REVERSED_FUNCTION: ${class_name}::${function_name} (${address})
22 changes: 22 additions & 0 deletions src/re_agent/cli/cmd_monitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Serve a local progress dashboard without importing model providers."""
from __future__ import annotations

import argparse
import contextlib
from pathlib import Path


def cmd_monitor(args: argparse.Namespace) -> int:
from re_agent.monitor.server import Monitor, make_server

if not 0 <= args.port <= 65535:
raise ValueError("Port must be between 0 and 65535")
root = Path(args.work_dir).resolve()
monitor = Monitor(root, root / args.state_dir, args.session_glob or ["re-agent-progress.json"],
args.log_glob, args.total, args.worker)
with make_server(monitor, args.port) as server:
print(f"Live monitor: http://127.0.0.1:{server.server_port}", flush=True)
print("Closing the monitor does not stop the worker. Use Stop run to terminate it.", flush=True)
with contextlib.suppress(KeyboardInterrupt):
server.serve_forever()
return 0
14 changes: 14 additions & 0 deletions src/re_agent/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ def build_parser() -> argparse.ArgumentParser:

sub = parser.add_subparsers(dest="command", help="Available commands")

monitor_p = sub.add_parser("monitor", help="Host a local live progress dashboard")
monitor_p.add_argument("--work-dir", default=".", help="Directory containing run sessions")
monitor_p.add_argument("--state-dir", default="reports/monitor", help="Monitor control state and worker logs")
monitor_p.add_argument("--session-glob", action="append", help="Relative session pattern (repeatable)")
monitor_p.add_argument("--log-glob", help="Relative activity-log pattern; shows most recently modified log")
monitor_p.add_argument("--total", type=int, default=0, help="Planned function count; 0 means unknown")
monitor_p.add_argument("--port", type=int, default=8765)
monitor_p.add_argument("--worker", nargs=argparse.REMAINDER, help="Optional worker argv; must be the last option")

doctor_p = sub.add_parser("doctor", help="Check configuration and exported evidence without LLM calls")
doctor_p.add_argument("--address", help="Check evidence for one function")
benchmark_p = sub.add_parser("benchmark", help="Run a differential harness manifest")
Expand Down Expand Up @@ -78,6 +87,11 @@ def _main(argv: list[str] | None = None) -> int:
parser.print_help()
return 0

if args.command == "monitor":
from re_agent.cli.cmd_monitor import cmd_monitor

return cmd_monitor(args)

if args.command == "evidence":
from re_agent.cli.cmd_evidence import cmd_evidence

Expand Down
1 change: 1 addition & 0 deletions src/re_agent/monitor/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Optional local progress dashboard; independent of the reversal worker."""
Loading
Loading