Skip to main content
The AgentMark client is the piece of your code that actually executes prompts. It renders each .prompt.mdx to AgentMark’s neutral { messages, text_config } shape; your code (or an executor) passes that to whatever LLM SDK you already use. One client powers three surfaces: Run buttons in the Dashboard (playground runs, experiments) dispatch to your hosted client over its registered Webhook URL. Until you register one, the Dashboard disables those buttons and points you to this page.
Your AI tool detects your stack and writes the client for it. See Let your agent set it up. The steps below are the manual path.

Prerequisites

  • An AgentMark project: agentmark.json + an agentmark/ directory with at least one prompt (Quickstart)
  • Node.js 18+ (the agentmark CLI runs on Node for both languages); Python projects also need Python 3.12+
  • Your model provider’s API key (for example, OPENAI_API_KEY)

Step 1: Install the client and CLI

The client is SDK-neutral. Install it once and keep whatever LLM SDK you already call:
You only need the two runtime packages above plus your own SDK. The model call lives in an executor you own. Copy a ready-made one from Connect your SDK below (Vercel AI SDK, raw OpenAI, raw Anthropic, agent frameworks), or write your own following the same contract. There is no per-SDK AgentMark package to install or version to track.

Step 2: Create agentmark.client.ts

Create this file at your project root (next to agentmark.json), not inside src/. The CLI (agentmark dev, agentmark doctor) loads agentmark.client.ts from the project root, and dev-entry.ts / handler.ts import it from there.The client wires together three things: a loader (where prompts come from), the neutral adapter (which renders prompts to { messages, text_config }), and your evals (registered once, here; everything else sources them from the client):
agentmark.client.ts
Generate the agentmark.types file with agentmark generate-types --root-dir agentmark > agentmark.types.ts. It’s safe to start without type arguments: drop the <AgentmarkTypes> generic and the import until you’re ready.
Two loader gotchas:
  • Gate the loader on AGENTMARK_APP_ID, not AGENTMARK_API_KEY. The API key is also the trace-exporter credential, so if the key is the switch, turning on tracing silently repoints prompt-loading to Cloud, which 404s every prompt until you’ve actually deployed. The app id is the loader-specific signal: you only have one once an app is provisioned.
  • Don’t point ApiLoader.local at process.env.AGENTMARK_BASE_URL. That variable overrides the cloud endpoint (managed deployments inject it), and reusing it for the local loader silently breaks agentmark dev whenever it’s set.
The neutral client doesn’t resolve models or tools: it renders, and your call site (or executor) handles the rest. For the pieces this file wires up, see Loaders, Tools and agents, MCP, Type safety, and Writing evals for real eval functions.

Step 3: Run locally with agentmark dev

agentmark dev starts a local API server (serves your prompt files) and a webhook server (executes prompts through your client). The webhook server boots from a dev-entry.ts file at your project root.It builds an executor (your one model call), wires it to your client with createWebhookRunner, and serves that runner locally:
dev-entry.ts
The placeholder executor above returns empty text: paste a real executor from Connect your SDK first to get actual model output. Then start the dev stack and run a prompt:
Experiments work the same way; datasets resolve through the local API server. Your prompt needs a dataset first (test_settings.dataset in its frontmatter; see Datasets):
If agentmark dev exits with No dev server entry point found, the dev-entry.ts file above is what it’s looking for.

Step 4: Add a deployment entry point (handler.ts)

AgentMark Cloud executes your client through a single handler function. Each Dashboard run (playground or experiment) arrives as one { type, data } event; the runner’s dispatch routes it:
handler.ts
@agentmark-ai/sdk owns tracing initialization. The managed server is long-lived, so the default batch span processor flushes on its own; you don’t call shutdown() here (that’s only for short-running scripts).

Step 5: Host it and register the webhook

  1. Host handler.ts as a webhook endpoint on your own infrastructure (Vercel, your own server, or wherever you already deploy). The export default above is the handler your hosting platform calls.
  2. Set your provider keys on that hosting platform (e.g. OPENAI_API_KEY). AGENTMARK_API_KEY, AGENTMARK_APP_ID, and AGENTMARK_BASE_URL come from the client’s Cloud loader — set them there too.
  3. Register the URL in the Dashboard under Settings → Integrations → Webhook, scoped to the environment. Run buttons in the playground and experiments go live against it as soon as it’s saved.

Connect your SDK

