Shareable contract between the agent engine (this repo) and the Appwrite Console (vibes). The agent posts UI metadata via the built-in console tool; the Console parses the tool result and turns it into components / side-effects.
This is not resource CRUD. Mutations still go through MCP (or other APIs). console only describes what the Console should show or do in the shell — including structured resource lists so the Console can render filters, validation, and deep links instead of parsing markdown.
| Layer | Behavior |
|---|---|
| Tool name | console |
| Tool argument | actions — JSON string (array of actions, or a single action object) |
| Tool result | Canonical JSON envelope (string). On validation failure: Error: invalid console actions — … |
| Delivery to Console | Same path as other tools: turn timeline tool_end with tool === "console" and output = envelope JSON. Cloud may also persist resultText / resultJson on AgentTool. |
Ignore console when classifying MCP resource mutations (it is a UI meta-tool, like appwrite_search_tools).
{
"protocol": "appwrite.console/v1",
"actions": [ /* 1–20 ConsoleAction objects */ ]
}- Unknown
protocolvalues → ignore the call (forward-compat). - Unknown
actions[].typevalues → skip that action; do not fail the whole batch. - Engine validates known types before emitting; Console should still be defensive.
function parseConsoleEnvelope(output: string): ConsoleEnvelope | null {
const text = output.trim()
if (text.startsWith('Error:')) return null
try {
const parsed = JSON.parse(text)
if (parsed?.protocol !== 'appwrite.console/v1') return null
if (!Array.isArray(parsed.actions)) return null
return parsed
} catch {
return null
}
}Apply actions in array order.
/** Wire protocol id — bump only on breaking changes. */
export type ConsoleProtocolId = 'appwrite.console/v1'
export type ConsoleEnvelope = {
protocol: ConsoleProtocolId
actions: ConsoleAction[]
}
export type CreateResourceType =
| 'database'
| 'bucket'
| 'user'
| 'team'
| 'function'
| 'site'
export type ConsoleDialog =
| 'invite_member'
| 'create_project'
| 'connect_mcp'
| 'shortcuts'
| 'docs_search'
| 'feedback'
| 'support'
export type ConsoleResourceItem = {
resourceId: string
title: string
subtitle?: string
/** Console-relative path, e.g. /project/{id}/databases/{db} */
href?: string
status?: string
metadata?: Array<{ label: string; value: string }>
/**
* Typed attributes for Console filters / validation
* (email, phone, status, enabled, region, …). Prefer this over parsing metadata.
*/
fields?: Record<string, string | number | boolean | null>
}
export type ConsoleAction =
| { type: 'set_theme'; theme: 'light' | 'dark' | 'system' }
| { type: 'navigate'; path: string; hash?: string; replace?: boolean }
| { type: 'open_create'; resource: CreateResourceType; projectId?: string }
| { type: 'open_dialog'; dialog: ConsoleDialog; projectId?: string }
| {
type: 'toast'
level: 'success' | 'error' | 'info' | 'warning'
message: string
description?: string
}
| { type: 'show_pane'; content: 'agent' | 'docs' | 'none' }
| { type: 'toggle_terminal' }
| { type: 'scroll_to_card'; cardId: string }
| ({
type: 'resource'
mutation: 'create' | 'update' | 'delete'
/** Appwrite resource kind, e.g. database, bucket, user, function, site, table, file, team */
resourceType: string
} & ConsoleResourceItem)
| {
type: 'resource_list'
resourceType: string
items: ConsoleResourceItem[]
/** Heading shown above the list UI */
title?: string
description?: string
/** Total matches (may be > items.length when truncated/paginated) */
total?: number
/** Deep link to the full Console list page */
listHref?: string
emptyMessage?: string
projectId?: string
/** Optional column hints for table layout; `key` should match `fields` keys */
columns?: Array<{ key: string; label: string }>
}
| {
/**
* Usage chart from usage_list_events / usage_list_gauges.
* Prefer passing `metrics` straight through from the usage API response.
*/
type: 'chart'
title: string
description?: string
/** Visual: area (default, time series) or bar (categorical / breakdown) */
chartType?: 'area' | 'bar'
/** `events` zero-fills; `gauges` carry-forward. Default events. */
kind?: 'events' | 'gauges'
/** Unit next to the total, e.g. "requests" */
unitLabel?: string
axisFormat?: 'count' | 'bytes' | 'gbhours'
/**
* Bucket size from the usage API (`1m`, `15m`, `30m`, `1h`, `1d`).
* Always set for time-series / "last N hours" questions.
*/
interval?: string
startAt?: string
endAt?: string
changePercent?: number
href?: string
projectId?: string
metrics: Array<{
metric: string
points: Array<{ time: string; value: number; label?: string }>
}>
}
| { type: 'refresh'; scopes: string[] }Aligned with Console Command Center handlers (onSetTheme, onProjectCreate, navigate, #card-*, terminal, MCP connect, …).
Switch Console appearance.
| Field | Required | Values |
|---|---|---|
theme |
yes | light | dark | system |
{ "type": "set_theme", "theme": "dark" }Console: call the same path as Command Center / ThemeToggle (onSetTheme).
Client-side route change.
| Field | Required | Notes |
|---|---|---|
path |
yes | Must start with / |
hash |
no | With or without #; maps to #card-* scroll targets |
replace |
no | History replace vs push |
{
"type": "navigate",
"path": "/project/64abc/databases",
"hash": "card-api-endpoint"
}Open a project create dialog/drawer (Command Center onProjectCreate).
| Field | Required | Values |
|---|---|---|
resource |
yes | database | bucket | user | team | function | site |
projectId |
no | Defaults to current project context |
{ "type": "open_create", "resource": "bucket", "projectId": "64abc" }Open a named Console dialog.
| Field | Required | Values |
|---|---|---|
dialog |
yes | invite_member | create_project | connect_mcp | shortcuts | docs_search | feedback | support |
projectId |
no | When the dialog is project-scoped |
{ "type": "open_dialog", "dialog": "connect_mcp" }Transient notification (sonner).
| Field | Required | Values |
|---|---|---|
level |
yes | success | error | info | warning |
message |
yes | Short title |
description |
no | Supporting text |
{
"type": "toast",
"level": "success",
"message": "Database created",
"description": "Main is ready to use"
}Control the Console right pane.
| Field | Required | Values |
|---|---|---|
content |
yes | agent | docs | none |
{ "type": "show_pane", "content": "docs" }Toggle the project terminal panel (Command Center onToggleTerminal).
{ "type": "toggle_terminal" }Scroll a settings/card section into view (useScrollToCard / #card-{cardId}).
| Field | Required | Notes |
|---|---|---|
cardId |
yes | With or without card- prefix; engine strips a leading card- |
{ "type": "scroll_to_card", "cardId": "api-endpoint" }Render a resource card (or inline summary) for a mutation the agent already performed via MCP.
| Field | Required | Notes |
|---|---|---|
mutation |
yes | create | update | delete |
resourceType |
yes | Free-form kind (database, bucket, user, table, …) |
resourceId |
yes | Appwrite $id |
title |
yes | Primary label (name) |
subtitle |
no | Secondary line |
href |
no | Deep link into Console |
status |
no | Status badge text |
metadata |
no | { label, value }[] for card footer |
fields |
no | Typed map for Console filters / validation |
{
"type": "resource",
"mutation": "create",
"resourceType": "database",
"resourceId": "main",
"title": "Main",
"subtitle": "TablesDB",
"href": "/project/64abc/databases/main",
"fields": { "type": "tablesdb" },
"metadata": [
{ "label": "ID", "value": "main" },
{ "label": "Region", "value": "fra" }
]
}Console UI: map onto ResourceCard (title / subtitle / resourceId / metadata) plus a mutation chip (Created / Updated / Deleted). Prefer this explicit payload over guessing from MCP tool names.
Agent rule: call MCP mutate first; only emit resource after a successful tool result (with the real $id / name from the response).
Render a structured list of resources in the agent chat (filters, validation, deep links) instead of a markdown bullet list or table.
Use this whenever the user asks to list/show/find resources (databases, users, buckets, functions, sites, teams, tables, files, …) and MCP (or another tool) returned rows.
| Field | Required | Notes |
|---|---|---|
resourceType |
yes | Kind for the whole list (database, user, …) |
items |
yes | Array of rows (may be empty). Max 50 per call |
title |
no | List heading (“Databases”, “Users”) |
description |
no | Short context under the heading |
total |
no | Total matches; defaults to items.length when omitted |
listHref |
no | Link to the full Console list page |
emptyMessage |
no | Shown when items is empty |
projectId |
no | Scope hint for Console URL builders |
columns |
no | { key, label }[] — key should match items[].fields keys |
Each item uses the shared ConsoleResourceItem shape:
| Field | Required | Notes |
|---|---|---|
resourceId |
yes | Appwrite $id |
title |
yes | Primary label |
subtitle |
no | Secondary line |
href |
no | Deep link (prefer always when project context is known) |
status |
no | Badge text |
metadata |
no | Display-only { label, value }[] |
fields |
no | Typed map for Console filters / validation |
{
"type": "resource_list",
"resourceType": "user",
"title": "Users",
"total": 2,
"listHref": "/project/64abc/auth/users",
"projectId": "64abc",
"columns": [
{ "key": "email", "label": "Email" },
{ "key": "status", "label": "Status" }
],
"items": [
{
"resourceId": "user_1",
"title": "Ada Lovelace",
"subtitle": "ada@example.com",
"href": "/project/64abc/auth/user/user_1",
"status": "verified",
"fields": {
"email": "ada@example.com",
"status": "verified",
"emailVerification": true
},
"metadata": [
{ "label": "ID", "value": "user_1" }
]
},
{
"resourceId": "user_2",
"title": "Alan Turing",
"subtitle": "alan@example.com",
"href": "/project/64abc/auth/user/user_2",
"fields": {
"email": "alan@example.com",
"status": "unverified",
"emailVerification": false
}
}
]
}Console UI: render a filterable list/table (reuse resource list patterns / ResourceCard grid). Use fields + columns for sorting/filtering; use href / listHref for navigation. Validate resourceType against known Console resource schemas when available.
Agent rules:
- MCP list first → then
consolewithresource_list(include real ids/names from the tool result). - Spoken/text answer should be brief (“Here are 2 users.”) — do not duplicate the rows as markdown.
- If truncated, set
totalto the full count and prefer linking vialistHref. - Empty results: still emit
resource_listwithitems: []and anemptyMessage.
Render a usage chart from usage_list_events / usage_list_gauges data. The Console draws it with the shared usage charts library (area or bar).
| Field | Required | Notes |
|---|---|---|
title |
yes | Chart heading |
metrics |
yes | Non-empty array; prefer pass-through from the usage API metrics[] |
metrics[].metric |
yes | Exact metric id (see table below) |
metrics[].points |
yes | { time, value } points from the usage API |
interval |
for time series | 1m / 15m / 30m / 1h / 1d — always set for "last 24h" / trend questions |
startAt / endAt |
recommended | ISO 8601 window passed to the usage tool |
unitLabel |
no | Display unit next to the total (e.g. "requests") |
chartType |
no | area (default) or bar (breakdowns / labeled points) |
kind |
no | events (default) or gauges |
axisFormat |
no | count / bytes / gbhours |
changePercent |
no | Vs previous period |
href / projectId |
no | Deep link to Console usage |
Canonical metric ids (do not invent short aliases):
| User asks about | Metric id | Tool |
|---|---|---|
| API / network requests | network.requests |
usage_list_events |
| Function executions | executions or functions.executions |
usage_list_events |
| Bandwidth | network.inbound / network.outbound |
usage_list_events |
| Storage total | storage / files.storage |
usage_list_gauges |
| MAU | users.mau |
usage_list_gauges |
Never use bare requests for API request counts — that is not the Console metric and returns empty/zero data. Use network.requests.
{
"type": "chart",
"title": "Requests (last 24 hours)",
"unitLabel": "requests",
"interval": "1h",
"startAt": "2026-08-03T08:00:00.000Z",
"endAt": "2026-08-04T08:00:00.000Z",
"projectId": "64abc",
"metrics": [
{
"metric": "network.requests",
"points": [
{ "time": "2026-08-04T08:00:00+00:00", "value": 49 }
]
}
]
}Agent rules:
- Call
usage_list_events(or gauges) with the exact metric id from the table (e.g.network.requests). - For time series / "last N hours/days", always pass
interval(prefer1hfor 24h,1dfor multi-day). Flat aggregates (intervalomitted) are only for single-number totals when the user does not want a chart. - After a successful usage tool result, call
consolewithtype=chart. Passmetricsthrough from the tool response; includeinterval,startAt,endAt, andprojectId. - Keep the spoken answer short (“49 requests in the last 24 hours.”) — do not paste the series as markdown.
Invalidate Console React Query (or equivalent) caches after mutations.
| Field | Required | Notes |
|---|---|---|
scopes |
yes | Non-empty string array, lowercased by the engine |
Suggested scope tokens (Console may alias):
databases, tables, buckets, files, users, teams, functions, sites, providers, topics, messages, project, organization
{
"type": "refresh",
"scopes": ["databases", "tables"]
}User: “Switch the console to dark mode.”
{
"protocol": "appwrite.console/v1",
"actions": [{ "type": "set_theme", "theme": "dark" }]
}- MCP
databases_create(or equivalent) succeeds. - Agent calls
console:
{
"protocol": "appwrite.console/v1",
"actions": [
{
"type": "resource",
"mutation": "create",
"resourceType": "database",
"resourceId": "main",
"title": "Main",
"href": "/project/64abc/databases/main"
},
{ "type": "refresh", "scopes": ["databases"] },
{
"type": "toast",
"level": "success",
"message": "Created database Main"
}
]
}actions may list up to 20 items. Prefer one console call with several actions over many round-trips. A single resource_list may contain up to 50 rows.
User: “List my databases.”
- MCP list succeeds.
- Agent calls
console:
{
"protocol": "appwrite.console/v1",
"actions": [
{
"type": "resource_list",
"resourceType": "database",
"title": "Databases",
"listHref": "/project/64abc/databases",
"projectId": "64abc",
"items": [
{
"resourceId": "main",
"title": "Main",
"href": "/project/64abc/databases/main",
"fields": { "type": "tablesdb" },
"metadata": [{ "label": "ID", "value": "main" }]
},
{
"resourceId": "analytics",
"title": "Analytics",
"href": "/project/64abc/databases/analytics",
"fields": { "type": "tablesdb" }
}
]
}
]
}- Text answer: “You have 2 databases.” (no markdown table).
current_time→ computestart_at/end_atfor the last 24 hours.- MCP
usage_list_eventswithmetrics: ["network.requests"],interval: "1h", and the window. - Agent calls
console:
{
"protocol": "appwrite.console/v1",
"actions": [
{
"type": "chart",
"title": "Requests (last 24 hours)",
"unitLabel": "requests",
"interval": "1h",
"startAt": "2026-08-03T08:00:00.000Z",
"endAt": "2026-08-04T08:00:00.000Z",
"projectId": "64abc",
"metrics": [
{
"metric": "network.requests",
"points": [{ "time": "2026-08-04T08:00:00+00:00", "value": 49 }]
}
]
}
]
}- Text answer: “There were 49 API requests in the last 24 hours.” (no markdown series).
- On timeline / tool replay, when
tool === "console"and output parses asappwrite.console/v1, dispatchactionsin order. - Reuse Command Center handlers where they already exist (
onSetTheme,onProjectCreate,onToggleTerminal,onOpenConnectMcp,navigate). - For
resource, render a card (or feedConversationResourceSummary) from the payload — do not re-classify from the tool name. - For
resource_list, render an inline filterable list/table with per-row links; do not fall back to parsing agent markdown. - For
chart, render with the Console usage charts library frommetrics/interval/startAt/endAt. - For
refresh, invalidate the listed scopes / query keys. - Treat validation-error strings (
Error: invalid console actions — …) as failed tool calls; show nothing in the shell. - Keep turn timeline UI (
status,route,tool_*, …) separate from these shell side-effects.
| Piece | Location |
|---|---|
| Validator + envelope | app/graph/console.py |
| LangChain tool | app/graph/tools.py → console |
| Agents with the tool | researcher, planner, platform (build_tools / build_appwrite_tools) |
| Protocol id constant | PROTOCOL = "appwrite.console/v1" |