Skip to content

Releases

consort 0.11.0 — Codex account failover, Termscape account discovery

Choose a tag to compare

@siimvene siimvene released this 29 Sep 13:56
1539cbf

Added

  • Codex account failover: CONSORT_CODEX_HOMES. A :-separated list of
    CODEX_HOME directories, one per Codex account, tried in order. A call
    moves on only when codex reports a usage or rate limit in its own error
    events or stderr (the exec transport now runs --json); the transcript is
    never read for it. Any other failure stops the walk. @default and
    @termscape expand to the inherited home and to every Codex account
    Termscape (nodeterm) has signed in (system account, then
    ~/.nodeterm/cx/<sha256(userData\0id)[:16]>). One stderr line per attempt
    names the home that ran. A home list forces the exec transport (the
    plugin broker is pinned to one account) and refuses an explicit
    CONSORT_CODEX_BACKEND=plugin. Delegation fails over only when the
    limited attempt did no work at all (no item in codex's event stream), so
    a half-applied edit or a leftover background process never reaches a
    second account. Keyring-backed logins (no auth.json) are recognised via
    codex login status. Only error events and a work-item count are kept
    from the stream; the transcript never touches disk. The Codex probe walks
    the same list. Tests: scripts/codex-backend.test.sh (stub codex).
  • Hardened by the gate on this change (Codex, Gemini via Pi, blind security
    agent): the first cut fingerprinted the work tree with git add -A in a
    throwaway index, which ran the repo's core.fsmonitor, wrote untracked
    files into .git/objects and missed edits to ignored files; replaced by
    the no-work rule above. A malformed Termscape settings.json no longer
    echoes its offending line (it can hold an API key); a refused call clears
    a stale result file; the walk and the probe survive a caller's
    set -euo pipefail without || true.

Fixed

  • codex exec calls without a payload and the Codex probe now read stdin
    from /dev/null; before, a call with no payload could sit on "Reading
    additional input from stdin..." when it inherited an open pipe that never
    closed (a harness or a background job).

consort 0.10.0 — principal review pass and test suite out of the authoring session

Choose a tag to compare

@siimvene siimvene released this 07 Sep 23:01
a278895

Changed

  • The principal's review pass runs in a non-inheriting agent, not in the
    authoring session.
    New agents/code-reviewer — same shape as the
    security side-agent: Read/Grep/Glob, no shell, fed the diff file, the pack
    paths and the test summary, returning findings JSON. It runs the
    blast-surface sweep with repo access and is the "Claude only" column of
    the merge. Why: a review pass is 30 to 40 tool calls, and every call
    re-reads the whole context it runs in. In an authoring session at 200k to
    400k tokens that pass alone cost more than every other leg combined, and
    it was the one leg nobody measured — the security side-agent reports ~110k
    tokens over 19 to 32 calls for its whole pass, the principal's had no
    boundary to report from. The review skill and /consort:review now have
    the session gather mechanical evidence (diff file, scanners, tests), spawn
    both agents and the panel in parallel, merge, verify and present; it no
    longer reads the packs or the diff into its own context.

Added

  • scripts/consort-test.sh [base] — blast-surface step 5 as a tool
    instead of a chore in the session. Runs the suite (CONSORT_TEST_CMD, or
    detected from test.sh, package.json, pytest markers, go.mod,
    Cargo.toml, a Makefile test: target) on head and in a detached
    worktree of the base ref (dependency dirs symlinked so a fresh checkout can
    start), normalises failure lines across pytest / jest / go / cargo / TAP,
    and prints ONE summary line plus the new failures (capped,
    CONSORT_TEST_MAX_LINES) and the run directory. Full logs and a
    result.json stay on disk for the code-reviewer agent. Exit 0 nothing
    new · 1 new failures, or a red head against a green baseline whose
    failures the parser could not read · 2 did not run (no command, bad ref,
    head timed out under CONSORT_TEST_TIMEOUT) · 3 head ran but the
    baseline could not — the last two are reported as such, never as green.
    Run directories are owner-only; CONSORT_TEST_DIR gets the panel's
    ownership and writability checks. Pinned by consort-test.test.sh (30
    checks: baseline diffing, no-baseline mode, log-on-disk output budget, the
    cap, the crash case, refusals, the parser).

consort 0.9.1 — merge clusters the same defect across files

Choose a tag to compare

@siimvene siimvene released this 07 Sep 15:39
5f68ef6

Fixed

  • merge-findings.mjs clusters the same defect anchored on different
    files.
    Two reviewers can pin one defect to two files — one on the script
    that fails to prune, the other on the unit file being deleted — and
    file+line clustering showed it as two "only" findings, understating
    agreement (measured on kvart PR #18: the panel's union was reported as
    1 agreed + 2 "only" where it was 2 agreed). A finding now also joins a
    cluster when its title names another member's file (last two path
    segments, or a bare basename of at least 8 characters, so run.py in a
    title is not a link); the detail is not used because it routinely lists
    neighbours, and word overlap was rejected because the real case shared one
    content word. Such clusters are tagged cross-file. One reviewer's two
    findings never merge, as before.
  • Hardened by the gate on that change (Codex, Gemini via Pi, blind security
    agent): every member of a cluster is printed under its representative, so
    a cluster can no longer delete a reviewer's text from the report; the
    title match requires a whole path token (subtasks/run.py does not name
    tasks/run.py) and a key that looks like a file (an extension or a path,
    at least 8 characters — a one-word file such as authorization cannot
    act as a keyword magnet); files compare as paths, not through the
    punctuation-stripping normaliser (src/foo-bar.js and src/foo/bar.js
    are distinct); a finding that relates to several clusters merges them
    when they share no reviewer, so the outcome no longer depends on argument
    order, and prefers the cluster it names explicitly over the one it merely
    sits near when they cannot merge; reviewer file strings no longer appear
    in the tag; a set is capped at 500 findings with a loud stderr line.
    Second round (Codex, Gemini via Pi): path tokens are extracted from the
    title and matched as whole-segment suffixes of the other file's path (a
    full repo-relative path and a trailing period both work now); a bare
    basename shared by two different paths in the merge names nothing; a
    finding joins the largest compatible subset of its candidate clusters
    (no reviewer twice, span within the proximity even after a merge) rather
    than only the first, so non-disjoint candidates resolve the same way in
    any argument order; members keep arrival order through merges so the
    tie-break "earliest set" holds. Still greedy, by construction and now
    documented: one finding relating to two findings of the same reviewer
    joins the earlier; proximity grouping within a file follows arrival order.

