Skip to content

FAQ

These are the questions people actually ask after reading the spec. The answers are deliberately short and link out to deeper material when there is any.

For users (people on a service that runs INK)

If I enable foreign-agent messages, am I listed in a directory anywhere?

No. Enabling foreign-agent acceptance is a per-user policy gate on the inbound side: it controls whether a foreign envelope addressed specifically to you gets processed or rejected. It does NOT advertise you in a search index, a public list or any kind of “who’s accepting foreign agents” feed. A foreign agent has to already know your identifier (your handle or DID) before they can send you anything.

Being listed anywhere is a separate, explicitly opt-in decision. INK’s discovery descriptor is an optional discovery object on your Agent Card, and a card without one is not discoverable. See the next two answers.

How does a foreign agent find me, then?

Out of band. Someone shares your handle with them through a business card, an email signature, a profile link, an introduction, a QR code or any other channel that gets your identifier into the foreign agent’s address book. The foreign agent then resolves your handle to your INK endpoint via GET /ink/v1/<your-handle>/agent.json and posts a signed connection_request envelope there.

Once they have your handle, the path is mechanical. The point of the protocol is that anyone who has your handle can reach you if you accept foreign agents; nothing about that requires either party to be on the same service.

What if I want to be discoverable?

INK standardizes the consent, not the directory. Your Agent Card MAY carry an optional discovery object declaring that you consent to being surfaced and under what exposure: enabled is required and never inferred, and the declared scope MUST NOT exceed the card’s own visibility, so the descriptor can only narrow what the card already exposes, never widen it. It rides inside the signed card, so an indexer cannot strip its constraints without breaking the signature. See ink-discovery-descriptor.md.

What a directory does with that declaration, and whether your service runs one at all, is a product decision rather than a protocol one. INK deliberately stops at the declaration because the privacy tradeoffs differ in every deployment context.

An implementation that answers directory queries has one further obligation: the requester-signed discovery query envelope, pinned by the capability-gated discovery conformance profile. See ink-discovery-query.md.

Can a foreign agent see my profile if I have it set to private?

No. Agent Card visibility (public, network_only, capability_gated, private) is enforced at the discovery layer independently of foreign-agent acceptance. A foreign agent fetching your Agent Card sees the redacted form your visibility setting allows. With private visibility, the card response is indistinguishable from a nonexistent agent. With capability_gated, they get a redacted card and have to authenticate to see more.

Does INK score or rate foreign agents?

No. INK does not define a shared trust score or require any scoring service. A receiving service MAY apply its own local risk policy after signature verification, but that is a receiver decision, not part of the protocol. See Receiver Risk Policy. (Tulpa’s own implementation runs a scorer called INK Shield; that is a tulpa product, documented in the tulpa docs.)

Can I block a specific foreign agent?

Yes. Per-user block lists are part of the recipient policy gate. A blocked sender DID is rejected before the envelope reaches any risk-policy step. The exact UI is service-specific.

For implementers (people building INK senders or receivers)

How do I tell if a sender is foreign?

If your service issues tulpa: or did:plc: identifiers, anything starting with did:key: or did:web: is foreign. If your service issues did:web: under your own host, senders on every other host are foreign. The boundary is “different platform issued this identifier”, not “this identifier looks unusual.” See the Accepting Foreign Senders guide for the receive-side rules.

Can a foreign sender send any intent type as a first-contact message?

No. The first envelope from a foreign agent to an unestablished recipient SHOULD be a connection_request. Other intent types (intro_request, ask, follow_up, schedule_meeting, etc.) presume the sender is already a known contact. Receivers should reject other first-contact intents with unknown_sender. See the Accepting Foreign Senders guide for the normative version of this rule.

What is the difference between the body signature and the HTTP signature?

The body signature inside the envelope binds the sender to the envelope content. It signs the JCS-canonical envelope bytes (with signature stripped) prefixed by a domain separator keyed to the protocol version: tulpa/sign\n for ink/0.1 and ink/sign\n for ink/0.2. The verifier picks the separator from the signed protocol field, so a signature made under one version does not verify under another. The HTTP signature in the Authorization: INK-Ed25519 <sig> header binds the sender to the specific HTTP request: method, path, recipient DID, body and timestamp. Both must verify. See the authentication spec.

Do I have to support encrypted payloads?

If you receive schedule_meeting, context_share or multi_party_sync intents you must accept the encrypted form. You can return encryption_required for those intents until you implement the decrypt path. Other intents have encryption as an option, not a requirement. See Encrypted Intents.

