Chat Location Tool
Status: v1, built (2026-08-26). Browser verification still pending.
1. What it is
A client tool location_get that the Agent can call in a Chat Session to
query the browser’s geolocation. It is the first client tool that is
bound to a Session, not a page: Cortex and Inbox only register their
tools when their page is open (CortexClientToolService,
InboxClientToolService); the chat session is always active, and the user is
always in front of it.
- Client Service:
@vance/vance-face→src/chat/chatClientToolService.ts(ChatClientToolService). - Host:
src/chat/ChatApp.vue(the/chatroute), where the tool is connected to the tab-singleton WebSocket. - Transport: the same client tool mechanism as Cortex/Inbox —
client-tool-registeron WS-open,client-tool-invoke/client-tool-resultwith Correlation-Id, Brain blocks the LLM-sampling loop on the pending future.
2. The central point: the tool does not block on the permission prompt
Browser geolocation is asynchronous and timeless: the user accepts in 5 seconds, in 5 minutes, or not at all. However, the client tool mechanism blocks the LLM loop on a future with the Brain timeout (30 s). The tool does not wait for the prompt — it has its own, short local timeout (5 s) and responds before the Brain timeout, with the status known at the time of expiration.
Thus, the Agent never gets a 30-s timeout, but always an answer in ≤ 5 s.
3. The four states
location_get responds with { status, ... }, not an exception. A non-GRANTED
status is not an error that the Agent needs to react to — it is a
result that the Agent manages itself.
status |
Meaning | Agent’s behavior |
|---|---|---|
GRANTED |
Location available: latitude, longitude, accuracy, timestamp |
complete |
DECLINED |
User has declined | do not ask again (terminal) |
PENDING |
Permission prompt is running, user has not responded in 5 s | may ask again; the browser prompt will then be gone, a second call reads the result, it does not trigger a second prompt |
UNAVAILABLE |
no geolocation support in the browser, or position not obtainable | do not ask again (terminal) |
PENDING is the only retryable status. The other three are terminal. The
Tool Description explicitly tells the Agent this: DECLINED and UNAVAILABLE
“Do not retry”, PENDING “You may call location_get again after a short wait”.
4. Permission status comes from the browser — no internal cache
The permission status check is done via navigator.permissions.query({ name:
'geolocation' }). The browser maintains the state per origin, across
reloads — once granted, it remains granted. Therefore, the tool does not
cache anything itself: it queries what the browser knows, and the browser is
the sole source of truth. A second call after PENDING reads granted if the
user clicked in the meantime, and responds with GRANTED — without a
second browser prompt.
The browser’s tri-state is mapped to four states:
granted→ directlygetCurrentPosition(no prompt, only the 5-s race for the callback, because the coordinates themselves are not yet available).prompt→getCurrentPositiontriggers the browser prompt; 5-s race.denied→ immediatelyDECLINED(terminal, no prompt, no race).
If navigator.permissions is missing (older browsers), the tool falls back to
the prompt branch: it queries directly via getCurrentPosition and lets the
result speak for itself.
5. The 5-s Race
getCurrentPosition(success, error, { timeout: 5000, maximumAge: 0 })
│
└─ parallel: 5-s-Timer
│
└─ whichever first wins:
├─ Permission comes in <5s → GRANTED / DECLINED / UNAVAILABLE
└─ 5-s-Timer runs first → { status: 'PENDING', ... }
PENDINGresponse contains a message telling the Agent: the user has been asked and has not yet responded, you can ask again or continue without location.- A single in-flight Request per Service: a second call while the first is
running awaits the first (
pendingRequest) instead of triggering a second permission prompt. DECLINED/UNAVAILABLE/PENDINGare not returned aserror— they areresult. A client tool that returns a denied permission aserrorwould be interpreted by the Agent as an error and retried; this is precisely the behavior that must be prevented.
6. What location_get does not do
- It does not block the LLM loop on the permission prompt (it has its own 5-s timeout that expires before the Brain timeout).
- It does not cache any permission state itself (the browser is the cache).
- It does not trigger a second prompt on a second call (it reads the existing state).
- It does not respond as
errorforDECLINED/UNAVAILABLE(they are results, not errors).
7. Integration
ChatApp.vue (the /chat route) connects the ChatClientToolService to the
tab-singleton WebSocket, analogous to ChatSidePanel’s attachedToolSocket-watch:
- One
ChatClientToolServiceinstance per page, which survives session switches (likeInboxClientToolService). - Attach only if
mode === 'live'and the socket is present — registration on a bare, unbound socket would result in a403 "requires a bound session"(the same gate asChatSidePanel). detachinonBeforeUnmount.watch([socket, () => mode.value === 'live'], …)— re-attach after each fresh socket (e.g., after auto-reconnect), as soon as the session is live.
8. Not implemented (intentionally)
- No
location_watch(continuous tracking). v1 is a one-time call. - No Reverse-Geocoding (the Agent only gets
lat/lng/accuracy; it must resolve place names itself via a search). - No Permission-Request-Tool (no
location_requestas a two-stage flow). The Agent does not need control (user design decision) —location_gettriggers the prompt directly. - No Headless-/Worker-Client integration. The tool is only registered by
the web chat page;
UNAVAILABLEcovers the in-browser case without geolocation. A client without a browser (Foot, Trillian-Worker) does not havelocation_getin its manifest, so there is no question it needs to answer. - No Precision Guarantees. The Description tells the Agent: Accuracy is a radius in metres, not a point — do not report false precision.
9. Tool Surface Budget
The tool is not deferred (deferred: false) and not primary: false
— it is a permanent, small UI state tool like cortex_get_active_tab or
inbox_show_thread. It occupies one slot in the manifest per web chat session
(not per Cortex/Inbox session). The Tool Surface Budget (server-tools.md §14)
does not apply here because it is not a ContextToolsApi.classify-tool (not a
Brain-side server tool, but a client-driven client-tool-register frame that
carries a single tool per session).
10. Verification
- Build:
pnpm buildin@vance/vance-face— green. - Browser verification still pending:
location_getin a real web session,GRANTED/DECLINED/PENDING/UNAVAILABLEpaths, reload after grant, second call afterPENDING.