.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:
| Surface | Entry point (TS / Python) | What runs it |
|---|---|---|
| Your application code | agentmark.client.ts / agentmark_client.py | You |
| Local development | dev-entry.ts / .agentmark/dev_server.py | agentmark dev |
| AgentMark Cloud | handler.ts / handler.py | The deployment pipeline |
Prerequisites
- An AgentMark project:
agentmark.json+ anagentmark/directory with at least one prompt (Quickstart) - Node.js 18+ (the
agentmarkCLI runs on Node for both languages); Python projects also need Python 3.12+ - Your model provider’s API key (for example,
OPENAI_API_KEY)
- TypeScript
- Python
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
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.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
test_settings.dataset in its frontmatter; see Datasets):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).The pipeline resolves your handler in this order: the handler key in agentmark.json if set, then handler.py, then handler.ts at the repository root. See handler detection.Step 5: Deploy
- Connect your repository in the Dashboard (the app’s setup card, or Deployments). Every push then triggers the deployment pipeline: file sync, then a code deploy of your handler to a managed machine.
- Set your provider keys under Settings → Environment Variables (e.g.
OPENAI_API_KEY). AgentMark Cloud injectsAGENTMARK_API_KEY,AGENTMARK_APP_ID, andAGENTMARK_BASE_URLautomatically, so your client’s Cloud loader is already wired for them. - Push. Watch the build under Deployments; when it goes green, Run buttons in the playground and experiments go live against your deployed client.
Deployed experiments stream their datasets from your linked repository (at the environment’s branch or pinned commit). The repo connection does more than sync: it’s how your datasets reach the deployed client at run time.
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 thedev-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 theai package’s generateText / streamText (and generateObject for structured output), with both one-shot and streaming text paths:
OpenAI (raw SDK)
The official OpenAI SDK’schat.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’sinvoke_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:text-delta / tool-call / tool-result events as the agent emits them. See Streaming SDKs.
Streaming SDKs
If your SDK streams (for example, BedrockConverseStream), 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:
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-4o → gpt-4o):
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):
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:agentmark run-prompt against the dev server.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
No dev server entry point found | Missing dev entry | Create dev-entry.ts at the project root (TS) or .agentmark/dev_server.py (Python); see Step 3 |
Local experiment fails Not authorized | AGENTMARK_API_KEY is present, so the client picked the Cloud loader | Unset AGENTMARK_API_KEY (check .env) when running against agentmark dev, or set a valid Cloud key; see Step 2 |
| Run buttons disabled in the Dashboard | No deployment for the selected environment | Deploy (Step 5) |
Deployed run fails Authentication failed | Stale deployment credentials | Trigger a Rebuild under Deployments |
| Deployed experiment finds no dataset | Dataset isn’t in the linked repo branch | Commit the .jsonl under agentmark/ and push |
Have questions?
Reach out any time:
- Email the team at hello@agentmark.co for support
- Schedule an Enterprise Demo to learn about AgentMark’s business solutions