Skip to content

Out-of-band approval CLI mode: blacktea approve <intent-id> #3

@nmrtn

Description

@nmrtn

Idea

Add an out-of-band approval mode where the agent stages a payment intent, the runtime waits in a paused state, and a separate operator process unlocks the payment via a CLI command:

# In the agent's terminal (paused on staged intent):
[blacktea] payment staged: intent_abc123
[blacktea] amount: 25 USDC -> 0x4f3a...
[blacktea] reason: "monthly subscription to data feed"
[blacktea] approve out of band:  blacktea approve intent_abc123
[blacktea] (waiting...)

# In a separate operator terminal:
$ blacktea approve intent_abc123
OK approved at 2026-06-01T14:23:51Z by user nmrtn

The agent does not call any approval function. It cannot self-approve. The approval stamp lives in a separate process under separate credentials.

Why

This is an alternative to the current onApprovalNeeded callback. Today, blacktea's approval flow fires a customer-supplied async function in the SAME process as the agent. The customer is expected to route that function to a human (Slack, custom UI, console prompt, etc.) and trust their own code not to bypass the call. The trust boundary depends on the customer's discipline.

The CLI-stamp model is structurally stricter. The agent process and the approval process do not share memory, do not share credentials, and don't even need to share a host. An agent that has been prompt-injected to "auto-approve this payment" cannot do so — it has no syscall available that produces a valid approval stamp.

Prior art

hermes-payguard implements this exact pattern for Hermes Agent. Their payguard approve <intent-id> command is the canonical reference. They cite CaMeL Guard as the security framing. The repo is stale (single commit, March 2026) but the design idea is solid and worth bringing into a cross-platform library.

Design sketch

  1. New policy action: approval: "out_of_band" (alongside existing console and callback).
  2. When the evaluator returns this action, pay() does NOT resolve immediately. It writes a staged-intent record to disk (./.blacktea/staged/<intent-id>.json), prints the approval instructions to stderr, and polls disk for an approval stamp.
  3. The CLI gains two subcommands:
    • blacktea approve <intent-id> [--reason "..."] — writes a stamp file
    • blacktea reject <intent-id> [--reason "..."] — writes a rejection
  4. pay() reads the stamp, validates it (timestamp, optional signature, integrity check against the staged intent), and either proceeds to settle or throws PolicyDeniedError.
  5. Configurable timeout — same timeout_seconds as the existing approval channels.

Open questions to resolve when picking this up

  • File-based vs. socket-based stamping. File is simpler, socket is faster and avoids polling. Probably file for v1, socket later if anyone needs sub-second approval.
  • Signing the stamp. Do we require the operator to have a signing key (e.g., GPG) so a compromised filesystem can't forge stamps? Real but heavy. v1 probably just relies on filesystem permissions; document the assumption.
  • MCP server integration. If an agent runs through MCP, the MCP server is the one calling pay(). How does the operator see the staged intent? Probably: the MCP transport's audit sink prints the staged-intent notice; the operator runs the CLI on the host machine.
  • Multi-intent staging. Can the same operator stamp file approve multiple intents (batch approve)? Probably yes for power users.
  • Auto-expire. Staged intents older than N hours should auto-reject. Default 24h.

Why not v0.1.0

  • Current callback pattern works and is enough for most setups.
  • Adds filesystem state with concurrency concerns (race between agent reading and operator writing).
  • Out-of-band approval is the right primitive for high-stakes wallets; most agents in v0.1.x are spending <$10/day on data APIs.
  • Better to ship after a real user asks for it. The PayGuard repo's lack of traction suggests the audience for this exists but isn't large yet.

When to actually build

After at least one of:

  • A user filing an issue about not trusting the callback model
  • A user with a >$1k/day agent asking for tighter approval boundaries
  • Blacktea picking up adoption in security-conscious orgs (fintech, ops automation) where the callback model is a non-starter

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions