---
title: Define an agent
description: Give a model a clear task, the tools it needs, and limits on each run.
pagination: false
sidebar:
  icon: bot
---

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](/docs/learn/concepts/functions).

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](/docs/api/integrations/langchain#defineagent).

See [Native runtime and DeepAgents](/docs/modules/ai/native-runtime) for the supported package cohort, public
typing boundary, and deep-agent limitations. For explicit workflows and durable continuation, use
[LangGraph, persistence, and resume](/docs/modules/ai/graphs-persistence).

## Pre-setup

Complete [agent setup](/docs/modules/ai#pre-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:

```sh title="Terminal — application root"
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](/docs/api/cli#relkit-add-agent-name).

## Scaffold a reusable prompt

Create a prompt before referencing it from an agent:

```sh
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](/docs/modules/service/context-values)
for their shared-value contract.

For every prompt scaffolding flag, see the [prompt command reference](/docs/api/cli#relkit-add-prompt-name).

## Install the native runtime

> **Before you begin**
>
> Start with an [existing minimal app](/docs/learn/create-an-app). Its generic agent contracts do
> not install native execution. When adding the declaration manually, install the explicit
> integration and SDK cohort before importing `defineAgent` from `@relkit/langchain`:

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

From this guide's application/package root, install only its selected requirements:

```sh
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](/guides/ai/build-an-offline-agent#configure-the-runtime). 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](/docs/modules/ai/function-tools).

```ts title="src/services/hello/agents/assistant.agent.ts"
/** 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](/guides/frontend/connect-react),
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:

```tsx title="src/Assistant.tsx"
"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`](/docs/api/client#useagent).
Continue with [streaming and controls](/docs/modules/ai/streaming) or
[thread recovery](/docs/modules/ai/threads-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](/guides/frontend/connect-react) establishes the provider and contract pull, and
[Agent streaming and controls](/docs/modules/ai/streaming#use-from-the-frontend) shows a complete
typed UI, including how a different agent's policy changes the available controls.

Reference: [`useAgent`](/docs/api/client#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](/docs/modules/ai/streaming) and [Threads and recovery](/docs/modules/ai/threads-recovery).
