---
title: "Kodama pipelines: CI/CD, agents and business automation"
description: "Author Kodama pipelines for CI/CD, agentic development, human-approved business processes and business signals, with reliable API submission and observation."
canonical_url: https://optimi.com/en/docs/kodama/developer-guide
md_url: https://optimi.com/en/docs/kodama/developer-guide.md
last_updated: 2018-10-20
---

# Pipelines and integrations

Start with the [first pipeline walkthrough](/en/docs/kodama/getting-started),
then adapt the examples below to your project. A pipeline is an immutable
definition; each run pins its inputs, content revision, source and configuration.

## The pipeline contract

Use `apiVersion: kodama/v1alpha1` and `kind: Pipeline`. Register the document
through Kodama rather than applying it to Kubernetes. Steps form a directed
acyclic graph through `needs`.

| Field | Rule |
| --- | --- |
| `spec.inputs` | Typed `string`, `boolean`, `integer` or `artifact` inputs, with optional defaults and required flags |
| `kind` | `container`, `agent` or `gate` |
| `image` | Container image pinned by digest |
| `workspace` | `access: none`, `read` or `write`; read/write steps select `from: run` or an ancestor writer |
| `command`, `args`, `env` | Explicit command/argument arrays and environment values |
| `timeoutSeconds` | Positive deadline within installation limits |
| `retry` | `maxAttempts` includes the first; retry only declared infrastructure/transient failures |
| `outputs` | Declared scalar, artifact or `ociArtifact` outputs; files stay under `/kodama/outputs` |
| `when` | An executable step may compare a declared run input to a scalar; gates remain unconditional |

Expressions must occupy a whole value: `${{ inputs.name }}` or
`${{ steps.test.outputs.report }}`. Output references require a dependency path.
Writers are totally ordered. A successful writer provides an implicit `commit`
output, and its readers explicitly select its source.

Commands write scalar and OCI outputs to `/kodama/results.json`; the entrypoint
collects declared file outputs. For example, a script declaring
`action_required: {type: boolean}` can write:

```json
{"outcome":"completed","outputs":{"action_required":true}}
```

The examples use illustrative private images with placeholder digests. Replace
them with your approved images. Scripts under `ci/` and `automation/` are
**application-owned scripts** you provide in the project repository; they are
not built-in Kodama commands. They must produce exactly the declared outputs
and fit the installation’s non-root, resource and network policy.

## Example 1: pure CI/CD

**Use case:** test a known commit, obtain approval, build a verified image and
promote that exact image. No model provider or agent profile is required.

Save `ci.yaml`. `ci/test.sh` runs your checks and writes `tests.json`;
`ci/build.sh` builds/pushes an image with a compatible rootless builder and
writes its OCI descriptor plus its digest-pinned reference to the result file.

```yaml
apiVersion: kodama/v1alpha1
kind: Pipeline
metadata:
  name: ci
spec:
  inputs: {}
  steps:
    - id: test
      kind: container
      image: registry.example.com/acme/ci@sha256:1111111111111111111111111111111111111111111111111111111111111111
      workspace: {access: read, from: run}
      command: ["/bin/sh", "/workspace/ci/test.sh"]
      timeoutSeconds: 600
      outputs:
        report: {type: artifact, path: /kodama/outputs/tests.json}
    - id: review
      kind: gate
      needs: [test]
      gate:
        evidence: ["${{ steps.test.outputs.report }}"]
    - id: build
      kind: container
      needs: [review]
      image: registry.example.com/acme/builder@sha256:2222222222222222222222222222222222222222222222222222222222222222
      workspace: {access: read, from: run}
      command: ["/bin/sh", "/workspace/ci/build.sh"]
      secretFiles:
        REGISTRY_TOKEN: secret://project/registry-publisher
      timeoutSeconds: 1200
      outputs:
        image: {type: ociArtifact}
        image_ref: {type: string}
```

The build script’s result has this shape, with the real pushed digest:

```json
{
  "outcome": "completed",
  "outputs": {
    "image": {
      "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "registryRef": "registry.example.com/acme/application"
    },
    "image_ref": "registry.example.com/acme/application@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  }
}
```

