{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreifs2n3erv7o3u7duut3hdweksl67azfjaf6avl46e5xsessyqymte",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mp5tfkjvmpo2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreienetmehdp63eb47fvk4qhveopr7e37sosipfmbs4r5xmspkier24"
},
"mimeType": "image/webp",
"size": 57938
},
"path": "/telleroutlook/your-ai-agent-called-a-tool-can-you-prove-it-followed-the-rules-2bgn",
"publishedAt": "2026-06-26T01:26:57.000Z",
"site": "https://dev.to",
"tags": [
"agents",
"ai",
"architecture",
"security",
"packages/aep",
"packages/otel-exporter",
"@wasmagent"
],
"textContent": "# Your AI agent called a tool. Can you prove it followed the rules?\n\nYour agent just wrote a file. You have logs. But can you answer this:\n\n> Was the policy gate applied _before_ the tool ran — or after?\n\nLogs can't tell you that. Here's how we solved it.\n\n## The gap in current agent frameworks\n\nMost frameworks give you a log line like:\n\n\n\n [2026-07-07T09:00:01Z] tool:fs_write path=/tmp/report.txt status=ok\n\n\nThat tells you the tool ran. It doesn't tell you:\n\n * Whether a policy evaluated the call first\n * What the pre-state looked like before the write\n * Whether the agent was within its token and risk budget\n * Which agent in a delegation chain authorized this\n\n\n\nFor a hobby project, that's fine. For anything touching real data, it's not.\n\n## AEP: structured proof, not a log stream\n\nWasmAgent's `@wasmagent/aep` package records every tool call as an `ActionEvidence` object — Zod-validated, schema-versioned, with pre/post state digests baked in.\n\n\n\n import { AEPEmitter } from \"@wasmagent/aep\";\n\n const emitter = new AEPEmitter({\n run_id: \"run-abc123\",\n repo_commit: \"5c1102f\",\n model_id: \"claude-sonnet-4-6\",\n });\n\n // Before tool execution:\n emitter.addAction({\n tool_name: \"fs_write\",\n state_changing: true,\n capability_decision: {\n capability: \"fs_write\",\n subject: \"agent:run-abc123\",\n resource: \"/tmp/report.txt\",\n decision: \"allow\",\n reason_code: \"policy:default-v1\",\n },\n precondition_digest: \"sha256:a1b2c3...\",\n input_taint_labels: [\"user_provided\"],\n });\n\n // After tool execution:\n emitter.addAction({\n tool_name: \"fs_write\",\n state_changing: true,\n post_state_digest: \"sha256:d4e5f6...\",\n });\n\n emitter.setBudgetLedger({\n token_budget: { limit: 4000, spent: 142 },\n risk_budget: { limit: 1.0, spent: 0.2 },\n });\n\n const record = emitter.build();\n\n\nThe `capability_decision` is part of the same record as the action — not a separate log entry that could be reordered or dropped.\n\n## OTel spans for everything else\n\nFor real-time observability, AEP also emits named OpenTelemetry spans:\n\n\n\n import { AEP_SPAN_NAMES } from \"@wasmagent/otel-exporter\";\n\n // Plug into any OTel collector:\n AEP_SPAN_NAMES.TOOL_CALL // \"tool.call\"\n AEP_SPAN_NAMES.POLICY_CHECK // \"policy.check\"\n AEP_SPAN_NAMES.SANDBOX_EXEC // \"sandbox.exec\"\n AEP_SPAN_NAMES.VERIFIER_CHECK // \"verifier.check\"\n AEP_SPAN_NAMES.LLM_GENERATE // \"llm.generate\"\n AEP_SPAN_NAMES.MCP_REQUEST // \"mcp.request\"\n // + 3 more\n\n\nThe spans go to Grafana/Jaeger/etc. The `AEPRecord` is what you keep for audit and training data.\n\n## Multi-agent: delegation chain\n\nIn a single-agent setup, this is useful. In a multi-agent setup — orchestrator delegates to a subagent — it becomes essential:\n\n\n\n run_context: {\n agent_id: \"orchestrator\",\n subagent_id: \"coder-agent\",\n delegation_chain: [\"orchestrator\", \"planner\", \"coder-agent\"],\n scope_lease_id: \"lease-xyz\", // ← subagent can only do what parent granted\n }\n\n\n## Try it\n\n\n git clone https://github.com/WasmAgent/wasmagent-js\n bun test packages/aep/src/\n\n\n**Next in this series:** MCP Trust Pack — the gateway layer that enforces policy before tools execute.\n\n**Code:** packages/aep · packages/otel-exporter",
"title": "Your AI agent called a tool. Can you prove it followed the rules?"
}