Inter-Shell Protocol
How Duct shells communicate with each other — open discovery, runtime consent, and scoped single-use delegation tokens.
Overview
Any public Duct shell can call any other public Duct shell — no bilateral integration or API key sharing required. The model has three parts:
Open discovery
POST /v1/registry/search finds shells by capability. No pre-registration between companies.
Gate checks
Before a cross-shell call executes, Duct verifies registry_visibility, intershell_enabled, agentAccessible, and optional allowedCallers on the target.
Consent matrix
Read actions with low sensitivity pass through. Side-effect or high-sensitivity actions always require explicit user consent before the delegated token is minted.
POST /v1/intershell/message
Send a message from one shell to another. The caller must present a valid shell token for the from_shell. There is no separate cross-shell invoke endpoint: use type: "action_request" on this endpoint for an exact action, or type: "query_request" with payload.query for a natural-language request.
duct_version
string — protocol version. Currently "1".
message_id
string — caller-generated UUID for idempotency.
from_shell
string — shell ID of the calling shell.
to_shell
string — shell ID of the target shell.
type
"action_request" | "query_request" | "context_push"
payload
object — action_id + params for action_request; query string for query_request; arbitrary JSON for context_push.
auth
object — { shell_token: string } — the calling shell's bearer token.
timestamp
string — ISO 8601 timestamp. Requests older than 60 seconds are rejected.
curl -X POST https://api.ductai.vercel.app/v1/intershell/message \
Message Types
action_request
Execute a specific manifest action on the target shell. Requires action_id and params in payload. Subject to all gate checks and the consent matrix.
query_request
Send a natural-language query in payload.query to the target shell's agent loop. Returns a synthesized text response. Useful when you do not know the exact action ID.
context_push
Push structured context into the target shell's session without triggering an action. Useful for pre-loading entity state before a follow-up call.
Gate Checks
Duct applies these checks in order before a cross-shell call reaches the target API. A failure at any step returns a 403 with a machine-readable reason field.
1. intershell_enabled
The target shell must have intershell_enabled: true. Default is false.
2. registry_visibility
The target shell must have registry_visibility: "public". Private shells are not reachable cross-shell.
3. agentAccessible
The specific action being called must have agentAccessible: true in the target manifest.
4. allowedCallers (optional)
If the target shell sets allowedCallers, the from_shell must appear in that list.
5. Consent matrix
Duct checks the action's sideEffects and sensitivityLevel against the consent matrix. Side-effect or high-sensitivity calls require a user consent token.
Consent Matrix
Cross-shell uses the same action fields as the Agent API. Full matrix (human shell, guest, agent, errors): Action Permissions → Enforcement by caller.
read + sensitivityLevel: low
No user consent required. Call proceeds immediately.
read + sensitivityLevel: high
User consent required. Shell must present X-Duct-User-Consent token.
sideEffects: true (any sensitivity)
User consent always required. Returns 202 confirmation_required first.
agentAccessible: false
Action is never reachable cross-shell, regardless of other settings.
Cross-shell replies in chat
When your shell answers using data fetched from another company's product, Duct treats that payload as untrusted input when composing the reply. End users see normal prose — not raw third-party content or instructions embedded in API responses.
Call Chain
Duct supports multi-hop call chains — shell A calls shell B which calls shell C — with the following guarantees:
Depth limit
Maximum 5 hops. Calls that would exceed depth 5 are rejected with 400 chain_depth_exceeded.
Circularity protection
The chain header carries every shell ID seen so far. A shell that appears twice in its own chain is rejected immediately.
HMAC signing
Each hop re-signs the chain header with Duct's internal signing key. Shells cannot forge a shorter chain to bypass depth limits.
Audit on both sides
Every hop in a call chain is independently logged by the calling shell and the receiving shell. You can reconstruct a full cross-company call chain from your own shell's audit log without relying on the other company.
Delegation Chains — Narrowing And Revocation
A cross-shell delegation token (dt_) can itself be re-delegated — e.g. an orchestrator shell passing a scoped slice of its authority to a worker shell. Sub-delegation is only ever allowed to narrow, never broaden, what the parent token could do.
Same action only
A sub-delegation minted with parent_jti must target the exact same action_id as its parent. It cannot add actions the parent could not already call.
Expiry cannot extend
A child token's expiry must be at or before its parent's. A delegation can never outlive the authority it was minted from.
Parent must be live
Minting fails immediately if the parent is already revoked or expired — you cannot delegate from a token that is no longer valid.
Chain depth
Bounded by the same 5-hop limit as the call chain above, walked from the token up through every ancestor.
Cascade revocation
Revoking any delegation token revokes that token and every token minted from it, at any depth. There is no way for a sub-delegated token to keep working after its parent is revoked — verification walks the full chain on every use, not just the token's own expiry.
Why this matters
Without monotonic narrowing, a worker shell could re-delegate itself broader authority than it was granted. Without cascade revocation, revoking a compromised or mistaken delegation would leave every token derived from it silently still valid. Both are enforced at delegation-mint time and at verification time — not just documented convention.
Rate Limits
Per caller→target pair
30 requests / minute. Applies to a specific (from_shell, to_shell) combination.
Per-target global
120 requests / minute across all inbound callers. Protects the target shell from aggregate abuse.
Rate limit response
429 with Retry-After header in seconds.