Run your first task
This guide builds one complete read operation. The name
menu.item.get remains unchanged from the tool definition through
the plan and handler.
Define the capability
Section titled “Define the capability”defineTools() is contract-only. It describes the operation, validates its input and output, and produces its registered name. It does not access application data.
import { defineTools } from "gruntend/tool";
import * as v from "valibot";
export const tools = defineTools({
menu: {
item: {
get: {
description: "Get one menu item by id.",
input: v.object({
itemId: v.string(),
}),
output: v.object({
item: v.object({
itemId: v.string(),
name: v.string(),
price: v.number(),
}),
}),
},
},
},
});
The nested path creates exactly one registered tool:
menu.item.get
Implement the app-owned handler
Section titled “Implement the app-owned handler”A handler is an async runtime closure owned by the application. It may access repositories, authorization state, secrets, and services that the generated plan cannot access.
For this first task, use a small in-memory application repository:
const menuRepository = {
async get(itemId: string) {
if (itemId !== "item_1") return undefined;
return {
itemId: "item_1",
name: "Truffle Fries",
price: 8,
};
},
};
Now implement the handler:
import type { ToolHandlerMap } from "gruntend/tool";
import { tools } from "./tools.ts";
const handlers = {
"menu.item.get": async ({ input, ok, err }) => {
const item = await menuRepository.get(input.itemId);
if (!item) {
return err({
code: "ITEM_NOT_FOUND",
message: "The menu item does not exist.",
retryable: false,
});
}
return ok({ item });
},
} satisfies ToolHandlerMap<typeof tools>;
Why the handler must return ok or err
Section titled “Why the handler must return ok or err”ToolHandlerMap<typeof tools> derives every handler type from the tool contracts. In this example, TypeScript checks that:
- the key is exactly
"menu.item.get" inputhas the type produced by the input schemaok(...)accepts only data matching the output schema- the handler returns either
ok(...)orerr(...)
Returning the output object directly is a type error:
return { item }; // TypeScript error
return ok({ item }); // valid handler result
ok and err are supplied in the handler context, so they are not imported.
return ok(output)completes the tool call. Gruntend validates and unwrapsoutput, so the plan receives the plain{ item }value.return err(error)reports an expected failure with a stablecodeandmessage. The optionalretryableflag controls whether Gruntend may try the call again.- Throw only for an unexpected fault.
TypeScript checks the application code while runtime schemas protect the boundary from generated plan values.
Write the plan
Section titled “Write the plan”A plan calls the tool through the namespace implied by its registered name.
const plan = `
const result = await tools.menu.item.get({
itemId: input.itemId,
});
return {
name: result.item.name,
price: result.item.price,
};
`;
The runtime unwraps the successful handler result before returning it to the plan. The plan receives { item }, not the internal Result wrapper.
Run the plan
Section titled “Run the plan”import { createGruntendClient } from "gruntend/client";
import { tools } from "./tools.ts";
const gruntend = createGruntendClient({ tools });
const result = await gruntend.runCodePlan(plan, {
input: { itemId: "item_1" },
handlers,
});
For an existing item, result has this exact shape:
{
status: "done",
result: {
name: "Truffle Fries",
price: 8,
},
errors: {},
}
Understand the validation boundary
Section titled “Understand the validation boundary”The runtime validates both sides of the handler:
- The plan passes
{ itemId }. - The runtime validates that value with the tool input schema.
- Only valid input reaches
handlers["menu.item.get"]. - The handler returns
ok({ item }). - The runtime validates
{ item }with the output schema. - Only valid output returns to the plan.
If input validation fails, the handler is never called. If output validation fails, the plan fails instead of receiving data that violates the contract.
Next: Code plans, Generated UI, or the API reference.