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:
research_agent.run(topic="durable execution").wait()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:
kitaru initThen deploy from the source target:
kitaru deploy flows/research.py:research_agent \
--input '{"topic": "durable execution"}'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.
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:
| Flow | Version | Tags |
|---|---|---|
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:
kitaru flow deployments list research_agent
kitaru flow deployments show research_agentshow defaults to the default tag when you do not pass --version or --tag.
Step 2: invoke the default route
Now invoke the deployed flow by name:
kitaru invoke research_agent \
--input '{"topic": "serverless routing"}'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:
from kitaru import KitaruClient
handle = KitaruClient().deployments.invoke(
flow="research_agent",
inputs={"topic": "serverless routing"},
)
print(handle.exec_id)If you have the flow object imported in the current process, .invoke() is the
remote invocation verb:
from flows.research import research_agent
handle = research_agent.invoke(topic="serverless routing")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 deploy flows/research.py:research_agent \
--tag canary \
--exclusive \
--input '{"topic": "durable execution"}'Kitaru creates version 2 and attaches canary*:
| Flow | Version | Tags |
|---|---|---|
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:
kitaru flow tag research_agent benchmark --version 2That gives version 2 an exclusive canary route plus a shared benchmark
label.
Now you can test only the canary route:
kitaru invoke research_agent \
--tag canary \
--input '{"topic": "tag routing"}'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
If your consumers use the implicit default route, move default to version 2:
kitaru flow tag research_agent default --version 2 --exclusiveAfter that, plain kitaru invoke research_agent ... routes to version 2.
The old version no longer has default.
Option B: publish a separate stable route
If you prefer an explicit production route, move stable to version 2:
kitaru flow tag research_agent stable --version 2 --exclusiveConsumers then invoke:
kitaru invoke research_agent \
--tag stable \
--input '{"topic": "consumer request"}'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:
| Role | Needs source target? | Typical commands |
|---|---|---|
| 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:
kitaru invoke research_agent --tag stable --input '{"topic": "..."}'Use an exact version when you need reproducibility:
kitaru invoke research_agent --version 2 --input '{"topic": "..."}'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:
kitaru flow tag research_agent default --version 2 --exclusive
kitaru flow deployments delete research_agent --version 1Shared 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:
kitaru flow tag research_agent benchmark --version 1
kitaru flow tag research_agent benchmark --version 2But 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:
kitaru invoke research_agent --tag benchmark --input '{"topic": "..."}'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 or API key that can access that server, and
- the project to use on that server.
You can provide those by logging in once:
kitaru login https://kitaru.example.com --api-key kat_abc123 --project productionOr, in CI and other headless environments, with environment variables:
export KITARU_SERVER_URL=https://kitaru.example.com
export KITARU_AUTH_TOKEN=kat_abc123
export KITARU_PROJECT=productionRun 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:
kitaru flow deployments curl research_agent \
--tag stable \
--input '{"topic": "consumer request"}'The output is copy-pasteable and uses your active connection's server URL:
# Resolved research_agent tag 'stable' to deployment version v2.
# This command is pinned to v2. Regenerate it if you move the tag.
KITARU_SERVER_ACCESS_TOKEN="$(kitaru auth token)"
curl -sS -X POST \
'https://kitaru.example.com/api/v1/pipeline_snapshots/7d3176d6-7453-411b-a3f5-91ca5c663d1c/runs' \
-H "Authorization: Bearer ${KITARU_SERVER_ACCESS_TOKEN}" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"run_configuration":{"parameters":{"topic":"consumer request"}}}'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. The generated curl snippet stores it 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:
kitaru flow deployments curl research_agent --version 2 -o jsonInvoke from MCP
MCP clients use the same deployment model with structured tool calls.
Deploy a canary:
{
"target": "flows/research.py:research_agent",
"inputs": {"topic": "durable execution"},
"tag": "canary",
"exclusive": true
}Invoke the stable route:
{
"flow": "research_agent",
"tag": "stable",
"inputs": {"topic": "consumer request"}
}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:
kitaru login
kitaru statusFor a remote workspace:
kitaru login my-workspace --api-key kat_abc123 --project production
kitaru statusFor headless environments:
export KITARU_SERVER_URL=https://kitaru.example.com
export KITARU_AUTH_TOKEN=kat_abc123
export KITARU_PROJECT=productionOnce 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.