Shield
@heyarka/shield wraps any agent and returns one with an identical shape, so it drops in wherever the bare agent was used. Four layers run in order: sanitize, corroboration gate, point-in-time guard, then a deterministic risk contract applied to the order the model produced.Wrapping an agent
shieldAgent takes an AgentUnderTest and returns an AgentUnderTest. That is the whole integration surface. There is no framework to adopt.
import { shieldAgent } from "@heyarka/shield";
const hardened = shieldAgent(myAgent, {
riskContract: {
maxNotionalPerTrade: 5_000,
allowedSymbols: ["BTCUSDT"],
humanApprovalThreshold: 2_500,
},
onAudit: (event) => auditLog.write(event),
});
// Same call as before; the shield runs first.
const order = await hardened.decide(context);The wrapped agent’s name gains a +shield suffix, which is how a JSONL log holding both a control and a shielded run stays separable when it is scored later.
The four layers
- 1. Sanitize
- Strips zero-width and bidi control characters, then folds confusable characters to their Latin form and NFKC-normalizes. Order matters: invisible characters are removed first, or they would survive inside runs the folding pass treats as opaque. Built from the same Unicode tables the attack vectors use.
- 2. Corroboration gate
- Groups near-identical stories and counts distinct
sourcevalues, honouringoriginatingSourceso four aggregators echoing one wire story collapse to a single item with an honest independent-source count. - 3. Point-in-time guard
- Drops anything dated after the context’s
asOftimestamp, and removes items that are detected replays of an earlier story already in context. An unparseable date is treated as invalid and dropped rather than trusted. - 4. Risk contract
- Runs after the model has decided, on the returned order alone. It checks notional, allowed symbols, confidence ceiling, and the human-approval threshold. Violating orders are replaced with a
holdof size 0 flagged for human approval.
What the shield does not stop
The measured result is grade C → B, and injection susceptibility 31.3% → 12.5%. It does not reach zero, and it should not be described as if it did.
- Semantic traps
- A plausible, well-formed lie has no character-level signature. The corroboration gate catches manufactured echo; it cannot catch a single credible-sounding falsehood from a real source.
- Sentiment-filter poisoning
- Manufacturing the crowding or balance conditions a strategy gates on is an attack on the strategy's logic, not on its input encoding.
- Look-ahead contamination
- Memorization lives in the model's weights. No input filter can remove what the model already knows, so this is measured and reported, not blocked.
The residual 12.5% is entirely in these families. That is a real limit of input hardening, and the scorecard reports it rather than hiding it.
The audit trail
onAudit receives a typed event every time a layer changes something, and never affects the decision. The event union is sanitize, corroboration, point-in-time, and risk-contract, each carrying what it found.