Repository navigation
Releases
conductor 1.9.0
Since 1.8.1:
story:instance-rules-text: an instance mapping takesstanding_rules, a text that every session of the instance reads, whatever its role.POSTandPUT /v1/instancestake it, at most 16384 bytes of UTF-8 without a NUL;GET /v1/instances/{name}answers it. A text that is, as a whole, asecret:<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/configandconductor config putrefuse 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 --versionconductor 1.8.1
Since 1.8.0:
story:anonymous-pod-no-forge-secret: the pod of an instance withforge_access: anonymouscarries no forge credential under any entry name. It leaves out everydaemon.kubernetes.enventry that names the secret of a forge token of the file (a daemon's, or any instance'sforge_token) or holds a forge token's value, and the variables that point git, ssh, git's HTTP library,ghorglabat a credential or a configuration file (GIT_CONFIG_GLOBAL,GIT_CONFIG_SYSTEM,GIT_SSH_COMMAND,SSH_AUTH_SOCK,NETRCand 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
sshasGIT_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 fetchesoriginfrom (remote.origin.url, or the legacy file.git/remotes/originor.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, carryGIT_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_tokenof 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 noghorglabcall for such an instance and say so in one line; oneincludeentry 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 ofexecand a name given to the program inargv[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> buildandexec -a cargo <program> buildare now decided ascargo buildandcargo testalone 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 --versionv1.8.0
Since 1.7.0:
story:forge-access-anonymous: an instance mapping may nameforge_access: anonymous. Its pod carries no credential of any forge and clones the public GitHub repositories itsincludeentries 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 --versionv1.7.0
Stories implemented since v1.6.0:
instance-git-identity: an instance mapping may namegit_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 ofconductor <conductor@localhost>or adaemon.kubernetes.enventry 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 namemodel_token_kind:api_key(the default, as before) orsubscription_token. Forsubscription_tokenthe runtime reads the value from its secrets source at each session's start and sets it asCLAUDE_CODE_OAUTH_TOKENin the environment of the harness's process; the session's settings name noapiKeyHelper. 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 untilPOST /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
201ofPOST /v1/instances/{name}/workstarts 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, areview <url>report, never merge). - The row's member
startsays why nothing started:ControllerLimit,AlreadyRunningorStartFailed. The call still answers201; 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'slinksthen holds the URL andlast_reportthe report's first line. Only anhttp://orhttps://URL is recorded, and only by areviewreport. A row with a review link is not finished: it readsDoneonce the session reportsdone. - The work call and
dispatch send-dispatchrefuse a brief with a NUL or a run of more than 255 backticks (InvalidBrief), and anissueorstart_refwith 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.0tov1.6.0every process a runtime started, an agent session among them, inherited the variableCONDUCTOR_API_TOKENwhere 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.0and later). Fromv1.7.0no process a runtime starts gets the variable. - Closed: the token as a file in a pod. From
v1.4.0tov1.6.0the token was also a file of the instance's Secret as the pod mounts it. Fromv1.7.0the 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,cacheandcheckouts; among the keys it changes aresources,placement,authority, andforge_tokenandmodel_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_keyreaches the harness through a helper command the session's settings name, and a session can run that command. Asubscription_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 aforge_token,model_token,sourcesorplacementyou 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 --versionv1.6.0
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}/workandGET /v1/instances/{name}/work/{reference}. Runtime:POST /v1/workandGET /v1/work/{reference}. - A request names a
reference, arepositoryand abrief, and may name astart_refand anissue. The call records a dispatch for that repository and answers its row: state, times, detail, links and the last report. - Answers:
201created;200for a reference a dispatch already holds;409RepositoryBusy;404WorkNotFound;411without aContent-Length;413above 1 MiB;422InvalidWorkRequest,InvalidReference,InvalidBrief,UnknownRepository;500WorkFailednaming the reference only. - The daemon marks a call made with the control token with
X-Conductor-Actor-Kind: control-plane.
- Daemon:
Also in this release:
message handle-messagetakes--dispatch-id, and only for a dispatch of the repository the message's first line names; otherwise it answersdispatch-not-of-messageand the message stays unhandled.- A report line
[REPORT <repo> <id>]moves a dispatch only where that dispatch is of<repo>. dispatch send-dispatchand the work call refuse astart_refthat is no git ref name and anissueabove 2048 bytes or with a control character.conductor statusand 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 --versionv1.5.0
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 takesforge_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 unlessdaemon.kubernetes.envnames them).source-include-list: agithuborgitlabsource takesinclude, the list of repositories it stands for, besideexclude.pod-source-checkouts: a runtime alone in an instance pod clones the repositories its sources name underinclude, at its start and at each collect pass, and nothing else. A clone that fails is a member failure inGET /v1/instances/{name}/healthand stops no other clone. A pod takes one snapshot at its start; a repository is trusted for the harness only where anincludeentry covers it.conductor doctorin the pod says how many included repositories have no checkout.daemon-version-json:GET /v1/versionanswers[{"config":"conductor.config/2","version":"<version>"}].instance-secret-state:GET /v1/instances/{name}/secretsanswers, 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 ofdaemon.kubernetes.envare 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
referencein place of a goal, and hold links. - An instance pod's environment carries
GLAB_SEND_TELEMETRY=falseunless 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 --versionv1.4.0
Stories implemented since v1.3.0:
release-container-image: the release workflow builds the container image around the release's own binary, pushesghcr.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,PUTandDELETE /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 principalcontrol-plane, admitted for the reads and those five routes and refused on every other write.conductor daemon serve --daemon-config FILE: a second file withversionanddaemononly, 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
restrictedpod security level and mounts no service-account token. The daemon's own secrets reach no instance pod. conductor doctorasks the cluster for the 10 rights the pod supervisor uses, one row each./metricscarriesconductor_build_infowith 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
ess0.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 --versionv1.3.0
Stories implemented since v1.2.0:
daemon-probes-and-metrics: the daemon answers/healthz,/readyzand/metricswithout 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 saysdaemon.unattendedseeds 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-onlyfilessecrets source, a directory of mounted files. With no user systemd manager a runtime atplacement: noneis a child process of the daemon.daemon.addressnames the listener's address; off loopback every/v1/path asks for a bearer.doctor-checks-guard-forge-writes:conductor doctorasks the guard about one forge write per role and printsallowedordeniedwith the guard's rule; a denied role makes the verdictnot ready.conductor setup --applywrites 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 iswebsite/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.mdstep 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 --versionv1.2.0
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 outcomeunread.box-crash-loop-reads-failed: a boxed runtime that ends within 10 s of its start is a failed start with the error text ofruntime.log; the instance readsUnreachable; restarts back off to 5 minutes;conductor doctordry-runs the box of aplacement: systemdinstance.checkout-name-differs-from-project: a GitLab checkout is one repository under its directory name; one project in two sources is recorded once;excludematches the directory name or the project path.gc-idle-targets:conductor gclists 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
Stories implemented since v1.0.1:
auto-resume: the watch rotates and resumes a pane stopped on the usage-limit banner (new config keyrotate).guard-admits-own-pipelines: underforge.route: directthe guard admits a pipeline write to the session's own project.guard-admits-own-mr-auto-merge: underforge.route: directthe 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:statusreads the current snapshot only.conductor-context-budget: usage reports context per session, and a session start denies unused tools.dispatch-refuses-unknown-repository:send-dispatchrefuses a repository nothing names.decision-options-spellings: an option writtenA. <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 intask 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.