# Your first agent

From an empty directory to a deployed agent server. No LangGraph experience assumed — the parts of
it you need are explained here, where you need them.

If you already have a LangGraph.js graph or a `langgraph.json`, you want
[getting-started.md](./getting-started.md) instead: there is nothing to scaffold, and adopting skein
is a one-line change.

**Prerequisites:** Node 24 is recommended, with Node 22.12+ within 22.x also supported, and a package
manager. Nothing in this guide needs an API key until you choose a model, and nothing needs Docker
until deployment. For a workspace, start with the [monorepo guidance](./scaffolding.md#nx-and-other-monorepos).

## 1. Create the project

```bash
npm create skein-js@latest my-agent
```

`pnpm create skein-js@latest my-agent` and `yarn create skein-js@latest my-agent` do the same thing.
Keep `@latest` to request the latest published scaffolder.

Interactive runs ask about the model provider, ESLint and Prettier, local storage, dependency
installation, and git initialization. **Pick "None" for the provider**, keep memory storage and
ESLint/Prettier enabled, and accept dependency installation. The directory is already supplied above;
it is prompted when omitted. `--yes` accepts defaults without prompting.

You can add a model in a minute. The generated echo graph runs without credentials.

Then:

```bash
cd my-agent
npm run dev
```

You now have an agent server on `http://localhost:2024`.

## 2. Look at it before you read any code

Open `http://localhost:2024/console`.

That's the **skein console**, served by your own process — no account, no hosted service, no tunnel.
In Playground, choose the **echo** Assistant, send "hello", and watch the run execute. You will get `echo: hello` back, because
the graph you just created echoes its input.

This is worth doing before anything else: everything below is about changing what happens between
your message and that reply, and it helps to have seen the loop close.

Run the generated graph tests in a second terminal:

```bash
npm test
```

Both echo cases should pass without a model key or a running server. As you change the graph,
update its behavior assertions; [Testing your project](./testing-your-project.md) adds an SDK smoke
test and checks for multi-turn state, persistence, and approval flows.

## 3. What you actually got

The project includes tests, default ESLint/Prettier tooling, and a `.nvmrc` selecting a supported
Node version. The three files to understand first:

**`src/echo-graph.ts`** — your agent. We'll come back to it.

**`langgraph.json`** — how skein finds your graphs:

```json
{
  "node_version": "24",
  "graphs": { "echo": "./src/echo-graph.ts:graph" },
  "env": ".env"
}
```

`"./src/echo-graph.ts:graph"` means _the export named `graph` in that file_. The key — `echo` — is
what clients ask for by name. This is the same file format the LangGraph CLI reads, which is why
skein is a drop-in for it.

**`package.json`** — the commands that are the whole lifecycle:

|                        |                                                               |
| ---------------------- | ------------------------------------------------------------- |
| `npm run dev`          | what you're running: hot reload, in-memory state, zero setup  |
| `npm run dev:services` | Postgres + Redis in Docker, for the durable `dev` and `start` |
| `npm run dev:postgres` | the same hot reload, against those services instead           |
| `npm run build`        | compile your graphs to plain JavaScript in `.skein/`          |
| `npm start`            | serve that build — this is what production runs               |

(If you picked Postgres at the storage prompt, `dev` is already the durable one and the in-memory
spelling is `dev:memory` instead — both are always there.)

The rest: `.env` (ready to use — the model key is commented out, the service URIs are not) and
`.env.example` (a committed reference copy of it), `compose.dev.yaml` (the services for `start`),
`tsconfig.json`, a test, and a README.

## 4. Understanding the graph

Here is the whole agent:

```ts
import { AIMessage, type BaseMessage } from "@langchain/core/messages";
import { MessagesAnnotation, StateGraph } from "@langchain/langgraph";

function echo(state: typeof MessagesAnnotation.State): { messages: BaseMessage[] } {
  const last = state.messages.at(-1);
  const text = typeof last?.content === "string" ? last.content : "";
  return { messages: [new AIMessage(`echo: ${text}`)] };
}

export const graph = new StateGraph(MessagesAnnotation)
  .addNode("echo", echo)
  .addEdge("__start__", "echo")
  .addEdge("echo", "__end__")
  .compile();
```

Four ideas, and they are the only LangGraph concepts you need to get started:

**State** is the data flowing through your agent. `MessagesAnnotation` is a ready-made state that
holds a list of chat messages and knows to _append_ new ones rather than replace the list. Most
conversational agents want exactly this.

**A node** is a function. It receives the current state and returns only the part that changed — here,
one new message. It is a plain function: you can call it, test it, and step through it in a debugger.

**Edges** say what runs next. `__start__` and `__end__` are the built-in entry and exit points, so
this graph is "start → echo → done." Real agents branch here — that is where the "graph" part earns
its name.

**`.compile()`** turns the definition into something runnable. What it returns is what skein serves.

Try it: change `echo:` to `you said:` in `src/echo-graph.ts` and save. The server hot-reloads and
**keeps your existing threads** — send another message in the console and you will see the new reply
in the same conversation.

Those four ideas are enough to finish this guide. When you want the rest — custom state, branching,
subgraphs, `interrupt()` — [LangGraph essentials](./langgraph-essentials.md) is the short version,
with a link out to the LangChain docs on each one.

<span id="5-give-it-a-real-model"></span>

## 5. Give it a real model

Now swap the echo for an LLM. Install a provider:

```bash
npm install @langchain/anthropic langchain zod
```

Create `src/agent-graph.ts`:

```ts
import { ChatAnthropic } from "@langchain/anthropic";
import { createAgent } from "langchain";

const model = new ChatAnthropic({ model: "claude-sonnet-5", temperature: 0 });

export const graph = createAgent({ model, tools: [] });
```

`createAgent` is LangChain's prebuilt agent loop: call the model, and if it asks for a tool, run the
tool and call the model again. You did not have to build that loop. It lives in the `langchain`
package — LangGraph v1 deprecated its own `createReactAgent` and moved it there.

Register it in `langgraph.json`:

```json
{
  "graphs": {
    "echo": "./src/echo-graph.ts:graph",
    "agent": "./src/agent-graph.ts:graph"
  },
  "env": ".env"
}
```

Add your [Anthropic API key](https://console.anthropic.com/settings/keys) to the existing `.env` file:

```bash
ANTHROPIC_API_KEY=your-api-key
```

The packages installed above provide the model, `createAgent`, and the tool schema used in the next
step. Keep the existing `env` setting in `langgraph.json` when adding the new graph.

Restart, pick `agent` in the console, and you are talking to a real model. Tokens stream as they are
generated.

> Prefer to skip this assembly? `npm create skein-js@latest my-agent -- --provider anthropic` scaffolds
> all of it, plus a working tool, in one step.

## 6. Give it a tool

Talking is half of it. Tools are what let an agent actually do things:

```ts
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const getWeather = tool(
  async ({ city }: { city: string }) => {
    const geo = await fetch(
      `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`,
    ).then((response) => response.json());
    const place = geo.results?.[0];
    if (!place) return `I couldn't find ${city}.`;

    const forecast = await fetch(
      `https://api.open-meteo.com/v1/forecast?latitude=${place.latitude}` +
        `&longitude=${place.longitude}&current=temperature_2m`,
    ).then((response) => response.json());
    return `It's ${forecast.current.temperature_2m}°C in ${place.name}.`;
  },
  {
    name: "get_weather",
    description: "Get the current weather for a city.",
    schema: z.object({ city: z.string().describe("City name, e.g. 'Nairobi'") }),
  },
);
```

Pass it in — `createAgent({ model, tools: [getWeather] })` — and ask "what's the weather in
Nairobi?". The console shows the model deciding to call the tool, the result coming back, and the
final answer. (Open-Meteo needs no API key of its own.)

The `description` and `schema` are how the model knows when to use it. Write them for a reader who
has no other context, because that is exactly the model's situation.

## 7. Talk to it from your own code

The server speaks the standard Agent Protocol, so the official SDK works — there is no skein client:

```ts
import { Client } from "@langchain/langgraph-sdk";

