Deploy and Invoke Flows
A practical producer-consumer guide to deploying Kitaru flows, moving tags, and invoking stable or canary routes
This guide walks through the deployment workflow you will probably use in a real team: one person publishes a flow, tests a canary version, promotes it, and then other people or agents invoke it by name.
The short mental model is:
Producer deploys from source:
kitaru deploy flows/research.py:research_agent.Kitaru versions the deployment automatically:
v1, thenv2, thenv3.Producer moves tags like
default,canary, orstableto control routes.Consumer invokes one route by flow name plus a selector: usually a tag, sometimes an exact version. Kitaru resolves that route to the saved snapshot.
The story: a research flow you want to share
Imagine you have this flow in flows/research.py:
from kitaru import checkpoint, flow
@checkpoint
def collect_notes(topic: str) -> str:
...
@checkpoint
def write_summary(notes: str) -> str:
...
@flow
def research_agent(topic: str) -> str:
notes = collect_notes(topic)
return write_summary(notes)When you are still developing locally, you can run it directly from source:
A deployment is for the next step: saving a reusable, versioned entrypoint so other processes can invoke the flow without importing flows/research.py.
Step 1: deploy the first version
If you deploy from a source target (path.py:flow_name), initialize the repository first:
Then deploy from the source target:
The --input value should contain representative deployment-time input values. Kitaru uses them to prepare the saved deployment snapshot. Consumers can override those values later when they invoke the deployment.
--image is for deploy-time image config. It accepts a base image string, a JSON object matching kitaru.ImageSettings, or @file. For @file, you can use either plain text (for example a file containing python:3.12-slim) or a JSON string/object. That image config is saved into the deployment snapshot. Later invokes can override flow inputs, but they do not rewrite the deployment image.
Each kitaru deploy command attaches exactly one routing tag at deploy time. That first deploy gets the reserved default route automatically. Later deploys can choose one other route such as canary, and any extra tags are added or moved afterward with kitaru flow tag.
Because this is the first deployment for research_agent, Kitaru creates:
research_agent
1
default*
The * means the tag is exclusive. Exclusive tags point to exactly one version at a time.
You can inspect the versions:
show defaults to the default tag when you do not pass --version or --tag.
Build a version without routing it
Sometimes you want the saved deployment version first, but you do not want consumers to hit it yet. Use kitaru build for that:
That creates the immutable version but leaves it untagged. Later, after review or smoke testing, attach a route explicitly:
The practical difference is simple: kitaru deploy creates a version and one route; kitaru build creates only the version.
Step 2: invoke the default route
Now invoke the deployed flow by name:
Because you did not specify --version or --tag, Kitaru tries the reserved implicit default route. It resolves research_agent + default to the saved deployment snapshot for that version, starts a new execution from it, and returns an execution ID.
If the flow has no deployments yet, Kitaru says so directly. If deployments exist but none is currently routed as default, invoke with --tag or --version, or move default with kitaru flow tag ... --exclusive.
The same invocation from Python looks like this:
If you have the flow object imported in the current process, .invoke() is the remote invocation verb:
Step 3: deploy a canary version
Now imagine you improve the flow and want to test the new behavior without moving the default route yet. Deploy it with an exclusive canary tag:
Kitaru creates version 2 and attaches canary*:
research_agent
1
default*
research_agent
2
canary*
At deploy time, that is still just one route. If you later want to add another label without redeploying, do it with kitaru flow tag, for example:
That gives version 2 an exclusive canary route plus a shared benchmark label.
Now you can test only the canary route:
This is the safe pattern: deploy a candidate, invoke it explicitly, inspect the execution, and only then move the stable route.
Step 4: promote a version
There are two common promotion styles.
Option A: move default
defaultIf your consumers use the implicit default route, move default to version 2:
After that, plain kitaru invoke research_agent ... routes to version 2. The old version no longer has default.
Option B: publish a separate stable route
stable routeIf you prefer an explicit production route, move stable to version 2:
Consumers then invoke:
This is nice when you want default for interactive testing, while stable is what other systems use.
Producer vs consumer responsibilities
A useful way to keep the model straight:
Producer
Yes, for deploys
kitaru deploy, kitaru flow tag, kitaru flow deployments list
Consumer
No
kitaru invoke, KitaruClient().deployments.invoke(...), MCP kitaru_deployments_invoke
The producer knows flows/research.py:research_agent. The consumer only needs research_agent plus a selector such as default, stable, canary, or v2.
When selectors matter
Most of the time, consumers should use a tag:
Use an exact version when you need reproducibility:
A version is a hard pin. It will not move when someone promotes stable or default later. Tags are movable route names; versions are the saved deployment snapshots those routes resolve to.
Exclusive tags
Use exclusive tags for routes that should point to one version:
defaultstableprodcanarywhen there is only one current canary
default is always exclusive and reserved. You cannot remove it directly. To move it, attach default to another version with --exclusive.
A deployment that still holds any exclusive tag cannot be deleted. Move the tag first, then delete the old version:
Shared tags
Non-exclusive tags can point to more than one version. A common mixed pattern is: deploy one exclusive route at create time (--tag canary --exclusive), then add shared labels later with kitaru flow tag. Shared tags are useful for marking a group of deployments:
But shared tags are not good invocation routes once they point to multiple versions. If benchmark points to both v1 and v2, then this selector is ambiguous:
When a tag should be invocable by consumers, make it exclusive.
Generate a curl command
If you need to trigger a deployment from a shell script, CI job, or another system that can make HTTP requests, ask Kitaru to generate the curl command for the active Kitaru server.
Before you do that, make sure Kitaru knows three things:
the Kitaru server URL,
an auth token that can access that server, usually a service-account API key,
the project to use on that server.
For CI and other automation, create a service-account API key first:
Store the one-time key value from the create response in your secret manager. Then provide the connection by logging in once:
Or, in CI and other headless environments, with environment variables:
Run kitaru info if you want to check which connection Kitaru is using. If one of those three pieces is missing, kitaru auth token exits with a short error that says what to set next.
Then generate the curl command:
The output is copy-pasteable and uses your active connection's server URL:
Kitaru resolves research_agent plus stable before printing the command. That means the generated URL is pinned to the resolved deployment version. If the producer later moves stable to another version, regenerate the command.
kitaru auth token exchanges your active Kitaru connection for a short-lived server bearer token. It does not create a long-lived API key; use kitaru auth api-keys create or kitaru auth api-keys rotate for that. The generated curl snippet stores the short-lived bearer token in KITARU_SERVER_ACCESS_TOKEN for the current shell command only; the curl generator itself does not print or store the real token.
One important practical detail: deployment creation is rejected up front when the selected stack is local or otherwise not executable remotely by the Kitaru server. Use a stack the Kitaru server can execute remotely (for example Kubernetes, Vertex, SageMaker, or AzureML) for deployment routes you plan to invoke remotely.
For older deployments created before this guard existed, kitaru invoke and kitaru flow deployments curl may fail with a stack-compatibility error until you redeploy using a stack the Kitaru server can execute remotely.
The official zenmldocker/kitaru server image already enables workload-manager support for snapshot-backed invocation. If you run a custom image or a plain ZenML server setup, preserve or configure workload-manager support explicitly (e.g. set ZENML_SERVER_WORKLOAD_MANAGER_IMPLEMENTATION_SOURCE) so deployment invoke/curl routes remain executable.
For machine-readable output, add -o json:
Invoke from MCP
MCP clients use the same deployment model with structured tool calls.
Deploy a canary:
MCP image accepts the same shapes as CLI/SDK deploy: a base image string or an object matching kitaru.ImageSettings.
Invoke the stable route:
Use kitaru_deployments_list and kitaru_deployments_get before invoking when the assistant needs to discover available versions or confirm where a tag points.
Auth: one workspace context, no deployment tokens
Deployments do not have their own tokens. The CLI, SDK, and MCP server all use the active Kitaru connection context.
For a local server:
For a remote workspace:
For headless environments:
Once that context is configured, deployment invocation uses it automatically. A consumer invokes one route by flow name plus tag/version; Kitaru resolves that route to the saved snapshot. There is no long-lived per-version service and no extra per-deployment token to pass to kitaru invoke, .invoke(), or kitaru_deployments_invoke.
A practical checklist
Before sharing a route with other people or agents:
kitaru deploy ... --tag canary --exclusivefor the candidate.kitaru invoke FLOW --tag canary ...and inspect the execution.Move
stableordefaultwithkitaru flow tag FLOW TAG --version N --exclusive.Tell consumers the flow name and tag, not the source file path.
Use exact
--version Nonly for reproducible one-off runs.
If you remember just one thing: deploys create Kitaru-managed saved versions; tags are the movable signposts consumers follow.
Last updated
Was this helpful?