client.agents
A Programmable Agent is a server-side “virtual user” backed by the edge AI runtime. App editors give it a persona (a system prompt), a model, named prompt “skills”, and triggers that decide when it speaks; members then chat with it inside a Space. The agent reads and replies to messages entirely on the accelerator — your client only manages it.
Privacy model (per-Space opt-in)
Section titled “Privacy model (per-Space opt-in)”An agent must read message plaintext to function, so a Space it’s enabled on is no longer server-blind — the app’s own infrastructure (the keeper) reads those messages. This is opt-in per Space: every Space the agent isn’t enabled on stays end-to-end-encrypted and blind. The agent appears in the roster flagged as an agent so members can see it can read the conversation.
Methods
Section titled “Methods”// List the app's agents.client.agents.list(appId, opts?): Promise<AgentConfig[]>
// Read one agent (config + its published identity keys).client.agents.get(appId, agentId, opts?): Promise<AgentProvisioned>
// Create an agent (owner/editor; paid tier).client.agents.create(appId, input, opts?): Promise<AgentProvisioned>
// Update an agent's editor-settable fields.client.agents.update(appId, agentId, patch, opts?): Promise<{ config: AgentConfig }>
// Delete an agent (also drops its signing identity + transcripts).client.agents.delete(appId, agentId, opts?): Promise<{ deleted: boolean }>
// Enable / disable the agent on a specific Space (per-Space opt-in).client.agents.enable(appId, agentId, spaceId, opts?): Promise<{ config: AgentConfig }>client.agents.disable(appId, agentId, spaceId, opts?): Promise<{ config: AgentConfig }>opts is { space?: string } — a non-owner editor passes the app’s management
Space id for delegated access; the owner can omit it.
Example
Section titled “Example”// 1. Create an agent (only an app owner / editor on a paid plan can).const { config } = await client.agents.create(appId, { handle: "@assistant", displayName: "Assistant", model: "meta/llama-3.1-8b-instruct", systemPrompt: "You are a concise, friendly teammate in this Space.", triggers: [{ type: "mention" }], // reply when @-mentioned});
// 2. Turn it on for the channel you want it in.await client.agents.enable(appId, config.agentId, channelSpaceId);
// 3. In that channel, a member @-mentions it — the agent replies in the Space.Configuration
Section titled “Configuration”AgentConfig
Section titled “AgentConfig”interface AgentConfig { agentId: string; handle: string; // the @-mention target, e.g. "@assistant" displayName: string; model: string; // a model id from the catalog (see below) systemPrompt: string; // the agent's persona / instructions skills: AgentSkill[]; // named, invokable prompts triggers: AgentTrigger[]; enabledSpaces: string[]; // Spaces it's active on (per-Space opt-in) caps: { dailyTokenBudget: number }; // hard daily cost circuit-breaker tools?: AgentToolsConfig; // optional tool-use grant (see below) createdAt: number; updatedAt: number;}
interface AgentSkill { name: string; prompt: string }
interface AgentTrigger { type: "mention" | "keyword" | "regex" | "always"; pattern?: string; // required for "keyword" / "regex" skill?: string; // run this named skill on a match}AgentCreateInput is the editor-settable subset (handle, displayName,
systemPrompt, and optional model, skills, triggers, caps, tools);
AgentUpdateInput is a partial of it.
AgentProvisioned
Section titled “AgentProvisioned”interface AgentProvisioned { config: AgentConfig; memberId: string; // the agent's Space member id (__agent__:<agentId>) ecdhPub: string | null; // identity keys (the agent signs its messages) ecdsaPub: string | null;}Triggers
Section titled “Triggers”Triggers decide when the agent runs, which bounds its cost:
mention— fires when the agent’shandleappears in a message. The direct “talk to the agent” path.keyword— a case-insensitive substring match onpattern.regex— a case-insensitive pattern match.always— every message (pair with a tightcaps.dailyTokenBudget).
The first matching trigger wins; its skill (if set) is composed with the
system prompt for that reply.
Tool-use
Section titled “Tool-use”By default an agent’s only output is a chat message. Grant it tools and it can act on your app through a server-side function-calling loop — query and write its database, call its functions, and resolve its channels — then summarize what it did back in the Space.
interface AgentToolsConfig { enabled: boolean; db: { mode: "off" | "read" | "write"; // read = query/get; write = + insert/update/delete tables: string[]; // table allowlist }; functions: string[]; // function-name allowlist channels: boolean; // may resolve the app's public channels maxIterations: number; // max tool-call rounds per reply (1–6)}Pass tools on create/update. The exact columns, function parameters, and the
closed list of callable tools are injected into the agent at runtime, so it
can’t invent tables or tools.
Describe your app with decorators
Section titled “Describe your app with decorators”Rather than hand-writing the prompt and the allowlist, you can declare your
app’s agent-facing surface in code and eject both. Annotate a plain class
with @MuhkooAgent (identity + behavioral guidance) and its members with
@MuhkooSpace, @MuhkooDB, and @MuhkooFunction, then call ejectAgentPrompt
and ejectAgentTools:
import { MuhkooAgent, MuhkooSpace, MuhkooDB, MuhkooFunction, ejectAgentPrompt, ejectAgentTools,} from "@muhkoo/connect";
@MuhkooAgent({ name: "Chat Assistant", purpose: "A helpful assistant inside a real-time team chat.", guidance: ["Keep replies short.", "Only chime in when relevant."],})class ChatAssistant { @MuhkooSpace({ description: "Main team discussion." }) general!: string; @MuhkooDB({ access: "read", description: "Chat message history." }) messages!: unknown; @MuhkooFunction({ description: "Open a support ticket." }) openTicket!: () => void;}
await client.agents.create(appId, { handle: "@assistant", displayName: "Assistant", model: "openai/gpt-oss-120b", // a function-calling model systemPrompt: ejectAgentPrompt(ChatAssistant), tools: ejectAgentTools(ChatAssistant),});The ejected prompt carries the semantic layer — what the app is, how to
behave, and what each channel, table, and function is for. The runtime
supplies the authoritative schema and tool list separately, so the prompt never
restates columns and can’t drift from your deployed app. The member name is the
default channel/table/function name; override it with name/table.
Since 0.6.0-alpha.1, the ejected prompt also includes a “How to respond”
section that compels the agent to finish its turn with a short, plain-language
reply after using tools — and never to end with only tool calls, an empty
message, or a recitation of its tool list. Without it, some models (notably
gpt-oss-*) run their tool loop and then go silent: you see the work happen but
get no reply. Toolless agents get the “always reply” rule without the
tool-specific lines.
Cost & billing
Section titled “Cost & billing”Each agent has a hard caps.dailyTokenBudget circuit breaker. Inference is
metered to your account as the ai_inference axis, billed in neurons
(a compute unit computed per call from the chosen model’s input/output rates) so
every model’s true cost is reflected.
Models
Section titled “Models”Pick any model from the edge model catalog (e.g.
meta/llama-3.1-8b-instruct, meta/llama-3.3-70b-instruct-fp8-fast,
mistralai/mistral-small-3.1-24b-instruct). Muhkoo exposes the full
catalog with per-model pricing at GET /api/apps/agent-models.