The installation must approve the registry and the project’s publishing
credential. Kodama verifies OCI provenance before exposing a successful image
output. The gate protects the build path; removing it is an explicit pipeline
policy change, not a command-line override.

### Promote the verified image

Register a separate `promote.yaml` and start it with the verified artifact ID,
image reference and candidate commit returned by the CI run. This avoids
interpolating an OCI object into a shell string. Configure a project delivery
environment first; replace `env_staging` with its actual ID.

The image below must contain Kodama’s deployment-tools scripts and be qualified
for the installation’s apply protocol. Application manifests under `deploy/`
use `@@IMAGE@@` where the selected image belongs.

```yaml
apiVersion: kodama/v1alpha1
kind: Pipeline
metadata:
  name: promote
spec:
  inputs:
    image_artifact_id: {type: string, required: true}
    image_ref: {type: string, required: true}
    commit: {type: string, required: true}
  steps:
    - id: deploy
      kind: container
      image: registry.example.com/acme/deployment-tools@sha256:3333333333333333333333333333333333333333333333333333333333333333
      environment: env_staging
      workspace: {access: read, from: run}
      command: ["/opt/kodama/deploy/apply.sh"]
      env:
        KODAMA_SERVER_URL: https://kodama.example.com
        DEPLOY_MANIFESTS: /workspace/deploy
        DEPLOY_REPOSITORY_URL: https://git.example.com/acme/application.git
        DEPLOY_COMMIT: ${{ inputs.commit }}
        DEPLOY_IMAGE_ARTIFACT_ID: ${{ inputs.image_artifact_id }}
        DEPLOY_IMAGE: ${{ inputs.image_ref }}
      timeoutSeconds: 600
      outputs:
        deployment_id: {type: string}
        deployment_status: {type: string}
        configuration_digest: {type: string}
        image_digest: {type: string}
        source_commit: {type: string}
        ready: {type: boolean}
        object_count: {type: integer}
```

Use the same candidate for `--source-commit` and the `commit` input. The apply
script records deployment identity before modifying target objects. Promotion
needs `deployment.call` authority and the environment’s scoped kubeconfig.
An unknown apply outcome requires reconciliation with that record, not an
unconditional second deployment.

## Example 2: agentic implementation and review

**Use case:** implement a work item, test the resulting commit and request a
human decision on the candidate. Prerequisites are an approved `coding@1` profile,
model settings, permitted tools and a qualified harness. The profile’s instructions
describe how to satisfy the work item and produce its conformance report.

Save `agentic-change.yaml`:

```yaml
apiVersion: kodama/v1alpha1
kind: Pipeline
metadata:
  name: agentic-change
spec:
  inputs: {}
  steps:
    - id: implement
      kind: agent
      agent: {profile: "coding@1"}
      workspace: {access: write, from: run}
      timeoutSeconds: 1800
      outputs:
        summary: {type: string}
        conformance: {type: artifact, path: /kodama/outputs/conformance.json}
    - id: test
      kind: container
      needs: [implement]
      image: registry.example.com/acme/ci@sha256:1111111111111111111111111111111111111111111111111111111111111111
      workspace: {access: read, from: implement}
      command: ["/bin/sh", "/workspace/ci/test.sh"]
      timeoutSeconds: 600
      outputs:
        report: {type: artifact, path: /kodama/outputs/tests.json}
    - id: review
      kind: gate
      needs: [test]
      gate:
        evidence:
          - ${{ steps.test.outputs.report }}
          - ${{ steps.implement.outputs.conformance }}
```

The test consumes `from: implement`, so it evaluates the agent’s published
candidate rather than the original source. The gate binds the candidate,
requirements and evidence. A later build must consume that same candidate.

Models and tools are selected through the profile and project configuration,
not embedded provider credentials. Tool names use `<registration>/<tool>`.
If the agent returns `needs_input`, clarify the specification and start a new
run. Rejection likewise creates a rework loop with new evidence and a fresh
gate, preserving the original decision.

## Example 3: a human-approved business process

**Use case:** validate an ERP correction, review the proposed change and apply
it once. The repository contains versioned automation rules, even though the
business request is external to Git. Its commit provides the rule baseline for
the report and approval.