const client = new Client({ apiUrl: "http://localhost:2024" });
const thread = await client.threads.create();

for await (const event of client.runs.stream(thread.thread_id, "agent", {
  input: { messages: [{ role: "user", content: "what's the weather in Nairobi?" }] },
})) {
  console.log(event.event, event.data);
}
```

For a UI, the `useStream` React hook talks to the same server with only a URL change — see
[react-sdk.md](./react-sdk.md). Vue, Svelte and Angular work too; so do
[Agent Chat UI](https://github.com/langchain-ai/agent-chat-ui) and LangGraph Studio.

## 8. Remember things between conversations

Threads persist a conversation. A **store** persists across conversations — what a user told you last
week. Any node can reach it:

```ts
import { getStore } from "@langchain/langgraph";

async function remember(state: typeof MessagesAnnotation.State) {
  const store = getStore();
  await store.put(["users", "alice"], "prefers", { units: "celsius" });
  const saved = await store.get(["users", "alice"], "prefers");
  // …use saved.value in your prompt
}
```

The default `dev` uses memory. A server can use Postgres with vector search for semantic recall;
a local application can use SQLite with text/filter search. Your node code does not change.
See [storage.md](./storage.md) and [memory.md](./memory.md).

## 9. Ship it

`dev` is not the production path. Production is `build` + `start`:

```bash
npm run dev:services    # Postgres + Redis via Docker
npm run build           # graphs → plain JavaScript in .skein/build
npm start
```

Nothing to edit first: the `POSTGRES_URI` and `REDIS_URI` in `.env` already match those services, and
`npm start` loads them itself.

`build` compiles your TypeScript graphs ahead of time; `start` serves that output with no TypeScript
toolchain in the loop. It is exactly what the production container runs.

The default `start` selects Postgres and Redis for durable state, queued work, and multiple
instances. It is one deployment option. For a desktop app, CLI, daemon, or single Node owner,
choose [SQLite](./local-deployment.md): scaffold with `--storage sqlite` to generate SQLite
dev/start scripts without Compose. The same graphs, checkpoints, and protocol are used; the
application decides whether to retry an abandoned attempt.

For a container, `npx skein up` brings up the whole stack, and `npx skein build` produces an image.
Then pick a host: [Cloud Run](./deploy-cloud-run.md), [Fly](./deploy-fly.md),
[Railway](./deploy-railway.md), [Render](./deploy-render.md), [AWS](./deploy-aws.md),
[Kubernetes](./deploy-kubernetes.md), or [a plain VPS](./deploy-vps.md). Full guide:
[deploy.md](./deploy.md).

## Where to next

**Read [building blocks](./building-blocks.md).** It is the map of everything this guide
skipped — checkpoints, threads, multitask, interrupts, memory, what production actually needs — a few
lines each, so you can tell which page you want before you need it.

If you would rather understand the graph model first,
[LangGraph essentials](./langgraph-essentials.md) covers state, reducers, branching, subgraphs and
`interrupt()`, and links out to the LangChain docs on each.

Then, as you need them:

- [Recipes](./recipes/) — auth, human-in-the-loop, background runs, CORS
- [The console](./console.md) — test your graph and inspect its output, state, and failures
- [Agent Protocol](./agent-protocol.md) — every endpoint your server exposes
- [Scaffolding reference](./scaffolding.md) — every flag, and the Nx generators
