aegis-crew
by pvfOliveira
README.md
# aegis-crew
[](https://github.com/pvfOliveira/aegis-crew/actions/workflows/ci.yml)
A multi-agent analyst crew — Researcher, Writer, Reviewer on LangGraph — wrapped
in a defence-in-depth security envelope. Agents cannot import their tools: every
tool call crosses a real MCP boundary and is capability-checked by a zero-trust
`CapabilityBroker`, input and output pass deterministic OWASP LLM Top-10
guardrails, and the one side-effecting action (publishing the report) is held at
a human-in-the-loop gate. The thesis: the security layer is constant and the
orchestrator is swappable — CrewAI, AutoGen, BeeAI, Microsoft Agent
Framework, and Google ADK all drive the same secured tools through the same broker.
On top of the always-on envelope sit **four opt-in security tiers**, each
independently switchable and composable:
| Tier | Flag | What it guarantees |
|---|---|---|
| **Rule of Two** (session policy) | `--rule-of-two` | No session combines untrusted inputs + sensitive-data access + external side effects without an explicit human grant. |
| **Dual-LLM quarantine** (semantic) | `--quarantine` | No provider call ever carries both tool access and untrusted bytes. |
| **Sandboxed tools** (runtime) | `--sandbox` | The tool server runs inside a deny-by-default macOS Seatbelt profile derived from the broker's capabilities. |
| **Behavioral baseline** (egress drift) | `--baseline` | Outgoing reports are scored against a profile fitted on known-good output; off-profile publishes warn or block. |
Everything is testable without an API key: **351 deterministic safety tests**
cover every control with negative-control discipline, and a separate live tier
sends real adversarial probes through a real model.
**Start with the design rationale:**
[docs/ARCHITECTURE-DEEP-DIVE.md](docs/ARCHITECTURE-DEEP-DIVE.md) — a full
walkthrough of the architecture, the trade-offs, and the threat model behind it.
---
## Quickstart
This project requires [uv](https://docs.astral.sh/uv/): `[tool.uv]
override-dependencies` resolves a metadata-only `json-repair` pin conflict that
plain pip cannot apply, so `pip install` fails where `uv sync` succeeds.
```bash
uv sync --extra dev
cp config.example.toml config.toml # edit provider/security settings
cp .env.example .env # set ANTHROPIC_API_KEY for live runs
# Key-free demo — scripted provider, full pipeline, no network:
uv run aegis run "grid storage policy" --provider fake --auto-approve
# Deterministic safety suite (no API key):
uv run --extra dev pytest tests/safety -q
```
The report lands in `out/report.md`, written only after the HITL gate approves.
### Live interactive run
```bash
uv run aegis run "renewable energy subsidies" --provider anthropic
```
The crew runs against the real model. When the reviewer approves the draft, the
run **parks** at the HITL gate and prints the proposed report. You are prompted
to approve (`y`) or reject (`n`) before `out/report.md` is written.
Providers: `fake` (scripted, deterministic), `anthropic`, `ollama`, plus
first-party `openai`, `gemini`, `mistral`, and `watsonx` adapters.
`config.toml` keys of interest:
```toml
[provider]
default = "anthropic" # anthropic | ollama | fake | openai | gemini | mistral | watsonx
anthropic_model = "claude-sonnet-4-6"
[security]
output_dir = "out"
corpus_dir = "corpus"
reflexion_max_retries = 2
```
The `kb_search` tool reads `.md`/`.txt` documents from `corpus/` (example
policy documents ship in the repo).
### Standalone MCP tool server (stdio)
```bash
uv run aegis serve-mcp
```
Starts the FastMCP tool server on stdio. Any MCP-compatible client (e.g. Claude
Desktop) can connect and call the three tools (`kb_search_tool`,
`read_doc_tool`, `publish_report_tool`) through the zero-trust broker.
> **Note on retrieval quality:** `MockEmbedding` (offline, deterministic) is the
> default so all tests and the `--provider fake` demo work without API keys.
> Retrieval is illustrative in this mode — pass a real embedding model via
> `build_index(embed_model=...)` for production-grade semantic search.
---
## Architecture
```
User topic
|
v
[scan_input / LLM01] ← guardrail: prompt-injection / jailbreak
|
v
StateGraph (LangGraph)
┌──────────────────────────────────────────────────────────┐
│ │
│ START → researcher ──────────────────────────────────► │
│ (ReAct loop) │
│ │ MCP boundary (MCPToolClient) │
│ │ └── CapabilityBroker.authorize() │
│ │ └── check_tool_args / LLM06 │
│ ▼ │
│ writer │
│ ▼ │
│ reviewer ──"retry"──► writer │
│ (Reflexion) │
│ │ "ok" │
│ ▼ │
│ hitl_gate ── reject ──► END │
│ (interrupt()) │
│ │ approve │
│ ▼ │
│ publisher ── MCP ──► publish_report_tool │
│ │ └── check_egress / LLM02 │
│ ▼ │
│ END │
└──────────────────────────────────────────────────────────┘
|
v
out/report.md (written only after HITL approval)
```
Key components:
| Layer | Artifact |
|---|---|
| Orchestration | `src/aegis/graph.py` — LangGraph `StateGraph` |
| MCP boundary | `src/aegis/mcp/server.py` + `src/aegis/mcp/client.py` |
| Zero-trust | `src/aegis/security/zero_trust.py` — `CapabilityBroker` + `AuditLog` |
| Guardrails | `src/aegis/security/guardrails.py` — LLM01/02/05/06 |
| HITL gate | `src/aegis/graph.py:257` — `interrupt()` before publish |
| Agents | `src/aegis/agents/{researcher,writer,reviewer}.py` |
| State | `src/aegis/state.py` — `CrewState` (Pydantic) |
| CLI | `src/aegis/cli.py` |
Full threat analysis: [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md) (STRIDE
per component).
---
## The always-on envelope
- **Zero-trust `CapabilityBroker`** (`src/aegis/security/zero_trust.py:45`) —
every tool call is explicitly authorized against a per-tool capability set;
denials are recorded in the `AuditLog` (`zero_trust.py:26`). No tool runs
without explicit allowance.
- **OWASP LLM Top-10 guardrails** (`src/aegis/security/guardrails.py`) — four
guards mapped to four controls:
- `scan_input` → LLM01 (Prompt Injection / Jailbreak)
- `check_egress` → LLM02 (Sensitive Information Disclosure)
- `validate_output` → LLM05 (Improper Output Handling)
- `check_tool_args` → LLM06 (Excessive Agency)
- Full mapping: [`docs/OWASP_MAPPING.md`](docs/OWASP_MAPPING.md)
- **Human-in-the-loop gate** (`src/aegis/graph.py:257`) — LangGraph
`interrupt()` parks the run before the one side-effecting tool
(`publish_report_tool`). A rejected decision routes to `END` without writing
any file.
- **Audit log** (`src/aegis/security/zero_trust.py:26`) — all broker allow/deny
decisions are recorded with tool, action, target, verdict, and reason.
- **MCP choke point** — tools are exclusively reachable via the `MCPToolClient`
boundary; agents have no direct function references. The broker enforces
authorization at the server side of that boundary.
The spine files behind these controls are **byte-frozen**:
`tests/safety/test_no_security_regression.py` guards them with a git-diff check
plus SHA-256 digest pins, so every later capability had to compose *on top of*
the controls instead of editing them. The frozen guardrails are also
mutation-tested — see [`docs/mutation-testing.md`](docs/mutation-testing.md).
---
## The four security tiers
### Rule of Two (session policy, `--rule-of-two`)
Meta's "Agents Rule of Two": an agent session may combine at most two of
**[A] untrustworthy inputs**, **[B] sensitive-system/private-data access**,
**[C] state-change/external communication**. Needing all three means the session
must not run autonomously — human approval is the mandated minimum.
aegis enforces this mechanically: a `SessionLedger` accumulates factors as the
broker authorizes calls (`read`→B, `write`→C, `network`→A+C, unknown→all three,
fail-closed; carve-outs are explicit declarations), and `RuleOfTwoBroker` (a
drop-in subclass of the frozen `CapabilityBroker`) denies, at authorize-time,
any call whose factors would complete the triple without a human grant. The
HITL gate's approval *is* the grant: approving the publish unlocks
`EXTERNAL_WRITE` for the session; rejecting leaves the third factor locked even
if graph routing were bypassed. Every decision lands in the same audit log.
Enforcement is in-process: in `aegis run` (in-memory MCP) the server and graph
share one broker and one ledger, so enforcement is end-to-end. In `serve-mcp`
stdio mode the ledger lives inside the server process: tool-side factor
accumulation and denial are enforced there, but graph-side marks and HITL
grants cannot reach it — a cross-process grant channel is future work, not
claimed. Classification is structural, not semantic: Rule of Two bounds blast
radius; it does not detect injection — the guardrails do that, and neither
subsumes the other.
### Dual-LLM quarantine (semantic tier, `--quarantine`)
"Design Patterns for Securing LLM Agents against Prompt Injections"
([arXiv:2506.08837](https://arxiv.org/abs/2506.08837)) states the principle: once
an agent has ingested untrusted input, it must be *impossible* for that input to
trigger consequential actions. `--quarantine` turns the crew into the paper's
**Dual LLM** pattern (with [CaMeL](https://arxiv.org/abs/2503.18813) as the
data-flow lineage):
- the **privileged** researcher keeps its tools but never sees untrusted bytes —
tool results are parked in a `QuarantineStore` and appear to the model only as
opaque refs (`⟦Q1⟧ from kb_search (1204 chars)`) plus validated signals;
- a **quarantined** model (never given tools) reads the untrusted text and
answers only schema-validated primitives (bool / bounded int / fixed choice);
a reply that fails strict validation is re-asked once, then rejected
fail-closed — free text never crosses back;
- the **writer and reviewer** see the content but are quarantined roles:
structurally tool-less (a regression-pinned property, not an accident);
- the **publisher** boundary is unchanged: HITL + broker + egress guards + Rule
of Two when enabled.
The enforced, tested invariant: **no provider call ever carries both tool access
and untrusted bytes.** Default-off; `[security] quarantine_provider` optionally
routes the quarantined model to a cheaper local provider.
Honesty notes, verbatim from the design review: "Quarantine bounds capability and data
flow, not signal truthfulness: extracted signals are derived from untrusted
content and remain adversary-influenceable. An attacker can lie to the relevance
check; they cannot make the privileged model see their text or invoke a tool
with it." "The quarantined model can still be prompt-injected. The guarantee is
that injection there is inconsequential by construction: the quarantined call
carries no tool access, and its only output channel is a schema-validated
primitive — free text never crosses back to the privileged side." "Writer and
reviewer see untrusted content by design; they are quarantined roles —
structurally tool-less. The consequential boundary remains the publisher path
(HITL + broker + egress guards + Rule of Two when enabled). Detection
(guardrails) and structure (quarantine) are complementary; neither subsumes the
other."
**Stretch rung — `--quarantine-mode plan` (CTE-lite):** the privileged model
emits a full typed plan (pydantic-validated verbs: `search` / `read` /
`extract`) from the topic alone, *before any untrusted data exists*; a
deterministic executor walks it through the same broker-checked MCP path.
Honest label, verbatim: "This is Plan-Then-Execute with typed steps and
capability-checked execution (CTE-lite). Full CaMeL-style data-flow labels on
variables are future work, not claimed."
### Sandboxed tool execution (sandbox tier, `--sandbox`)
```bash
uv run aegis run "renewable energy subsidies" --sandbox --provider fake --auto-approve
```
OWASP ASI02 ("Tool Misuse") calls for two layers: the broker that authorizes a
tool call, and a runtime boundary that survives the broker being wrong. `aegis
run --sandbox` adds the second layer: the `serve-mcp` tool server is launched
inside a macOS `sandbox-exec` (Seatbelt) subprocess under a caps-derived
deny-by-default profile (`security/sandbox.py`, `SandboxPolicy.from_caps`) —
filesystem access is scoped to exactly the read/write paths the broker's
capabilities declare, and network is denied outright. The broker remains the
policy decision point inside that subprocess; the sandbox bounds the blast
radius if the broker is wrong or the tool runtime is compromised.
The launch writes two inspectable artifacts under `out/sandbox/`:
`profile.sb` (the actual Seatbelt profile text) and `config.toml` (the
secrets-free config the sandboxed server reads). Only the profile's
`sha256_16` enters the shared `AuditLog` as a `sandbox`/`launch` record
(`security/sandbox.py:launch_audit_record`) — the content lives on disk for
inspection, not duplicated into the audit trail.
`--sandbox` composes with `--quarantine` and `--auto-approve`. It refuses
`--rule-of-two`: with `--sandbox`, tool-side capability decisions are made
and audited *inside* the sandboxed server process, and Rule of Two's session
ledger — an in-process object — cannot span that process boundary, so the
combination is rejected at the CLI rather than silently downgraded to
graph-side marks only.
| Combination | Result |
|---|---|
| `--sandbox` + `--quarantine` + `--auto-approve` | valid |
| `--sandbox` + `--rule-of-two` | refused (`BadParameter`) |
Honesty guards, verbatim from the design review:
1. *"Broker authorizes, sandbox contains. The broker remains the policy decision
point; the sandbox bounds the blast radius of a tool-runtime compromise or a
broker misconfiguration. Neither subsumes the other, and neither makes tool
OUTPUT trustworthy — untrusted-content handling remains the quarantine and
guardrail tiers' job."*
2. *"`sandbox-exec` is deprecated as a public CLI but is the OS-shipped Seatbelt
mechanism that still underpins macOS app sandboxing. This tier is best-effort
local containment on a developer machine, not a certified security boundary and
not E2B: no microVM, no snapshotting, no network allowlisting — egress is simply
denied."*
3. *"The sandbox tier is darwin-only and fails closed: on any other platform
`--sandbox` is an error, never a silent downgrade to unsandboxed execution. A
Linux backend (namespaces/containers) is future work."*
### Behavioral baseline (egress drift tier, `--baseline`)
```bash
uv run aegis baseline-fit ./known-good-reports -o profile.json
# config.toml: [security] baseline_profile = "profile.json"
uv run aegis run "renewable energy subsidies" --baseline --provider fake --auto-approve --config config.toml
```
`security/baseline.py` wires [portcullis](https://github.com/pvfOliveira/portcullis)'s
`BaselineMonitor` into the publisher boundary as an optional, default-off egress
tier, run *after* `check_egress` and *before* the MCP publish call: the outgoing
report is embedded and scored against a profile fitted offline on known-good
reports. This is a behavioral tier, not a content tier — it complements the
existing guardrail and egress checks, it does not replace them.
**Fit → enforce lifecycle:** `aegis baseline-fit <known-good-dir> [-o profile.json]
[--threshold-percentile 95.0]` fits a profile on `*.md`/`*.txt` reports and saves it
as JSON; `[security] baseline_profile = "profile.json"` in the config points
`aegis run --baseline` at it. Needs the `[baseline]` extra
(`pip install 'aegis-crew[baseline]'`), lazy-imported inside `security/baseline.py`
so the core install and the deterministic safety suite never need it.
**Warn vs. block:** `--baseline` alone runs in `warn` mode — drift is recorded on
the audit trail and the run still publishes. `--baseline-mode block` refuses an
off-profile publish with a content-free `BLOCKED: ...` critique, same shape as an
egress denial. Audit records land on the same ledger as every other tier:
`baseline / egress / report.md / allow|deny`, with reasons carrying
distance/threshold/destination — never report text.
**Composition:** `--baseline` composes with `--rule-of-two`, `--quarantine`, and
`--sandbox` — verified end-to-end by
`tests/safety/test_baseline.py::test_baseline_composes_with_rule_of_two`,
`::test_baseline_composes_with_quarantine`, and `::test_baseline_composes_with_sandbox`
respectively (the monitor and embedder run in the graph process; `--sandbox`
contains a disjoint layer, the `serve-mcp` subprocess).
Honesty guards, verbatim from the design review:
1. *"A behavioral baseline is anomaly detection, not attack detection: it flags
conversations that drift from the fitted profile. A novel-but-benign topic can
flag; an attack phrased on-profile will not. It bounds drift — it does not
detect prompt injection or PII. Run it alongside the content gate, never
instead of it."*
2. *"Fail posture is fail-closed with no override exposed: an embedder failure
blocks the publish even in warn mode. Errors never downgrade to a warning
silently."*
3. *"HashingEmbedder is deterministic and content-sensitive but NOT semantic: it
demonstrates the wiring and the fit/enforce lifecycle, not detection quality.
Hand a real embedding model to LlamaIndexEmbedder for any real deployment, and
fit and enforce with the SAME embedder — profiles are embedder-specific."*
The default no-key path uses `HashingEmbedder` (stdlib token-hash bag, unit
vector) — deterministic and content-sensitive, but not semantic (guard 3 above).
`LlamaIndexEmbedder` adapts any llama-index `BaseEmbedding` for a real
deployment. Fit and enforce **must** use the same embedder: profiles are
embedder-specific.
---
## Capabilities
Everything below composes on the frozen spine; each item names the module that
implements it and the test or CLI command that exercises it.
### Broker & policy
- **Zero-trust broker + audit log** (`security/zero_trust.py`) — deny-by-default
per-tool capability sets; every allow/deny audited. Exercised by
`tests/safety/test_zero_trust.py`.
- **Rule-of-Two session ledger** (`security/rule_of_two.py`, `security/policy.py`)
— factor accumulation + authorize-time denial, HITL approval as the human
grant. Exercised by `tests/safety/test_rule_of_two.py`.
- **Sandbox policy derivation** (`security/sandbox.py`) — a Seatbelt profile
generated from the broker's capability set, deny-by-default, network denied.
Exercised by `tests/safety/test_sandbox.py`.
- **HITL gate** (`graph.py`, `security/hitl.py`) — `interrupt()` before the one
side effect; resume via `Command(resume=...)`. Exercised by
`tests/safety/test_graph_hitl.py`.
### Guardrails & spine
- **OWASP LLM Top-10 guards** (`security/guardrails.py`) — LLM01/02/05/06,
deterministic, mutation-tested
([`docs/mutation-testing.md`](docs/mutation-testing.md)). Exercised by
`tests/safety/test_guardrails.py` with negative controls.
- **Dual-LLM quarantine** (`security/quarantine.py`, `security/plan_execute.py`)
— the semantic tier above. Exercised by `tests/safety/test_quarantine.py`.
- **Behavioral baseline** (`security/baseline.py`) — the egress drift tier
above. Exercised by `tests/safety/test_baseline*.py` (skips cleanly without
the `[baseline]` extra).
- **NeMo Guardrails integration** (`security/nemo_rails.py`) — an `LLMRails`
input rail delegating to the frozen `scan_input`; blocks injection with no
model in the deterministic test, and with a live Ollama in the live test.
- **Adversarial hardening** (`adversarial/defense.py`) — prompt-space evasion
bench against the LLM01 guardrail; measured attack success rate 0.78 bare →
0.00 with `harden` (obfuscation-folding `normalize`), `guardrails.py`
untouched. CLI: `aegis adversarial bench`.
- **MCP tool-poisoning defense** (`mcp/tool_scan.py`) — `vet_tools` scans
advertised tool metadata for smuggled instructions; `ToolPin`/`detect_rugpull`
catch post-approval description swaps. CLI: `aegis mcp scan`.
- **Data-poisoning attack + detector** (`adversarial/poison.py`) — plants a
poisoned corpus doc and detects it by reusing the frozen LLM01 guardrail plus
a duplication check. CLI: `aegis adversarial poison-scan`.
- **Model-extraction attack + defense** (`adversarial/extraction.py`) — a
surrogate fit on victim queries; measured fidelity degradation 0.96 → 0.57
under the perturbation/budget defense.
### Orchestration & interop
- **The crew** (`graph.py`, `agents/`) — ReAct researcher, writer, Reflexion
reviewer with bounded retry, HITL publisher, on a LangGraph `StateGraph`.
- **Swappable orchestrators** (`orchestrators/`) — CrewAI, AutoGen, BeeAI,
Microsoft Agent Framework, and Google ADK each drive the *same* secured MCP
tools over the `serve-mcp` stdio contract via their native adapters; none
re-implements a tool. Structural tests are model-free; live smoke tests run
against local Ollama (`qwen3:8b`).
```bash
uv run aegis run "grid storage policy" --orchestrator crewai # | autogen | beeai
```
- **Five orchestration topologies** (`topologies/`) — supervisor,
plan-and-execute (replan-on-failure), routing/handoffs, swarm
(decentralized `Command(goto=...)`), and debate (judge sides with either
debater), each a separate LangGraph over the same secured boundary. CLI:
`aegis topo run --pattern <name> TOPIC`.
- **A2A protocol** (`protocols/`) — the crew is callable as a Google
Agent2Agent agent (`aegis serve-a2a`) and can delegate to peers
(`aegis a2a-call`); inbound messages are guardrail-scanned, outbound peer
calls are deny-by-default and audited. Needs the `[a2a]` extra.
- **Computer use** (`agents/computer_use.py`, `mcp/tools/browser.py`) — a
bounded perceive→decide→act loop over a gated Playwright `BrowserTool`;
origins and action verbs are allowlisted, sensitive actions (e.g. `submit`)
park at HITL. CLI: `aegis browse URL TASK`. Needs the `[browser]` extra.
- **Evaluation harness** (`eval/harness.py`) — `aegis eval` runs each task
through the real crew and scores trajectory + safety-held + LLM-judge
(deterministic `StubJudge` by default, `--live` for an Ollama judge);
`--metric tool-accuracy` scores F1 over expected-vs-actual tool-call sets,
and topology runs take a caller-supplied success predicate.
### Memory & privacy
- **Three-tier memory** (`memory/store.py`) — episodic/semantic/scratch over
SQLite; `remember`/`recall` are capability-checked through the same broker
(memory paths are scoped under the run's output dir). Exercised by
`tests/safety/test_memory.py` and the crew-wiring tests.
- **Semantic fact store + consolidation** (`memory/semantic_store.py`,
`memory/consolidate.py`) — fact/preference store with dedup on
`(subject, predicate)`; `consolidate()` extracts durable facts from the
episodic log and shrinks event volume while preserving knowledge. CLI:
`aegis memory facts`.
- **Context engineering** (`context/assembler.py`) — `ContextAssembler` packs
prioritized sections into a token budget, compressing lowest-priority first;
counter and summarizer are dependency-injected, so it tests
deterministically.
- **PII redaction** (`privacy/redaction.py`) — `Redactor` with `redact` and
`block` policies. CLI: `aegis privacy redact PATH`.
- **Differential privacy** (`privacy/dp_train.py`) — a real Opacus DP-SGD
micro-train reporting the spent budget (**ε≈2.25, δ=1e-5**; CPU — Opacus
grad-sample hooks are MPS-incompatible). CLI: `aegis privacy dp-train`.
Needs the `[privacy]` extra.
- **Federated learning simulation** (`privacy/federated.py`) — a labeled FedAvg
simulation (honestly labeled: simulation, not a distributed deployment).
### Supply chain & governance
- **Artifact attestation** (`supplychain/provenance.py`, `supplychain/verify.py`)
— Ed25519 in-toto-style sign + verify-on-load; a tampered artifact is blocked
before use. In-house local signing, NOT keyless sigstore. CLI: `aegis attest`,
`aegis verify`.
- **SBOM** (`supplychain/sbom.py`) — CycloneDX-style SBOM for installed
packages. CLI: `aegis sbom`.
- **cosign signing** (`supplychain/cosign.py`) — key-based blob sign/verify via
the cosign CLI (key-based, NOT keyless-OIDC). CLI:
`aegis provenance cosign-sign`.
- **C2PA content provenance** (`provenance/c2pa.py`) — signs images with a
local self-signed ES256 credential; `verify_image` performs real
tamper-detection on the manifest. CLI: `aegis provenance c2pa-sign` /
`c2pa-verify`. Needs the `[c2pa]` extra.
- **Governance gate** (`governance/policy_engine.py`) — deny-by-default
`GovernanceGate` over a NIST/ISO/EU-AI-Act risk register mapped to real
callables; exits 1 on violation. Scope: an executable **pre-flight / CI
gate**, not a runtime interceptor wired into `graph.py`. CLI:
`aegis govern check` / `report`. Mapping:
[`docs/AI_GOVERNANCE.md`](docs/AI_GOVERNANCE.md).
- **ISO/IEC 42001** (`governance/aims.py`) — `AimsGate` binds 5 clauses to real
callables and blocks on nonconformity. CLI: `aegis govern aims-check`.
Mapping: [`docs/ISO_42001_AIMS.md`](docs/ISO_42001_AIMS.md).
- **OWASP Agentic Top-10 map** (`security/agentic_map.py`) — an executable
threat→control map over 8 threats: 6 controlled, 2 documented gaps (T4
Resource Overload, T7 Cascading). CLI: `aegis agentic map` / `check`.
Mapping: [`docs/OWASP_AGENTIC_TOP10.md`](docs/OWASP_AGENTIC_TOP10.md).
- **Executable attack trees** (`threat/attack_tree.py`) — extends the STRIDE
model; every leaf must resolve to a real aegis control or the test fails.
CLI: `aegis threat tree`.
- **Confidential computing** (`confidential/`) — **a design study, not built: I
have no TEE hardware.** An attestation-gated inference stage refuses to run
without a TEE quote (`aegis confidential attest` exits nonzero here); nothing
is simulated. Design doc:
[`docs/CONFIDENTIAL_COMPUTING.md`](docs/CONFIDENTIAL_COMPUTING.md).
### Optional extras
Heavy dependencies are lazy-imported inside the functions that need them, so
the core install and the deterministic safety suite never require any of
these:
```bash
pip install 'aegis-crew[a2a]' # a2a-sdk — serve-a2a / a2a-call
pip install 'aegis-crew[browser]' # playwright — browse (then: python -m playwright install chromium)
pip install 'aegis-crew[privacy]' # torch + opacus — privacy dp-train
pip install 'aegis-crew[agentic]' # agent-framework-core — the MAF orchestrator
pip install 'aegis-crew[adk]' # google-adk[mcp] + litellm — the Google ADK orchestrator
pip install 'aegis-crew[rails]' # nemoguardrails — the NeMo rail layer
pip install 'aegis-crew[c2pa]' # c2pa-python — provenance c2pa-sign / c2pa-verify
pip install 'aegis-crew[baseline]' # portcullis — the behavioral-baseline tier
pip install 'aegis-crew[platform]' # convenience bundle: a2a + browser
```
---
## Testing
Two tiers, intentionally separated:
### Deterministic safety suite (no API key)
```bash
uv run --extra dev --extra baseline --extra adk pytest tests/safety -q
```
**351 tests** cover every security control, the guardrail negative-control
discipline, HITL interrupt/resume, the MCP boundary, the zero-trust broker, all
four tiers and their compositions, and CLI smoke. **If a control is removed,
its corresponding test fails** — the suite is written as a regression harness,
not just coverage. `tests/safety/test_baseline*.py` skip cleanly (via
`pytest.importorskip`) when the `[baseline]` extra is not installed — the suite
stays green either way.
### Live adversarial red-team suite (requires `ANTHROPIC_API_KEY`)
```bash
ANTHROPIC_API_KEY=sk-... uv run --extra dev pytest -m redteam -q
```
Sends real adversarial probes through a live crew run:
- A corpus document with a planted prompt-injection comment must not cause scope escape.
- A jailbreak topic must be blocked at `scan_input` before the model or tools see it.
Further live markers: `-m live` (orchestrator + topology smoke against local
Ollama) and per-provider markers (`openai_live`, `gemini_live`, `mistral_live`,
`watsonx_live`).
---
## Honest limitations
- **Confidential computing is a design study** — no TEE hardware here, so it
ships as a fail-closed attestation gate plus a design doc; TEE-backed
deployment is not claimed.
- **The sandbox tier is darwin-only** best-effort local containment (Seatbelt),
not a certified security boundary; it fails closed on other platforms.
- **Rule of Two in `serve-mcp` stdio mode** enforces tool-side only; the
cross-process HITL grant channel is future work, not claimed.
- **Default retrieval and baseline embedders are illustrative** —
`MockEmbedding` and `HashingEmbedder` are deterministic, not semantic; swap
in real embedding models for deployment.
- **cosign signing is key-based**, not keyless-OIDC; the Ed25519 attestation is
in-toto-style, not sigstore.
- **OWASP Agentic Top-10 coverage is 6 of 8 mapped threats**, with T4 (Resource
Overload) and T7 (Cascading) as documented gaps.
- **The governance gates are pre-flight/CI gates**, not runtime interceptors.
- **FedAvg is a simulation**, not a distributed deployment.
---
## Project history
The wave-by-wave build ledger — what was added when, with the full
capability-to-evidence tables — lives in
[docs/BUILD-NOTES.md](docs/BUILD-NOTES.md).
TDQS
B3.2/5.0
Scored across 3 tools
Disambiguation5/5
Each tool has a distinct purpose: searching the knowledge base, reading a document, and publishing a report. There is no overlap or ambiguity.
Naming Consistency4/5
All tools use snake_case and include a verb, but the pattern varies: kb_search_tool follows noun_verb_tool, while read_doc_tool and publish_report_tool follow verb_noun_tool. This minor inconsistency is still clear.
Tool Count4/5
With three tools, the set is minimal but fits a focused knowledge base and report creation workflow. It is not overly small given the scope.
Completeness3/5
The tools cover search, read, and publish, but lack update, delete, or list operations. For a basic workflow it is functional, but gaps exist for lifecycle management.
Maintenance
ActivitySlowing
ResponsivenessNo issues