`validate-correction.sh` reads the referenced request and writes the proposed
change to `proposal.json`. `apply-correction.sh` revalidates the approved request
and writes a receipt. Both use a scoped ERP credential from a secret file.
The apply script must use the stable request ID as the ERP’s idempotency key
and verify the proposal has not changed since review.

Save `business-correction.yaml`:

```yaml
apiVersion: kodama/v1alpha1
kind: Pipeline
metadata:
  name: business-correction
spec:
  inputs:
    request_id: {type: string, required: true}
    request_version: {type: string, required: true}
  steps:
    - id: validate
      kind: container
      image: registry.example.com/acme/business-tools@sha256:4444444444444444444444444444444444444444444444444444444444444444
      workspace: {access: read, from: run}
      command: ["/bin/sh", "/workspace/automation/validate-correction.sh"]
      env:
        REQUEST_ID: ${{ inputs.request_id }}
        REQUEST_VERSION: ${{ inputs.request_version }}
      secretFiles:
        ERP_TOKEN: secret://project/erp-connector
      timeoutSeconds: 300
      outputs:
        proposal: {type: artifact, path: /kodama/outputs/proposal.json}
    - id: approve
      kind: gate
      needs: [validate]
      gate:
        evidence: ["${{ steps.validate.outputs.proposal }}"]
    - id: apply
      kind: container
      needs: [approve]
      image: registry.example.com/acme/business-tools@sha256:4444444444444444444444444444444444444444444444444444444444444444
      workspace: {access: read, from: run}
      command: ["/bin/sh", "/workspace/automation/apply-correction.sh"]
      env:
        REQUEST_ID: ${{ inputs.request_id }}
        REQUEST_VERSION: ${{ inputs.request_version }}
      secretFiles:
        ERP_TOKEN: secret://project/erp-connector
      timeoutSeconds: 300
      outputs:
        receipt: {type: artifact, path: /kodama/outputs/receipt.json}
```

The immutable `request_version` prevents a mutable external record from silently
changing the approved action. If the ERP cannot enforce conditional/idempotent
updates, implement an action ledger and reconciliation in your connector.
Kodama’s run-submission key does not deduplicate an arbitrary ERP side effect.
No automatic command retry is declared for `apply`.

## Example 4: business signals

**Use case:** a CRM, ERP, analytics system or monitoring rule reports a meaningful
change—such as an exception-count threshold—and Kodama evaluates it using
versioned rules. Your signal adapter authenticates the sender, resolves the
tenant/project and submits typed inputs to the public run API.

Save `assess-signal.yaml`. `assess-signal.sh` produces a report and a structured
boolean `action_required` in `/kodama/results.json`:

```yaml
apiVersion: kodama/v1alpha1
kind: Pipeline
metadata:
  name: assess-signal
spec:
  inputs:
    event_id: {type: string, required: true}
    source_system: {type: string, required: true}
    observed_count: {type: integer, required: true}
    threshold: {type: integer, default: 100}
  steps:
    - id: assess
      kind: container
      image: registry.example.com/acme/business-tools@sha256:4444444444444444444444444444444444444444444444444444444444444444
      workspace: {access: read, from: run}
      command: ["/bin/sh", "/workspace/automation/assess-signal.sh"]
      env:
        EVENT_ID: ${{ inputs.event_id }}
        SOURCE_SYSTEM: ${{ inputs.source_system }}
        OBSERVED_COUNT: ${{ inputs.observed_count }}
        THRESHOLD: ${{ inputs.threshold }}
      timeoutSeconds: 120
      outputs:
        report: {type: artifact, path: /kodama/outputs/signal.json}
        action_required: {type: boolean}
```

The adapter submits, for example:

```json
{
  "workItemId": "wi_signal_1843",
  "definitionId": "pd_assess_signal",
  "contentRevision": 1,
  "sourceCommit": "1111111111111111111111111111111111111111",
  "inputs": {
    "event_id": "evt_1843",
    "source_system": "erp",
    "observed_count": 147,
    "threshold": 100
  }
}
```

```sh
curl --fail-with-body -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: erp:evt_1843:assess-v1' \
  --data-binary @signal-run.json \
  "$FACTORY/api/v1/tenants/$TENANT_ID/projects/$PROJECT_ID/runs"
```

