Skip to content

Runnable quickstart agent

A complete Configure agent you can run in about two minutes: hosted sign-in, a profile read at the start of every conversation, configure_* tool calls during the turn, a background commit after it, and streaming over Server-Sent Events. It is roughly 380 lines of Express plus one static page, and every part of the loop is visible in one file.

Use it to see the shape working before you wire Configure into your own app.

Get it

The same shell ships inside the npm package:

bash
npm install configure
npx configure setup
cp -R node_modules/configure/template ./my-agent
cd my-agent

If you work in the Configure repo, examples/quickstart/ is the same shell wired to the local SDK with a file: dependency. Differences between the two copies are listed at the end of this page.

Configure it

Create your .env from the example. This step is easy to miss: npm start reads .env, and .env.example is only a template.

bash
cp .env.example .env

npx configure setup already wrote the three values the server hard-requires:

VariableRequiredNotes
CONFIGURE_API_KEYYesServer exits at startup without it.
CONFIGURE_PUBLISHABLE_KEYYesSent to the browser by /api/config for the sign-in component.
CONFIGURE_AGENTYesYour agent handle.
MODEL_PROVIDERNoanthropic (default), openai, or openrouter.
MODEL_NAMEDependsDefaults to claude-sonnet-4-6 for Anthropic and gpt-4o for OpenAI. Required for OpenRouter.
ANTHROPIC_API_KEY or OPENAI_API_KEY or OPENROUTER_API_KEYFor chatOnly the key for the selected provider.
CONFIGURE_AGENT_NAMENoDisplay name in the shell.
PORTNoDefaults to 4000.

CONFIGURE_OAUTH_CLIENT_ID and CONFIGURE_OAUTH_CLIENT_SECRET appear in .env.example, but this example never reads them. Sign-in here runs through the hosted Link component with the publishable key. You need the OAuth pair when you put a Configure sign-in button in your own app: see Add the sign-in button.

Run it

bash
npm install
npm start
Configure quickstart running at http://localhost:4000

Open http://localhost:4000 in a browser.

What working looks like

  1. The page loads and fetches /api/config. You get the sign-in surface, not the composer.
  2. You sign in through the hosted component. The browser receives a configure:linked event carrying a token and userId, stores both, and calls /api/hello to greet you by name.
  3. You send a message. The reply streams in token by token.
  4. When the model calls a connector tool, a status line appears for that tool and flips to complete or error.
  5. After the turn ends, the server commits the exchange in the background. Nothing about that is visible in the UI, which is the point.

What failure looks like

Missing Configure keys, before the server binds a port:

Missing required environment variables: CONFIGURE_API_KEY, CONFIGURE_PUBLISHABLE_KEY, CONFIGURE_AGENT
Run `npx configure setup` for Configure keys, then restart the template.

A bad provider selection, also at startup:

MODEL_PROVIDER must be one of: anthropic, openai, openrouter

No provider key at all is not fatal. The server starts, /api/config reports runtimeReady: false, and the shell tells you chat is off while sign-in and the hosted profile surfaces keep working. That is deliberate: you can prove the Configure half of the integration before you have a model key.

The loop, line by line

All references are to server.mjs.

Build the client once, at boot (line 92). new Configure({ apiKey, agent }) holds credentials that never touch the browser.

Build a per-conversation runtime (line 310). configure.profile({ token, sessionId }) binds the request to the signed-in user. profileRuntimeOptions (line 48) derives a stable sessionId from the conversation id, and falls back to a hash of the token when there is none. The session id is what correlates a read with the commit that discharges it.

Declare the tools (line 312). profile.tools({ connectors: ENABLED_CONNECTORS }) returns tool definitions in Anthropic shape and records which names are executable. ENABLED_CONNECTORS is defined on line 17. Anything you do not declare here rejects at execution with ACCESS_DENIED. For the OpenAI-compatible providers the same array passes through toOpenAIFunctions (line 241).

Read the profile at the start of the conversation (line 315). profile.read({ sections: ['identity', 'summary', 'integrations', 'imports'] }), then read.profile.format({ guidelines: true }) on line 319 turns it into a prompt block that is appended to the system prompt. This is the read that makes the agent know the user in its very first sentence, rather than after a tool round trip.

Run the turn (line 327). Up to MAX_TOOL_ROUNDS (line 14, currently 5) rounds of stream, execute tools, feed results back. The cap is what stops a tool loop from running forever.

Execute tool calls (line 340). profile.executeTool({ name, arguments }) for every configure_* call the model made. The result is stringified back into the provider's tool-result shape: tool_result blocks for Anthropic (line 348), role: "tool" messages for the others (line 350).

Commit after the turn (line 369). The finally block calls profile.commit({ messages }) with just the user message and the assistant reply, and it is deliberately not awaited: the response has already ended, so the write happens in the background and a failure cannot break the turn. It only runs when the profile read succeeded, because a commit exists to discharge a read.

The commit packet is bounded on purpose. It is the turn, not the transcript. See Read and write memory for the limits and for when to use import instead.

The browser half

public/index.html is a single static page with no build step.

The hosted sign-in component and every other Configure surface come from the package itself. The server maps them onto a URL at line 88:

js
app.get('/configure-components.global.js', (_req, res) => {
  res.sendFile(require.resolve('configure/components/cdn'));
});

The page mounts Configure.link() for sign-in, listens for configure:linked to capture the token, and mounts the connections, memory import, and profile editor surfaces from the action buttons. Those are the same components documented under Components.

The user token goes from the browser to your own backend, which is where the profile runtime lives. Your secret key never leaves the server.

Honest gaps

Things the example does not do, which you may expect from reading the code:

  • The inline reconnect surface never fires. The client watches for a failed tool call that looks like a disconnected connector, and would mount an inline reconnect card. The server sends { type: "tool_status", status: "error", tool } with no error code or message, so that check cannot match. Send the classified error along with the status if you want that behavior. See Repair broken connectors.
  • The client handles a ui_component event that the server never sends. The branch exists; nothing emits it.
  • History lives in the browser. The page sends its own history array with each request. There is no server-side conversation store. Add one before you ship.
  • Neither README tells you to create .env. They go straight from npm install to npm start.

The two copies

The packaged template under node_modules/configure/template/ and the repo's examples/quickstart/ are the same shell with two intentional differences.

Packaged templateexamples/quickstart/
SDK dependencyPublished configure packagefile: link to the local SDK source
Profile readThe model calls configure_profile_read as a tool when it needs contextThe server reads the profile before the first model call and puts it in the system prompt
Commit triggerOnly when a read-backed tool ran during the turnWhenever the opening read succeeded

Reading up front costs one request per conversation and guarantees context. Leaving it to the model costs nothing on turns that do not need it. Both are supported. Pick per surface.

Next

Personalization infrastructure for agents