consort 0.9.0 — review panel: several cross-vendor reviewers on every diff

Choose a tag to compare

@siimvene siimvene released this 07 Sep 12:12
d02de5f

Added

  • Review panel — several cross-vendor reviewers on every diff, chosen
    with CONSORT_REVIEWERS.
    scripts/consort-panel.sh [base] runs
    consort-review.sh once per leg (codex[:model], gemini[:model],
    pi[:provider[:model]]; e.g. codex,pi:google-vertex = the Codex CLI
    plus Gemini through Pi), in parallel, each under its own backend env, and
    writes one findings file per leg plus a manifest (label, backend, model,
    status, exit, seconds, file). A leg that fails, returns nothing, or runs
    past CONSORT_PANEL_TIMEOUT (default 1800 s; the leg's whole process
    group is killed) fails the panel (exit 3) with that leg's file emptied —
    the other legs' results stay on disk, but the configured panel did not
    run and the caller is told so; an all-excluded diff propagates as exit 4;
    stale leg files from a previous run are removed first. Every leg's stderr
    is relayed with a [label] prefix, so the backends' evidence lines stay
    visible. A provider-pinned Pi leg with no model unsets an ambient
    CONSORT_PI_MODEL so the provider default applies. Bash-native wall-clock
    cap (no timeout binary needed); scripts/consort-panel.test.sh pins the
    grammar, env plumbing, hard-fail, exit-4, timeout and stale-file rules
    against a stub reviewer.
  • merge-findings.mjs merges any number of findings sets. Arguments
    are <principal.json> <reviewer.json>... (labels from basenames or
    label=path); findings cluster across all sets by file and line
    proximity, one finding per set per cluster, the representative being the
    most severe member. Sections: caught by more than one reviewer (tagged
    with every reviewer that caught it, sorted by agreement then severity),
    each reviewer's "only" section, then the principal's. A file that is
    missing, empty, {} or {"findings":null} is reported as NO RESULT
    in the report and exits 3 — previously it merged silently as an empty
    (clean) set. scripts/merge-findings.test.mjs pins the contract.
  • Pi backend honours consort-scoped Google settings for google
    providers, exported to the Pi process only and winning over the ambient
    Google variables when set: CONSORT_GCP_PROJECT → GOOGLE_CLOUD_PROJECT,
    CONSORT_GEMINI_LOCATION → GOOGLE_CLOUD_LOCATION,
    CONSORT_GCP_CREDENTIALS → GOOGLE_APPLICATION_CREDENTIALS. The
    credentials path must be a readable file or the backend refuses to start,
    so a typo cannot fall through to whatever ADC the shell holds. Lets a
    static config keep a Gemini leg alive where the user's ADC is an
    hourly-expiring workforce token.

