Your first agent
This tutorial builds a small research assistant, Scout, one step at a time. Each step is a complete agent.ts you can run with npx tsx agent.ts; each adds one capability of the kit.
Before you start, set up a project as in Installation and make sure authentication works (CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in the environment).
Step 1: a chat with a system prompt
import path from "node:path";
import {
buildSessionOptions,
ensureClaudeAuth,
runChatInk,
type AgentSpec,
type BaseSessionConfig,
} from "@falkenslab/agent-kit";
// 1. Who the agent is. Only buildSystemPrompt says something here; the rest is "nothing".
const spec: AgentSpec<BaseSessionConfig> = {
buildSystemPrompt: () => "You are Scout, a concise research assistant. Answer in short paragraphs.",
buildMcpServers: () => ({}),
pluginRoots: () => [],
buildSubagents: () => undefined,
};
// 2. Credentials: offers to create a token if there's none.
await ensureClaudeAuth();
// 3. How it runs: the oversight mode and the folder it works in.
const config: BaseSessionConfig = { mode: "autonomous", projectDir: process.cwd() };
// 4. The chat. With a session opener and runsDir, every run gets its own folder under .run/.
await runChatInk((run) => buildSessionOptions(config, run.dir, spec, { run }), {
runsDir: path.resolve(".run"),
header: { title: "Scout" },
welcomeMessage: "Ask me anything. /exit to leave.",
});
Run it and you get a full chat: streamed replies with markdown, /exit or Ctrl+C to leave, ↑/↓ and Ctrl+R for history, ? for the shortcuts.
What happened:
buildSessionOptions()built the SDK'sOptions: the system prompt (plus one line about the reply language), the tools this configuration gets (onlyWebFetchandWebSearchhere), the security hooks and the settings the kit always sets.runChatInk()called your opener with a new run folder (.run/2026-09-29T10-15-00-000Z/), started the session and drew the chat. Everything the terminal showed goes to that folder'ssession.log; the conversation, toconversation.jsonl.
.run/ to .gitignoreThe run folders hold whole conversations and tool results, not redacted. Never commit them.
Step 2: full screen, guided mode
const config: BaseSessionConfig = { mode: "guided", projectDir: process.cwd() };
await runChatInk((run) => buildSessionOptions(config, run.dir, spec, { run }), {
runsDir: path.resolve(".run"),
header: { title: "Scout", fields: { mode: config.mode } },
mode: config.mode,
fullscreen: true,
welcomeMessage: "Ask me anything. /exit to leave.",
});
fullscreen: truetakes the whole terminal: the history scrolls in its own view (PageUp/PageDown, the wheel), the prompt stays at the bottom, dragging selects text and a right-click copies it.- In
guidedmode the agent gets arequest_human_approvaltool and must call it before anything visible to others or hard to undo; an approval panel appears in the chat. Shift+Tab switches tointeractive, where every tool call asks first. See Modes.
Step 3: a tool of its own
Tools are MCP servers. The simplest is an in-process server built with tool() and createSdkMcpServer(), returned from buildMcpServers():
import { createSdkMcpServer, tool } from "@falkenslab/agent-kit";
import { z } from "zod";
const clock = createSdkMcpServer({
name: "clock",
version: "1.0.0",
tools: [
tool(
"current_time",
"The current date and time, in UTC and in the local time zone.",
{},
async () => {
const now = new Date();
return { content: [{ type: "text" as const, text: `${now.toISOString()} (${now.toString()})` }] };
},
{ annotations: { readOnlyHint: true } },
),
tool(
"days_until",
"How many whole days until a date.",
{ date: z.string().describe("An ISO date, e.g. 2026-12-31") },
async ({ date }) => {
const days = Math.ceil((Date.parse(date) - Date.now()) / 86_400_000);
return { content: [{ type: "text" as const, text: `${days} days` }] };
},
),
],
});
const spec: AgentSpec<BaseSessionConfig> = {
buildSystemPrompt: () => "You are Scout, a concise research assistant. Use the clock tools for anything about dates.",
buildMcpServers: () => ({ clock }),
pluginRoots: () => [],
buildSubagents: () => undefined,
};
Tool schemas use zod (npm install zod); a tool without parameters takes {}. The kit approves every mcp__* tool automatically, so the agent can call mcp__clock__current_time right away. See Tools and MCP servers.
Step 4: notes that survive the session
Give the agent a knowledgeDir and it gets the file tools, scoped to its own folders, and the kit's knowledge base: rules and skills to keep its notes as an interlinked wiki.
const config: BaseSessionConfig = {
mode: "guided",
projectDir: process.cwd(),
knowledgeDir: path.resolve("knowledge"), // the agent's notes: readable and writable
sourcesDir: path.resolve("sources"), // originals: readable, never modified
};
Now Scout can Read, Write, Edit, Glob and Grep, but only write inside knowledge/, only search inside knowledge/ and sources/, and never modify sources/ (it adds files there with save_to_sources, which never overwrites). Try /knowledge:ingest after dropping a PDF in sources/.
Step 5: resume a conversation
It's already there since step 1: start with npx tsx agent.ts --continue to pick up the latest conversation, or type /resume in the chat to choose one. See Runs and resuming.
Step 6: make it yours
import { ui } from "@falkenslab/agent-kit";
await runChatInk((run) => buildSessionOptions(config, run.dir, spec, { run }), {
runsDir: path.resolve(".run"),
header: {
title: "Scout",
fields: { mode: config.mode },
art: [ui.accent(" ▄▀▀▄ "), ui.accent(" █ █ "), ui.accent(" ▀▀ ")],
},
mode: config.mode,
fullscreen: true,
promptLabel: `${ui.user("you>")} `,
agentLabel: ui.agent("Scout>"),
firstPromptSuggestion: "what's new in TypeScript this year?",
historyPath: path.resolve(".run/history.jsonl"),
theme: { accent: "#56b6c2", selection: "#56b6c2" },
});
header.art: a small logo left of the title, one string per row, single-column characters only.promptLabelandagentLabel: the labels in the plain chat and the session log.firstPromptSuggestion: the suggestion shown (and taken with Tab) before the first turn.historyPath: the ↑/↓ history, kept across runs.theme: only the colors you want to change. See Themes.
Where to go next
- The whole shape of a spec: AgentSpec.
- Delegating to specialists: Subagents.
- Running without a chat (a job, a script): One-shot runs.
- Driving the agent from a desktop app: Custom hosts.
- A complete agent to read: Captain Whiskers.