Effect ledger and undo for AI agents — MCP proxy with R/C/I classifier, policy gates, and compensation packs
Sagaz toybox is a deliberately dangerous demo server with mixed annotation coverage. 12 tools with complete schemas (Zod-based, visible in source). Descriptions are present and clear (avg ~100 chars), but lack LLM-optimized guidance on when/why to use each tool. Tool names follow verb_noun convention well (list_*, get_*, create_*, delete_*, send_*, post_*, transfer_*). Critical gap: no output schemas documented, no pagination support on list tools, no error recovery guidance. Annotations deliberately mixed (some tools carry readOnlyHint/destructiveHint, others omitted for testing). Error handling wraps WorldError but provides minimal recovery hints. Composition is sound, each tool has one responsibility, but chaining IDs are not explicitly documented in responses.
Create a CRM contact. Email must be unique. company may be null (no company). id is optional and restores a previously deleted contact under its original identity (restore semantics); omit it for new contacts.
Delete a CRM contact by id.
Delete a tweet from the public timeline.
Fetch a single CRM contact by id.
List bank accounts with their current balances (in cents).
List all CRM contacts.
List all emails sent so far.
No output schemas documented. LLMs cannot infer what fields list_contacts, get_contact, or send_email return, forcing them to guess downstream field names for chaining.
List tools (list_contacts, list_inbox, list_timeline, list_accounts) lack pagination parameters (limit, offset, cursor). No indication of result count or truncation. Large result sets will blow context windows.
Descriptions lack LLM-optimized guidance. 'List all CRM contacts' does not explain when to call this vs get_contact, or what structure is returned. Missing dependency hints and use-case context.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 64 | 2026-07-28+ | v2 |
List tweets currently visible on the timeline.
Post a public tweet (max 280 chars).
Send an email. Once sent it cannot be unsent; a correction can be sent with in_reply_to.
Transfer money between accounts (acc-ops, acc-payroll, acc-vendor). Amount in cents. This cannot be reversed.
Update fields of an existing CRM contact by id. Pass company: null to clear it.
Error handling returns WorldError messages but provides no recovery guidance. 'Contact not found' does not suggest calling list_contacts or search_contacts. Errors are not categorized as retryable vs fatal.
Irreversible operations (delete_contact, delete_tweet, send_email, transfer_funds) lack confirmation or dry-run support. Agents can cause data loss without a safety gate.