Changed

  • /consort:review, the review skill and the lifecycle skill call
    consort-panel.sh and merge every leg; the presented order is now
    "caught by more than one reviewer", each reviewer's "only" section, then
    the principal's (was: both / Claude only / Codex only).
  • Hardened by the gate before release (Codex, the blind security agent and
    the principal, on the panel's own diff): each panel run gets a fresh,
    exclusively created subdirectory of CONSORT_PANEL_DIR (concurrent
    panels sharing a parent no longer corrupt each other; a stale manifest
    can no longer read as the current verdict), the parent must be owned by
    the caller and not world-writable, every file is created with noclobber
    so a planted symlink is refused rather than followed, leg start times
    stay in memory (a stamp read back from disk was substituted into bash
    arithmetic, which expands array subscripts — command execution for anyone
    who could write the file), exit codes read from disk are validated as
    integers, the manifest is built by a real JSON serializer, reaped pids are
    cleared so a recycled pid is never signalled, cleanup is armed before the
    first leg starts and escalates TERM→KILL, a base ref starting with - is
    rejected (both in the panel and in consort-review.sh), /consort:review
    quotes its argument, merge-findings.mjs strips control characters from
    reviewer text, renders a severity outside the schema enum as unknown,
    and opens the report with a data-fence line. Pi backend:
    CONSORT_GCP_CREDENTIALS is resolved to an absolute regular file before
    the run changes directory, refused for workspace-write runs (an unfenced
    shell must not have a long-lived key in its environment), a run whose
    final message stopped at the output token limit is discarded as truncated,
    the JSON extractor says why it found no result, and CONSORT_PI_RAW
    captures the raw event stream as evidence. Second gate round (Codex + Gemini
    via Pi on the hardened diff): group-writable parents refused and run
    directories created 0700, legs resolving to the same backend+model refused
    (one reviewer twice is not agreement), empty spec fields rejected, HUP and
    EXIT covered by the cleanup with KILL sent only to a group that still
    exists, expired legs stopped together, a leg whose leader dies from outside
    has its group swept, the merge no longer chains clusters past the
    proximity, a Google key file inside the workdir is refused (the fence would
    let the reviewer read it), the workspace-write refusal covers the ambient
    GOOGLE_APPLICATION_CREDENTIALS too, and CONSORT_PI_STDERR/CONSORT_PI_RAW
    must be owned regular files (made owner-only; symlinks refused). Deferred, pre-existing: a
    repo-local .claude/rules/ is auto-injected into every reviewer's prompt
    (0.4.0 design), which a hostile repo could use to talk every leg into an
    empty result at once — opt-in or fencing is tracked as follow-up.

consort 0.8.0 — Pi backend, any vendor through one CLI

Choose a tag to compare

@siimvene siimvene released this 07 Sep 11:11
5805e99

CONSORT_BACKEND=pi drives the Pi coding agent (@earendil-works/pi-coding-agent >= 0.84.0) for any provider: Anthropic, OpenAI (API key or ChatGPT/Codex subscription OAuth), Google Vertex (ADC / service-account key) and more, behind one headless JSON-lines protocol. Attestation is native: every assistant message names its provider, model and usage (plus the server-reported model where an adapter has it); a run served by anything but the requested pair, or marked failed, is discarded. A fence extension (scripts/pi-fence.mjs) bounds Pi's tools to the workdir, mirroring Pi's own path normalisation (@, file://, ~, unicode spaces, read-path variants, symlinks incl. dangling); read-only refuses bash/edit/write; workspace-write is opt-in because Pi has no OS sandbox. anthropic as provider is refused unless the principal is stated not to be Claude. Every run is --offline; rg/fd must be resolvable up front; results are validated against the schema. Measured on a 25-file diff with 5 known defects: same gpt-5.6-sol through Pi used a third of the tokens of the Codex CLI and found the HIGH, with lower recall (2/5 vs 4/5); Gemini 3.1-pro through Pi found the HIGH that gemini-cli missed. See CHANGELOG 0.8.0 and PR #4.

consort 0.7.1 — Gemini cli transport pins the served model

Choose a tag to compare

@siimvene siimvene released this 07 Sep 11:11
c53e28c

gemini-cli (0.58.0, Vertex auth) rewrote any model id ending in flash to its own flash default: CONSORT_GEMINI_MODEL=gemini-3.8-flash reviewed as gemini-3.5-flash and only the CLI's session log said so. The cli transport now enables the CLI's pass-through resolver through a throwaway system settings directory (system-defaults path pinned, real system settings copied JSONC-tolerantly, fail-closed), runs every call with --output-format json and discards any run not served solely by the requested model; one stderr evidence line per call (tool calls, tokens). Stderr capture is resolved before the cd and created owner-only without a process-wide umask. See CHANGELOG 0.7.1 and PR #3.

consort 0.7.0 — lean backends

Choose a tag to compare

@siimvene siimvene released this 07 Sep 09:10
388102e

Changed

  • Codex exec transport runs read-only calls with --ignore-user-config,
    reasoning effort pinned via CONSORT_CODEX_REASONING (default high). The
    reviewer no longer inherits ~/.codex/config.toml plugins, plugin hooks,
    MCP servers or developer_instructions. Measured on a 25-file diff: a stock
    config whose oh-my-codex prompt hook matched "parallel mode" inside a rule
    pack fanned the review into three subagents and re-prompted from its Stop
    hook — 10.7M input tokens, 909 s; the bypass reviewed the same diff in 2.46M
    tokens, 495 s, with the same real findings. Workspace-write (delegation)
    keeps the user config, where sandbox narrowing and approval policy live;
    CONSORT_CODEX_USER_CONFIG=1 keeps it for read-only calls too. A CLI
    without the flag falls back to the stock config with a stderr warning.
    (Gate: a free-form CONSORT_CODEX_ARGS passthrough was dropped before
    release — env-supplied argv after -s could have revoked the sandbox.)

Added

  • Opt-in diff path excludes in consort-review.sh (CONSORT_DIFF_EXCLUDE,
    colon-separated git pathspec globs matched from the repo root) for repos
    that commit generated artefacts. Nothing is excluded by default: lockfiles
    stay in, as the supply-chain rule requires. Excluded paths are listed on
    stderr and handed to the reviewer as fenced data; an all-excluded diff
    exits 4 instead of passing as clean. The diff is now taken from the repo
    root (:/) regardless of the caller's cwd.
  • CONSORT_GEMINI_STDERR: file to append the Gemini CLI's stderr to
    (created owner-only; dropped by default).

Fixed

  • Gemini cli transport reviewed diff-only. Headless gemini made zero
    tool calls in every review run (three measured), so the "repo-reading"
    transport could not do the caller sweeps the rule packs ask for. The prompt
    now orders it to read every touched file and sweep callers, consumers,
    schedulers and docs of every removed symbol before answering; runs after the
    fix made 25–34 tool calls.
  • Gemini cli transport @ expansion. The CLI's @file expander
    ((?<!\\)@ + path chars) read a unified diff's @@ hunk headers as file
    references and injected unrelated repo files into the prompt. Every @ in
    the prompt is now backslash-escaped, the CLI's own escape, which headless
    mode passes through as literal text.
  • Gemini api probe false negative. consort_gemini_probe capped output at
    16 tokens, which a thinking model spends on thoughts (finishReason: MAX_TOKENS, empty text), so a live backend read as dead. Cap raised to 256
    and any candidate with a finishReason now counts as alive — the probe
    proves reachability, auth and quota, nothing more.

consort 0.6.0 — Gemini backend

Choose a tag to compare

@siimvene siimvene released this 04 Sep 15:17
3688bff

Adds a first-class Gemini (Vertex AI) backend as a cross-vendor peer to Codex, chosen at will with CONSORT_BACKEND=codex|gemini. Both are non-Anthropic, so either satisfies the cross-vendor axis when the principal is Claude.

Added

  • scripts/consort-backend.sh — backend dispatcher (consort_impl_call / consort_backend / consort_impl_model / consort_impl_probe). codex-backend.sh and gemini-backend.sh are peer modules; the caller scripts source the dispatcher. Default (Codex) behavior unchanged.
  • scripts/gemini-backend.sh — reaches Gemini on Vertex AI via ADC (gcloud/WIF), no API key. Full parity with Codex: reviewer and implementer (workspace-write edits files). Two transports (CONSORT_GEMINI_TRANSPORT=cli|api): the gemini CLI (@google/gemini-cli) run in the workdir for repo-aware review and edits, and a stdlib generateContent companion as a diff-only, read-only CI fallback. Plus a liveness probe.
  • Config: CONSORT_GEMINI_MODEL (default gemini-3.1-pro-preview), CONSORT_GEMINI_LOCATION (global, or a region for data residency), CONSORT_GCP_PROJECT.

Changed

  • consort-review.sh, consort-consult.sh, consort-delegate.sh now honor CONSORT_BACKEND via the dispatcher.

Self-reviewed through consort's own Gemini backend (Codex was unavailable); the review's findings — a workspace-write blast-radius bug, an ARG_MAX risk, and backend/token hardening — were fixed before merge.

consort 0.5.0 — the security review tier

Choose a tag to compare

@siimvene siimvene released this 01 Sep 21:58

Security review grows real teeth: a deterministic scanner tier and a blind security side-agent join the cross-model duet. Rule-based scanners and model reviewers catch nearly disjoint defect sets — so 0.5.0 runs both, and neither substitutes for the other.

Scanner tier (model-free third voice)

  • scripts/consort-scan.sh + scan-to-findings.mjs — runs Trivy when on PATH (dependency CVEs, leaked secrets, IaC misconfigurations) and SonarQube when a server is reachable (SONAR_HOST_URL + SONAR_TOKEN + sonar-project.properties), including the CE-task poll and issue/hotspot fetch. Output lands in the shared findings.schema.json, so scanner findings merge alongside model findings.
  • Loud degradation, never silent: every skipped or failed scanner leg is reported on stderr; a scan that ran nothing says "this scan verified nothing" instead of passing as clean. Failed fetches and malformed scanner output are failures, not empty-but-clean results.
  • Hardened by its own review gate: tokens never appear in process argv, the CE task URL is validated against the configured host, .scannerwork stays in an invocation-owned temp dir, truncated result pages warn, Maven-style project keys with colons parse correctly.
  • New command: /consort:scan — scanner tier standalone, with reachability triage.

Blind security side-agent

  • agents/security-reviewer — a security-only subagent the review loop spawns in parallel with the cross-vendor pass. Non-inheriting by contract: it reads the diff without the author's assumptions.
  • No shell by design. The agent gets Read/Grep/Glob only and receives the diff as a file written by the principal — a reviewer of possibly-hostile diffs must not be able to execute anything the diff says.
  • Findings appear as their own report column and are treated as leads, verified like any cross-model finding. Same-vendor, so it complements — never satisfies — the cross-vendor requirement.

Third built-in methodology pack

  • rules/security-review.md, always injected into both reviewers: authorization scoping, injection at construction sites, trust-boundary shifts, secrets in motion, dangerous sinks, supply chain. Scanner findings are deterministic evidence; the model owes each one a reachability verdict. Live credentials and criticals in newly added dependencies gate regardless of diff scope.

Review reports now carry five sections: Both agree / Codex only / Claude only / Security agent / Scanners — a report missing the last two is incomplete even when the duet ran.

Full details in CHANGELOG.md.

consort v0.1.0

Choose a tag to compare

@siimvene siimvene released this 20 Jul 07:02

First release of consort — a two-vendor development lifecycle for Claude Code. The principal (any strong Claude Code session model) orchestrates, synthesizes, and verifies; Codex (gpt-5.6) implements and cross-reviews. The value is the finding only one model caught.

What's in it

  • Pipeline skills — /consort:run (interview → spec → plan → implement → review → gate), /consort:review (standalone cross-model review of the working diff), /consort:plan.
  • Panel + synthesis — blind independent drafts from both voices in the front phases, scored and merged with per-idea attribution.
  • Delegation spine — the principal hands one task at a time to Codex with a five-part brief; verbose output stays on disk, structured results come back.
  • Pluggable Codex backend — auto-uses the official Codex Claude Code plugin's companion runtime when installed, falls back to the raw codex exec CLI. Force either with CONSORT_CODEX_BACKEND=plugin|exec.
  • State on disk — every phase resumes from .consort/; the session dying loses nothing.
  • Shared JSON schemas (spec / findings / task-result) so two vendors are machine-comparable; MIT licensed.

Requirements

Claude Code, plus either the Codex plugin or a local codex CLI (ChatGPT or API auth). Swap the implementer model via CONSORT_IMPL_MODEL.