STACK
MENU
DOCS / CONCEPTS / DROP-OFFS

Drop-offs

A Drop-off transfers one payload from a producer agent to a named consumer agent. STACK validates the payload against a JSON Schema, encrypts it at rest, and deletes it after collection or expiry.

A Drop-off is not a queue, pub/sub system, or stream. It has one producer, one consumer, and one payload.

Core Concepts

Schema Validation

Every drop-off location is created with a JSON Schema that defines the shape of the data it accepts. When a producer deposits data, STACK validates the payload against this schema using Ajv (Another JSON Schema Validator) before accepting it. If the data does not match, the deposit is rejected with a 400 error.

Schema validation is mandatory. STACK rejects a payload that does not match the declared schema. The schema controls structure. It does not make arbitrary string content safe.

json
{
  "type": "object",
  "properties": {
    "summary": { "type": "string", "maxLength": 5000 },
    "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
    "sources": {
      "type": "array",
      "items": { "type": "string", "format": "uri" }
    }
  },
  "required": ["summary", "confidence"]
}

Encryption

All deposited packages are encrypted at rest using AWS KMS envelope encryption. A unique data encryption key (DEK) is generated for each package, the data is encrypted with AES-256-GCM, and the DEK is wrapped by the KMS master key. The plaintext DEK is never stored -- only the encrypted DEK and the ciphertext are persisted.

Decryption happens only at collection time, in-memory, and the plaintext is returned directly to the collecting agent. The decrypted data is never written to disk or cached.

TTL & Expiry

Every drop-off has a time-to-live (TTL) specified at creation. After the TTL expires, the package is no longer collectible and is scheduled for deletion by the background worker process. The default TTL is 1800 seconds (30 minutes), and the maximum is 86400 seconds (24 hours).

Expired packages are permanently deleted -- there is no recovery mechanism. If the consumer fails to collect within the TTL window, the producer must create a new drop-off and re-deposit the data.

Lifecycle

A drop-off progresses through a strict sequence of states. Each transition is recorded in the audit log with timestamps and actor identities. Drop-off IDs use the dof_ prefix.

1. Create

The producer (or a coordinator) creates a drop-off location by specifying a schema, the sender and receiver agents, and a TTL. Use notify for the expiry action. Legacy retry and fail values are accepted but normalized to the same expire-and-purge behavior.

bash
curl -X POST https://api.getstack.run/v1/dropoffs \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from_agent": "agt_producer456",
    "to_agent": "agt_consumer123",
    "schema": {
      "type": "object",
      "properties": {
        "summary": { "type": "string" },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 }
      },
      "required": ["summary", "confidence"]
    },
    "ttl_seconds": 1800,
    "on_expire": "notify"
  }'
json
{
  "id": "dof_a1b2c3d4e5",
  "from_agent_id": "agt_producer456",
  "to_agent_id": "agt_consumer123",
  "status": "created",
  "on_expire": "notify",
  "expires_at": "2026-04-15T11:00:00Z",
  "created_at": "2026-04-15T10:30:00Z"
}

Input Fields

  • from_agent (string, required) -- Agent ID that will deposit the package
  • to_agent (string, required) -- Agent ID that will collect the package
  • schema (object, required) -- JSON Schema for deposit validation
  • ttl_seconds (number, optional) -- Time-to-live in seconds. Default: 1800 (30 min), max: 86400 (24h)
  • on_expire (string, optional) -- Use "notify". Expiry always purges the payload.

2. Deposit

The producer deposits data into the drop-off location. The data is validated against the schema, encrypted, and stored. The status transitions from created to deposited. Only one deposit is allowed per drop-off -- attempting a second deposit returns a 409 Conflict. Mission attribution comes from an active Passport, and the deposited-byte count remains after payload deletion so its cap is cumulative.

bash
curl -X POST https://api.getstack.run/v1/dropoffs/dof_a1b2c3d4e5/deposit \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_producer456",
    "payload": {
      "summary": "Analysis of Q1 market trends shows 15% growth in AI infrastructure spending.",
      "confidence": 0.87
    }
  }'

3. Collect

The designated consumer collects the package. The data is decrypted in-memory and returned. The status transitions from deposited to collected. Once collected, the encrypted data is deleted from storage -- it cannot be collected again.

bash
curl -X POST https://api.getstack.run/v1/dropoffs/dof_a1b2c3d4e5/collect \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_consumer123"
  }'
json
{
  "payload": {
    "summary": "Analysis of Q1 market trends shows 15% growth in AI infrastructure spending.",
    "confidence": 0.87
  }
}

4. Terminal States

A drop-off reaches a terminal state when it is either collected or expires. In both cases, the encrypted package data is purged from storage. The audit log keeps the lifecycle metadata for the operator's configured retention period.

  • collected -- consumer successfully retrieved the data; package deleted from storage
  • expired -- TTL elapsed before collection; package deleted by worker process

Manual Expiry

You can manually expire a drop-off before its TTL elapses. This permanently deletes the package from storage.

bash
curl -X POST https://api.getstack.run/v1/dropoffs/dof_a1b2c3d4e5/expire \
  -H "Authorization: Bearer sk_live_..."

Listing Drop-offs

Account credentials list all drop-offs for the operator. Agent JWTs and Passports list only drop-offs where that authenticated agent is the producer or consumer.

bash
curl https://api.getstack.run/v1/dropoffs \
  -H "Authorization: Bearer sk_live_..."

Custody Chain & Audit

Every state transition in a drop-off's lifecycle is recorded in an append-only audit log with hash chaining. The audit entries include:

  • Actor identity (agent ID, operator ID, passport reference)
  • Action (create, deposit, collect, expire)
  • Timestamp (server-side, not client-provided)
  • Previous hash (each entry references the hash of the prior entry, forming a tamper-evident chain)
  • Payload hash (SHA-256 hash of the payload for deposit/collect actions)

The audit log is INSERT-only -- no UPDATE or DELETE operations are permitted on audit records. Hash chaining makes changes to retained rows detectable. The payload itself is not retained after collection or expiry.

MCP Tools

All drop-off operations are available as MCP tools, allowing agents to create and manage handoffs directly through the STACK MCP server:

  • stack_create_dropoff -- create a new drop-off location with schema, agents, and TTL
  • stack_deposit -- deposit data into an existing drop-off (validates against schema)
  • stack_collect -- collect and decrypt the deposited package
  • stack_get_dropoff_status -- check current status without collecting
  • stack_list_dropoffs -- list drop-offs visible to the active credential
  • stack_expire_dropoff -- manually expire a drop-off and delete its package
typescript
// Example: agent creates a drop-off via MCP
const result = await client.callTool("stack_create_dropoff", {
  from_agent: "agt_producer456",
  to_agent: "agt_consumer123",
  schema: {
    type: "object",
    properties: {
      result: { type: "string" },
      score: { type: "number" }
    },
    required: ["result", "score"]
  },
  ttl_seconds: 1800,
  on_expire: "notify"
});