Android app-connector for the Agimate platform: it advertises a catalog of tools and triggers, reports device events to the backend, and executes incoming tool calls delivered over a Centrifugo WebSocket.
- Kotlin + Jetpack Compose (Material 3)
- Koin for dependency injection
- Retrofit + OkHttp for HTTP
- centrifuge-java for Centrifugo WebSocket
- Room + DataStore for local storage
- Foreground Services for trigger monitoring and tool execution
Tool and trigger names are bare snake_case — the namespace is added by the server, so the device
never sends a dotted name. The catalog is assembled in util/DeviceCatalog.kt, and only enabled
tools and triggers are advertised: linking replaces the catalog wholesale, so anything advertised is
something the agent may call.
| Name | Params |
|---|---|
call_incoming |
timestamp |
call_missed |
ringDurationMs, timestamp |
battery_low |
batteryLevel, threshold, timestamp |
wifi_connected |
ssid, bssid, connected, signalStrength, timestamp |
wifi_disconnected |
ssid, bssid, connected, signalStrength, timestamp |
shake_detected |
acceleration, x, y, z, timestamp |
location_entered |
name, latitude, longitude, timestamp |
location_exited |
name, latitude, longitude, timestamp |
Caller identity is not reported: since Android 9 the number is only exposed to apps holding the phone or call-screening role.
| Name | Input |
|---|---|
tts_speak |
text |
notification_show |
title, message |
notification_ask |
question, options |
contacts_search |
query, limit |
calendar_search |
from, to, query, limit |
calendar_create_event |
title, start, end, durationMinutes, location, description, calendarId |
geofence_add |
name, latitude, longitude, radiusMeters |
geofence_remove |
name |
geofence_list |
— |
camera_capture |
camera, reason |
The full and authoritative specification lives in docs/backend-protocol.md.
| Purpose | Method | Path |
|---|---|---|
| Link device and advertise catalog | POST | /control/app/registration/link |
| Centrifugo tokens | POST | /control/app/centrifugo/token |
| Send trigger event | POST | /control/app/trigger/new |
| Return tool call result | POST | /control/app/tools/result |
| Upload file artifact | POST | /control/app/files |
| Download file argument | GET | /control/app/files/{id} |
| WebSocket | WS | /connection/websocket |
Bodies are JSON in camelCase. Success is { "response": <T> }, failure is
{ "error": { "message": "..." } }.
X-App-Auth-Key: {appKey}
-
Link device — POST
/control/app/registration/linkwith{deviceId, deviceName, deviceOs, triggers, tools}. Linking fully replaces the catalog, so it is re-sent whenever a tool or trigger is toggled. -
Fetch Centrifugo tokens — POST
/control/app/centrifugo/token, returns{connectionToken, subscriptionToken, channel, wsUrl?}. -
Connect WebSocket — using the connection token, subscribe to the server-provided
channel(app:{appId}) with the subscription token. Take the channel from the response; do not build the name yourself. -
Receive tool calls — publications carry an envelope:
{ "type": "toolCall", "payload": { "id": "019f6c63-...", "name": "tts_speak", "input": { "text": "Hello" }, "connectorCode": "app", "connectionId": "01951234-...", "agentSessionId": "0193b8e3-..." } }Delivery is at-least-once, so calls are deduplicated by
payload.id. -
Return the result — POST
/control/app/tools/resultwith the sameidand eitheroutput(a JSON string) orerror.
If the server doesn't provide wsUrl in the token response:
- Multi-level domain: replace first subdomain with
centrifugo, usewss://(e.g.api.agimate.io->wss://centrifugo.agimate.io/connection/websocket) - Single-level host: keep host, match scheme (e.g.
http://localhost:8080->ws://localhost:8080/connection/websocket)
Default server: https://api.agimate.io
Settings are stored in DataStore:
- Auth key (
X-App-Auth-Key) - Server URL
- Device ID (auto-generated)
- Debug Logging toggle
./gradlew assembleDebug