Invoking tools
List the tools, call one, and read what actually changed.
Listing tools
curl https://api.intern.kim/v1/tools \
--header "Authorization: Bearer $INTERNKIM_TOKEN"The response is {"tools": [...]}, holding the full descriptor of every tool
the key's permission can call, and a catalog field saying which list you got.
The base catalog is answered without asking the company machine, so it is the
fast one and it is the same for everyone; it is also documented on the
tool catalog page. Whether a tool can run right now is
known only on the machine, and tools that come and go with a companion are not
in the base list at all. Ask for those with ?live=true, which costs the round
trip. X-INTERNKIM-CATALOG says base on either discovery answer.
Reading a descriptor
curl https://api.intern.kim/v1/tools/task_add \
--header "Authorization: Bearer $INTERNKIM_TOKEN"A tool the token cannot call answers 404 here, as if it did not exist. The
descriptor fields worth reading before a call:
| Field | Says |
|---|---|
inputSchema | the JSON Schema your input must satisfy |
outputSchema | the shape of result on success |
sideEffectClass | which scope rank the call requires |
idempotency | whether idempotencyKey means anything for this tool |
resultContract.effects | which effects entries a successful call promises |
availability | whether the tool can run right now, and why not |
Invoking
curl https://api.intern.kim/v1/tools/task_add/invoke \
--header "Authorization: Bearer $INTERNKIM_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"input":{"title":"draft the quarterly report","size":"M"}}'The body carries input and, where supported, idempotencyKey. Anything else
you send for actor or context is overwritten from the token before the
request leaves the gateway; the test
TestPublicToolGatewayOverridesActorFromBearerToken pins this down.
No approval pause
In a messenger conversation the agent stops and asks before a delete or a
send. The gateway marks every non-read invocation as an already-approved
continuation, so the same tools act immediately here. The scope on the token
is the consent: a destructive token deletes without a question.
The response
{
"provider": "internkim",
"selectedBackend": "device",
"toolName": "task_add",
"outcome": "succeeded",
"status": "created",
"effects": [
{ "objectType": "task", "effect": "created", "id": "..." }
],
"result": { "taskID": "...", "title": "draft the quarterly report" }
}| Field | Meaning |
|---|---|
provider, selectedBackend | where the call ran: device, companion, or remote |
outcome | succeeded, failed, or denied |
status | a tool-specific status string |
effects | the record of what changed |
result | the tool's output, matching its outputSchema |
isError, message, errorCode, failureStage | failure detail |
retryable, safeRetry | whether a retry is sensible, and whether it is safe |
Effects
effects is the record of what actually changed, written by the runtime from
the tool's result contract. Each entry names an objectType, an effect
(created, updated, deleted, ...), and one identity: an id, a path,
or a url. Some entries add visibility, durability, filename,
contentType, or a summary.
Verify through effects, not through result. An empty effects list means
nothing changed, whatever the result text says.
Idempotency
idempotencyKey is honored only where the descriptor's
idempotency.supported is true. In the base catalog that is message_send;
everywhere else the key is carried but changes nothing. For agent messages,
messageID deduplicates instead.
Choosing a target with a hint
Mutating tools take no separate ID parameter. task_update and task_delete
take a taskHint: the exact task ID, or the exact current title as a
task_list result shows it. Never the new or intended title. The server
resolves the hint deterministically; when it does not resolve to exactly one
task, the call fails and returns the list of matching candidates to retry
against. It never guesses.
Person references work the same way: participantPersonHints takes names,
@handles, or emails.
End to end
With a write token from Getting a token:
curl https://api.intern.kim/v1/tools/task_add/invoke \
--header "Authorization: Bearer $INTERNKIM_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"input":{"title":"draft the quarterly report","size":"M"}}'
curl https://api.intern.kim/v1/tools/task_update/invoke \
--header "Authorization: Bearer $INTERNKIM_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"input":{"taskHint":"draft the quarterly report","status":"in_progress"}}'The first response's effects carries
{"objectType":"task","effect":"created","id":...}; the second carries the
same task with "effect":"updated". Deleting it takes a destructive token:
curl https://api.intern.kim/v1/tools/task_delete/invoke \
--header "Authorization: Bearer $INTERNKIM_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"input":{"taskHint":"draft the quarterly report"}}'With only write, this answers 403 tool is not available for this token
before anything reaches capabilityd.
Where it is implemented
| Concern | Where |
|---|---|
| List, read, invoke handlers | writePublicTools, writePublicTool, invokePublicTool in internal/admind/public_tool_gateway.go |
Descriptor fetch from capabilityd | fetchCapabilityRegistry over the unix socket; device and companion descriptors are merged in exposableCapabilityDescriptors |
| The actor override | invokePublicTool sets Actor, Context, PrivacyClass, RequiresUserPresence after decoding your body |
| The approval-continuation flag | publicToolInvokeContext, keyed on publicToolScopeForDescriptor |
| Request and response types | ToolInvokeRequest, ToolInvokeResponse, ResourceEffect in pkg/capabilityprotocol/protocol.go |
| Input and output schemas | pkg/capabilityprotocol/generated/capability-tools.json |