Inspiration

What it does

How we built it

Challenges we ran into

Accomplishments that we're proud of

What we learned

What's next for Who Signed That

Inspiration

Foxit's challenge premise is one we agree with: signing doesn't belong in an agent's tool catalog. Leave it out, and an agent cannot sign on its own. But removing the tool only stops the agent — it produces no record of why a human said yes. When something goes wrong, that is exactly the question you get asked. The missing half: make the human's approval itself a first-class object — versioned, portable, and bound to the exact document it approved.

What it does

Who Signed That is a working, end-to-end agent signing workflow where the reversible work is done by the agent and the irreversible act is gated on a verifiable human decision:

  1. The agent renders a consulting agreement, turns HTML into a real PDF via the Foxit PDF Services API, and verifies the contract text with real text extraction — recording what it verified on its own machine.
  2. At the signing boundary it stops: it writes the question ("approve or reject, and attach the sha256 of the PDF you see") into a versioned task passport and packs the document + its sealed facts into a single portable handoff file.
  3. A human reviews the handoff — facts the agent verified on its machine are shown as unverified on the reviewer's machine, not as green checks — and returns a signed receipt pinned to the document's sha256.
  4. The agent lands the receipt back into the passport and refuses to sign unless all four gates pass: someone answered, the answer is approve, the receipt's hash equals the live document's hash, and only then the Foxit eSign API is called. The envelope is created and sent; the whole chain stays in one versioned record.

Two refusal cases are part of the demo, because they are the point:

  • no approval → the agent refuses to sign (exit code 3)
  • the document is modified after approval → the agent refuses to sign (exit code 2)

Real run (2026-08-22, sandbox): contract PDF with sha256=22177d55...e0a7483, human approval receipt, hash match, real eSign send — envelope folderId=35504526, and the signing invitation arrived in the recipient's inbox ([email protected]).

How we built it

Node.js, zero runtime dependencies. The handoff format is TaskPassport (npm: task-passport, MIT) — a protocol for handing long-running task state between agents, machines, and humans: versioned, locked, checksummed. The Foxit side was built against the live gateway and its OpenAPI spec (/pdf-services/v3/api-docs): upload → operation → task poll → download for PDF Services; the unified eSign API (/esign/api/v1) using the same client_id/client_secret header auth — no OAuth token dance. A fully deterministic mock mode runs the same workflow with no network (same input → same sha256), so every reviewer can run the demo in one command:

bash examples/foxit/demo.sh                 # full flow
bash examples/foxit/demo.sh --no-approval   # refuses: exit 3
bash examples/foxit/demo.sh --tamper        # refuses: exit 2

Challenges

The challenge brief's API facts did not match the live API: the documented document-generation endpoint returned 404, the documented Basic/OAuth auth was rejected, and eSign lived on a legacy host — all replaced by real calibrations against the unified Foxit gateway (header-based credentials, upload → task → download flow). eSign activation itself needed a company name on the Foxit account and a fresh session before the portal accepted it. The honest-boundary rule was kept throughout: receipts are not notarization — they prove which version was approved and by whom, not that a person existed.

What's next

One-click signature links for the reviewer instead of a draft + send round, template-driven envelopes, webhook status tracking, and packaging the gate as a reusable npm service so any agent harness can adopt the same "stop at irreversible" boundary.

Built With

  • bash
  • foxit-esign-api
  • foxit-mcp-server
  • foxit-pdf-services-api
  • git
  • mcp
  • node.js
  • task-passport-(npm)
Share this project:

Updates

Submission history