Register the agent
The full mechanics of agent registration — the SDK helpers and raw /api/agents endpoints, the draft→active lifecycle, the per-mode config shapes on the wire, the merge-vs-replace rules for updates, and the minimal manifest for the secondary dystopic agents push path.
Registering an agent creates (or updates) an agent row on the platform: a name, a mode, and a mode-specific config describing how the platform runs it. The primary surface is the SDK and the raw REST API — you build the payload in code and POST /api/agents. The web dashboard wraps the same endpoints for click-through registration. A secondary dystopic agents push path builds the identical payload from a small local manifest.
The walkthrough in /docs/flow/register-agent is the happy path. This page is the lookup surface: exactly what each call sends, how the mode is translated, and the sharp edges — code-mode wholesale replace, the update merge rules — that bite when you go off the golden path.
A "code agent" (a repo-backed Python agent) ships as mode: "sandbox" on the wire.
Register with the SDK
create_code_agent(...) builds the code-agent payload from a source directory (or an in-memory {path: text} mapping), POSTs it to /api/agents, and returns the created row. It lives in dystopic.odyssey.registration, which ships in the optional [odyssey] extra:
pip install 'dystopic[odyssey]'from dystopic import DystopicClient
from dystopic.odyssey.registration import create_code_agent
agent = create_code_agent(
name="research-agent",
entrypoint="run", # top-level callable in entrypoint_file
entrypoint_file="main.py", # must be a key in the walked source tree
source_dir="./agent_src", # walked into {path: text}; .py only
requirements=["httpx>=0.27", "trafilatura"],
python_version="3.12",
tools_schema=[...], # OpenAI-style tool declarations
ledger_schema=None, # None → fully-open ledger (default)
concurrency_cap=5, # per-org concurrency (1–100)
run_timeout_s=300, # per-run wall clock (1–1800s)
api_key="pk_live_…", # org key from Settings → API Keys
)
agent_id = agent["id"]Registration lands the agent as a draft. Move it to active with publish_agent:
client = DystopicClient(api_key="pk_live_…") # base_url defaults to https://api.pipelines.tech
client.publish_agent(agent_id)update_code_agent(*, agent_id, api_key, ...) mirrors create_code_agent but PUTs to /api/agents/{agent_id}. Its arguments are all optional so you can update a single field — but code-config changes carry the wholesale-replace rule below. build_code_agent_payload(...) is the pure payload builder underneath both, for callers that want the dict without the HTTP round-trip.
create_code_agent / update_code_agent register the agent only. They do not set the gate or the CI suite binding — those are separate endpoints.
Register with the raw REST API
The typed client exposes the three endpoints directly (dystopic.DystopicClient):
| Method | Endpoint | Purpose |
|---|---|---|
client.create_agent(payload) | POST /api/agents | Create the agent row (lands as draft). |
client.update_agent(id, payload) | PUT /api/agents/{id} | Update an existing row. |
client.publish_agent(id) | POST /api/agents/{id}/publish | Move draft → active. |
The shared top-level payload:
{
"name": "research-agent",
"mode": "sandbox",
"description": "…",
"tools_schema": [ /* OpenAI-style tool declarations */ ],
"ledger_schema": { /* optional typed entity ontology */ },
"concurrency_cap": 5,
"run_timeout_s": 300,
"config": { /* mode-specific, below */ }
}name and mode are required; the platform assigns id and status: "draft" on create. tools_schema, ledger_schema, concurrency_cap, and run_timeout_s are top-level. config carries the mode-specific execution recipe.
Code config — a Python entrypoint
The form create_code_agent produces. config holds the full source tree plus the entrypoint coordinates:
{
"source_files": { "main.py": "def run(…): …", "search.py": "…" },
"entrypoint_file": "main.py",
"entrypoint": "run",
"requirements": ["httpx>=0.27", "trafilatura"],
"python_version": "3.12"
}entrypointmust be a single Python identifier — dotted paths are rejected.entrypoint_filemust be a key insource_files. The SDK defaults it tomain.py.requirementsis an inline list of pip specifiers;python_versionpins3.9–3.13.- Source caps (enforced client-side before the round-trip, mirroring the platform validator): max 50
.pyfiles, 200,000 bytes per file, 1,000,000 bytes total..pyonly; non-UTF-8 files and case-conflicting paths (Main.pyvsmain.py) are rejected; only__init__.pymay be empty. Directory walks respect.dystopicignore(one glob per line,#comments) at the source root, appended to the built-in defaults (__pycache__/,.venv/,.git/, …). See /docs/reference/port-agent/code-mode and /docs/reference/port-agent/gotchas.
Command-runner config — a sandbox command
A repo-backed agent can instead ship as a plain command runner (also mode: "sandbox" on the wire). config requires a non-empty run_command; source and environment fields are optional:
{
"execution_topology": "in_sandbox",
"run_command": "python -m myapp",
"source_files": { "relative/path.py": "…" },
"system_packages": ["curl"],
"setup_command": "apt-get update",
"env": { "LOG_LEVEL": "info" }
}The entrypoint keys (entrypoint, entrypoint_file, requirements, python_version) belong to code config and are forbidden here — install dependencies via setup_command or a dockerfile instead.
The draft → active lifecycle
create_agent / create_code_agent publish_agent
POST /api/agents → POST /api/agents/{id}/publish
status: "draft" status: "active"The status is:
drafton create — you cannot create an agent straight toactive.- Untouched on update —
PUT /api/agents/{id}never changesstatus. Publishing only moves forward; there is no draft-revert through this surface.
publish_agent never creates: an agent must exist (and thus have an id) before it can be published.
Mode normalization
The wire mode is sandbox. One friendlier spelling normalizes before it reaches the platform: code → sandbox — code is a client-side alias for a Python-entrypoint agent; the backend runs it in a sandbox, so the wire mode is "sandbox".
Update: merge vs. replace
Whether an update merges or replaces depends on the field. This is the single most important distinction to get right when re-pushing an agent.
Non-config fields — always partial-safe
description, tools_schema, ledger_schema, concurrency_cap, and run_timeout_s are partial-safe on PUT: fields you omit are left untouched by the platform's model_fields_set diff.
ledger_schema (via update_code_agent) is three-state:
- omitted → left unchanged.
- a dict → sets/replaces the ontology.
- explicit
None→ clears it (reverts to the fully-open ledger).
A tool left on ledger_write_policy='adapter' while you clear ledger_schema (or vice versa) is rejected 422 — send the matching tools_schema in the same call when you flip either side.
Code config — replaced wholesale
The platform replaces a code agent's entire config blob whenever the request includes config; it does not merge a partial source tree. Any code-config change — source / entrypoint / entrypoint_file / requirements / python_version / topology — must re-ship the full file tree in the same call. update_code_agent enforces this client-side: passing any code-config field without source_dir / files raises a ValueError rather than silently dropping files.
This is the code-mode footgun: a partial PUT that drops files will drop them from the agent. Always send the complete, current source tree when updating a code agent.
Gate and CI suite are set separately
Registration attaches an agent's execution recipe — nothing else. The gate (PUT /api/agents/{id}/gate) and the CI suite binding (PUT /api/agents/{id}/ci-suite) are their own platform-authoritative endpoints, RBAC'd and audited, and are not touched by create_code_agent / update_code_agent or by the manifest below. Author suites, scenarios, and the gate on the dashboard or via the REST endpoints documented in /docs/reference/create-suite.
Secondary: dystopic agents push
For a repo-local workflow, dystopic agents push builds the same /api/agents payload from a minimal registration manifest and creates or updates the agent row. The manifest declares only the agent's identity and execution config — name, mode, and entrypoint/source. It never carries suites, scenarios, seeds, or the gate; those live on the platform.
agents:
research-agent:
mode: code
entrypoint: run
entrypoint_file: main.py
source:
dir: ./agent_srcdystopic agents push research-agent # create or update from the manifest
dystopic agents push research-agent --publish # …and POST /publish in one shotOn create, the numeric id returned by POST /api/agents is stamped back into the manifest by a naive, line-based text edit (not a YAML re-dump), so your comments, ordering, and formatting survive:
agents:
research-agent:
id: 4217 # <- stamped by dystopic agents push
mode: code
entrypoint: run
entrypoint_file: main.py
source:
dir: ./agent_srcWrite-back happens only on create. On update the manifest is read but never rewritten: once an id is present, push does a PUT /api/agents/{id}. Commit the stamped id and let push manage it — a hand-edited id that no longer matches the server row is PUT against the wrong agent without a drift check.
The command runner (mode: sandbox, run_command) shape is also accepted in the manifest, and additional code-config keys (requirements, python_version) may appear alongside entrypoint/source. See /docs/reference/port-agent/code-mode for the full field list. Building the payload for a code agent or any dir/file source upload needs the [odyssey] extra:
pip install 'dystopic[odyssey]'Error reference
| Where | Error | Cause |
|---|---|---|
| Payload | unsupported mode <mode> | mode is not sandbox / code. |
| Payload | sandbox mode requires a non-empty 'run_command' | Command-runner config with no run_command. |
| Payload | code mode requires a non-empty 'entrypoint' | Code config with no entrypoint. |
| Payload | 'entrypoint' must be a single Python identifier … | Dotted entrypoint path. |
| Payload | 'entrypoint_file' … is not present in the resolved source files | entrypoint_file not a key in the walked tree. |
| Payload | received code-config field(s) … without 'source_dir' or 'files' | Partial code update without re-shipping the tree. |
| Payload | needs the optional 'odyssey' extra … | Code/dir/file source without dystopic[odyssey]. |
| HTTP | AgentAPIError / DystopicAPIError (409 / 422 / 401 / 403) | Name conflict, validation failure, bad key, or missing org permission. |
Next
With the agent registered and active, define the environment it runs against: /docs/flow/declare-world (walkthrough) or the declare-world reference. Then author its suite and gate in /docs/reference/create-suite.
Register: the walkthrough
The happy-path register → publish flow, step by step.
Port: code mode
The run() entrypoint, source caps, and sandbox footguns.
Create the suite & gate
Author scenarios, bind a suite, and set the platform gate.
Execution modes
Simulated, executed, and live tool responses on the wire.
Runtime gotchas
The runtime footguns that bite a ported agent under load — subprocess env wiring, proxy retries and terminal errors, sub-agent actor-id attribution, empty and malformed responses, and unschema'd tools.
Declare the world: topology & ledger
The lookup-grade reference for the two optional world declarations — the multi-agent topology (sub_agents + topology, extraction vs. hand-authoring, actor-id grammar, declared-vs-undeclared runtime behavior) and the ledger schema ontology (canonical singular snake_case, adapter consistency, three-state update).