Skip to main content
The AgentMark CLI automatically detects and runs Python projects with the appropriate dev server configuration.

Starting the dev server

The CLI detects Python projects and spawns the Python webhook server alongside the API server and UI.

Project detection

The CLI identifies Python projects by checking, in order:
  1. AgentMark setup files: agentmark_client.py, .agentmark/dev_server.py, or a root dev_server.py. These are decisive: once you set up your project, an unrelated manifest file no longer overrides TypeScript/Python detection. (agentmark.client.ts is the equally decisive TypeScript marker.)
  2. Python manifests: pyproject.toml, requirements.txt, or setup.py. These matter before any AgentMark files exist, so a fresh pip project gets Python guidance from doctor and dev on first contact.
If none match, the CLI assumes TypeScript.

Virtual environment detection

The CLI automatically detects and uses virtual environments:
When the CLI finds a virtual environment, it prints:

Entry point resolution

The CLI resolves the dev server entry point in this order: The entry point serves a webhook runner so the CLI and the local dev UI can execute your prompts (once deployed, the same handler serves the Dashboard). You build the runner once from an executor (the function that calls your LLM SDK), and the runner’s dispatch method handles every job it receives (prompt-run, dataset-run, and get-evals, which lists the evals registered on your client).

Neutral client and runner

The entry point builds a webhook runner from three pieces and serves it. Keep agentmark_client.py minimal (loader, client, and your evals) and put the executor, tracing init, and runner in .agentmark/dev_server.py:
  1. Client (agentmark_client.py): create_agentmark(loader=ApiLoader.local(base_url="http://localhost:9418")), plus your evals.
  2. Executor + runner (.agentmark/dev_server.py): write a create_executor for your SDK, then runner = create_webhook_runner(client, executor). The full executor contract and copy-paste handlers (object, streaming, every SDK) live on Connect your SDK.
  3. Serve: serve_webhook_runner(runner) (see Entry-point file).
For the complete agentmark_client.py + .agentmark/dev_server.py files, see Client setup.
Local tracing differs from production. The runner wires span hooks, but spans only export once you initialize tracing, and in local dev you point the exporter at the dev API server (unauthenticated, no cloud keys) so agentmark doctor --smoke and the local trace UI see your runs:
See Tracing setup for the production configuration.

Entry-point file

The CLI boots .agentmark/dev_server.py (or dev_server.py at your project root) and passes --webhook-port / --api-server-port. It strips cloud credentials from the spawned environment, so a client that picks its loader by API-key presence (the client setup pattern) lands on the local loader; the example above pins ApiLoader.local directly, which behaves the same in local dev. The entry point must keep serving HTTP: serve_webhook_runner(runner) is the line that does that. It serves runner.dispatch, the same handler a managed deployment exposes as handler = runner.dispatch. Without it the process builds the runner, exits, and agentmark dev reports Webhook server stopped. serve_webhook_runner is the Python counterpart of the TypeScript createWebhookServer (@agentmark-ai/cli/runner-server).
npm create agentmark@latest scaffolds agentmark.json, the agentmark/ prompt directory, MCP configs for your editor, and the AgentMark agent skill (plus git init in new directories). It doesn’t create the client or dev-server entry: you (or the agent skill) write agentmark_client.py and .agentmark/dev_server.py. See Client setup for the full dev-server and handler.py files.

Environment variables

The CLI sets the following environment variables for the spawned dev_server.py: The CLI also removes AGENTMARK_API_KEY, AGENTMARK_APP_ID, and AGENTMARK_BASE_URL from the spawned process, so a client that picks its loader by API-key presence stays in local mode instead of loading deployed prompts from the Cloud.

Server architecture

When you run agentmark dev, three servers start:
The Python webhook server:
  • Receives prompt execution requests from the CLI
  • Uses your agentmark_client.py configuration
  • Executes prompts through your executor, which calls your LLM SDK
  • Returns streaming or non-streaming responses

Port configuration

Override default ports with CLI options:

Wire contract

You normally never implement this; serve_webhook_runner(runner) is the whole server. The contract below is for custom entry points (your own framework, extra routes) and for debugging with curl. Every job arrives as POST / with a JSON {type, data} body. Three event types exist:

prompt-run

Executes a single prompt:

dataset-run

Executes a prompt across a dataset:

get-evals

Control-plane job: lists the eval names registered on your client, which populates the Dashboard’s New Experiment dialog. Carries no AST; the response is a flat JSON body {"type": "evals", "result": [...names], "traceId": null}.

Responses

The CLI (run-prompt, run-experiment) and the Dashboard switch parsing on one response header:
  • Streaming (runner.dispatch returned a result with a stream): respond with AgentMark-Streaming: true and Content-Type: application/x-ndjson, then write the stream’s NDJSON lines as they arrive (one JSON event per line: text / object / dataset / error chunks). After the stream drains, append a final {"type": "done", "traceId": "..."} line when the result carries a traceId; that’s where the CLI reads the trace link from.
  • Non-streaming: respond 200 with the dispatch result as a plain JSON body.
  • Errors: respond with a non-2xx status and a {"message": "..."} JSON body (400 for malformed/unknown jobs, 500 for executor failures).
This is the same contract the TypeScript dev server (createWebhookServer) and the managed-deployment server implement; serve_webhook_runner keeps the Python side pinned to it.

Running prompts

With the dev server running, execute prompts from another terminal:
Or run experiments:
Need a working starter? See Example prompts: four copy-paste recipes (object, text+tools, image, speech) you can drop into your agentmark/ directory.

Troubleshooting

agentmark doctor reports missing dependencies

agentmark doctor checks .venv/, then venv/, then the system pip, in that priority order, so it correctly finds packages installed inside a virtual environment. If you still see a missing-package warning after installing into your venv, confirm the venv pip resolves the packages:

Virtual environment not found

If you see “python not found” errors:

Module not found

Confirm you installed dependencies in the correct virtual environment:

Port already in use

If ports are busy, specify alternative ports:

agentmark_client.py not found

The CLI requires agentmark_client.py in your project root:

Agent frameworks

An agent-framework executor works the same in the dev server. Your handler runs an agent loop instead of a single completion, but it’s still a create_executor wired into the runner above. See Connect your SDK for the agent-loop executor, including streaming with tool-call events.

Next steps

Client setup

Python client, dev server, and deploy handler files

Connect your SDK

Wire any LLM SDK with an executor

Reference executors

Copy-paste executors for OpenAI, Anthropic, agent frameworks

Running prompts

Execute prompts from CLI

Have questions?

Reach out any time: