docs
// Tools

erectl evolution

Inspect and control a workspace's two self-evolution lanes from the terminal: read their state, switch each one on or off independently, queue a pass now, and roll a bad version back.

Overview

Self-evolving guidance lets a workspace refine its own guidance layer over time. Two lanes do it: one refines the workspace chat instruction, the other refines the MCP tool guidance. Each lane drafts a new version, applies it if it passes the lane's gates, and keeps every prior version so a bad one can be undone. That page explains what the feature does and what it guarantees; this one is the command reference.

The evolution command group is the terminal-side twin of the dashboard's Evolution rail: lane state, the per-workspace switch, the active text, the version history, rollback, the circuit breaker, and one control the dashboard does not offer -- run a lane now instead of waiting for its next scheduled pass.

Every subcommand hits the router's management plane at /{project_id}/v1/management/evolution and requires an API key carrying the management scope. A key without it gets 403 with code: "scope_insufficient". Each subcommand resolves the active erectl context and authenticates with that project's API key; use --context <name> to target a different deployment, and --dry-run on any writing verb to print what would be sent without sending it.

Reads require any membership of the project. Writes -- enable, disable, run, breaker-reset, rollback -- require the project owner or an operator, and return 403 with code: "access_denied" otherwise. A workspace you may not read is reported as 404 with code: "workspace_not_found", the same answer a workspace that does not exist gets: the surface does not confirm a workspace to someone who may not see it.

The key must be bound to a user: both gates above evaluate a caller identity, so a key that carries none reads nothing and writes nothing here. It gets 403 with code: "user_identity_required" rather than being admitted by default. Issue a key from a user account if you intend to script this group.

Two Independent Lanes

The --lane option takes chat_instruction or mcp_guidance. It is required on every per-lane subcommand and it has no default.

Lane What it refines
chat_instruction The workspace chat instruction
mcp_guidance The workspace's MCP tool guidance

The two lanes are independent everywhere (what each lane refines). Two enable flags, two circuit breakers, two cooldowns, two version histories, two deployment switches. Turning chat_instruction off says nothing about mcp_guidance, and nothing in this group reads one lane's state to decide the other's. Read both with erectl evolution status <wid> rather than inferring one from the other.

Each lane also has two switches stacked in front of it, and either one can stop it: the per-workspace flag you set with enable / disable, and a deployment-wide switch held by whoever operates the router. A workspace flag reading enabled while the deployment switch for that lane is off is the confusing case, so status prints both side by side and enable tells you when it has just turned on a lane the deployment has switched off.

Usage Pattern

bash
erectl evolution status <wid> # Both lanes' state erectl evolution enable <wid> --lane <lane> # Turn one lane on erectl evolution disable <wid> --lane <lane> # Turn one lane off erectl evolution run <wid> --lane <lane> [--wait] # Queue one pass erectl evolution run-status <wid> --lane <lane> \ # Poll a queued pass --run-id <id> erectl evolution breaker-reset <wid> --lane <lane> # Close the breaker erectl evolution show <wid> --lane <lane> # Active text erectl evolution history <wid> --lane <lane> # Version history erectl evolution rollback <wid> --lane <lane> \ # Restore a version --version <n>

status is the group's default subcommand, so erectl evolution <wid> and erectl evolution status <wid> are the same command. Every other verb needs its name and its --lane.

-o json switches any verb to a machine-readable dump of the whole response. enable, disable, breaker-reset, and rollback print a human-readable success line before their JSON, so add -q if you intend to parse them; run and run-status emit the run and nothing else under -o json.

status

Prints one row per lane. This is the only verb that does not take --lane: it always reports both, because the question it answers is usually "which of these two is actually running".

bash
erectl evolution status ws_1a2b3c4d erectl evolution status ws_1a2b3c4d -o json

Columns

Column Meaning
Lane chat_instruction or mcp_guidance
Enabled The per-workspace flag: yes or no
Platform The deployment-wide switch for that lane: on or OFF
Breaker closed (the lane may write) or open (it may not)
Active Ver The active evolved version number, or - when none is active
Last Auto Apply When the lane last applied a version on its own, or never
Last Manual Run When a run was last accepted for this lane, or never

A lane changes nothing unless Enabled is yes, Platform is on, and Breaker is closed. The first two stop the lane from running at all; an open breaker lets it run and stops it writing. OFF is printed in capitals because it is the state that most often explains a workspace that looks configured and is doing nothing.

enable / disable

Sets the per-workspace flag for one lane. The other lane is untouched; confirm with erectl evolution status <wid> rather than assuming.

bash
erectl evolution enable ws_1a2b3c4d --lane chat_instruction erectl evolution disable ws_1a2b3c4d --lane mcp_guidance

When you enable a lane whose deployment switch is off, the command reports the write and then says so: the flag is now on and the lane still will not run. Saying only the first half would be technically true and practically misleading.

run

