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.
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.
{
"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"]
}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.
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.
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.
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.
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"
}'{
"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"
}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.
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
}
}'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.
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"
}'{
"payload": {
"summary": "Analysis of Q1 market trends shows 15% growth in AI infrastructure spending.",
"confidence": 0.87
}
}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.
You can manually expire a drop-off before its TTL elapses. This permanently deletes the package from storage.
curl -X POST https://api.getstack.run/v1/dropoffs/dof_a1b2c3d4e5/expire \
-H "Authorization: Bearer sk_live_..."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.
curl https://api.getstack.run/v1/dropoffs \
-H "Authorization: Bearer sk_live_..."Every state transition in a drop-off's lifecycle is recorded in an append-only audit log with hash chaining. The audit entries include:
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.
All drop-off operations are available as MCP tools, allowing agents to create and manage handoffs directly through the STACK MCP server:
// 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"
});