Define an agent
Give a model a clear task, the tools it needs, and limits on each run.
An agent uses a language model to decide which steps and tools can answer a request. For a fixed sequence of steps, use a function.
Pass compatible LangChain models, tools, and middleware directly. RELKIT validates the declared input, output, limits, tool policies, and public client contract around the native execution. For exact options, use the native agent reference.
See Native runtime and DeepAgents for the supported package cohort, public typing boundary, and deep-agent limitations. For explicit workflows and durable continuation, use LangGraph, persistence, and resume.
Pre-setup
Complete agent setup to install the native runtime and configure
Docker Redis for agent-state and realtime. Keep the agent’s stateProfile aligned with
the configured default. A hosted model additionally needs its SDK and secret environment
value; the deterministic example needs no model key. Filesystem state remains an explicit
alternative in the offline tutorial.
Scaffold an agent
Add a native agent to an existing service with the installed CLI:
bun run relkit add agent Assistant --service hello \
--instructions "Answer greeting questions."
This creates src/services/hello/agents/assistant.agent.ts with an offline model and no tools.
Replace its input/output schemas and instructions with your task. Add tools explicitly when
needed; scaffolding does not expose an HTTP endpoint or enable an agent client automatically.
As an alternative to manually creating the agent, this scaffold plans and normally installs the native
integration and four SDK peers itself. With --no-install, run bun install and bun run check
afterward. It does not select your realtime/agent-state profiles for you. The scaffold uses a
deterministic offline model. Replace that model in
server code when you are ready to configure a production provider.
For every agent scaffolding flag, see the agent command reference.
Scaffold a reusable prompt
Create a prompt before referencing it from an agent:
bun run relkit add prompt greeting-policy --service hello \
--text "Answer greeting questions." --text "Keep answers brief."
The prompt is saved as src/services/hello/prompts/greeting-policy.prompt.ts; repeated non-empty --text
values preserve separate prompt segments. In headless execution at least one --text is required.
Prompts are internal descriptors, not public service members. Pass the prompt descriptor as the
agent’s instructions when you need reusable policy text.
When creating a new agent, select it with --prompt greeting-policy instead of --instructions.
Use --tool <tool> for each existing tool to attach. For an existing agent, edit its descriptor
instead of running add agent again. See constants and prompts
for their shared-value contract.
For every prompt scaffolding flag, see the prompt command reference.
Install the native runtime
From this guide’s application/package root, install only its selected requirements:
bun add @langchain/core@1.2.9 @langchain/langgraph@1.4.14 @relkit/langchain@0.7.13 @relkit/local@0.7.12 langchain@1.5.10 langsmith@0.10.8
Keep these owner-specific versions and the lockfile together. Runtime SDKs belong in dependencies; build/type tooling belongs in devDependencies. Installing an adapter does not supply its optional SDK peers or enable the feature’s configuration.
The recipe includes the optional filesystem providers used by the
offline tutorial. For the default running
application, keep the Docker Redis profiles from pre-setup. Generic contracts from @relkit/agents
or @relkit/app/agents do not install LangChain.
Model-provider packages are application dependencies. The generated sample uses LangChain’s offline
FakeToolCallingModel, so its tests and first run need no account, network request, or paid service.
Define the task
This agent answers greeting questions using a single tool. Create it after defining the lookup tool.
/** Runs an offline greeting agent with RELKIT/native tools, todo middleware and public state observation. */
import { defineAgent } from "@relkit/langchain";
import { z } from "@relkit/app/schema";
import { FakeToolCallingModel, todoListMiddleware, tool } from "langchain";
import lookup from "@app/services/hello/tools/lookup.tool.js";
export const uppercase = tool(async ({ text }: { readonly text: string }) => text.toUpperCase(), {
name: "uppercase",
description: "Uppercase text without leaving the process",
schema: z.object({ text: z.string() }),
});
export default defineAgent({
id: "hello.assistant",
input: z.object({ message: z.string().min(1) }),
output: z.object({ answer: z.string() }),
model: new FakeToolCallingModel({
toolCalls: [
[
{
name: "write_todos",
args: { todos: [{ content: "Answer the greeting", status: "completed" }] },
id: "todo-1",
},
],
[
{
name: "relkit_output",
args: { value: { answer: "Hello from the offline LangChain model." } },
id: "output-1",
},
],
],
}),
instructions: "Answer greeting questions with the available tools.",
tools: [lookup, uppercase],
middleware: [todoListMiddleware()],
stateProfile: "default",
client: {
public: true,
state: ["todos"],
events: { notice: z.object({ message: z.string() }) },
},
chat: { input: "message", output: "answer" },
controls: ["stop"],
limits: {
maxSteps: 4,
maxToolCalls: 4,
timeoutMs: 10_000,
},
});
input describes what your app sends; output describes the answer your app expects. RELKIT validates both. Output validation checks the shape, not whether the model’s answer is factually correct.
Write instructions that explain when to use each tool and how to answer. The agent can call only tools in its tools array. Start with the smallest set needed for the task.
Use this agent from the frontend
After frontend setup and pulling the greeting agent’s contract,
render this component under the provider. Supply a stable business threadId owned by your
application, such as the ID of the conversation the user opened:
"use client";
/** Sends one greeting to the documented hello.assistant agent and observes its typed answer. */
import { useAgent, useRelkitClient } from "@relkit/client/react";
import { useState } from "react";
/** Keeps the application-owned thread identity stable across rendering and observation.
* @param props - Authorized conversation identity retained by the application.
* @returns A greeting action, the observed answer, and explicit submission failure. */
export function Assistant({ threadId }: { readonly threadId: string }) {
const { status } = useRelkitClient();
const assistant = useAgent("hello.assistant");
const [failure, setFailure] = useState("");
/** Submits once and preserves uncertain outcomes for explicit recovery.
* @returns Completion after acceptance or displaying the failure. */
async function sendGreeting() {
try {
await assistant.send("Hello, RELKIT!", { threadId });
} catch (error) {
setFailure(String(error));
}
}
return (
<section>
<button
disabled={status !== "ready" || assistant.status !== "idle" || failure !== ""}
onClick={sendGreeting}
>
Say hello
</button>
<p role="status">{assistant.output?.answer ?? assistant.status}</p>
{failure && <p role="alert">{failure}. Check this thread before submitting again.</p>}
</section>
);
}
This is the same hello.assistant declaration: client.public exposes it, chat.input: "message"
makes send accept a string, and chat.output: "answer" selects its typed answer. Mounting
useAgent does not start a run. The backend owns models, credentials, tools, and authorization.
Reference: useAgent.
Continue with streaming and controls or
thread recovery.
Set limits
All three limits are required positive integers:
| Option | What it limits |
|---|---|
maxSteps |
Model steps in a run. |
maxToolCalls |
Total tool calls in a run. |
timeoutMs |
Total run time in milliseconds. |
If a run exceeds a limit, it fails rather than continuing indefinitely. Set limits that fit your task and handle failures in the calling application.
Choose a model
model accepts a compatible native LangChain model instance or a lazy application model. Install and
configure the provider package in server code; never put credentials, live instances, or provider
payloads in browser contracts. Subagents inherit the parent model unless they declare an override.
Run the typed client
Expose the agent through client, then select it with useAgent. Run, observe, stop, and resume calls
all require the caller’s stable business thread ID:
For this declaration, select hello.assistant; send(message, { threadId }) accepts a string
because chat.input maps it to message, and the final output.answer follows the output schema.
stop({ threadId }) is available because the agent declares stop. The
Frontend guide establishes the provider and contract pull, and
Agent streaming and controls shows a complete
typed UI, including how a different agent’s policy changes the available controls.
Reference: useAgent.
The generated selector narrows initial input, final output, selected middleware state, tools, custom
events, waiting replies, and controls. A resume uses the same thread with { resume: true }; it does not
start a replacement run.
Keep authorization in your application functions. Model instructions are not a replacement for checking whether the current user may read or change data.
Streaming
The client-exposed agent has a durable thread journal: useAgent restores its authorized snapshot and
observes text parts, tool calls, progress, approvals, selected state, nested execution, and terminal
output incrementally. Mounting the hook never creates a thread or starts a run. See
Agent streaming and Threads and recovery.