Skip to content

Releases

conductor 1.9.0

Choose a tag to compare

@b10x-bot b10x-bot released this 14 Nov 01:18

Since 1.8.1:

  • story:instance-rules-text: an instance mapping takes standing_rules, a text that every session of the instance reads, whatever its role. POST and PUT /v1/instances take it, at most 16384 bytes of UTF-8 without a NUL; GET /v1/instances/{name} answers it. A text that is, as a whole, a secret:<name> reference is refused.
  • The same story: each session's prompt file starts with the text, under one heading line, inside a fenced block that no line of the text closes; the role's profile follows it unchanged. A mapping without the member renders the same pod and the same prompts as before.
  • The same story: in a pod the text is in the rendered config file of the instance's Secret and no part of the pod's digest, so a change of the text alone replaces no pod. A session started after the change reads the new text; a running session keeps the text it started with.
  • The same story: the instance calls alone write the text. The instance's own settings write, PUT /v1/config and conductor config put refuse the key.

Upgrade: the daemon's Role for instance pods needs one more verb on Secrets of the instance pods' namespace, update; the guide's Role names it. With an older Role, conductor doctor and conductor setup fail on that row, whether or not an instance names the member; a text set when the pod starts is still delivered, and a later change or removal is refused by the API, which the instance's detail says, naming the right. create, delete and update on Secrets can write or replace any Secret of that namespace, so nothing else belongs in it.

Not shown by a test: a kubelet updating the mounted file of a running pod, and anything on a cluster.

Container image

ghcr.io/beyond10x/conductor:1.9.0 at digest sha256:db89298a29c748f4eaf79b362137cd0bfc09e318ba5174a464a8fd0b611ab9b7

docker run --rm ghcr.io/beyond10x/conductor:1.9.0 conductor --version

conductor 1.8.1

Choose a tag to compare

@b10x-bot b10x-bot released this 13 Nov 23:21

Since 1.8.0:

  • story:anonymous-pod-no-forge-secret: the pod of an instance with forge_access: anonymous carries no forge credential under any entry name. It leaves out every daemon.kubernetes.env entry that names the secret of a forge token of the file (a daemon's, or any instance's forge_token) or holds a forge token's value, and the variables that point git, ssh, git's HTTP library, gh or glab at a credential or a configuration file (GIT_CONFIG_GLOBAL, GIT_CONFIG_SYSTEM, GIT_SSH_COMMAND, SSH_AUTH_SOCK, NETRC and the like); it keeps the installer's proxy and CA settings of git. This fixes the known issue of 1.8.0.
  • The same story: the anonymous clone and fetch read no system or global git configuration, no netrc file and no template, and ask no program for a login. Every command a process starts for such an instance gets plain ssh as GIT_SSH_COMMAND. A fetch is not run while the checkout's own configuration holds an extra header, a cookie file, a TLS client certificate or key or a URL rewrite that cannot be removed, or while the URL git fetches origin from (remote.origin.url, or the legacy file .git/remotes/origin or .git/branches/origin) holds a user or a password; the pass names the key or the place, never the value. Every fetch of such an instance takes this one route, the blockers' and the catalog's read included; it fetches no submodule, and every git the runtime starts, and the pod's environment, carry GIT_NO_LAZY_FETCH=1, so a read of a partial clone fetches no object by itself.
  • The same story: an anonymous instance that also has a forge_token of its own gets no token in its pod, and the daemon does not start it.
  • The same story: the guard denies more spellings of a push, a name given to git in argv[0] included (env -a git-push git …, exec -a git-push git …); the watch and the start's report make no gh or glab call for such an instance and say so in one line; one include entry the listing refuses is one member failure of the pass.
  • For every instance: the guard reads a launcher's options that take a value, a -- after a launcher, an option cluster, the options of exec and a name given to the program in argv[0], so each rule finds the program word behind them. sudo -u root cargo build, timeout -s KILL 5 cargo test, env -u X cargo build, env -a cargo <program> build and exec -a cargo <program> build are now decided as cargo build and cargo test alone are; 1.8.0 allowed them.
  • For every instance: the blockers collector's failure line passes over git's warning: lines when another line follows.

