# Deploy and Invoke Flows

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:

1. **Producer deploys** from source: `kitaru deploy flows/research.py:research_agent`.
2. **Kitaru versions** the deployment automatically: `v1`, then `v2`, then `v3`.
3. **Producer moves tags** like `default`, `canary`, or `stable` to control routes.
4. **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`:

```python
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:

```python
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:

```bash
kitaru init
```

Then deploy from the source target:

```bash
kitaru deploy flows/research.py:research_agent \
  --input '{"topic": "durable execution"}' \
  --image '{"requirements":["kitaru[openai]"],"secret_environment_from":["openai-creds"]}'
```

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:

| 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:

```bash
kitaru flow deployments list research_agent
kitaru flow deployments show research_agent
```

`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:

```bash
kitaru build flows/research.py:research_agent \
  --input '{"topic": "durable execution"}'
```

That creates the immutable version but leaves it untagged. Later, after review or smoke testing, attach a route explicitly:

```bash
kitaru flow tag research_agent canary --version 2 --exclusive
```

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:

```bash
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:

```python
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:

```python
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:

```bash
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:

```bash
kitaru flow tag research_agent benchmark --version 2
```

That gives version `2` an exclusive `canary` route plus a shared `benchmark` label.

Now you can test only the canary route:

```bash
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`:

```bash
kitaru flow tag research_agent default --version 2 --exclusive
```

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

If you prefer an explicit production route, move `stable` to version `2`:

```bash
kitaru flow tag research_agent stable --version 2 --exclusive
```

Consumers then invoke:

```bash
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:

```bash
kitaru invoke research_agent --tag stable --input '{"topic": "..."}'
```

Use an exact version when you need reproducibility:

```bash
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:

* `default`
* `stable`
* `prod`
* `canary` when 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:

```bash
kitaru flow tag research_agent default --version 2 --exclusive
kitaru flow deployments delete research_agent --version 1
```

### 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:

```bash
kitaru flow tag research_agent benchmark --version 1
kitaru flow tag research_agent benchmark --version 2
```

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:

```bash
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:

1. the Kitaru server URL,
2. an auth token that can access that server, usually a service-account API key,
3. the project to use on that server.

For CI and other automation, create a service-account API key first:

```bash
kitaru auth service-accounts create ci-runner \
  --description "CI runner for deployment invocation"
kitaru auth api-keys create ci-runner default -o json
```

Store the one-time `key` value from the create response in your secret manager. Then provide the connection by logging in once:

```bash
kitaru login https://kitaru.example.com --api-key KITARU_API_KEY_VALUE --project production
```

Or, in CI and other headless environments, with environment variables:

```bash
export KITARU_SERVER_URL=https://kitaru.example.com
export KITARU_AUTH_TOKEN=KITARU_API_KEY_VALUE
export KITARU_PROJECT=production
```

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:

```bash
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:

```bash
# 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. 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`:

```bash
kitaru flow deployments curl research_agent --version 2 -o json
```

## Invoke from MCP

MCP clients use the same deployment model with structured tool calls.

Deploy a canary:

```json
{
  "target": "flows/research.py:research_agent",
  "inputs": {"topic": "durable execution"},
  "tag": "canary",
  "exclusive": true,
  "image": {
    "requirements": ["kitaru[openai]"],
    "secret_environment_from": ["openai-creds"]
  }
}
```

MCP `image` accepts the same shapes as CLI/SDK deploy: a base image string or an object matching `kitaru.ImageSettings`.

Invoke the stable route:

```json
{
  "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:

```bash
kitaru login
kitaru status
```

For a remote workspace:

```bash
kitaru login my-workspace --api-key KITARU_API_KEY_VALUE --project production
kitaru status
```

For headless environments:

```bash
export KITARU_SERVER_URL=https://kitaru.example.com
export KITARU_AUTH_TOKEN=KITARU_API_KEY_VALUE
export KITARU_PROJECT=production
```

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:

1. `kitaru deploy ... --tag canary --exclusive` for the candidate.
2. `kitaru invoke FLOW --tag canary ...` and inspect the execution.
3. Move `stable` or `default` with `kitaru flow tag FLOW TAG --version N --exclusive`.
4. Tell consumers the flow name and tag, not the source file path.
5. Use exact `--version N` only for reproducible one-off runs.

If you remember just one thing: deploys create Kitaru-managed saved versions; tags are the movable signposts consumers follow.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.zenml.io/kitaru/guides/deployments.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
