IntegrityScore with a recommended action, and the result is appended to a hash-linked attestation chain. Framework adapters block nothing unless you turn enforcement on; the coding-agent hook plugins enforce by default.
The three self-securing capabilities
The framework defines three capabilities a self-securing agent needs and assigns one layer to verify each. A fourth layer, governance, holds the organization’s policy rules.
The capabilities are what the agent has. The layers are how the library measures whether it has them.
The four layers
default_layers() builds the pipeline every zero-config entry point uses, in this order:
evaluate(profile, context) -> LayerResult. A LayerResult carries a score between 0.0 and 1.0, a passed flag, an action (pass, alert, escalate or block), and a details dict. The layer only sees what is in context, so the checks below run on whatever the caller supplies.
Adversarial layer
AdversarialLayer scans text channels with a regex taxonomy and inspects tool calls by their arguments.
- Pattern scan. 32 default patterns across nine threat types:
prompt_injection,jailbreak,role_confusion,system_prompt_extraction,data_exfiltration,prompt_obfuscation,action_injection,tool_poisoningandpeer_coercion. It readsinput,reasoning_chain,memory_reads,tool_outputs,tool_definitions,retrieved_documents,peer_messages, and theshared_memoryandbroadcast_messageslists undertopology_context. Matches are grouped into one threat per channel and threat type. Add patterns withextra_patterns=or replace the set withpatterns=. - Tool errors. Each
tool_outputsentry with anerroradds a low-severity (0.30)tool_manipulationthreat. ToolArgumentDetector. Reads the categoriesclassify_tool_call()assigned to the current call.remote_code_execraises a 0.95 threat andlog_tampera 0.90 threat, so both block under the defaultblock_on_critical=True.obfuscated_commandraises 0.70 and alerts.runs_inline_coderaises no threat; GOV-002 acts on it.ToolSequenceDetector. Flags a sensitive read followed later in the session by an external send, matched by tool name or by argument category. A shellcat ~/.aws/credentialsfollowed by acurl -X POSTcounts.- Peer authority. When a topology is declared, a peer message or broadcast from a sender outside it raises a
peer_authoritythreat. See Multi-agent systems.
severity × confidence × 0.5 per threat. The layer blocks when any threat reaches severity 0.90 and block_on_critical is on, alerts when coherence falls below coherence_threshold or a threat reaches 0.50, and passes otherwise. Custom detectors plug in through threat_detectors=: each is a callable (profile, context) that returns a list of ThreatAssessment.
Cortical layer
CorticalLayer scores three things and averages them with fixed weights: reasoning consistency (0.35), memory integrity (0.35) and one minus drift (0.30).
- Reasoning. Looks for contradiction phrases between consecutive
reasoning_chainsteps, and for instructions that try to overridegoals(“ignore your goal”, “new objective”, “override”). An override conflict escalates. An empty chain scores 0.85. - Memory. Reads the
provenanceof eachmemory_readsentry.unknown, missing orexternalprovenance counts as suspicious, andconflicts_with_baselinecosts 0.15 per entry. - Drift. Jensen-Shannon distance with Laplace smoothing between the session’s action and tool-usage distributions and a learned baseline (
metric="wasserstein"uses scipy with the[stats]extra). A dimension is only scored once both sides havemin_drift_samples(default 20) observations.
drift_tolerance alerts. Drift above twice the tolerance also alerts unless you construct the layer with block_on_drift=True. Scores and actions covers how the baseline is learned.
Governance layer
GovernanceLayer runs policy rules against context["action"] and writes an audit entry with a SHA-256 content hash for every evaluation. The enterprise-default set:
A
DENY rule produces block, a REQUIRE_APPROVAL rule produces escalate, and a CONDITIONAL rule produces alert. A rule whose condition raises counts as REQUIRE_APPROVAL. GOV-002 and GOV-003 decide which calls count from the trusted tool name and the classifier’s categories, never from fields the agent puts in its arguments. Routine commands (ls, git, pytest, python script.py) do not trigger GOV-002, and a payment call without a numeric amount, such as a status lookup, does not trigger GOV-003. Other policy sets are minimal (no rules) and strict (every tool call requires approval). Add rules with custom_rules=[PolicyRule(...)] from agentegrity.layers.governance.
Recovery layer
RecoveryLayer averages four sub-scores:
Sustained degradation alerts, a broken chain escalates, and two or more degrading peers (cascade detection) alert. Adapters and
IntegrityMonitor bind their own attestation chain to the recovery layer, so the continuity check covers the chain they write; a chain you pass as RecoveryLayer(chain=...) takes precedence. With a checkpoint= backend, snapshot() saves the score history and chain, and restore_to() rolls the score history back without removing any chain record. See Attestation.
Integrity properties
The evaluator maps layer results onto four properties by reading a score key from each layer’sdetails:
The mapping is literal: in this implementation environmental portability is the cortical score and verifiable assurance is the policy score. Signed attestations do not raise
verifiable_assurance. When a property’s layer did not run (for example, the adversarial layer blocked and evaluation stopped), portability falls back to the lowest layer score, assurance to the mean layer score, and recovery integrity stays 0.0. Scores and actions covers weights and the composite.
The evaluator pipeline
IntegrityEvaluator runs the layers in order and builds the IntegrityScore:
fail_fast=True (the default), a block from one layer skips the layers after it, and confidence drops by the share of layers that did not run.
AsyncIntegrityEvaluator (in agentegrity.core.evaluator) has the same constructor and an async evaluate(). It calls a layer’s aevaluate() when it has one and runs sync layers with asyncio.to_thread. With fail_fast=False all layers run concurrently through asyncio.gather; with fail_fast=True they run one after another so a block can stop the rest.
IntegrityEvaluator.aevaluate() does the same with fail_fast ordering, and is what adapters use. The model-backed layers only do model work on the async path. CorticalLLMLayer, AdversarialLLMLayer and AdversarialSLMLayer keep their sync evaluate() pattern-based, and call the model in aevaluate(). An adapter runs the async path at each tool-call gate and the sync path for every other event, so text from a tool result or a peer message is classified at the next gate, before the next call can run. Adapters do not capture the reasoning chain, memory reads or behavior descriptions that CorticalLLMLayer checks, so inside an adapter only the adversarial classifiers call a model.
From framework hooks to evaluated events
Every framework adapter inherits_BaseAdapter and translates its framework’s callbacks into canonical event types. The base class owns the session context, the evaluator call, the chain and the exporter fan-out, so every framework produces the same records.
1
The framework fires a hook
For example, the Claude Agent SDK calls
PreToolUse, LangChain calls on_tool_start, or a Strands agent fires BeforeToolCallEvent. The adapter maps it to pre_tool_use with tool_name and tool_input.2
The context buffer is updated
A tool call is stored with its arguments nested under
arguments (so an argument named tool or type cannot shadow the fields policy rules match on) and with the categories classify_tool_call() assigned. Tool output goes to tool_outputs, prompts to inputs. Each channel is capped at 1,000 entries per session; the first overflow emits a <channel>_overflow event.3
The evaluator runs
The buffer becomes an evaluation context (latest prompt, live tool outputs, tool-call history and categories, action and tool-usage counts, and
topology_context when a topology is declared). If the cortical layer has a learned baseline, it is added as behavioral_baseline. The composite is recorded for degradation tracking.4
Records are appended
An
AttestationRecord holding the full score is appended to the chain. At decision boundaries a DecisionRecord is appended first. See Attestation and decision provenance.5
The event is emitted
A
FrameworkEvent (event_type, timestamp, adapter_name, data, evaluation_result) is stored and passed to every registered exporter. Exporter errors are logged and never reach the agent.6
Enforcement decides
With
enforce=True, a pre_tool_use whose score action is block, or escalate without approval, returns a deny payload the adapter hands back to its framework.
The full payload of each event is in the event reference.
Session state lives on the adapter.
get_summary() (what each module’s report() returns) gives the evaluation and record counts, chain_hash_linked, chain_signatures, enforce_mode, the attached exporters, declared credentials and, when any source reported it, usage. close() ends the session: it teaches the drift baseline when the session ended without block or escalate, then sends on_session_end to exporters.
The shipped adapters fill the latest prompt, tool outputs and errors, tool-call history and categories, and the action and tool-usage counts. They do not fill
memory_reads, retrieved_documents, tool_definitions, goals, instructions or reasoning_chain, so the memory, reasoning-conflict and those pattern-scan channels only run when you call the evaluator with a context that contains them.What it does not do
It is not a guardrail by default. Every adapter except the coding-agent hook plugins starts measure-only. Enforcement is opt-in withenforce=True, and the AutoGen and Bedrock boto3 adapters can record a block but cannot stop the call.
The regex tier catches known shapes. The adversarial patterns match how an attack is phrased. Paraphrased or non-English injections will evade them, and separating an injected goal from a legitimate user task needs session context that content patterns do not have. Detection numbers, last measured 2026-08-02 with the regex taxonomy alone (no model layers, no embedding similarity):
ToolSequenceDetector adds behavioral evidence that phrasing cannot hide, but it does not change those numbers. The full method and caveats are in STATUS.md.
Model-backed layers are opt-in and send content out. AdversarialLLMLayer and CorticalLLMLayer need pip install "agentegrity[llm]" and send the text they classify to Anthropic’s API. AdversarialSLMLayer sends it to AGENTEGRITY_SLM_BASE_URL (a local Ollama server by default). All three fail open on errors, run their model checks only on the async path (in an adapter, at each tool-call gate), and have no published detection numbers. Because the LLM and SLM classifiers can block on a model verdict of severity 0.90 or more, measure them on your own traffic before you enforce with them.
Command analysis is static. classify_tool_call() reads what a command says. Anything the shell computes at runtime (variable-built paths, command substitution, $IFS splicing) is tagged obfuscated_command, not resolved. On Codex, write_stdin, still-running commands and hosted tools are not visible to a hook.
Chains are unsigned until you add a key. Hash linkage alone does not prove a chain was not edited. Attestation covers signing and pinned-key verification.