Replace sample IDs and save the JSON as `signal-run.json`. The adapter creates
or associates the work item/workspace before submission and persists the event
mapping. A repeated delivery uses the same principal, request and key. For
deduplication beyond Kodama’s retained idempotency window, keep a durable event
ledger in the adapter.

After success, read `action_required`. If true, submit a separate authorized
response pipeline—such as the business-correction flow—with its own stable key
and the identified business request. This keeps assessment and mutation separate.
Run-input conditions do not provide arbitrary branching on a previous step’s
output; the adapter handles that decision explicitly.

Schedule recurring assessments from your existing scheduler or a Kubernetes
CronJob that calls the API. Authenticate business webhooks in your adapter;
the native Git hook is for Git notifications, not a generic unauthenticated
business-event endpoint. Outbound ChatOps destinations can notify teams of the
result and provide an authenticated link to its review.

## Register, run and observe any example

Select an existing work item and available workspace. Capture its current
`contentRevision` and accepted commit, then register the chosen definition:

```sh
kodama pipeline register --file business-correction.yaml --output json
kodama run start --work "$WORK_ID" --definition "$DEFINITION_ID" \
  --content-revision "$CONTENT_REVISION" --source-commit "$SOURCE_COMMIT" \
  --input request_id=correction-42 --input request_version=7 \
  --idempotency-key correction-42-v7 --output json
kodama run watch "$RUN_ID" --exit-on-terminal --timeout 30m
kodama run steps "$RUN_ID" --output json
kodama run logs "$RUN_ID" --step validate --tail 100
```

Use returned IDs in subsequent commands. `--input` supplies a string;
`--input-json observed_count=147` supplies an integer and
`--input-json enabled=true` a boolean. Offline `pipeline validate` checks
self-contained definitions; references to secrets, environments and profiles
are authoritatively validated during server registration.

`run watch` resumes through SSE cursors and returns one final result. A deadline
ends observation, not the run. `run cancel` requests cancellation; continue
observing until the run settles. Logs identify an attempt and their completeness
is separate from execution success. Specify `--attempt` if a step has several.

## Review an identified subject

Read the gate and its evidence using a reviewer identity. Use exactly the
version and subject digest you inspected:

```sh
kodama gate get "$GATE_ID" --output json
kodama gate decide "$GATE_ID" --decision approve \
  --subject-digest "$SUBJECT_DIGEST" --if-version "$GATE_VERSION" \
  --idempotency-key correction-42-review-v7 \
  --reason "Verified the proposed correction and its evidence."
```

A gate holds no compute while pending. Its decision is immutable; requirements,
source or deployment changes can make its subject stale. Reread and review a
new subject deliberately rather than refreshing versions automatically. An
administrator or engineer is not automatically a reviewer.

## Reliable API integration

The [OpenAPI document](https://github.com/optimiware/kodama/blob/main/api/openapi.yaml)
defines public requests and responses. Project routes start at
`/api/v1/tenants/{tenantId}/projects/{projectId}`. Use bearer access tokens and
JSON. The generated Go client uses a base URL **including `/api/v1`**, while
the CLI’s `--factory` takes only the origin.

| Situation | Required behavior |
| --- | --- |
| Versioned edit or transition | Send the observed `If-Match: "N"`; CLI equivalent: `--if-version N` |
| Keyed submission loses its response | Repeat the identical request/key, after current authorization is checked |
| `409 idempotency_conflict` | Inspect the earlier operation; a changed request cannot reuse its key |
| `412 version_conflict` / `428 version_required` | Read current state and make a deliberate versioned change |
| List pagination | Preserve `nextCursor`; default 50 items, maximum 200 |
| SSE reconnect | Send `Last-Event-ID`; on `410 cursor_expired`, obtain a snapshot and resume from its cursor |
| `429` | Honor `Retry-After` within bounded retry limits |
| Lost non-keyed mutation | Read the resource/history before deciding to resubmit |

Errors use `{error: {code, message, requestId, details}}`. Retain request IDs
with your integration’s event/action IDs. A run passing, a gate approval and
business completion are separate outcomes.

Next: **[operating and troubleshooting the installation](/en/docs/kodama/operations)**.
