Public
AI mailroom propose/commit action-gate agent
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

Mailroom Action Gate (ga5-mailroom-action-gate/v2)

A single public HTTPS endpoint implementing the propose → commit action-gate protocol for an AI mailroom triage agent.

Endpoint

POST /   (this val's main.ts)

Both operations go to the same URL, selected by the JSON body's operation field.

Architecture (layered, per the build order)

Rendering mermaid diagram...
  • main.ts — HTTP entrypoint. Parses the envelope, dispatches propose/commit, enforces response size/content-type rules, and owns the idempotency/replay/conflict state machine.
  • lib/validate.ts — structural request validation (before any AI/tool work) plus per-action frozen target/payload shape validation and evidence-lineId checking for model output.
  • lib/db.ts — SQLite persistence: one row per evaluationId (propose state, stored response, commit state, stored response) and a dossier_cache table keyed by canonical dossier content hash so repeat Checks/Saves over the same 64 stable dossiers cost no model call.
  • lib/ai.ts — the only layer that calls a model. Uses Val Town's free std/openai proxy by default (gpt-5.4-nano), or your own OPENAI_API_KEY if set. Batches every uncached dossier from one propose call into a single chat completion.
  • lib/verify.ts — Ed25519 signature verification (@noble/ed25519) over the exact canonical receipt-verification message shape from the spec.
  • lib/canonical.ts — recursively key-sorted, compact JSON canonicalization and SHA-256 hex hashing, shared by inputDigest, proposalDigest, and cache keys.

Protocol behavior

  • Idempotency: keyed by evaluationId. An exact replay (same canonical dossiers → same inputDigest) returns the stored response byte-for-byte with no model call. A reused evaluationId with different content returns 409 IDEMPOTENCY_CONFLICT.
  • Caching: each dossier's decision is cached by sha256(canonical(dossier)), independent of evaluationId/batch. A cache hit reuses the same callId (derived as call-<contentHash>), so stable dossiers produce byte-identical proposals across evaluations and later Checks.
  • Commit validation: every receipt must reference a callId that exists in the stored proposal set, with matching dossierId, action, and proposalDigest; every signature is verified against the receiptVerifier stored from the matching propose call. The whole commit is rejected (no partial effects) if any receipt fails these checks — checked before any outcome is recorded.
  • Terminal state: once completed, the evaluation is immutable. A byte-identical commit replay returns the same stored response; a commit with different receipts returns 409 ALREADY_COMPLETED.
  • Safety: the system prompt treats all dossier content as untrusted data, instructs the model to quarantine_item on prompt-injection / tool-control / exfiltration attempts, and every proposal is independently re-validated in code against the frozen per-action target/payload shapes and evidence-lineId rules — a decision that doesn't parse or match the schema falls back to a safe request_confirmation, never an outbound or record-changing action.

Environment variables (optional)

By default this val uses Val Town's free hosted OpenAI proxy (no key required). To use your own OpenAI account instead (e.g. to raise rate limits), set:

  • OPENAI_API_KEY — your OpenAI API key.
  • OPENAI_MODEL — override the model (defaults to gpt-5.4-nano).

👉 Add OPENAI_API_KEY here: https://www.val.town/x/kumar49/mailroom-action-gate/environment-variables?key=OPENAI_API_KEY