AGENTS.md · git:20260302.f1fd9df · 2026-03-02 · sha256 802d1bbc9132a5da

AGENTS.md git:20260302.f1fd9dfA

Immutable. This exact content is served forever at /api/v1/blob/802d1bbc9132a5da.

# AGENTS.md

## Commands

- `pip install -e ".[dev]"` -- Install with dev dependencies
- `pytest` -- Run all 157 tests (no network required)
- `pytest tests/test_pipeline.py -v` -- Run single test module
- `pytest --cov=little_canary --cov-report=term-missing` -- Run with coverage (80% threshold)
- `ruff check little_canary/` -- Lint
- `ruff check little_canary/ --fix` -- Lint with auto-fix
- `mypy little_canary/` -- Type check
- `python -m build` -- Build wheel and sdist

## Testing

- Framework: `pytest` with `pytest-cov`
- Config: `pyproject.toml` under `[tool.pytest.ini_options]`
- Test location: `tests/` directory, one file per source module
- All tests run offline -- no Ollama or network required
- Tests mock HTTP calls via monkeypatching `requests.post`
- Marker `@pytest.mark.slow` for tests needing network mocks
- Coverage floor: 80% (`fail_under = 80` in pyproject.toml)

Run benchmarks (requires Ollama running locally):
```bash
cd benchmarks && python3 run_fp_test.py                                    # false positive test
cd benchmarks && python3 red_team_runner.py --canary qwen2.5:1.5b         # adversarial dashboard
cd benchmarks && python3 full_pipeline_test.py --canary qwen2.5:1.5b      # end-to-end
```

## Project Structure

```
little_canary/               # Installable package
  __init__.py                # Public exports: SecurityPipeline, CanaryProbe, etc.
  structural_filter.py       # Layer 1: regex + decode-then-recheck (base64, hex, ROT13)
  canary.py                  # Layer 2: sacrificial LLM probe via Ollama /api/chat
  analyzer.py                # Behavioral analysis of canary response (regex strategies)
  judge.py                   # Experimental LLM-based judge (replaces analyzer)
  pipeline.py                # Orchestrator: wires layers, mode logic (block/advisory/full)
tests/                       # Unit tests (157 tests, all offline)
examples/                    # Integration examples
benchmarks/                  # TensorTrust red team, false positive tests, dashboard
```

Pipeline flow: `SecurityPipeline.check(input)` -> structural filter (regex, ~1ms) -> canary probe (Ollama, ~250ms) -> behavioral analysis -> verdict.

## Code Style

- Python 3.8+ compatibility required (no walrus `:=`, no `match`, no `X | Y` unions)
- Dataclasses for all structured data (no Pydantic, no TypedDict)
- Type hints on all public method signatures
- `logging.getLogger(__name__)` in every module
- Private methods prefixed with `_`
- Line length: 120 (ruff config)
- Linter: ruff with `E, F, W, I, UP, B, SIM` rules

Good:
```python
@dataclass
class CanaryResult:
    response: str
    latency_ms: float
    model: str
    blocked: bool = False
    reason: str = ""
```

Bad:
```python
# No raw dicts for structured data
result = {"response": resp, "latency": t, "model": m}

# No Python 3.10+ syntax
def check(self, text: str | None = None) -> bool:  # use Optional[str]
```

Detection patterns are tuples of `(regex_pattern, description_string)`:
```python
# Good: add to _build_patterns() in structural_filter.py
raw_patterns = [
    (r"\bignore\s+(all\s+)?(previous|prior|above)\b", "instruction override attempt"),
]
```

## Git Workflow

- Branch from `main`
- Conventional commits: `feat:`, `fix:`, `test:`, `docs:`, `chore:`
- Run `pytest` and `ruff check` before pushing
- Do not modify `benchmarks/prompts.json` or `benchmarks/prompts_fp_realistic.json` without re-running benchmarks

## Boundaries

**Always:**
- Run `pytest` after modifying source files
- Keep `requests` as the only runtime dependency
- Maintain Python 3.8 compatibility
- Use dataclasses for new data structures
- Preserve fail-open behavior (errors return `should_block=False`)

**Ask first:**
- Adding new runtime dependencies
- Changing the Ollama API contract (`/api/chat` format)
- Modifying benchmark prompt datasets
- Changing the mode logic in `pipeline.py`
- Altering the public API surface (`__all__` in `__init__.py`)

**Never:**
- Break Python 3.8 compatibility
- Add blocking behavior to error paths (fail-open is deliberate)
- Include raw user input in `PipelineVerdict.to_dict()` output
- Commit API keys or model credentials
- Delete or skip failing tests without understanding root cause