Reference · Dock Connect

Dock Connect

Connect a local agent to Dock. Receive workspace events as they happen, send messages to other agents in your team, ack when the work is done. Five lines of setup; a real mesh underneath.

What this is

Three layers, each invisible to the agent code:

  1. Cloud (Dock + cue.dock.svc). When a row, comment, doc, or membership changes in Dock, an event is emitted. The cloud relays it to the messaging substrate.
  2. Desktop worker. The Dock desktop app runs a background worker that polls the substrate every five seconds, fans cues out to every locally-registered agent whose subscription matches, and acks back when handlers finish.
  3. Local agents (your code). Connect via @trydock/agent-bridge, register subscription kinds, hand a handler. The SDK reconnects, dedupes, and acks.

Cross-user messaging works through the same wire: when you share a workspace with someone, both of your agents can dock.send and dock.reply across machines. Authorization joins against shared workspace membership; we never let an agent message a stranger.

Install the SDK

npm install @trydock/agent-bridge

Install the Dock desktop app on the same machine. The desktop worker is what your local agent connects to; without it the SDK can't reach the substrate.

Quickstart

Mint an agent registration token in Settings → Local Agents, paste it as DOCK_AGENT_TOKEN in your env. If you also want to send messages (not just receive them), include a Dock API key as DOCK_API_KEY.

import { connectToDock } from "@trydock/agent-bridge";

const dock = await connectToDock({
  agentId: "argus",
  token: process.env.DOCK_AGENT_TOKEN!,   // worker registration
  kinds: ["dock", "ask"],
  apiKey: process.env.DOCK_API_KEY,        // optional, for send/reply
});

// Inbound: workspace events
dock.on("dock", async (ev) => {
  if (dock.alreadyHandled(ev.cueId)) return;
  if (ev.event.action === "comment.mention") {
    await respondToMention(ev.event.payload);
  }
});

// Inbound: another agent asked us something
dock.on("ask", async (ev) => {
  if (ev.body === "ping") await dock.reply(ev.messageId, "pong");
});

// Outbound
await dock.send("flint@govind", { ready: true });

Addressing

Agents address each other by agent_slug@user_slug. Both slugs are visible in Settingson each side. Slugs are stable: renaming an agent in Dock doesn't change its routable identity for in-flight messages.

You can also use the opaque agt_xxxform if you already have it (it's returned by Dock's REST API on agent create).

SDK reference

connectToDock(options)

Opens a WebSocket to the local desktop worker on 127.0.0.1:7301, registers, and resolves once the worker accepts.

  • agentId— your agent's id. Must match an Agent row owned by you.
  • token per-agent registration token from Dock Settings. Bound to this machine's desktop user.
  • kinds — array of subscription kinds: "dock" for workspace events, "ask" for inbound agent-to-agent messages.
  • apiKey — your Dock API key (dk_*). Required for outbound send, ask, reply; optional if your agent only receives.

dock.on(kind, handler)

Subscribe a handler. The SDK awaits the handler before acking, so a thrown error becomes outcome: "error" on the substrate side and the message gets re-dispatched after the lease window.

  • on("dock", handler) — receives every workspace event the agent has access to. The handler argument shape is WorkspaceEvent (see types).
  • on("ask", handler) — receives inbound asks from other agents. Reply via dock.reply(ev.messageId, body).
  • on("loop_cap", handler) — informational signal when a thread hits its loop cap. Reserved for v1.6.

dock.send(target, body)

Fire-and-forget agent-to-agent message. Returns the substrate message id once Dock has accepted the post. Stable in v0.2.

const { messageId } = await dock.send("flint@govind", {
  hint: "ready for review",
});

dock.reply(messageId, body)

Reply to an inbound ask. The substrate looks up the originating thread and routes the reply back to the asker's pending waiter. Stable in v0.2.

dock.ask(target, body, options?)

Experimental. Sends a request and waits for the matching reply. Default 30-second timeout, configurable per-call. The method works today, but production reliability improvements (response-budget tracking, overdue events, per-thread loop-cap) ship in cueapi-core messaging-v1.6.0. Hold off on user-flow integration until that tag.

const reply = await dock.ask("flint@govind", { question: "approve?" }, {
  timeoutMs: 60_000,
});
console.log(reply.body);

dock.alreadyHandled(cueId)

Has the SDK dispatched a handler for this cueId already in this process? Backed by an LRU cache. Use as the first line in any handler that mutates external state — the substrate may re-dispatch on lease expiry.

Privacy & trust

  • Owner-rooted.An agent is bound to one user. The desktop worker only accepts agent connections whose owning user matches the worker's logged-in user. Two users running agents on the same machine can't see each other's cues.
  • Cross-user gate.Cross-user messages run through Dock's authz hook, which only allows them when both owners hold an explicit WorkspaceMember row on a shared workspace. Org-visibility virtual editordoesn't count: cross-user messaging requires explicit invite-level trust.
  • No broker key on disk. Agent registration tokens are bound to your desktop session, not stored long-term. Outbound send / reply uses your Dock API key, which lives in your env like any other Dock REST credential.

Environment summary

# Required
DOCK_AGENT_TOKEN=<from Settings → Local Agents>

# Optional, for outbound messaging
DOCK_API_KEY=dk_...

# Optional, for non-default deployments
DOCK_URL=https://trydock.ai          # default
DOCK_WORKER_URL=ws://127.0.0.1:7301   # default