run is asynchronous. It enqueues one pass for this workspace and lane, prints a run handle, and returns; the router executes the pass and settles the handle. It does not hold the connection open while the pass runs, and it does not report the pass's outcome -- poll the handle with run-status, or pass --wait.

A queued pass runs through the same gates a scheduled pass runs: the per-workspace flag, the circuit breaker, and the lane's rolling edit budget all still apply when it executes. run changes when a lane runs, never what it is allowed to do.

bash
erectl evolution run ws_1a2b3c4d --lane chat_instruction erectl evolution run ws_1a2b3c4d --lane chat_instruction --wait erectl evolution run ws_1a2b3c4d --lane mcp_guidance --wait --wait-timeout 300

Options

Option Description
--lane <lane> chat_instruction or mcp_guidance (required, no default)
--wait Poll the handle until the run succeeds or fails, then exit accordingly
--wait-timeout <seconds> Seconds to poll with --wait before giving up. Default 900; must be positive.

Responses

Status Meaning
202 A new pass was enqueued. The body carries the run handle, deduplicated: false, and cooldown_seconds.
200 A pass for this workspace and lane was already queued or running. The body carries that run and deduplicated: true.
429 The cooldown holds. See The Cooldown.
503 The lane cannot run at all. See When a Run Is Refused.

deduplicated: true is not an error and not a second pass. Asking again while a pass is in flight hands back the run already in flight rather than starting another, so a client that polls by retrying run converges on one handle instead of a queue of duplicates. The CLI says which of the two happened before it prints the handle.

With --wait, the command polls the handle until the run reaches succeeded or failed, then exits 0 or non-zero to match. If --wait-timeout elapses first, the run is still going -- the command prints the exact run-status invocation to resume with and exits non-zero. A timeout is a statement about the wait, not about the run.

run-status

Reads the handle run printed. --run-id takes that handle; a value that is not a UUID is rejected before any request is issued. This is a read, so any project member can call it.

bash
erectl evolution run-status ws_1a2b3c4d \ --lane chat_instruction \ --run-id 11111111-2222-3333-4444-555555555555 erectl evolution run-status ws_1a2b3c4d \ --lane chat_instruction \ --run-id 11111111-2222-3333-4444-555555555555 --wait

Run states

Status Meaning
queued Accepted and waiting; no router has claimed it yet
running A router claimed it and the pass is executing
succeeded The pass completed. processed reports the units of work it did.
failed The pass did not complete. failure_reason always says why.

processed: 0 on a succeeded run is a real pass that found nothing eligible, not a failure; the CLI says so in those words rather than printing a bare zero. --wait and --wait-timeout behave exactly as they do on run.

The command exits non-zero for a failed run whether or not --wait was passed, so a script can branch on the exit code without parsing output. A run that never settles is failed after an hour, so a handle cannot sit in running forever.

A handle that does not belong to this workspace and lane returns 404 with code: "run_not_found".

When a Run Is Refused

A queue makes "accepted" the easy answer to everything, which is how a surface stops being useful for debugging. run checks the things that can prove a pass will never execute before it enqueues anything, and refuses with a specific code instead of returning a handle that would sit queued forever.

Three 503 codes, deliberately kept apart. "Nothing happened" has three different remedies and one generic error would hide which one you need.

Code What it means Cooldown What to do
evolution_lane_disabled_by_platform The deployment-wide switch for this lane is off, so no scheduled pass runs either. The response names the setting an operator has to change. Not consumed Ask whoever runs the deployment to turn the lane on. Nothing you can set on the workspace changes this.
evolution_runner_unavailable This router carries no evolution wiring at all, so there is nothing to run a pass. Every other verb in this group still works. Not consumed You are pointed at a deployment that does not run evolution. Check --context, or use one that does.
evolution_lane_not_running The deployment switch is on, but the lane's controller is not running in this router. Consumed The router is misconfigured or came up incomplete. This is an operator problem; retrying at speed does not help, which is why this arm alone spends the allowance.

The first two are reported before the cooldown is claimed, so discovering a deployment setting never costs you your allowance. The third consumes it on purpose: the process is broken, and hammering it at HTTP speed helps nobody.

None of the three is reported as queued. A handle you cannot poll to a real outcome would be worse than an error.

The Cooldown

One accepted run per lane per workspace every 900 seconds (15 minutes). The two lanes hold separate allowances, and so does every workspace; the limit is on the pair, not on you and not on the project.

The window equals the lane's own scheduled interval. A run does exactly the work one scheduled pass does for one workspace, so the most an operator can do is double that workspace's duty cycle -- enough for the "I just fixed the prompt, run it again" case, not enough to turn the control into a way of spending the project's inference budget in a loop.

The allowance is claimed at enqueue, not when the pass executes. Claiming it at execution would let a caller queue many runs inside one window and the limit would mean nothing.

A refused run returns 429 with status: "cooldown", retry_after_seconds, and a Retry-After header; the CLI exits non-zero. An accepted run reports the same window as cooldown_seconds so a script knows when it may ask again.

What spends the allowance

