Skip to content

Cross-Platform Agent Identity

This guide covers the practical steps for deploying one logical agent across multiple platforms and ensuring it resolves to a single identity in the Kanoniv graph.

Architecture overview

A cross-platform agent deployment has three layers:

                    ┌──────────────────────────┐
                    │   Kanoniv Identity Graph  │
                    │                          │
                    │   Canonical Agent Entity  │
                    │   agt_001: support-agent  │
                    └──────────┬───────────────┘

            ┌──────────────────┼──────────────────┐
            │                  │                  │
   ┌────────▼────────┐ ┌──────▼──────────┐ ┌─────▼───────────┐
   │  Web Instance   │ │ Slack Instance  │ │ Telegram Instance│
   │                 │ │                 │ │                  │
   │ DID: a1b2c3...  │ │ DID: a1b2c3... │ │ DID: a1b2c3...   │
   │ instance: web-1 │ │ instance: slk-1│ │ instance: tg-1   │
   │ API key: kn_web │ │ API key: kn_slk│ │ API key: kn_tg   │
   └─────────────────┘ └────────────────┘ └──────────────────┘

Each instance has its own API key and instance ID, but they share a DID (cryptographic path) or resolve to the same entity via matching signals (probabilistic path). The canonical agent entity in the graph is the single source of truth.

The simplest approach. Generate one keypair and distribute it to all instances.

Step 1: Generate the keypair

bash
# Generate once, store securely
npx @kanoniv/agent-auth generate-keypair > support-agent-keypair.json

Or programmatically:

python
from kanoniv_agent_auth import AgentKeyPair
import json

keypair = AgentKeyPair.generate()
secret = {
    "secret_key_hex": keypair.secret_key_hex(),
    "did": keypair.identity().did
}
# Store in your secrets manager
print(json.dumps(secret))

Step 2: Store in your secrets manager

bash
# AWS Secrets Manager
aws secretsmanager create-secret \
  --name kanoniv/support-agent/keypair \
  --secret-string "$(cat support-agent-keypair.json)"

# Or environment variable in your orchestrator
kubectl create secret generic support-agent-key \
  --from-file=keypair.json=support-agent-keypair.json

Step 3: Deploy across platforms

Each deployment reads the same secret:

bash
# Web deployment
KANONIV_AGENT_NAME=support-agent \
KANONIV_INSTANCE_ID=web-1 \
KANONIV_AGENT_KEY_PATH=/secrets/support-agent-keypair.json \
KANONIV_API_KEY=kn_live_web_... \
npx @kanoniv/mcp

# Slack deployment
KANONIV_AGENT_NAME=support-agent \
KANONIV_INSTANCE_ID=slack-1 \
KANONIV_AGENT_KEY_PATH=/secrets/support-agent-keypair.json \
KANONIV_API_KEY=kn_live_slack_... \
npx @kanoniv/mcp

# Telegram deployment
KANONIV_AGENT_NAME=support-agent \
KANONIV_INSTANCE_ID=telegram-1 \
KANONIV_AGENT_KEY_PATH=/secrets/support-agent-keypair.json \
KANONIV_API_KEY=kn_live_tg_... \
npx @kanoniv/mcp

All three instances produce the same DID. The agent registry tracks them as separate instances of the same agent. The identity graph links them to one canonical entity. Memory is shared from the first request.

Step 4: Verify resolution

bash
# List all instances of the agent
curl "https://api.kanoniv.com/v1/agent-registry/?name=support-agent" \
  -H "X-API-Key: kn_live_..."

Response:

json
[
  {
    "name": "support-agent",
    "instance_id": "web-1",
    "status": "online",
    "last_seen_at": "2026-03-15T14:30:00Z"
  },
  {
    "name": "support-agent",
    "instance_id": "slack-1",
    "status": "online",
    "last_seen_at": "2026-03-15T14:28:00Z"
  },
  {
    "name": "support-agent",
    "instance_id": "telegram-1",
    "status": "idle",
    "last_seen_at": "2026-03-15T14:10:00Z"
  }
]

Three instances, one agent. All share the same canonical entity and memory.

Option 2: Delegation chains from the same root

When you cannot share keypairs - different security domains, different teams, compliance requirements - use delegation chains from a common root authority. The shared root DID becomes the strongest matching signal for probabilistic resolution.

Step 1: Create the root authority

The root is typically a human operator or a platform-level identity:

python
from kanoniv_agent_auth import AgentKeyPair

root = AgentKeyPair.generate()
print(f"Root DID: {root.identity().did}")
# did:agent:root_9f8e7d6c5b4a3f2e1d0c9b8a

Step 2: Generate per-instance keypairs

python
web_agent = AgentKeyPair.generate()
slack_agent = AgentKeyPair.generate()
telegram_agent = AgentKeyPair.generate()

Step 3: Delegate from root to each instance

python
from kanoniv_agent_auth import Delegation, Caveat
import json

# Same scopes for all instances
scopes = ["resolve", "search", "memorize", "create_task"]

web_delegation = Delegation.create_root(
    root, web_agent.identity().did,
    json.dumps([
        {"type": "action_scope", "value": scopes},
        {"type": "expires_at", "value": "2026-06-01T00:00:00.000Z"}
    ])
)

slack_delegation = Delegation.create_root(
    root, slack_agent.identity().did,
    json.dumps([
        {"type": "action_scope", "value": scopes},
        {"type": "expires_at", "value": "2026-06-01T00:00:00.000Z"}
    ])
)

telegram_delegation = Delegation.create_root(
    root, telegram_agent.identity().did,
    json.dumps([
        {"type": "action_scope", "value": scopes},
        {"type": "expires_at", "value": "2026-06-01T00:00:00.000Z"}
    ])
)

