gc or the dashboard. When
you want them reachable from somewhere else — a voice front end, a chat app, a
bot, another service — you connect that client to the city’s external
messaging API. The client posts each turn in; the agent answers with a reply
command; the city delivers the answer to a callback URL the client serves.
This guide builds a working client for a provider named voice. Substitute
your own provider name throughout.
How it works
Every endpoint is city-scoped under
/v0/city/{cityName}/extmsg/ on the
supervisor API (default http://127.0.0.1:8372). Mutations need the
X-GC-Request header — see Supervisor REST API.
Identify the conversation
Every turn and reply carries a conversation reference. Pick fixed values for your client and a stable ID per conversation:1. Give agents a reply command
When a turn arrives, the agent is told to answer by runninggc <provider> reply-current --conversation-id <id> --body-file <path>. That
command comes from a pack, so add a small one to the city and import it under
your provider name:
run.sh turns the agent’s reply into an outbound publish. It runs inside the
agent’s session, so GC_SESSION_ID identifies the replying agent. gc sets
GC_CITY_NAME for pack commands, and GC_SUPERVISOR_URL when the supervisor
API is plain http on loopback. It needs curl and jq:
chmod +x) and import the pack in the city’s pack.toml.
The import binding name — voice here — is the command namespace, so it must
equal your provider name:
gc voice --help now lists reply-current. See
Create and Share Packs for more on pack commands;
the Command Directory section of the
pack specification lists the environment they
run with.
2. Route conversations to an agent
A turn reaches an agent only when its conversation is routed. The simplest route sends every newvoice conversation to one agent:
gc extmsg bind or
POST /v0/city/{cityName}/extmsg/bind.
3. Serve a callback and register the adapter
Replies arrive asPOST {callback_url}/publish:
2xx with a receipt that echoes the conversation. delivered: true is
what tells the agent its reply landed:
(provider, account_id):
callback_url is the base URL; omit the trailing slash, because gc appends
/publish.
The response is 201 Created; registering the same (provider, account_id)
again replaces the earlier entry. Registrations live only in the running
city’s memory, so register on every client start and again whenever the city
starts or restarts — not only when the supervisor restarts. A restart can
happen between turns without the client seeing it, so the simplest client
registers again before every turn, as the Go example does.
Each registration emits an extmsg.adapter_added event, so that client adds
one event per turn to the city’s event log.
4. Send a turn
200 returns the routing result. TargetAgentName or TargetSessionID
names who received the turn; both empty means no route matched and the turn
was dropped. The agent is notified in the background, so its reply arrives
later on your callback.
Delivery semantics
Sending a turn:
The city-scoped routes answer only while the city is running; see
City Scope for that readiness boundary.
The transcript keeps one entry per
provider_message_id, so a retry never
duplicates the turn. The agent is notified on every successful attempt,
though, so retrying a turn whose 200 was lost — a timeout or a dropped
connection — can notify it twice.
Receiving a reply — how your /publish response is reported back to the
agent’s reply command:
To report a reply you received but could not deliver, answer
2xx with
"delivered": false and a failure_kind of transient, rate_limited,
permanent, auth, not_found, or unsupported.
The city does not retry a failed publish and does not record it in the
conversation transcript; the reply command prints the failure kind and exits
nonzero, so the agent sees why its reply did not land. Delivered replies are
recorded, and
GET /v0/city/{cityName}/extmsg/transcript (filtered by the conversation
fields) lists a conversation’s history — useful for catching up after a
client restart.
Go example
A line-oriented client that follows the delivery rules above, except that it gives up on a refused write: it serves/publish, registers itself, and sends
each line of stdin as a turn in conversation call-001, waiting out a city
restart instead of dropping the turn.
X-GC-City-Write grant. On a city that requires one,
its first registration is refused with 401 and the client exits; a turn
refused later with 401 or 403 is logged as not delivered and skipped.
Fixing a refused write needs an operator, so a production client keeps the
turn and resends it with the same provider_message_id after the fix.