Upgrade: none for a file without the member, but for the guard decisions named above. Beside forge_access: anonymous, a mapping that names secret_syncs, or another instance's forge_token secret under any key, is refused at validation from this release on; a checkout of such an instance whose origin's URL holds a login (https://user@host/..., ssh://git@host/...) is not fetched until the URL is plain.

Not covered, with a draft story each: an extra header in a checkout's worktree configuration file; taskset with a mask, env -S, a shortened long option of env and zsh's ARGV0= before a push.

Container image

ghcr.io/beyond10x/conductor:1.8.1 at digest sha256:b87e4839e5430db0106f6cf65af152181b9514586fc39ba0ec9dd2aa3d6d3715

docker run --rm ghcr.io/beyond10x/conductor:1.8.1 conductor --version

v1.8.0

Choose a tag to compare

@b10x-bot b10x-bot released this 13 Nov 13:44

Since 1.7.0:

  • story:forge-access-anonymous: an instance mapping may name forge_access: anonymous. Its pod carries no credential of any forge and clones the public GitHub repositories its include entries name over HTTPS with none. The instance writes nothing to its forge (no push, pull request or comment) and reads no pull requests, issues, releases or workflow runs. Without the member nothing changes.

Known issue: with forge_access: anonymous, two kinds of daemon.kubernetes.env entries still reach the instance's pod: an entry under another name that names the same secret as a forge token variable (GH_TOKEN, GITLAB_TOKEN and the like), with that secret in the pod's Secret, and an entry GIT_CONFIG_GLOBAL or GIT_CONFIG_SYSTEM. Do not use the member with such a configuration before 1.8.1.

Upgrade: none. A daemon of an earlier release refuses a mapping with the member as an unknown key.

Container image

ghcr.io/beyond10x/conductor:1.8.0 at digest sha256:17df2aa4677c549ec05b608efdf9fb5e896c705832b4e25b044177e38d979cfc

docker run --rm ghcr.io/beyond10x/conductor:1.8.0 conductor --version

v1.7.0

Choose a tag to compare

@b10x-bot b10x-bot released this 13 Nov 07:29

Stories implemented since v1.6.0:

  • instance-git-identity: an instance mapping may name git_identity: {name, email}. Where the daemon runs its instances as pods, every commit in that instance's pod carries it as author and committer, in place of conductor <conductor@localhost> or a daemon.kubernetes.env entry of those variables. A change of the member alone replaces the pod once. GET /v1/instances/{name} answers it.
  • model-token-kind: an instance mapping may name model_token_kind: api_key (the default, as before) or subscription_token. For subscription_token the runtime reads the value from its secrets source at each session's start and sets it as CLAUDE_CODE_OAUTH_TOKEN in the environment of the harness's process; the session's settings name no apiKeyHelper. The value is in no command line, settings file or log line; it is in the environment of the harness's process, as that route requires. A placement that opens a box refuses the kind. A running pod keeps the value it started with until POST /v1/instances/{name}/restart; nothing refreshes a subscription token. GET /v1/instances/{name} answers the kind.

Built in this release under control-plane-work-handler, which stays a draft until the operator accepts it:

  • The work call starts the work. A 201 of POST /v1/instances/{name}/work starts one controller session for the repository when it has none; the session gets the dispatch's members and the brief, verbatim, in a fenced block. A repository with no charter gets a default one (merge: human, one unit of work, a review <url> report, never merge).
  • The row's member start says why nothing started: ControllerLimit, AlreadyRunning or StartFailed. The call still answers 201; the loop tries the start again at its next cycle, and leaves a unit to the operator after three starts of it failed at the launch.
  • A session reports [REPORT <repo> <dispatch-id>] review <url>; the row's links then holds the URL and last_report the report's first line. Only an http:// or https:// URL is recorded, and only by a review report. A row with a review link is not finished: it reads Done once the session reports done.
  • The work call and dispatch send-dispatch refuse a brief with a NUL or a run of more than 255 backticks (InvalidBrief), and an issue or start_ref with a control character (C0 or C1), U+2028, U+2029, a bidirectional control or a backtick (InvalidWorkRequest).
  • The Kubernetes guide has a section "The work call": what a call starts, the default charter, the row's start, the limit, and how the review link comes back.

Security

This release hardens what a runtime hands to the processes it starts.

  • Closed: the runtime's API token in the environment of its children. From v1.0.0 to v1.6.0 every process a runtime started, an agent session among them, inherited the variable CONDUCTOR_API_TOKEN where the daemon had given the runtime one: an instance the daemon starts as a unit, and an instance that runs as a pod (v1.4.0 and later). From v1.7.0 no process a runtime starts gets the variable.
  • Closed: the token as a file in a pod. From v1.4.0 to v1.6.0 the token was also a file of the instance's Secret as the pod mounts it. From v1.7.0 the mount holds no such file.
  • What the token admits. The writes of that one runtime's API: answer a question, write a plan, hand work to the instance, and change the instance's settings. The settings write changes every key of that instance's mapping except name, session_prefix, dashboard_port, records, state, cache and checkouts; among the keys it changes are sources, placement, authority, and forge_token and model_token, each of which holds the name of a secret and never a value. No call of that API returns a secret's value; its settings read shows the names of the secrets the instance refers to. The runtime of another instance refuses the token, and so does the daemon's API.
  • What stays, as the trust model. A session and the runtime that started it run as one user, so a session can act as its runtime towards that runtime's own API. In a pod that reach ends at the instance: the file the settings write changes is the pod's own copy, which the runtime replaces at each start. On a host where the daemon starts instances as units, a session runs as the daemon's user; it follows that it has that user's access to the daemon's files and to the other instances the daemon starts. In a box, the daemon's config file is mounted read-only and a session there can read it: that file names every instance of the daemon and each secret a mapping refers to, by name, and holds no secret's value.
  • A session can read the model token its instance runs on. This was so before this release and stays so. An api_key reaches the harness through a helper command the session's settings name, and a session can run that command. A subscription_token (new in this release) is in the environment of the harness's process, which runs as the session's user. Whether the harness keeps that variable from a session's tools is not established; treat the model token as readable by the session.
  • What to do on an older version. Upgrade, then restart each instance (POST /v1/instances/{name}/restart, or stop and start it): its runtime gets a new token, and an instance that runs as a pod gets a new pod. A restart without the upgrade changes the token, and a session started after it inherits the new one. A restart replaces the runtime's token only. Where you suspect that a session used the token on an older version, read the instance's settings for a forge_token, model_token, sources or placement you did not set, and replace the forge and model tokens of that instance yourself.
  • Found by a security review of this release's own change. We have no report of its use.

Upgrade from v1.6.0: the daemon replaces each instance pod once at its first pass, because the pod's rendering changed. No change to a config file is needed.

Known limits: those of v1.6.0 stand, except that the work call now starts a controller. Not shown yet: the work call against an instance pod on a cluster, an agent session started inside an instance pod with a real model token, a push from a pod with a real forge token; the changed mount of the instance's Secret is held by a test against a stand-in API server and was not run on a cluster. max_working does not count a session started by hand beside the runtime. The stop after three failed starts holds for a unit's start; the loop's other start of a controller retries as before.

Verified before the release: every test of conductor-cli and conductor-docs at 930a71f, "2244 tests run: 2241 passed (3 slow), 3 failed, 14 skipped", with fmt, clippy under the default toolchain and under 1.100.0, and the generated docs check at exit 0. The three failed cases were a fixture directory of a new test that nothing removed (fixed in fe4a559) and two timing cases of build run and of the dashboard's cache; rerun at fe4a559, each timing case passed alone and their four test files together, "108 tests run: 108 passed, 0 skipped". eb4e0bc adds a plan commit to that, and the release head 951330f a change to one test file of the collector, whose stand-in program ended at once on the runner image ubuntu-26.04 and failed the pull request's check twice on eb4e0bc. The wide run was not repeated on either; fmt, clippy under both toolchains and that test file ran on the release head, "30 tests run: 30 passed, 1 skipped". The four checks of the pull request ran on the release head, its task check on ubuntu-26.04: "2300 tests run: 2300 passed (3 slow), 14 skipped".

Container image

ghcr.io/beyond10x/conductor:1.7.0 at digest sha256:3c55c9519577386a097045929ac01b76db50137ee86d50681bb1bb559feb09de

docker run --rm ghcr.io/beyond10x/conductor:1.7.0 conductor --version

v1.6.0

Choose a tag to compare

@b10x-bot b10x-bot released this 13 Nov 00:03

Stories implemented since v1.5.0: none moved to implemented. Built in this release under one story that stays a draft until the operator accepts it:

  • control-plane-work-handler, first part: a control plane hands one unit of work to an instance over HTTP and reads its state back.
    • Daemon: POST /v1/instances/{name}/work and GET /v1/instances/{name}/work/{reference}. Runtime: POST /v1/work and GET /v1/work/{reference}.
    • A request names a reference, a repository and a brief, and may name a start_ref and an issue. The call records a dispatch for that repository and answers its row: state, times, detail, links and the last report.
    • Answers: 201 created; 200 for a reference a dispatch already holds; 409 RepositoryBusy; 404 WorkNotFound; 411 without a Content-Length; 413 above 1 MiB; 422 InvalidWorkRequest, InvalidReference, InvalidBrief, UnknownRepository; 500 WorkFailed naming the reference only.
    • The daemon marks a call made with the control token with X-Conductor-Actor-Kind: control-plane.

Also in this release:

  • message handle-message takes --dispatch-id, and only for a dispatch of the repository the message's first line names; otherwise it answers dispatch-not-of-message and the message stays unhandled.
  • A report line [REPORT <repo> <id>] moves a dispatch only where that dispatch is of <repo>.
  • dispatch send-dispatch and the work call refuse a start_ref that is no git ref name and an issue above 2048 bytes or with a control character.
  • conductor status and the text and Markdown of every view write control characters, U+2028, U+2029 and bidirectional controls as a space or a line break, so text of a brief or a report acts on no terminal.

No step at upgrade from v1.5.0.

Known limits: those of v1.5.0 stand. The work call records the dispatch; it does not start a controller for it, so the unit is taken up where the instance's loop dispatches it. Not shown yet: the work call against an instance pod on a cluster, an agent session started inside an instance pod. Two findings of the security reviews of the work call were there before it and wait as drafts: a session of one repository can write a report line that names another repository and its dispatch consistently, and the loop takes it; each read of a unit's state reads whole month files of the dispatch log.

Container image

ghcr.io/beyond10x/conductor:1.6.0 at digest sha256:ad7992ef37285c4b0a702b8f89acaa60321bf195a576914df6b0738e967198ce

docker run --rm ghcr.io/beyond10x/conductor:1.6.0 conductor --version

v1.5.0

Choose a tag to compare

@b10x-bot b10x-bot released this 12 Nov 22:03

Stories implemented since v1.4.0: none moved to implemented. Built in this release under six stories that stay drafts until the operator accepts them:

  • instance-forge-token: an instance mapping takes forge_token: secret:<name>; its pod carries that token and not the daemon's. Without it a pod gets the daemon's forge token only where the instance has a source on that token's forge host.
  • pod-git-reaches-forge: git over HTTPS in an instance pod authenticates through git's credential helper with the token the pod carries (GIT_CONFIG_COUNT, GIT_CONFIG_KEY_<n>, GIT_CONFIG_VALUE_<n>, written by the daemon unless daemon.kubernetes.env names them).
  • source-include-list: a github or gitlab source takes include, the list of repositories it stands for, beside exclude.
  • pod-source-checkouts: a runtime alone in an instance pod clones the repositories its sources name under include, at its start and at each collect pass, and nothing else. A clone that fails is a member failure in GET /v1/instances/{name}/health and stops no other clone. A pod takes one snapshot at its start; a repository is trusted for the harness only where an include entry covers it. conductor doctor in the pod says how many included repositories have no checkout.
  • daemon-version-json: GET /v1/version answers [{"config":"conductor.config/2","version":"<version>"}].
  • instance-secret-state: GET /v1/instances/{name}/secrets answers, per secret an instance mapping names, its name, whether it is set and since when. No value; the daemon's own secrets, the forge secrets and those of daemon.kubernetes.env are withheld.

Also in this release:

  • A snapshot keeps its commands in its own process group and, at its end, stops what they left running in that group.
  • The dispatch records take a work source's reference in place of a goal, and hold links.
  • An instance pod's environment carries GLAB_SEND_TELEMETRY=false unless the operator's own entry sets it.
  • The workspace compiles clean under Rust 1.100.0.

At upgrade from v1.4.0 the daemon replaces every instance pod once, at its first pass, since the pod's environment changed; the volume claims are kept.

Known limits: those of v1.4.0 stand, except that a forge token in a pod is now shown, on a test cluster against a forge double. Not shown yet: a clone, fetch or push against a real forge with a real token, the github clone path on a cluster, an agent session started inside an instance pod, a clone that hangs at a pod's start. Handing work to an instance over HTTP is not in this release.

Container image

ghcr.io/beyond10x/conductor:1.5.0 at digest sha256:07338dda8b5981475556f081e18af90a5f22b4ed7fecf2a5cb13e50306573fb5

docker run --rm ghcr.io/beyond10x/conductor:1.5.0 conductor --version

v1.4.0

Choose a tag to compare

@b10x-bot b10x-bot released this 12 Nov 07:54

Stories implemented since v1.3.0:

  • release-container-image: the release workflow builds the container image around the release's own binary, pushes ghcr.io/beyond10x/conductor:<version> and names tag and digest in the Release's notes (closed by the v1.3.0 run of that workflow).

Built in this release under instance-pods, a story that stays a draft until the operator accepts it:

  • daemon.kubernetes (namespace, image, owner, api_port, volume, resources, env): a daemon in a Kubernetes namespace creates, lists and deletes one pod per instance through the Kubernetes API with the service account its own pod mounts; per instance a Secret, a volume claim and a pod, each with four labels. Without the key nothing changes.
  • Instance routes on the daemon: POST /v1/instances, GET, PUT and DELETE /v1/instances/{name}, POST /v1/instances/{name}/restart; each change validates the whole resulting config first and records one config revision.
  • daemon.control_token: secret:<name>: a bearer equal to that secret is the machine principal control-plane, admitted for the reads and those five routes and refused on every other write.
  • conductor daemon serve --daemon-config FILE: a second file with version and daemon only, written into the config file at every start; the instances the file holds are kept.
  • A runtime alone in a pod serves its API on TCP, with a token on every request but GET /healthz.
  • A lost instance pod is created again under the failed-start back-off; a daemon restart adopts its pods by label and restarts none; removing an instance deletes its pod and Secret and keeps its claim.
  • Nothing a caller sends reaches a pod's image, command, volumes, env sources, namespace, service account, security context or labels; the pod passes the restricted pod security level and mounts no service-account token. The daemon's own secrets reach no instance pod.
  • conductor doctor asks the cluster for the 10 rights the pod supervisor uses, one row each.
  • /metrics carries conductor_build_info with the binary's version and config protocol.
  • The guide website/docs/guides/run-in-kubernetes.md, with manifests a test holds to the daemon's code: the daemon in one namespace, the instance pods in another, the Role, a NetworkPolicy and the six calls. Conductor ships no chart.

Also in this release:

  • The prerequisites name ess 0.93; the image carries that version.

Known limits: one daemon per owner label and one replica; no drain, stop or start as a desired state for an instance pod, and upgrade answers 409 for one (the image names its version); a daemon that starts on an empty config file deletes the pods and Secrets of its owner label and keeps the claims, so keep the daemon's volume; every secret of the daemon's secrets source that the daemon section does not name can be named into an instance pod by whoever holds the control token; not shown yet: an agent session started inside an instance pod, a forge token in a pod, postgres as the store of pod instances.

Container image

ghcr.io/beyond10x/conductor:1.4.0 at digest sha256:b4707c780d1955d30bd5a882fec830588be57c56390e624ae89a3e1ae2913633

docker run --rm ghcr.io/beyond10x/conductor:1.4.0 conductor --version

v1.3.0

Choose a tag to compare

@b10x-bot b10x-bot released this 11 Nov 23:54

Stories implemented since v1.2.0:

  • daemon-probes-and-metrics: the daemon answers /healthz, /readyz and /metrics without a bearer; readiness is 503 with the reason while the store does not answer or the config is not read; the metrics are in the Prometheus text format (instances and sessions by state, snapshot age, build queue length).
  • container-start-without-prompt: a daemon whose config says daemon.unattended seeds its records, the trust of its checkouts and the worktree workspace at its start, each only where it is missing, and serves its probes while it seeds. The model token is read from a read-only files secrets source, a directory of mounted files. With no user systemd manager a runtime at placement: none is a child process of the daemon. daemon.address names the listener's address; off loopback every /v1/ path asks for a bearer.
  • doctor-checks-guard-forge-writes: conductor doctor asks the guard about one forge write per role and prints allowed or denied with the guard's rule; a denied role makes the verdict not ready. conductor setup --apply writes the forge route it reads from the origins of the checkouts under the root.
  • need-repository-from-tag: a NEED's repository is read from its tag, without trailing punctuation; a NEED for a repository the instance does not hold is refused in one line.
  • handover-restart-by-controller-id: a pending hand-over restart holds the controller id it was asked of and is dropped once the repository's controller has another id; the loop never stops a session started after the entry's delivery.

Also in this release:

  • A container image: this is the first release whose workflow builds and pushes ghcr.io/beyond10x/conductor:<version>; the guide is website/docs/guides/run-in-a-container.md.
  • Every process a runtime starts is built without the slot variable of a boxed runtime, and the daemon's token reaches a boxed runtime through a sealed slot.
  • SETUP.md step 5: the planning store is migrated in the repository's own checkout, not in a worktree of it, and steps 6 to 8 go on while the person decides on the migration (found by this release's onboarding runs, docs/analysis/onboarding-run.md).

