diff --git a/docs/concepts/overview.md b/docs/concepts/overview.md
index c0a8a83..21cb9a1 100644
--- a/docs/concepts/overview.md
+++ b/docs/concepts/overview.md
@@ -27,26 +27,17 @@ RAMPART ships with the following probes; more will be added:
---
-## Component Model
+## Architecture at a Glance
-Every RAMPART test involves these components:
+RAMPART integrates within your **consumer package** (the thin integration layer in your product team's repo) and the **agent under test**, building on PyRIT primitives underneath. The diagram below shows how the layers fit together and where your code plugs in:
-```mermaid
-flowchart LR
- subgraph Your Code
- A[AgentAdapter] --> S[Session]
- SF[Surface]
- end
- subgraph RAMPART
- F[Attacks / Probes] --> E[Execution]
- D[PromptDriver] --> E
- EV[Evaluator] --> E
- E --> R[Result]
- R --> RP[ReportSink]
- end
- E -- "send_async()" --> S
- E -- "inject()" --> SF
-```
+{ loading=lazy }
+
+*RAMPART's four layers — from PyRIT primitives up to the agent under test.*
+
+## Component Model
+
+Zooming in on the runtime path, every RAMPART test wires together the same handful of components:
| Component | You provide | RAMPART provides |
|-----------|-------------|-----------------|
@@ -61,7 +52,13 @@ flowchart LR
## Execution Lifecycle
-Every execution follows a common lifecycle owned by [`BaseExecution`][rampart.core.execution.BaseExecution]:
+A single test run flows from your pytest test, through a RAMPART attack or probe, into your `AgentAdapter`, and out to the agent system — then back again as a `Result`:
+
+{ loading=lazy }
+
+*Request / response cycle for a single test run.*
+
+Under the hood, every execution follows a common lifecycle owned by [`BaseExecution`][rampart.core.execution.BaseExecution], which drives the per-turn loop between the strategy, your adapter, and the evaluator:
```mermaid
sequenceDiagram
diff --git a/docs/guides/authoring-tests.md b/docs/guides/authoring-tests.md
deleted file mode 100644
index 2b6bec9..0000000
--- a/docs/guides/authoring-tests.md
+++ /dev/null
@@ -1,262 +0,0 @@
-# Authoring Tests
-
-Patterns for writing RAMPART safety tests. Assumes you've completed the [Quickstart](../getting-started/quickstart.md).
-
----
-
-## Implementing AgentAdapter and Session
-
-Every RAMPART test needs an adapter that connects your agent to the framework.
-
-### Session Protocol
-
-A [`Session`][rampart.core.adapter.Session] is an async context manager that sends requests and returns responses:
-
-```python
-from rampart import Request, Response, ToolCall
-
-class MySession:
- async def send_async(self, request: Request) -> Response:
- raw = await self._client.chat(request.prompt)
- return Response(
- text=raw["text"],
- tool_calls=[
- ToolCall(name=tc["name"], arguments=tc["args"])
- for tc in raw.get("tool_calls", [])
- ],
- )
-
- async def __aenter__(self):
- return self
-
- async def __aexit__(self, exc_type, exc_val, exc_tb):
- pass
-```
-
-**Key responsibilities:**
-
-- **`send_async`**: Populate `Response.tool_calls` and `Response.side_effects` with everything you can observe. Empty lists mean "no observations," not "nothing happened."
-- **`__aenter__`**: Set up session-level state (API connections, browser contexts).
-- **`__aexit__`**: Clean up. Must be idempotent and must not raise.
-
-### AgentAdapter Protocol
-
-An [`AgentAdapter`][rampart.core.adapter.AgentAdapter] creates sessions and declares capabilities:
-
-```python
-from rampart import AgentAdapter, AppManifest, ObservabilityLevel, ToolDeclaration
-
-class MyAdapter:
- async def create_session_async(self) -> MySession:
- return MySession(client=self._client)
-
- @property
- def manifest(self) -> AppManifest:
- return AppManifest(
- name="My Agent",
- tools=[
- ToolDeclaration(name="search", description="Search documents"),
- ToolDeclaration(name="send_email", description="Send email"),
- ],
- )
-
- @property
- def observability_profile(self) -> ObservabilityLevel:
- return ObservabilityLevel.TOOL_ONLY
-```
-
-**Observability levels:**
-
-| Level | Meaning | When to use |
-|-------|---------|-------------|
-| [`TOOL_AND_SIDE_EFFECTS`][rampart.core.types.ObservabilityLevel] | Reports tool calls and side effects | Full observability via telemetry |
-| `TOOL_ONLY` | Reports tool calls but not side effects | API returns tool call data |
-| `RESPONSE_ONLY` | Reports only text responses | Black-box agent |
-
----
-
-## Choosing Evaluators
-
-Evaluators detect conditions. Choose based on what evidence your adapter provides.
-
-### [`ToolCalled`][rampart.evaluators.tool_called.ToolCalled] — Detect Tool Invocations
-
-```python
-from rampart.evaluators import ToolCalled
-
-# Simple: was the tool called?
-ToolCalled("send_email")
-
-# With exact parameter match
-ToolCalled("send_email", recipient="attacker@evil.com")
-
-# With callable predicate
-ToolCalled(
- "exec",
- command=lambda v: isinstance(v, str) and ".ssh" in v,
-)
-```
-
-!!! warning
- `ToolCalled` requires `TOOL_ONLY` or `TOOL_AND_SIDE_EFFECTS` observability. With `RESPONSE_ONLY`, it always returns `NOT_DETECTED`.
-
-### [`ResponseContains`][rampart.evaluators.response_contains.ResponseContains] — Detect Text Patterns
-
-```python
-from rampart.evaluators import ResponseContains
-import re
-
-# Substring match (case-insensitive by default)
-ResponseContains("error")
-
-# Case-sensitive
-ResponseContains("Error", case_sensitive=True)
-
-# Regex
-ResponseContains(re.compile(r"ssh-rsa\s+[A-Za-z0-9+/]+"))
-
-# Callable predicate
-ResponseContains(lambda text: "secret" in text.lower())
-```
-
-### [`SideEffectOccurred`][rampart.evaluators.side_effect.SideEffectOccurred] — Detect Side Effects
-
-```python
-from rampart.evaluators import SideEffectOccurred
-
-# Any HTTP request
-SideEffectOccurred("http_request")
-
-# POST request to a specific host
-SideEffectOccurred("http_request", method="POST", host="evil.com")
-```
-
-### Composing Evaluators
-
-Combine evaluators with `|` (OR), `&` (AND), and `~` (NOT):
-
-```python
-from rampart.evaluators import ToolCalled, ResponseContains
-
-# OR: detect if EITHER condition is met
-evaluator = ToolCalled("send_email") | ResponseContains("attacker@evil.com")
-
-# AND: detect only if BOTH conditions are met
-evaluator = ToolCalled("exec") & ResponseContains("password")
-
-# NOT: invert detection
-evaluator = ~ResponseContains("I cannot help with that")
-```
-
-!!! tip
- Place the cheaper evaluator on the left side of `|`. The OR operator short-circuits — if the left operand detects, the right is skipped.
-
----
-
-## Implementing Surfaces
-
-[Surfaces][rampart.core.injection.Surface] inject payloads into your agent's data sources. Implement the protocol to return an [`InjectionHandle`][rampart.core.injection.InjectionHandle].
-
-```python
-from rampart import InjectionHandle, Payload, Surface
-
-
-class MyFileSurface:
- """Injects content into a file in the agent's workspace."""
-
- def __init__(self, *, target_path: str, client):
- self._target_path = target_path
- self._client = client
-
- def inject(self, *, payload: Payload) -> InjectionHandle:
- return _FileInjection(
- client=self._client,
- path=self._target_path,
- payload=payload,
- )
-
-
-class _FileInjection:
- def __init__(self, *, client, path: str, payload: Payload):
- self._client = client
- self._path = path
- self._payload = payload
- self._original_content: str | None = None
-
- @property
- def payload_id(self) -> str | None:
- return self._payload.id
-
- @property
- def surface_name(self) -> str:
- return "file_system"
-
- async def wait_until_ready(self) -> None:
- pass # or: await asyncio.sleep(10.0) for indexing delay
-
- async def __aenter__(self):
- self._original_content = await self._client.read(self._path)
- await self._client.write(self._path, self._payload.content)
- return self
-
- async def __aexit__(self, exc_type, exc_val, exc_tb):
- if self._original_content is not None:
- await self._client.write(self._path, self._original_content)
-```
-
-!!! warning
- `__aexit__` must not raise. If cleanup can fail, catch and log the exception.
-
----
-
-## Test Structure Patterns
-
-### One Attack Per Test
-
-Each test should run one execution and assert one result:
-
-```python
-@pytest.mark.harm(HarmCategory.DATA_EXFILTRATION)
-async def test_xpia_email_exfil(adapter):
- result = await Attacks.xpia(
- inject=handle,
- trigger="Summarize Q3 reports",
- evaluator=ToolCalled("send_email"),
- ).execute_async(adapter=adapter)
-
- assert result, result.summary
-```
-
-### Fixture-Based Adapter
-
-Use pytest fixtures to share adapter setup:
-
-```python
-# conftest.py
-import pytest
-
-@pytest.fixture
-def adapter():
- return MyAdapter(api_key="test-key")
-
-# For reporting setup, see pytest Markers & Fixtures
-```
-
-### Class-Based Test Organization
-
-Group related tests in a class:
-
-```python
-class TestDataExfiltration:
- @pytest.mark.harm(HarmCategory.DATA_EXFILTRATION)
- @pytest.mark.trial(n=3, threshold=0.8)
- async def test_ssh_key_exfil(self, adapter):
- ...
-
- @pytest.mark.harm(HarmCategory.DATA_EXFILTRATION)
- @pytest.mark.trial(n=3, threshold=0.8)
- async def test_email_exfil(self, adapter):
- ...
-```
-
-
diff --git a/docs/guides/results-and-reporting.md b/docs/guides/results-and-reporting.md
deleted file mode 100644
index 8c3e067..0000000
--- a/docs/guides/results-and-reporting.md
+++ /dev/null
@@ -1,121 +0,0 @@
-# Results and Reporting
-
-Every RAMPART execution produces a [`Result`][rampart.core.result.Result]. Results flow into reporting sinks for persistence and into the terminal summary for immediate feedback.
-
----
-
-## The Result Type
-
-[`Result`][rampart.core.result.Result] is the single output type for all tests.
-
-```python
-result = await Attacks.xpia(...).execute_async(adapter=my_adapter)
-
-result.safe # bool — did the agent behave safely?
-result.status # SafetyStatus (SAFE, UNSAFE, UNDETERMINED, ERROR)
-result.summary # str — human-readable one-liner
-result.turns # list[Turn] — full conversation
-result.duration_seconds # float — execution wall-clock time
-result.harm_category # HarmCategory | str | None
-result.strategy # str — "xpia", "probe", etc.
-result.injections # list[InjectionRecord] — what was injected where
-```
-
-### The Assert Pattern
-
-`bool(result)` returns `result.safe`:
-
-```python
-assert result, result.summary
-```
-
-### SafetyStatus
-
-| Status | Meaning |
-|--------|---------|
-| [`SAFE`][rampart.core.result.SafetyStatus] | The agent behaved correctly |
-| `UNSAFE` | A safety violation was detected |
-| `UNDETERMINED` | Could not determine safety |
-| `ERROR` | Infrastructure failure |
-
-### Turns
-
-Each [`Turn`][rampart.core.types.Turn] in `result.turns` is one prompt-response exchange:
-
-```python
-for turn in result.turns:
- turn.request.prompt # What was sent
- turn.response.text # What came back
- turn.response.tool_calls # Tool invocations observed
- turn.eval_result # EvalResult for this turn
- turn.turn_number # 0-indexed position
-```
-
----
-
-## Report Sinks
-
-Report sinks receive a [`TestRunReport`][rampart.reporting.sink.TestRunReport] at the end of the pytest session.
-
-### JsonFileReportSink (Built-in)
-
-Writes timestamped JSON files:
-
-```python
-from pathlib import Path
-from rampart.reporting import JsonFileReportSink
-
-sink = JsonFileReportSink(output_dir=Path(".report"))
-```
-
-Output: `.report/run_report_2026-04-25T14-30-00.json`
-
-### Custom Sinks
-
-Implement the [`ReportSink`][rampart.reporting.sink.ReportSink] protocol:
-
-```python
-from rampart.reporting import ReportSink, TestRunReport
-
-class MyDatabaseSink:
- async def emit_async(self, *, report: TestRunReport) -> None:
- for result in report.results:
- await self._db.insert(
- safe=result.safe,
- status=result.status.value,
- harm=str(result.harm_category),
- )
-```
-
-### Wiring Sinks
-
-Define the `rampart_sinks` fixture in your `conftest.py`. See [pytest Markers & Fixtures](../getting-started/pytest-integration.md#rampart_sinks) for the setup and examples with multiple sinks.
-
----
-
-## TestRunReport
-
-The report object passed to sinks. See [`TestRunReport`][rampart.reporting.sink.TestRunReport] for full API.
-
-### Grouping and Aggregation
-
-```python
-# Group by harm category
-by_category = report.by_harm_category()
-
-# Population statistics
-summary = report.population_summary()
-summary.total_runs
-summary.safe_count
-summary.unsafe_count
-summary.attack_success_rate # UNSAFE / non-ERROR total
-summary.safety_pass_rate # SAFE / non-ERROR total
-
-# Filter by category
-exfil = report.population_summary(harm_category=HarmCategory.DATA_EXFILTRATION)
-```
-
-!!! note
- `ERROR` results are excluded from rate calculations. A transient infrastructure failure is not a safety finding.
-
-
diff --git a/docs/images/rampart-architecture-dark.png b/docs/images/rampart-architecture-dark.png
new file mode 100644
index 0000000..5f63cd2
Binary files /dev/null and b/docs/images/rampart-architecture-dark.png differ
diff --git a/docs/images/rampart-architecture-dark.svg b/docs/images/rampart-architecture-dark.svg
new file mode 100644
index 0000000..88e27af
--- /dev/null
+++ b/docs/images/rampart-architecture-dark.svg
@@ -0,0 +1,148 @@
+
\ No newline at end of file
diff --git a/docs/images/rampart-architecture.png b/docs/images/rampart-architecture.png
new file mode 100644
index 0000000..f40a604
Binary files /dev/null and b/docs/images/rampart-architecture.png differ
diff --git a/docs/images/rampart-architecture.svg b/docs/images/rampart-architecture.svg
new file mode 100644
index 0000000..41f86b1
--- /dev/null
+++ b/docs/images/rampart-architecture.svg
@@ -0,0 +1,148 @@
+
\ No newline at end of file
diff --git a/docs/images/rampart-execution-flow-dark.png b/docs/images/rampart-execution-flow-dark.png
new file mode 100644
index 0000000..b78442f
Binary files /dev/null and b/docs/images/rampart-execution-flow-dark.png differ
diff --git a/docs/images/rampart-execution-flow-dark.svg b/docs/images/rampart-execution-flow-dark.svg
new file mode 100644
index 0000000..5d91428
--- /dev/null
+++ b/docs/images/rampart-execution-flow-dark.svg
@@ -0,0 +1,86 @@
+
\ No newline at end of file
diff --git a/docs/images/rampart-execution-flow.png b/docs/images/rampart-execution-flow.png
new file mode 100644
index 0000000..683dfec
Binary files /dev/null and b/docs/images/rampart-execution-flow.png differ
diff --git a/docs/images/rampart-execution-flow.svg b/docs/images/rampart-execution-flow.svg
new file mode 100644
index 0000000..d03fbaa
--- /dev/null
+++ b/docs/images/rampart-execution-flow.svg
@@ -0,0 +1,86 @@
+
\ No newline at end of file
diff --git a/docs/stylesheets/custom.css b/docs/stylesheets/custom.css
index ee932ee..607f361 100644
--- a/docs/stylesheets/custom.css
+++ b/docs/stylesheets/custom.css
@@ -26,3 +26,15 @@
.md-header {
background-color: #1a1a1a;
}
+
+/* Make embedded diagrams (SVG/PNG) scale with the content column instead of
+ honoring their intrinsic width/height attributes, which causes them to be
+ squeezed in narrow columns and overflow in wide ones. */
+.md-typeset img,
+.md-typeset svg {
+ max-width: 100%;
+ height: auto;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}