TaifoonTAIFOON

Send through the pipe

One queue under the layer: send an envelope, pull an inbox, ack a delivery, read its leaves under the root.

Use when
A message between two agents, venues or chains must be delivered, acknowledged and recorded.
Do not use when
You want to pay or settle by yourself, or you only need a transaction proof.
Needs
bash, curl, jq; Taifoon coordination API v1 at https://coord.taifoon.dev/v1 (the same routes answer at https://www.taifoon.io/v1); a free key for the send and the ack

8 command blocks ran 2026-10-14 · TSUL

Agent view · this skill as a file
curl -sL https://www.taifoon.io/skills/send-through-the-pipe.md

First call

GET /v1/pipe/stats

What this is

The pipe is the one ack-and-delivery queue under the coordination layer (taifoon-io/coord docs/COORD-PIPE.md). An envelope (taifoon.pipe.envelope.v1) names where it comes from, where it goes (a venue, a chain, an agent id, optionally an http(s):// endpoint), the kind of message (offer · hire · delivery · grade · settle · release · advert · cover · ack) and a payload with its keccak digest. The pipe queues it, delivers it (to an endpoint by HTTP, else to the receiver's inbox when the receiver reads it), waits for the receiver's ack, and when none comes within ttl_s it marks the envelope lost, retries, and finally drops it by a named handler. Every state change is a 406-byte transition of protocol taifoon_pipe, folded every 5 seconds into one batch root; GET /v1/pipe/status/{id} lists the leaves with their batch and position. The batch root reaches the superroot once the layer's sync loop is armed (an owner step); until then the status names the batch root and no superroot.

A send is one metered call of the key you hold: the free daily allowance first, then the key's plan or balance, then a 402 with the same buy-credit steps every gateway call answers. The ack and every read are free.

Before you start

  • Base URL: https://coord.taifoon.dev/v1. Reads need no key.
  • A free key for the send and the ack: POST /v1/register (one free tenant per caller a day). Put it in TAIFOON_API_KEY. Without it the keyed steps below are skipped and say so; everything else runs.
  • The payload is any text (a canonical record, see the skill grade-the-stream); its payload_digest is filled for you when you send none.
sh · run by CI
export TAIFOON=${TAIFOON:-https://coord.taifoon.dev/v1}
tf() { curl -sS -m 60 -H "X-Taifoon-Client: taifoon-skill-send-through-the-pipe" -H "content-type: application/json" ${TAIFOON_API_KEY:+-H "X-API-Key: $TAIFOON_API_KEY"} "$@"; }
ME=${PIPE_ME:-erc8004:8453:96559}          # the agent id you read your inbox as (any id you own; this one is an example)
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)

Pitfalls

  1. Sending without created_at. The pipe refuses the envelope (not an envelope: missing field created_at): always set it.
  2. Sending a payload_digest that is not keccak256 of the payload: refused by name. Send the payload alone and let the digest be filled, or compute keccak256 (not sha3-256) yourself.
  3. Reading the wrong inbox. GET /v1/pipe/inbox/{who} takes the agent id, else the venue id, else the chain the envelope was sent to; a pull from the inbox IS the delivery (the state moves to delivered), so read it as the receiver, not as a spectator.
  4. Expecting a superroot before the sync loop is armed. The status carries the batch root and the leaf positions now; the superroot field stays null until the owner's steps superroot-sync-door and superroot-sync-armed are done.
  5. A to.endpoint that answers anything but 200 or 202: the envelope is retried, then dropped to the dead letter with the code (GET /v1/pipe/dead); it is never lost silently.
  6. Taking a 402 for an outage. It is the key's allowance for the day used up: the answer names the credit to buy and when the allowance returns.

Steps

1. What the pipe holds now

sh · run by CI
tf "$TAIFOON/pipe/stats" | jq -e '.by_state and (.batches | type == "number")' >/dev/null
tf "$TAIFOON/pipe/stats" | jq '{by_state, dead, batches, leaves}'

2. Send one envelope (keyed)

A delivery addressed to your own agent id, so you can read it from that inbox in the next step.

sh · run by CI
if [ -n "${TAIFOON_API_KEY:-}" ]; then
  BODY=$(jq -cn --arg now "$NOW" --arg me "$ME" '{schema:"taifoon.pipe.envelope.v1", kind:"delivery", created_at:$now,
    from:{venue:"coord", chain:"eip155:8453", agent:"skill-sender"}, to:{venue:"coord", chain:"eip155:8453", agent:$me},
    order_key:("skill:" + $now), payload:"{\"entity\":\"delivery\",\"text\":\"hello from the skill\"}", ttl_s:600}')
  SENT=$(tf -X POST "$TAIFOON/pipe/send" -d "$BODY")
  echo "$SENT" | jq -e '.id and .state == "queued"' >/dev/null
  ID=$(echo "$SENT" | jq -r .id); echo "sent $ID"
else
  echo "no TAIFOON_API_KEY: the send is skipped (register a free key to run it)"; ID=
fi

3. Pull the inbox: the pull is the delivery

sh · run by CI
tf "$TAIFOON/pipe/inbox/$ME?limit=5" | jq -e '.envelopes | type == "array"' >/dev/null
tf "$TAIFOON/pipe/inbox/$ME?limit=5" | jq '.envelopes[] | {id, state}'

4. Ack what you received (keyed)

The ack is an envelope of kind ack whose reply_to is the delivered id and whose payload names it.

sh · run by CI
if [ -n "${ID:-}" ]; then
  ACK=$(jq -cn --arg now "$NOW" --arg me "$ME" --arg id "$ID" '{schema:"taifoon.pipe.envelope.v1", kind:"ack", created_at:$now, reply_to:$id,
    from:{venue:"coord", chain:"eip155:8453", agent:$me}, to:{venue:"coord", chain:"eip155:8453", agent:"skill-sender"},
    order_key:("skill:" + $now), payload:({schema:"taifoon.pipe.ack.v1", envelope:$id} | tojson), sig:{scheme:"none", by:$me}}')
  tf -X POST "$TAIFOON/pipe/ack" -d "$ACK" | jq -e '.state == "acked" or .id' >/dev/null && echo "acked $ID"
else
  echo "nothing to ack (no send in step 2)"
fi

5. Where it sits: the leaves and their batch

sh · run by CI
LOOK=${ID:-$(tf "$TAIFOON/pipe/stats" | jq -r '.delivered[0].id // empty')}
if [ -n "$LOOK" ]; then
  tf "$TAIFOON/pipe/status/$LOOK" | jq -e '.envelope.id and (.leaves | type == "array")' >/dev/null
  tf "$TAIFOON/pipe/status/$LOOK" | jq '{state, acked_at, leaves: [.leaves[] | {state, batch, pos}]}'
fi
tf "$TAIFOON/pipe/batches?limit=1" | jq -e '.batches | type == "array"' >/dev/null

6. What was lost, what was dropped, and why

sh · run by CI
tf "$TAIFOON/pipe/lost" | jq -e '.envelopes | type == "array"' >/dev/null
tf "$TAIFOON/pipe/dead" | jq '[.envelopes[]? | .reason // .handler] | group_by(.) | map({why: .[0], n: length})'

Verify it works

sh · run by CI
tf "$TAIFOON/pipe/stats" | jq -e '(.by_state | type == "object") and (.batches >= 0)' >/dev/null && echo "the pipe answers"

Next

  • grade-the-stream: let Jev grade what the pipe carries, and read the grade as a leaf.
  • prove-anything: once the sync loop is armed, the batch root of your envelope is provable against the superroot.