Step 4: Register each instance with root authority metadata

bash
# Web instance registration
POST /v1/agent-registry/
{
  "name": "support-agent-web",
  "description": "Customer support - web channel",
  "capabilities": ["resolve", "memorize", "search"],
  "metadata": {
    "root_authority_did": "did:agent:root_9f8e7d6c5b4a3f2e1d0c9b8a",
    "platform": "web",
    "delegation_id": "del_web_..."
  }
}

# Slack instance registration
POST /v1/agent-registry/
{
  "name": "support-bot-slack",
  "description": "Customer support - Slack channel",
  "capabilities": ["resolve", "memorize", "search", "create_task"],
  "metadata": {
    "root_authority_did": "did:agent:root_9f8e7d6c5b4a3f2e1d0c9b8a",
    "platform": "slack",
    "delegation_id": "del_slack_..."
  }
}

The resolution engine picks up root_authority_did as a matching signal. Combined with similar names and overlapping capabilities, the agents resolve to the same entity.

Step 5: Configure the resolution spec

Use the agent spec from Probabilistic Resolution with root_authority_did as the strongest signal:

yaml
rules:
  - name: root_authority_exact
    type: exact
    field: root_authority_did
    weight: 0.5

  - name: name_fuzzy
    type: similarity
    field: agent_name
    algorithm: jaro_winkler
    threshold: 0.75
    weight: 0.3

  - name: capabilities_overlap
    type: similarity
    field: capabilities
    algorithm: cosine
    threshold: 0.65
    weight: 0.2

decision:
  thresholds:
    match: 0.80
    review: 0.60

Integration patterns

Pattern 1: Slack bot + web widget + API client

The most common multi-channel deployment. One agent handles customer interactions across three surfaces.

Customer asks on website  -->  Web widget (support-agent, web-1)
                                    |
                                    v  resolves customer entity
                                 Stores: "Customer asked about pricing"
                                    |
Customer follows up on Slack  -->  Slack bot (support-agent, slack-1)
                                    |
                                    v  resolves same customer entity
                                 Retrieves: "Customer asked about pricing"
                                 Stores: "Sent pricing PDF via Slack"
                                    |
API call from internal tool  -->  API client (support-agent, api-1)
                                    |
                                    v  resolves same customer entity
                                 Retrieves: both prior memories
                                 Stores: "Created Salesforce opportunity"

Each interaction adds to the shared memory. The agent knows the full context regardless of which channel the customer uses next.

Pattern 2: Regional instances with unified identity

Deploy the same agent in multiple regions for latency, but maintain a single identity:

bash
# US East
KANONIV_AGENT_NAME=data-agent \
KANONIV_INSTANCE_ID=us-east-1 \
KANONIV_API_KEY=kn_live_use1_... \
npx @kanoniv/mcp

# EU West
KANONIV_AGENT_NAME=data-agent \
KANONIV_INSTANCE_ID=eu-west-1 \
KANONIV_API_KEY=kn_live_euw1_... \
npx @kanoniv/mcp

Both instances share the keypair and resolve to the same entity. Memory is unified. A customer resolved in US East is recognized by the EU West instance on the next request.

Pattern 3: Specialized sub-agents with shared root

A manager agent delegates to specialized sub-agents, each handling a different channel but sharing a root authority:

Manager Agent (root)
  did:agent:manager_...
       |
       |-- delegates to --> SDR Agent (web)
       |                    did:agent:sdr_web_...
       |                    scopes: [resolve, memorize, search]
       |
       |-- delegates to --> SDR Agent (email)
       |                    did:agent:sdr_email_...
       |                    scopes: [resolve, memorize]
       |
       |-- delegates to --> SDR Agent (LinkedIn)
                            did:agent:sdr_li_...
                            scopes: [resolve, memorize, search]

All three SDR agents share the same root authority DID. The resolution engine matches them based on this signal plus name similarity ("SDR Agent" prefix) and capability overlap.

The registry as the authoritative source

The agent registry is the ground truth for agent instances within a tenant. It tracks:

  • Which agents exist - auto-registered on first API contact
  • Which instances are running - online/idle/offline status per instance
  • What each agent can do - capabilities list
  • Who delegated to whom - root authority metadata

After resolution, the registry still shows individual instances (you need operational visibility into which pods are running), but the identity graph consolidates them into a single canonical entity. Query the registry for operational status. Query the identity graph for unified identity, memory, and reputation.

bash
# Operational view: which instances are running?
GET /v1/agent-registry/?name=support-agent

# Identity view: who is this agent?
GET /v1/canonical/agt_001/linked

# Memory view: what does this agent know?
GET /v1/memory/by-entity/agt_001

Troubleshooting

Agents not resolving to the same entity

Check the matching signals:

  1. Shared keypair? Verify both instances produce the same DID. If the DIDs match, cryptographic resolution should be instant.
  2. Root authority? If using delegation chains, verify both instances include root_authority_did in their metadata.
  3. Spec configured? Ensure the agent identity spec is active for your tenant. Check that thresholds are not too high for the available signal strength.
  4. Blocking keys? Agents must share at least one blocking key value to be compared. If names are very different and there is no shared root authority, they may never enter the same candidate block.

Merged agent has wrong canonical name

Survivorship controls which instance's values win. Adjust source_order in the spec to prioritize the preferred source:

yaml
survivorship:
  strategy: source_priority
  source_order: [web-registry, slack-registry, telegram-registry]

Memory not appearing after merge

Memory retrieval requires the include_memory: true flag on resolve calls. Without it, the response includes entity data but not memory entries. Also verify the memory entries are in active status - archived or superseded entries are filtered by default.

The identity and delegation layer for AI agents.