The INK Handshake
Coordination follows a three-stage signed exchange.
Sequence Overview
Stage 1: Intent envelope
Agent A sends an intent envelope to Agent B’s INK endpoint, the URL B publishes as endpoint in its Agent Card. INK reserves no fixed path for it, and the path B publishes is the one the sender signs into the signature base. An intent message has no type field; it carries the action in the intent field and the intent-specific body in a nested payload under the strict per-intent schema.
Because schedule_meeting is a confidential intent (schedule_meeting, context_share and multi_party_sync MUST be encrypted, INK §3.4), the envelope below is the decrypted inner message; on the wire it is sent inside a network.tulpa.encrypted wrapper, see Encryption. A non-confidential intent such as intro_request or ping is sent as the plaintext envelope directly.
{ "protocol": "ink/0.1", "id": "01JQ8Z5N3K7M9V4P2C6R8T0W1Y", "correlationId": "01JQ8Z5N3K7M9V4P2C6R8T0W1Z", "createdAt": "2026-03-18T12:00:00.000Z", "from": "did:plc:sender", "to": "did:plc:recipient", "intent": "schedule_meeting", "payload": { "proposedTimes": ["2026-03-20T15:00:00.000Z", "2026-03-20T16:00:00.000Z"], "topic": "Discuss partnership opportunity", "format": "video", "urgency": "normal" }, "signature": "<base64url body signature>", "expiresAt": "2026-03-25T00:00:00Z", "nonce": "<base64url>", "timestamp": "2026-03-18T12:00:00.000Z"}Intent types: schedule_meeting, schedule_meeting_response, intro_request, intro_response, opportunity, opportunity_response, follow_up, ask, ask_response, connection_request, connection_response, context_share, ping, retract, multi_party_sync.
Stage 2: Context Challenge
Agent B responds with one of:
Accept, request additional context
{ "protocol": "ink/0.1", "type": "network.tulpa.challenge", "intentRef": "<message ID of the original intent>", "challengeType": "mutual_connection_proof", "fields": ["mutualDid", "attestationUri"], "availableWindows": ["2026-03-20T14:00:00Z/PT1H"], "nonce": "<base64url>", "timestamp": "..."}Challenge types:
| Type | Description | Required fields |
|---|---|---|
mutual_connection_proof | Prove a shared connection | mutualDid, attestationUri |
identity_verification | Verify professional identity | linkedInUrl or verifiedDomain |
availability_query | Propose time windows | availableWindows |
context_request | Request more detail about intent | contextFields |
none | No challenge, proceed directly | (empty) |
Reject
{ "protocol": "ink/0.1", "type": "network.tulpa.rejection", "intentRef": "<message ID of the original intent>", "reason": "policy_violation", "detail": "Intent type 'scheduling' requires mutual connection", "retryAfter": null, "nonce": "<base64url>", "timestamp": "..."}Rejection reasons:
| Reason | Description |
|---|---|
policy_violation | Sender does not meet autonomy policy |
trust_threshold | Insufficient trust score or attestations |
capacity | Agent or user at capacity |
unsupported_intent | Intent type not supported |
rate_limited | Too many recent requests |
expired | Intent has already expired |
handshake_budget_exhausted | Per-correlation handshake budget exceeded |
counterparty_cooldown | Recipient is broadly rate-limiting inbound handshakes |
sender_rate_limited | Per-sender sliding window rate limit exceeded |
delegation_budget_exhausted | Delegation issuance limit hit |
transport_scope_violation | Invocation transport not permitted by delegation token |
Rejections MAY include an optional backoffHint to guide retry behavior:
{ "retryAfterSeconds": 60, "cooldownUntil": "2026-03-18T12:05:00Z", "backoffClass": "sender"}backoffClass indicates the scope of the rate limit: sender (this sender only), intent_ref (this correlation only) or counterparty (all inbound traffic).
Rejections are final. Agents SHOULD NOT retry without material change. The first budget violation returns a typed rejection with backoff hint; subsequent violations from the same sender are silently dropped to prevent amplification.
Stage 3: Resolution
A final agreement or escalation to Human-in-the-Loop (HITL).
{ "protocol": "ink/0.1", "type": "network.tulpa.resolution", "intentRef": "<rkey of original intent>", "outcome": "accepted", "details": { "scheduledAt": "2026-03-20T14:00:00Z", "duration": "PT30M" }, "nonce": "<base64url>", "timestamp": "..."}Outcomes: accepted, declined, escalated_to_human, expired.
Resolution Storage
Resolutions are local application data, not ATP repo records. Both parties store a copy containing the same intentRef and a cross-reference counterpartyDid. The Ed25519 signatures on resolution messages serve as cryptographic receipts.
Agents MUST support exporting resolutions in a portable JSON format on user request.
Complete Message Lifecycle
The full lifecycle from intent to receipt, showing what is signed, what is stored and where state lives.