Skip to content

Handler slots: description / agent

This page documents the description and agent fields available on r.writeHandler(...) and r.queryHandler(...). It is hand-written, not generated: agent isn’t a pattern kind and there is no r.agent(...) builder, the two fields just live on the handler definitions themselves.

description and agent are optional fields in a handler definition. Together they control what the AI-agent tool manifest says about that handler.

type AgentHandlerHints = {
expose?: boolean;
risk?: "low" | "mid" | "high";
};

expose is a plain boolean, not a union, and risk defaults per handler kind: "low" for r.queryHandler(...), "mid" for r.writeHandler(...).

Whether a handler shows up in the agent manifest follows one rule:

expose = agent.expose ?? (description !== undefined)

A handler with no description and no agent.expose stays invisible to the model. Adding a description is what makes it visible by default:

r.queryHandler("widget:list", z.object({ id: z.string() }), async () => ({}), {
access: { roles: ["admin"] },
description: "Lists widgets for a workspace.",
});

agent.expose: true makes a handler visible even without a description (there’s just no prose for the model to read). agent.expose: false hides it even when a description is present, an explicit opt-out that wins over having written a description:

r.queryHandler("widget:internalDebug", z.object({ id: z.string() }), async () => ({}), {
access: { roles: ["admin"] },
description: "Internal debug dump, not for agent use.",
agent: { expose: false },
});

The access role check runs independently of this, and after it: an exposed handler only appears in a given caller’s manifest if that caller’s roles satisfy the handler’s access rule.

risk classifies the blast radius of the action, "low" | "mid" | "high". Set it explicitly when the default for the handler kind doesn’t fit:

r.writeHandler("workspace:createStep", z.object({ name: z.string() }), async () => ({}), {
access: { roles: ["user"] },
description: "Creates a step.",
agent: { expose: true, risk: "high" },
});

Screens can also declare agent: { expose: false } (or agent: { expose: true }) in their definition (added in fw#2690):

r.screen({
id: "internal-debug",
type: "dashboard",
description: "Internal diagnostics dashboard.",
agent: { expose: false },
panels: [],
});

This prevents the screen from entering the agent navigation/tool surface even when a description is authored.

The bundled agent-tools feature reports documentation gaps at boot (a warning, not a startup failure). findAgentDocGaps() from @cosmicdrift/kumiko-bundled-features/agent-tools reports the same gaps on demand, see Make your app agent-ready for the script. A gap is:

  • a handler with no description that wasn’t deliberately excluded via agent: { expose: false },
  • a custom screen with no description,
  • an entity with no description that’s reachable through an agent-visible handler.

There is no separate rule for high-risk handlers, the same gap check applies regardless of risk.


See also r.writeHandler(...) and r.queryHandler(...).