Skip to main content
The Komodor Agentic Operation Platform (KAOP) lets you author your own agent and keep the code yours. You write three things — a spec that declares the agent’s identity, a handler that does the work, and whatever tool access that handler needs — and KAOP supplies everything around them: registration, work delivery, evidence, secrets, and cost accounting. This page walks the whole path, from installing the SDK to the surfaces you can create an agent from.

What you actually write

Everything else — heartbeating, claiming runs, streaming evidence, resolving credentials — is the SDK’s job.

Install the SDK

Agents are built with the komodor-agentops Python SDK, which requires Python 3.11 or later. The runtime is framework-agnostic: your handler can be a plain function or a full agent loop, and adapters for the common frameworks ship as extras.
A Go SDK is also available if you would rather write the worker in Go. See Go SDK.

Lay the project out

Keep the agent’s identity and knowledge as files next to the code. The SDK reads the whole directory in one call, so there is nothing to register by hand.

The agent spec

agent-spec.yaml is the source of truth for what the agent is. It ships inside the image, so the control plane learns the agent’s shape from the worker itself rather than from a form somebody filled in once.
The instructions themselves do not go in the spec. Put them in agent.md next to it — AgentSpec.from_dir picks that file up automatically, and the console shows the same content on the agent’s Agent instructions step.

Write the handler

A worker is a spec, an async handler, and a run call.
.run() sets up logging, pulls the credentials bound to the agent, opens the connection to the control plane, and starts heartbeating. If you need to embed the worker in an existing service, await AgentOpsWorker(agent=AGENT_SPEC, on_run=handler).serve() gives you the same runtime without taking over the process.
The worker binds no port and serves nothing. It dials out and receives its work over that one connection, so there is no inbound firewall rule to open and nothing to route traffic to.
Raising from the handler fails the run and reports the exception as the error. There is deliberately no way to return a failed result. A cancellation from the control plane arrives as a cancelled await inside your handler — let it propagate rather than swallowing it.

What else arrives on the run

Beyond run.input, the handler receives context it would otherwise have to fetch: Three things are reported separately rather than returned: run.set_snapshot({...}) leaves state for the next run, run.set_diagnostics({...}) records run mechanics such as turn counts and stop reasons, and record_usage({...}) reports token usage so the run’s cost is attributed correctly.

Two output channels

A result can carry both structured fields and descriptive text, and both live in the single dict your handler returns:
  • Structured output — your own top-level keys. This is what a script, an MCP client, or a later workflow step reads to make a decision.
  • Descriptive output — the reserved text key, holding Markdown for a person. The console, Slack, and history render it when someone opens the run.
An automation-only agent can return structured fields alone. An agent whose result a human reads should provide both, and they should agree — do not bury machine values inside prose, and do not invent a second human-readable field.

Give it tools

Tools reach your agent from three directions, and they compose:
Whatever your framework already offers — Claude Agent SDK tools, LangChain tools, ADK tools. These live inside your handler and the adapters record their calls as evidence automatically.
Remember the boundary: there are exactly two ways for an agent to reach an external system — the built-in integration catalog, or the MCP Gateway. See Integrations overview.

Emit evidence

Your agent can stream the same evidence the Komodor-built agents do, so a run you authored is just as auditable as one you installed. If you use a framework adapter, most of this is automatic — model calls, tool calls, and token usage are recorded without extra code. See Runs & evidence.

Use secrets, do not bake them in

Store API keys as credentials and bind them to the agent. The SDK then delivers them:
  • Per run — bound credentials arrive with the run. Call apply_secret_to_env("ANTHROPIC_API_KEY") or get_secret(...) inside the handler.
  • At boot — for tokens the worker needs before any run, nothing is required: it pulls the on-demand credentials bound to it at start-up.
Values are delivered in memory and never written into the run’s evidence.

Test it locally

The SDK installs an agentops-run-worker command that calls your handler once, with no control plane involved.
It loads your env files, exposes detected secrets the way a real run does, builds a Run from the flags, writes the returned output to --output, and drops anything the handler uploaded into --artifact-dir. The same flow is available in code as run_worker_once and run_worker_once_sync, which makes it straightforward to assert on your handler in tests.

Make it available in chat

Chat is opt-in per agent, advertised on its agent card:
See Chat & history.

The create surfaces

Writing the code is one half; the agent also has to exist in the control plane. Two surfaces do that, and they produce the same agent.
Create an agent opens on a starting-point picker with three choices — Start from catalog, Build from scratch, and Import an existing agent. Choose Build from scratch and the wizard walks you through Agent card, Where it runs, Agent instructions, Model, MCP tools, and Triggers, then an Activate step that registers the agent, mints a worker token — shown once — and hands you what your worker needs to start. Your progress is saved as a draft as you go, so you can leave and come back.
Either way the agent starts life as a draft and goes live on its worker’s first heartbeat. There is no separate activation step.

Importing an agent you already run

If the agent already exists on your own infrastructure, the wizard’s Import an existing agent path connects it rather than deploying anything. Pick the framework it is built with — Google ADK, LangChain, Claude Agent SDK, Agno, or something else — and the wizard generates the wrapper snippet, three environment variables, and a one-time worker token. An imported agent keeps its instructions, model, tools, and skills in your code, where the console does not change them. What the import collects is the parts KAOP owns: its identity and labels, its triggers, and its roles and bound secrets.
Re-registering an agent name that already exists reuses the same agent rather than creating a second one — it rotates the token and redeploys. A workspace can hold up to 60 active agents; archiving one frees a slot.

Bring the framework you already use

The runtime does not care how your handler reaches its conclusion, and the adapters make that concrete: whichever framework you install, the same worker runtime, evidence trail, and output contract sit underneath.
The clearest reference for what a well-formed agent looks like is a Komodor-built one. Deploy a catalog investigator, run it, and read the run’s evidence trail — that is the shape your own agent’s runs should have. See Use specialized agents.

Patterns worth copying

  • Return both channels. Structured fields for automation, text for the person reading it.
  • Keep identity in the spec. agent-spec.yaml next to the code means the agent’s shape is versioned with the agent.
  • Keep procedures in files. A documented process belongs in skills/, not in the handler.
  • Let configuration be configuration. What starts the agent and what it may reach are set in the console, not compiled in.

Next steps

Deploy an agent

Turn the worker into a running process and watch it register.

Skills

Package the procedures your agent should follow.

Credentials & secrets

Store and bind the secrets the handler resolves.

Python SDK

The full SDK surface.