Outcome Consumes the cooldown
202, a new run enqueued Yes
200, deduplicated: true No
503 evolution_lane_disabled_by_platform No
503 evolution_runner_unavailable No
503 evolution_lane_not_running Yes

The deduplicated case is exempt for a specific reason: a client polling by retrying run must not spend its allowance being told its own pass is still going.

breaker-reset

Closes one lane's circuit breaker so the lane may write again. This is the terminal form of Resume After a Pause: the safety system opens the breaker itself when results regress after a change. It runs nothing -- use run for that, and expect to run both commands when you are recovering a tripped lane.

bash
erectl evolution breaker-reset ws_1a2b3c4d --lane chat_instruction

show

Prints the lane's active version number and the active text itself. A lane with no active version says so instead of printing an empty body.

bash
erectl evolution show ws_1a2b3c4d --lane chat_instruction erectl evolution show ws_1a2b3c4d --lane mcp_guidance -o json

history

Lists the lane's versions, newest first: the version number, whether it is active, its origin, who created it, when, and the recorded change summary. A version with no recorded timestamp shows - rather than a fabricated time.

bash
erectl evolution history ws_1a2b3c4d --lane chat_instruction erectl evolution history ws_1a2b3c4d --lane chat_instruction -o json

Origins

Origin Meaning
auto The lane drafted and applied it
owner A person wrote it
reverted A rollback wrote it, restoring an older version's text

rollback

Restores a historical version's text. --version takes the version number from history and must be positive.

bash
erectl evolution rollback ws_1a2b3c4d --lane chat_instruction --version 4

History is never deleted. The target version's text is written forward as a new version, so the version number you end up with is higher than the one you asked for -- restoring v4 of a lane at v9 produces v10, with origin reverted. The command prints both numbers. Rolling back a rollback is therefore just another rollback.

A version number that does not exist returns 404 with code: "version_not_found". A stored version that no longer passes content policy returns 422 with code: "content_rejected"; pick a different version.

Not Intelligence Reconciliation

Intelligence reconciliation is a different subsystem. It has nothing to do with the evolution lanes: different commands, different switch, different cooldown, no shared state. It is documented here only because the two are easy to confuse when both are described as "the workspace improving itself in the background".

Evolution refines a workspace's prompt text. Reconciliation is a pass over what a workspace has already recorded -- its memories, decisions, milestones, and relations. Nothing in the evolution group affects it, and nothing below affects the lanes. Both commands live in the workspaces group.

Turning the pass on or off

bash
erectl workspaces update ws_1a2b3c4d --intelligence-reconciliation true erectl workspaces update ws_1a2b3c4d --intelligence-reconciliation false

--intelligence-reconciliation governs whether the periodic reconciliation pass runs for this workspace. It is distinct from project intelligence extraction, which is what produces the records in the first place; this flag governs the pass over what extraction produced.

Running the pass now

bash
erectl workspaces revalidate-intelligence ws_1a2b3c4d

Queues one reconciliation pass and returns as soon as the job is accepted, printing the queued job's id. Like evolution run it is asynchronous, and unlike it there is no status verb to poll -- the command's job is to get the pass scheduled.

Rate limited to one accepted trigger per workspace per hour, and the allowance is shared with the dashboard's equivalent control, so alternating the two surfaces does not double the rate. A refused trigger returns 429 with retry_after_seconds and a Retry-After header. That hour is a different window from the evolution cooldown's 15 minutes, on a different subject.

A workspace whose reconciliation flag is off is skipped when the pass runs rather than being refused at the trigger, so a queued job that appears to do nothing is usually a workspace that opted out. Turn it back on with --intelligence-reconciliation true.

Examples

Find Out Why a Lane Is Doing Nothing

bash
# Both lanes, both switches, the breaker, and the last activity erectl evolution status ws_1a2b3c4d # Platform OFF -> a deployment setting; nothing here will fix it # Enabled no -> erectl evolution enable ws_1a2b3c4d --lane chat_instruction # Breaker open -> erectl evolution breaker-reset ws_1a2b3c4d --lane chat_instruction

Run a Lane and Wait for the Outcome

bash
erectl evolution run ws_1a2b3c4d --lane chat_instruction --wait # Or enqueue now and poll later RUN_ID=$(erectl evolution run ws_1a2b3c4d --lane chat_instruction -o json -q | jq -r .run.id) erectl evolution run-status ws_1a2b3c4d --lane chat_instruction --run-id "$RUN_ID"

Undo a Bad Version

bash
erectl evolution history ws_1a2b3c4d --lane mcp_guidance erectl evolution show ws_1a2b3c4d --lane mcp_guidance # Restore v4. The result is a NEW version with v4's text. erectl evolution rollback ws_1a2b3c4d --lane mcp_guidance --version 4

Turn One Lane Off Without Touching the Other

bash
erectl evolution disable ws_1a2b3c4d --lane mcp_guidance erectl evolution status ws_1a2b3c4d # chat_instruction is still enabled