Known limits: a boxed runtime cannot reach a forge host yet (one egress aperture per box in the substrate SDK); on substrate-daemon 0.20.0 a runtime at placement: systemd does not serve its API, as in v1.2.0; the local clients do not follow daemon.address.

Container image

ghcr.io/beyond10x/conductor:1.3.0 at digest sha256:367e10eb4f8edccb5c9c8d0c6da6d1cc807ce880bbdfdafc355e1c40a403abda

docker run --rm ghcr.io/beyond10x/conductor:1.3.0 conductor --version

v1.2.0

Choose a tag to compare

@b10x-bot b10x-bot released this 11 Nov 11:47

Stories implemented since v1.1.0:

  • deliver-reaches-bg-session: a delivery to a background session counts as delivered only after a look that shows the text read; a delivery nobody read has the new outcome unread.
  • box-crash-loop-reads-failed: a boxed runtime that ends within 10 s of its start is a failed start with the error text of runtime.log; the instance reads Unreachable; restarts back off to 5 minutes; conductor doctor dry-runs the box of a placement: systemd instance.
  • checkout-name-differs-from-project: a GitLab checkout is one repository under its directory name; one project in two sources is recorded once; exclude matches the directory name or the project path.
  • gc-idle-targets: conductor gc lists and removes cargo targets no build holds, under cargo's lock.
  • getting-started-substrate-steps-portable: the substrate steps of the getting-started page and SETUP.md work on distributions whose CA bundle is a link.