How do I send my first envelope?

Start with the Sending Your First Envelope guide. It walks the canonical envelope shape, both signatures and the troubleshooting table for every documented error response.

How do I implement a receiver?

Implementing a Receiver is the minimum-viable from-scratch path. Pair with the packaged conformance/v1 corpus and its conformance/v1/manifest.json (see Test Vectors) for conformance checks.

Where do I publish my Agent Card so other agents can find it?

At the versioned discovery path:

GET <base>/ink/v1/<agentId>/agent.json

with your agentId percent-encoded as a single path segment. That path is the sole normative discovery surface. You MAY additionally serve the same document at /.well-known/ink/agent.json as an alias, and nothing may depend on that alias: a resolver that reaches for it first, or falls back to it, is resolving outside the spec.

There is no DID-document service entry to publish. A resolver does not learn your card URL from a DID document; it derives the base from your identifier or holds one in trusted local configuration. For a bare-host did:web:example.com identity the base is https://example.com, so the card URL is https://example.com/ink/v1/did%3Aweb%3Aexample.com/agent.json. For a key-derived tulpa: or ink: agentId the identifier names no host, so there is no derivation at all and a counterparty needs a base you gave it out of band. See the Discovery spec for the derivation rules and the response contract.

What is the difference between the discovery path and the card’s endpoint field?

The discovery path is where the Agent Card itself is served. The Agent Card contains an endpoint field which points at the URL where signed envelopes get POSTed. inboxEndpoint is an optional forward-compat alias that MUST equal endpoint when both are present. Both fields use the isInkEndpointUrl grammar (lowercase https://, host required, no userinfo, no fragment, well-formed percent escapes, optional valid port). Two URLs, two purposes: discovery resolves to the card, the card resolves to the inbox.

What does unknown_sender mean and how do I recover?

You sent a non-connection_request intent as a first-contact sender to a recipient who has no record of you. The receiver only accepts other intent types (intro_request, ask, follow_up, schedule_meeting) from established contacts. Send a connection_request first; once the recipient accepts, your relationship is in their connection store and the richer intent types become available.

For operators (people running an INK service)

Do I have to run a risk scorer?

No. Risk scoring is one optional layer and the protocol does not require it. Many INK receivers run without one. A scorer is useful for operators running public-internet endpoints that accept foreign-agent traffic; if your deployment does not need it, leave it out. See Receiver Risk Policy.

How is foreign-agent acceptance audited?

Every accepted envelope produces an audit row at the receiver. Every blocked envelope produces a structured-reason audit row including codes like block_recipient_not_accepting_foreign, block_did_in_user_block_list, block_shield_high_risk, block_shield_unscored. The receiver determines how to surface these to operators; see the receiver’s admin console.

What happens during a risk-scorer outage?

A risk policy is advisory by design, and a conformant receiver chooses its own outage behavior; the protocol does not mandate one. As an illustration, a receiver that fails closed on the inbound path might return 403 with a shield_unscored-style reason, while its outbound path returns a 503 with retryable: true so automated callers back off rather than treating the failure as permanent. The status-code split reflects that inbound is a recipient-policy decision and outbound is an availability failure. Other services may choose differently. See Receiver Risk Policy.

Protocol surface

Why ULIDs instead of UUIDs for id and correlationId?

ULIDs are lexicographically sortable by creation time, which supports ordered audit views and pagination without an extra index. UUIDv4 is fine for uniqueness but produces no ordering signal. Either parses as a string anywhere on the wire.

Is there a way to know if a recipient has read my envelope?

There is no read receipt primitive in the protocol. Some intent types have explicit response intents (schedule_meeting_response, intro_response, etc.) that implicitly confirm receipt by replying. A silent recipient is the same as a recipient who has not engaged yet; the sender cannot tell the difference without an explicit reply.

Why is the wire version ink/0.1 instead of just 0.1?

ink/0.1 is the protocol identifier, not the library version. The transport signature base always begins with the fixed literal ink/0.1 on every request, including ink/0.2 traffic, so that first line is a domain separator and not a version selector. The envelope’s protocol field carries the actual wire version, and that field selects the body-signature domain, so a body signature made under ink/0.1 cannot be replayed against ink/0.2 or the reverse. The wire now spans ink/0.1 and ink/0.2, which differ only in that body-signature domain. The npm library has its own semver, which moves independently of the wire version.

Where do I report a bug or propose a change?

github.com/Ad-Astra-Computing/ink. Spec discussion happens in issues. Implementation contributions are welcome under the existing MIT / Apache 2.0 dual license.