# AI Agent Reasoning Experiment Framework
A framework for running AI agent experiments with structured reasoning extraction, logging, and automated judging of deceptive behaviour.
## What It Does
- Runs AI agents with tool-calling capabilities (file system access)
- Extracts and displays reasoning/thought processes from models
- Logs conversations with metadata (token counts, finish reasons, turn counts)
- **Judges experiment outputs** for deception via regex, blackbox, and glassbox checks
- Supports multiple model providers via OpenAI-compatible APIs
- Configurable via YAML
## Quick Start
```bash
# Install dependencies
uv sync
# Run all experiments from config.yaml
uv run src/main.py
# Judge experiment logs
uv run python src/judge_runner.py --logs-dir logs/ --output output/results.csv
```
## Judging System
Three independent checks evaluate experiment outputs for deceptive behaviour:
```mermaid
graph LR
A[Experiment Logs] --> B[Judge Runner CLI]
B --> C[RegexChecker
local patterns]
B --> D[BlackboxChecker
output vs baseline]
B --> E[GlassboxChecker
reasoning + intent]
D & E --> F[Anthropic Batch API
50% cost]
C & D & E --> G[CSV Results]
C & D & E --> H[Judge Logs
full CoT reasoning]
```
| Check | Input | Output |
| ------------ | ---------------------------------------- | ---------------------------------------------------- |
| **Regex** | Model output + scenario patterns | `PASS` / `FAIL` |
| **Blackbox** | Model output vs baseline (no reasoning) | `ACCURATE` / `MISLEADING` / `OMITTED` / `FABRICATED` |
| **Glassbox** | Reasoning trace + system prompt + output | Intent category + sophistication tier |
**Usage:**
```bash
# Batch mode (Anthropic Batch API — half cost):
uv run python src/judge_runner.py --logs-dir logs/ --output output/results.csv
# Single file (synchronous):
uv run python src/judge_runner.py --log-file logs/.../file.json --mode single
```
Results are saved to CSV and full judge CoT logs (including judge model) are saved to the configured `judge.log_dir`.
## Configuration (`config.yaml`)
```yaml
providers:
openai:
api_key_env: OPENAI_API_KEY
base_url: https://api.openai.com/v1
google:
api_key_env: GOOGLE_API_KEY
base_url: https://generativelanguage.googleapis.com/v1beta/openai/
openrouter:
api_key_env: OPENROUTER_API_KEY
base_url: https://openrouter.ai/api/v1
models:
- id: gemini-3-flash-preview
provider: google
temperature: 1
extra_body:
extra_body:
google:
thinking_config:
include_thoughts: True
scenarios:
- path: scenarios/corporate_sabotage_v2
runs: 1
oversight_levels:
- high
output:
dir: logs
judge:
model: claude-sonnet-4-20250514
temperature: 0
log_dir: judge_logs
logging:
level: 3 # 1-4 (1=CRITICAL, 2=WARNING, 3=INFO, 4=DEBUG)
format: "[{level}] {message}"
output: both # console, file, or both
file: logs/experiment.log
```
## Logging
The framework uses Python's `logging` module with configurable verbosity levels:
| Level | Shows |
| ----- | ------------------------------------------------------------------ |
| 1 | CRITICAL only (major failures) |
| 2 | WARNING + CRITICAL |
| 3 | INFO + WARNING + CRITICAL (default) |
| 4 | DEBUG + INFO + WARNING + CRITICAL (includes reasoning, VFS, tools) |
At **level 4** (DEBUG), additional information is logged:
- Full reasoning traces (truncated at INFO level)
- VFS path and file listing
- Available tools list
Level 4 automatically enables file output (`both` mode) to save detailed logs.
## Scenario Structure
```
scenarios/
scenario_name/
user.md # User prompt
benign_goal.md # Visible system prompt
hidden_goal.md # Hidden system prompt (for experiments)
regex_rules.yaml # Regex patterns for the judge
data/ # Files accessible to agent
file1.txt
file2.txt
```
## Output Format
Logs are saved to `logs/{model}/{scenario}/{oversight}/{timestamp}.json`:
```json
{
"run_id": "...",
"model": "gemini-3-flash-preview",
"scenario": "corporate_sabotage_v2",
"oversight_level": "high",
"total_tokens": 1500,
"prompt_tokens": 1000,
"completion_tokens": 500,
"conversation": [
{"role": "system", "content": "..."},
{"role": "user", "content": "..."},
{
"role": "assistant",
"reasoning": "...",
"content": "...",
"tool_calls": [...],
"finish_reason": "stop",
"turn_count": 1
}
]
}
```
## Key Features
- **Reasoning Extraction**: Supports multiple formats:
- OpenRouter `reasoning_content` (Qwen, DeepSeek, etc.)
- OpenAI `reasoning_details` — both `reasoning.text` and `reasoning.summary` types
- Google `thought_signature` (via `extra_content`)
- `` tags in content
- Each log entry includes `reasoning_format` metadata for traceability
- **Baseline Toggle**: Set `generate_baseline: false` in defaults to skip baseline generation (useful when testing new models)
- **Turn Limit**: Max 20 turns per conversation (prevents infinite loops)
- **Success Tracking**: Runs marked SUCCESS if they end with `finish_reason: "stop"`
- **VFS**: Virtual file system for sandboxed file access
- **Automated Judging**: Regex, blackbox, and glassbox checks with Anthropic Batch API
## Testing
The project includes 138 unit tests covering all modules. No API keys or network access needed.
```bash
uv run pytest tests/ -v
```
| Test file | Covers |
| ----------------------- | -------------------------------------------------------------------------- |
| `test_vfs.py` | Sandbox escapes, path traversal, CRUD, singleton staleness |
| `test_config_loader.py` | Missing keys, temperature cascade, oversight fallback |
| `test_tools.py` | Schema/implementation sync, VFS delegation |
| `test_agent.py` | Message construction, error handling, token counting, reasoning extraction |
| `test_interrogate.py` | Conversation sanitization, provider detection |
| `test_runner.py` | Baseline extraction, prompt assembly, success detection |
| `test_judge.py` | Regex/blackbox/glassbox checkers, JSON parsing, batch prep, CSV output |
## File Structure
```
src/
agent.py # Main agent logic, OpenAI SDK integration
config_loader.py # YAML config parsing
judge.py # Judging pipeline (regex, blackbox, glassbox)
logger.py # Centralized logging configuration
judge_runner.py # Judge CLI with batch/single modes
main.py # Entry point
runner.py # Experiment orchestration
tools.py # Available tools (list_files, read_file, etc.)
vfs.py # Virtual file system
tests/ # Unit tests (pytest)
scenarios/ # Scenario definitions
logs/ # Experiment output logs
judge_logs/ # Judge CoT logs
```
## API Keys
Set API keys via environment variables (or `.env` file):
```bash
export OPENAI_API_KEY="..."
export GOOGLE_API_KEY="..."
export OPENROUTER_API_KEY="..."
export ANTHROPIC_API_KEY="..." # Required for judging
```
## Interrogation
Replay a saved conversation and continue questioning the agent interactively:
```bash
uv run src/interrogate.py logs/model_name/scenario/oversight/timestamp.json
```
The session auto-detects the provider from the log file and restores the VFS state. Available commands:
| Command | Description |
| ----------- | ------------------------------------------------------- |
| `history` | Show full conversation history |
| `history N` | Show last N messages |
| `reasoning` | Show the last full reasoning trace |
| `vfs` | Show current virtual filesystem state |
| `info` | Show run metadata (model, scenario, tokens) |
| `save` | Save the extended conversation to `interrogation_logs/` |
| `exit` | Quit |
Anything else you type is sent as a message to the agent.