Known limit: on substrate-daemon 0.20.0 a runtime at placement: systemd does not serve its API, because substrate denies the Unix socket it binds in a box. The box, its four writable roots and the failed-start record hold; placement: none is not affected.

v1.1.0

Choose a tag to compare

@b10x-bot b10x-bot released this 10 Nov 08:52

Stories implemented since v1.0.1:

  • auto-resume: the watch rotates and resumes a pane stopped on the usage-limit banner (new config key rotate).
  • guard-admits-own-pipelines: under forge.route: direct the guard admits a pipeline write to the session's own project.
  • guard-admits-own-mr-auto-merge: under forge.route: direct the guard admits the PUT that sets merge-on-green on the session's own merge request, with the project named by its path.
  • kpi-scope-active-repositories: repository-scoped KPIs count Active repositories only.
  • status-current-snapshot: status reads the current snapshot only.
  • conductor-context-budget: usage reports context per session, and a session start denies unused tools.
  • dispatch-refuses-unknown-repository: send-dispatch refuses a repository nothing names.
  • decision-options-spellings: an option written A. <label> and a cost after . Cost: or a dash are read.
  • dashboard-live-controllers-and-report-text: the dashboard counts a live controller whatever the store recorded, and a report row carries the report's text as one bounded line.
  • dashboard-ui-loop: round 1 of the dashboard's UX loop, with a layout, axe and ratchet gate in task check.
  • board-hide-done: tests for the board's four columns and the hide-done toggle.
  • documented-commands-parse: every conductor line of the two guides is parsed by the binary, and a test holds each to its handler's flags.
  • build-shared-target-dir, context-watch-by-envelope: closed on a test result, no code change.