Steps 3 and 4 wire an executor into your runner: the one function that calls your SDK. This section is the reference for that function. Most apps that run prompts in their own code never need it (see Running prompts); it matters only when you let AgentMark Cloud run a prompt for you, via the Dashboard Run button and Cloud-driven experiments. Copy the setup that matches your SDK into the dev-entry and handler from the steps above.

Write an executor

createExecutor takes a pair of handlers (text / object). Each receives formatted (the neutral rendered prompt) and returns { text | object, usage }. That’s the whole contract; Client setup handles wiring it into a runner and serving it.

Reference setups

Complete, copy-paste executors for the SDKs teams reach for most. Each calls your SDK directly. Copy the closest one, adjust the model mapping, and you’re done. Every one takes the neutral render and returns { text | object, usage }.

Vercel AI SDK

Wraps the ai package’s generateText / streamText (and generateObject for structured output), with both one-shot and streaming text paths:

OpenAI (raw SDK)

The official OpenAI SDK’s chat.completions.create. In TypeScript the neutral messages need a cast to OpenAI’s ChatCompletionMessageParam[]. The shapes are structurally compatible, but TypeScript won’t infer it, so the call doesn’t type-check without the cast. In Python, formatted is a Pydantic model so model_dump the messages:

Anthropic (raw SDK)

The @anthropic-ai/sdk messages.create. Anthropic takes system as a top-level field and requires max_tokens, so split the system message out of the neutral render:

Amazon Bedrock (Python)

Bedrock’s invoke_model takes a different request shape: anthropic_version lives in the body, the request must include max_tokens, and the model ID is a full cross-region inference profile ID, not the short alias in the prompt’s model_name. Map it explicitly. The runner automatically stamps gen_ai.operation.name = "chat" and the config alias on the span, so the Requests view and cost attribution work with no extra code. To surface the full inference profile ID in the dashboard instead of the alias, override gen_ai.request.model on the span after your call (set_attribute is last-write-wins):

Agent frameworks (Pydantic AI, Mastra, Claude Agent SDK)

Agent frameworks follow the identical shape; the only difference is that your handler runs an agent loop instead of a single completion. Feed the render’s messages into your agent, run it, and return its final output plus token usage:
For token-by-token output and tool-call events, use a streaming handler and yield text-delta / tool-call / tool-result events as the agent emits them. See Streaming SDKs.

Streaming SDKs

If your SDK streams (for example, Bedrock ConverseStream), use the streaming handlers instead of buffering. They yield the same content events (text-delta, tool-call, …) and report usage plus the finish reason on a finish event you yield; the builder emits the single terminal finish for you:
Streaming object handlers yield object-delta / object-final events (ObjectDeltaEvent / ObjectFinalEvent in Python) and a finish carrying usage. If your SDK only streams cumulative partials (no explicit final), the builder uses the last delta as the resolved value, so AgentMark Cloud always receives a complete object.

Validate your executor

Run the conformance suite. One call confirms your executor emits a protocol-correct stream for every kind, streaming and one-shot, including the error path. errorInput is a malformed render your handler rejects before any network call:
Unless you pin ctx, the suite runs your executor twice, once streaming and once one-shot, so if you supply both a one-shot and a streaming handler, the suite validates both branches (a broken one-shot path won’t hide behind a working stream).
Provider-specific parameter mapping (tool wiring, custom settings, full request control) also lives in your executor: its handlers receive the neutral render and build the exact request your SDK expects. See the resolve-by-name tools pattern for wiring frontmatter tool names to implementations.

Model names vs provider model IDs

formatted.text_config.model_name is the prompt’s model_name verbatim: a registry ID in provider/model form. Your executor owns the translation to whatever ID your SDK expects. Two common shapes: Strip the provider prefix when the registry ID is your SDK’s model ID, the usual case (openai/gpt-4ogpt-4o):
Map names explicitly when your prompts declare one provider’s names but your executor calls another (for example, prompts on anthropic/claude-sonnet-4-6, production on Bedrock). Keep the dict in the executor so it’s versioned with the code, and fail loudly on unmapped names instead of passing them through (an unmapped name otherwise surfaces as a confusing provider-side 404):
Either way, declare the names your prompts use in builtInModels (a non-empty list is an allowlist). agentmark pull-models --provider bedrock lists the registry’s Bedrock IDs.

Let your agent set it up

The AgentMark skill gives your AI tool (Claude Code, Cursor, etc.) a setup workflow that scaffolds everything on this page (the client file, the dev entry, and the handler) matched to your stack’s language and SDK. Prompt it with:
The agent verifies its work the same way you would: agentmark run-prompt against the dev server.

Troubleshooting

Have questions?

Reach out any time: