Skip to content

Releases: Devail1/chelamux

v0.19.1

Choose a tag to compare

@github-actions github-actions released this 30 Oct 00:43

Fixed

  • Moving the v0 major tag no longer leaves a red Release run. The Release workflow now
    triggers only on a full vX.Y.Z tag (v[0-9]+.[0-9]+.[0-9]+) instead of v*, so the
    major-tag force-move that follows every release no longer starts it and fails looking for a
    ## [0] CHANGELOG section. tests/test_release_workflow.py evaluates the filter with
    GitHub's documented pattern syntax. (CMX-366)

Changed

  • The install instructions now point at PyPI. The landing hero is uvx chelamux dashboard,
    the judge-sweep one-liner uses --from chelamux, and the docs quickstart leads with
    uv tool install "chelamux[dashboard,telegram]", keeping git clone + uv sync --all-extras
    as the from-source path. PyPI gets tagged releases only; a git+https:// install tracks the
    default branch. The README and Getting Started install block stays on git+https://,
    because the fresh-install check swaps that URL for the commit under review. (CMX-367)

Added

  • chela --version (and chelamux --version) prints chela <version> from the installed
    package's metadata, or chela 0.0.0+unknown from a source tree with none. It used to fail
    with unrecognized arguments. A test now also reads the landing hero's install command out
    of the page and fails if README.md stops showing it verbatim. (CMX-368)

v0.19.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 07:08

Added

  • Every judge round records where its time went. Each round in the private judge
    record ($CHELA_DIR/judge-heldout/<task>.jsonl) now has a timings object: wall-clock
    for setup (worktree checkout and the judge agent, up to chela judge run), worktree
    rebuild, provisioning, the baseline suite, each visible and held-out experiment (with
    its selected test count and whether it was confirmed on the full suite), the
    consistency re-runs and the whole round. Every phase also reports how much of it was
    spent waiting for a suite slot, and the round records how many agents and judges were
    running when it started. chela judge show <run> prints the breakdown under each
    round; rounds recorded before this change print "no timings recorded".
    chela judge timings [--since 7d] [--json] gives the median, p90 and max per phase
    across every task, biggest phase first. Nothing about what the judge runs or decides
    has changed. (CMX-196)

  • Keeper mode runs on its own, and a workflow can be keeper-only. A WORKFLOW.md with
    keeper: {enabled: true, every: 45m} gets one chela keeper run every every from the
    daemon. A run is skipped while the queue is held, while the previous one is still going,
    and when no session in the repo typed a prompt or edited a file since the last run.
    dispatch: {enabled: false} (or dispatch: false) makes the workflow keeper-only: the
    dispatcher claims, reconciles and judges nothing for it, auto-merge and the nightly sweep
    skip it, and it is removed from the merge gate's registry, so your own sessions can push
    to its base. A bad value for either key fails the parse, so the last good config stays
    in force. chela keeper stats [--since 7d] [--json] reports proposals made, accepted,
    rejected and dropped, the acceptance rate, which is flagged under the 70% kill line, and
    a by-action breakdown. (CMX-348)

  • chelamux is on PyPI: uvx chelamux dashboard runs the wall with nothing to clone. Each
    release tag now builds the sdist and wheel, smoke-tests the wheel and publishes it with
    Trusted Publishing (no API token). The package gains a chelamux command next to chela,
    so uvx chelamux … needs no --from. CI builds and runs the wheel on every PR, and a test
    checks the wheel carries the dashboard bundle, the ttyd shims, the templates, the plugin's
    merge-gate script and the statusLine hook. (CMX-363)

Changed

  • The dashboard's packages are core dependencies. Flask, flask-sock and cryptography
    install with chelamux itself, since a bare uvx chelamux cannot name an extra. The
    dashboard extra still exists (empty), so chelamux[dashboard] install lines keep
    working. chela install-statusline now finds the hook inside an installed wheel too, not
    only in a checkout. (CMX-363)

Fixed

  • chela ops scan no longer reports a short-lived test child that was re-parented to init
    for a moment (four false "orphaned (ppid 1)" notices between 2026-10-24 and 10-27, each
    process gone a minute later). An orphan is now a finding only when the same process (pid
    and start time) is still alive with ppid 1 on the next scan. An orphan with no named owner
    is also never one while it shares a chela-suite-* scope with a live process of that
    suite and is within the suite timeout. A real leak, like a test fixture left running for
    two days, is still reported once. (CMX-332)

  • A merged or closed run no longer leaves its judge behind. When a PR merged (an
    operator-approved chela merge --override included) or closed while its judge was still
    running, the daemon left that judge alone as long as its tmux window existed. A window
    holding only a bare shell counted too, so it stayed open long after the run was done and
    was mirrored into every webterm session. Now the tick that reconciles the run to done,
    closed or failed stops its judge the same way a send-back does: the round is
    cancelled, the detached run is sent SIGTERM, the judge window is killed and its worktree
    removed. A judge on a merged PR therefore no longer gets to record a blocked-race verdict
    after the merge. (CMX-336)

  • The "CI flake, re-run green" notice names the test that flaked. It used to name the
    first log line that said "failed" ("re-register failed nope", "Test Files 1 failed 1"),
    because pytest's FAILED <nodeid> line can carry a long assertion message that pushes the
    line out of the log tail chela read. The test id is now read from the whole failing log:
    pytest's FAILED/ERROR lines, or Vitest's FAIL <spec> > <suite> > <test> header (which
    wins when a JS suite ran under pytest). When no line names a test, the notice says "failing
    test not identified" next to the failing job. Flakes are recorded under that id, and
    chela ops scan now counts repeat Vitest flakers as well as pytest ones. (CMX-351)

  • The demo-env leak guard no longer fails on a machine without $LANG. Its probe
    os.environ returned a sentinel for an allowed name that was unset, so demo_env's
    os.environ.get("LANG", "C.UTF-8") saw the sentinel instead of its default and the
    nightly sweep (which runs with no LANG) went red. The probe now reads PATH/LANG
    exactly as os.environ does, and the test runs with LANG both set and unset. (CMX-364)

  • The "self-check still working" notice no longer fires on every normal hand-in. Its
    ceiling was a fixed 1h, but a normal self-check with a 2400 s budget routinely runs past
    that. Unless CHELA_SELFCHECK_ALERT_S is set, the ceiling now comes from the run's own
    WORKFLOW.md: the larger of judge.self_check_budget_seconds and
    judge.sweep_budget_seconds, plus judge.suite_timeout_seconds, times 1.5. That is 2h with
    the current budgets. When the workflow cannot be read, the ceiling is 90 minutes. The
    re-notify interval is unchanged. (CMX-365)

v0.18.2

Choose a tag to compare

@github-actions github-actions released this 27 Oct 23:51

Added

  • chela task show: a dispatched agent reads its own Linear issue. It prints the issue's
    current title, description and comments (oldest first, author and timestamp), so a comment
    on the issue now steers the next attempt. Only comments by the API key's own user and the
    workflow's linear.comment_authors are shown; bot and integration comments are dropped and
    counted. A full page says TRUNCATED, and a failed read says "could not fetch" and exits
    non-zero. It is read-only, and the key stays in chela's process, never in the agent's
    environment. A Linear workflow's dispatch and rework prompts tell the agent to run it at the
    start of every attempt. (CMX-287)

  • A notification digest with interrupt rules. Set digest.enabled in
    ~/.chela/config.json and only needs-you events (a run that needs a human, an override or
    release waiting for approval, a failed run/merge/release, CI still red on known flakes
    after its re-runs, a low-memory kill, a refused permission) are pushed to the orchestrator
    at once. Everything else is held for a digest sent at digest.times (default 09:00 and
    16:00) and on demand with chela digest: needs you (at most 3), waiting on others, done
    since last, changed — at most 150 words, a link on every line. Held events are still in
    chela events and the dashboard. Off by default; digest.interrupt replaces the default
    list of kinds that interrupt. (CMX-320)

  • chela keeper run <WORKFLOW.md> proposes board changes from the digest, and a person
    approves them.
    It builds the keeper digest, asks one headless claude -p call (no
    tools) for proposals, and checks each one in code: a proposal must cite evidence that is
    in the digest and quote it, and each item it cites must be about that issue (it names
    the id or quotes a word of the title), or it is dropped as evidence_unrelated. It must
    also use an allowed action: in_progress only from Todo or Backlog, comment,
    new_backlog_issue, flag_stale only for an In Progress issue that nothing in the
    window names, and done only when it cites a merged PR that names the issue. The survivors go to the inbox as ONE item.
    chela keeper approve <batch> [--only 1,3] applies them to the tracker, each with a
    "chela keeper" attribution comment naming the approver and the evidence;
    chela keeper dismiss <batch> writes nothing. Every proposal and its outcome is a row in
    $CHELA_DIR/keeper/log.jsonl. The Linear source can now add comments. (CMX-347)

  • A daily worktree GC. A worktree whose run closed without removing it, or whose run row
    was pruned or requeued, used to stay on disk for good (26 worktrees, 1.9 GB, 21 of them
    orphaned). The daemon now runs chela ops worktrees --gc once a day. It removes each
    worktree under a workflow's workspace.root that no run owns, or whose run has been done
    or closed for at least 3 days, and keeps the branch. A worktree with uncommitted changes is
    first saved to $CHELA_DIR/worktree-salvage/<name>-<date>.tar.gz (the diff plus its untracked
    files, without venvs, node_modules or caches). A worktree with a live run or a process
    running in it is never touched, and neither is one the pass cannot read. chela ops worktrees reports the count and size of orphaned worktrees without removing anything.
    (CMX-357)

Changed

  • A spent rework cap re-queues the task instead of waiting on a person. The first time a
    run hits its rework cap with the PR still blocked, chela closes the PR (the branch is kept),
    comments every round's survivors grouped by mutation shape, and re-queues the task as a
    fresh attempt on a new branch. The new attempt's prompt gets an addendum: what the earlier
    rounds tried, every surviving group, and an instruction to start fresh from the base and
    read the old branch for reference. Only a second exhaustion of the same task goes to
    needs_human, and the notice says it is the second. (CMX-290)

  • The public landing page, docs page and README now name the project chelamux, at
    chelamux.com.
    Page titles, Open Graph and Twitter meta, canonical links, the nav
    wordmark and the hero copy say chelamux; the command-line tool is still chela, and
    absolute chela.pages.dev links now point to https://chelamux.com. The package name,
    CLI entry point, ~/.chela and CHELA_* settings are unchanged. (CMX-361)

  • The landing site serves a robots.txt and a sitemap.xml for chelamux.com, so
    /robots.txt no longer falls back to the home page. (CMX-361)

  • The GitHub Action's display name is now "chelamux judge" (was "chela judge sweep"). The
    uses: Devail1/chelamux@v0 reference, inputs and outputs are unchanged. (CMX-361)

  • The landing page leads with the dashboard wall. One hero (every Claude Code session live
    on one screen, on desktop or phone, with recaps and a flag on the ones that need you), the
    wall demo right under the headline, and one primary call to action, Install. (CMX-362)

  • Install on the landing is one line:
    uvx --from 'chelamux[dashboard] @ git+https://github.com/Devail1/chelamux' chela dashboard, with the prerequisites
    on one small line under it. (CMX-362)

  • The merge gate and judge move to a section lower down ("And when agents open PRs…"),
    with a short mention of the GitHub Action. The early / single-user status note moves into
    the FAQ, and encrypted terminal sharing is cut to one line under the wall. (CMX-362)

  • Hotfix: a parked run's idle-clock write is committed at once, so the dispatch tick no longer locks itself out (database is locked on every tick, which stalled hand-ins and claims).

Fixed

  • A parked run is no longer re-nudged and then failed by the idle watchdog. chela park
    kept a run out of the inbox's STUCK notice, but the dispatcher's idle-at-empty-prompt
    watchdog never checked it: an agent parked and told to wait was re-sent its task prompt,
    then failed "idle at empty prompt after re-nudge" about an hour later. The watchdog now
    stands down for a parked run (a manual park, or the automatic park of a suite-slot wait)
    and restarts its idle clock on every parked tick, so after chela unpark, a reached
    --until or the park max, the agent gets the full idle budget again. (CMX-339)

  • A red nightly-sweep baseline now names its failing tests and keeps its evidence. The
    notice and nightly.log used to say only "1 failed, 11024 passed", and the JUnit report
    went with the checkout. A red baseline now lists each failing test id (the first five, plus
    a count) with the first line of its failure, says what re-running them alone showed, and
    keeps the JUnit report and the output tail in
    $CHELA_DIR/sweep-nightly/<date>/<project>/ (the newest 14 dates are kept). A failing test
    that passes when re-run alone is reported as baseline flake: <ids> and is deselected
    from every mutation run, so the battery still runs that night. The isolated re-run now
    also finds failing test ids in the JUnit report when the output tail no longer holds
    pytest's FAILED lines. (CMX-341)

  • A changelog fragment named after a reused issue number no longer turns the suite red
    late.
    Issue numbering restarted, so a new task often shares its number with an old one
    that already shipped, and its bare changelog.d/CMX-<id>.md read as an already-released
    fragment. Fragments are now named CMX-<id>-<slug>.md (slug from the branch), and
    chela task-finished refuses a bare name that collides before any suite runs, with a
    rename hint. A fragment whose text already shipped (bare or slugged) is told to go
    instead: renaming it would only hide it until it republishes. (CMX-356)

  • A watchdog write no longer locks out a later run's hand-in in the same tick. The
    dispatcher's reconcile loop left several watchdog writes uncommitted (a parked run's idle
    clock, a window-gone or idle failure, a nudge stamp), so the tick held the database write
    lock while a later run's chela task-finished request was applied on its own connection,
    which then failed with "database is locked" and stalled the queue. Each of those writes
    now commits before the loop moves on, with a two-run regression test per write. (CMX-359)

v0.18.1

Choose a tag to compare

@github-actions github-actions released this 26 Oct 23:47

Added

  • chela keeper digest <WORKFLOW.md> prints what happened in a workflow's repo since the
    last digest.
    For each live session launched in the repo or one of its worktrees it lists
    the window, its live session id, busy/idle, the latest recap, the prompts typed and the
    files edited. It also lists the commits on any branch, the PRs opened or merged, the issue
    ids mentioned anywhere, and every open issue on the workflow's tracker. It uses no LLM and
    never writes to the tracker. Each section is capped and says how many rows it dropped. A
    git, gh or tracker read that fails is shown as unreadable, never as empty. A cursor in
    $CHELA_DIR/keeper/ makes the next run start where this one ended; --since 90m
    overrides it, and --json is the input for the next keeper slice. (CMX-346)

  • An agent's own pytest run is memory-capped per test process. A suite started by
    chela task-finished or the judge runs in a memory-capped chela-suite-* scope; an
    agent's own pytest did not, and one runaway xdist worker reached 11 GB and got other
    agents' suites reaped. tests/conftest.py now sets a soft RLIMIT_AS of 3 GiB on each
    process that runs tests, unless a chela-suite-* or memcap cgroup already caps it, so
    a runaway test fails with MemoryError in its own worker. Subprocesses (node, vitest,
    Chromium) are exempt. CHELA_TEST_MEMORY_CAP=6G raises it; 0 turns it off. The
    standing rules now say never to run a full suite yourself. (CMX-354)

  • Tests no longer inherit the live CHELA_* environment (e.g. CHELA_AUTO_MERGE=true from chela.env), which turned every nightly-sweep and agent-sweep baseline red (CMX-355).

Changed

  • A blocking verdict lists every same-shape site, not only the one the judge named. Each
    surviving mutation is grouped by its shape (a dropped or swapped call, a flipped comparison,
    or the exact line) and its target symbol, and the verdict lists every line of the PR's diff
    with that shape, found from the diff rather than recalled by the model. The rework is asked
    to fix the whole group and to corrupt each site by hand before hand-in. Held-out survivors
    are never grouped or named. (CMX-335)

Fixed

  • A Telegram photo is no longer reported "not submitted" when the agent got it. The
    bridge now confirms the submit from the agent's own UserPromptSubmit hook, which fires
    when Claude Code takes the turn; the pane read is only the fallback. When neither confirms
    it, the reply says the submit is unconfirmed and asks you to check /screenshot before
    pressing Enter, since an Enter on an already-submitted prompt sends an empty or duplicate
    turn. (CMX-344)

  • The frontend gate test no longer runs the whole Vitest suite a second time.
    test_each_ci_gate_passes_the_committed_tree[test] was vitest run again, beside
    test_frontend_vitest_suite, with a 180 s budget; it timed out twice in CI while xdist
    shared the runner. That case is gone, and the remaining full Vitest run over the fixture
    copy gets the 300 s budget the main suite already had (the suite's measured p95 is 112 s
    on an idle runner). (CMX-352)

  • chela ops scan no longer reports a live suite's test PM2 daemon as leaked. It
    decided liveness by matching PM2_HOME in other processes' /proc environ, which an
    xdist worker never shows (conftest sets it after start-up), so a running suite's daemon
    was flagged, killed, and the suite hung to its timeout. A /tmp/chela-tests-pm2-* God
    Daemon is now spared while any suite of its run is live: a process in its
    chela-suite-* scope, one of the run's worktree in such a scope, or a test run of that
    worktree. Once no suite is live it is reported only on its second scan in a row, and the
    finding says "do not kill while a suite runs". (CMX-353)

v0.18.0

Choose a tag to compare

@github-actions github-actions released this 26 Oct 06:54

Added

  • Agents review their own diff before handing in. chela task-finished refuses a code
    PR whose body has no ## Review section (prose-only PRs are exempt). An agent that cannot
    review passes --no-review-reason "…", which is recorded on the run and shown to the judge
    and on the PR. (CMX-209)

  • Every judge and self-check suite run now has its own memory ceiling. chela wraps each
    run in its own systemd-run --user --scope (CHELA_SUITE_MEMORY_MAX, default 8.3G, which
    is 3x the measured 2.75 GiB anon peak of a full suite run; 0 turns it off). A run that
    blows through its ceiling is killed inside its scope and reported as CANNOT VERIFY ("suite
    exceeded 8.3G"), never as a pass. chela claims the ceiling only when the suite's own
    /proc/self/cgroup names the scope. Whatever a suite leaks into its scope, such as a
    daemon that ignores SIGTERM, is SIGKILLed when the run ends. (CMX-119)

  • At most K suite runs at once, judges and agents together. Each suite run takes one of
    K slots first (CHELA_SUITE_SLOTS, default half the RAM over the measured peak: 2 on a
    16 GB box). A queued run says ⏳ waiting for a suite slot, is served first come first
    served, and is never reported as stuck, and the wait does not count against its budgets or
    the judge's wall. (CMX-119)

  • Spike: chela's merge gate and event posting as a Claude Code Mod. mods/chela-hooks/
    runs the same gate script and posts the same event bodies as the shell plugin. It needs
    Claude Code 2.1.301+, chela never installs it, and it replaces nothing.
    docs/MODS_SPIKE.md holds the side-by-side measurements: it posts faster than the shell
    plugin, missed no events, and denied the same commands. Its recommendation is to build
    the in-session UI items as an opt-in Mod and to keep the shell plugin as the baseline.
    (CMX-157)

  • The judge keeps a failing suite's output. Every non-green infrastructure suite run in a
    judge round (the baseline, its coverage run, and the base-branch diagnostic) is written to
    $CHELA_DIR/judge-logs/<task>/<round>-suite-<n>.log with the command, cwd, env diff vs the
    operator, start/end time, exit code and up to 200,000 chars of output. Before this, only a
    one-line summary survived, so a cannot_verify caused by a flaky baseline could not be
    diagnosed later. chela judge show lists each round's logs, and a cannot_verify verdict
    line and PR comment start with a ~-relative pointer to them. A green baseline writes
    nothing. Logs older than 30 days, or past the newest 20 per task, are pruned whenever a
    new one is written. (CMX-163)

  • Settings > Dispatch has "Agents at once" and "Judges at once" rows. One stepper per
    workflow sets an operator-local override of WORKFLOW.md's concurrency.max (Reset goes
    back to the file value), and one stepper sets the judge count, which now wins over
    CHELA_JUDGE_MAX_CONCURRENT. Both are bounded 1 to 8, validated by
    /api/config/concurrency, read on the dispatcher's next tick (no file edit, no restart),
    logged to the event log as settings.concurrency (who, old, new), and shown with what the
    count means for memory (N x the measured judge peak, next to available memory). Lowering
    a count never stops a running agent or judge. judge_max_concurrent left the generic
    Dispatch knob list for this row. (CMX-164)

  • chela judge sweep mutates TypeScript and TSX. A changed .ts/.tsx file is no
    longer UNKNOWN: it gets the JS mutations (if, comparison flips, return 0/1) plus
    &&↔||, return true↔false, a ternary's condition forced each way, and JSX
    conditional rendering ({c && <X/>} → {false && <X/>}). Every mutant is parse-checked
    with the repo's own typescript package; one that does not parse is skipped, never counted
    as killed or survived. With no typescript to parse with, the sweep reports it cannot
    verify. .d.ts files and *.spec.ts/__tests__/ files are not mutated. (CMX-173)

  • A TS mutation runs only its related Vitest tests. When judge.test_cmd is one Vitest
    invocation (pnpm exec vitest run), the baseline also writes Vitest's JSON report, and each
    mutated .ts/.tsx file runs only the test files whose import graph reaches it, directly or
    through other modules (./a, ./a.js and ./dir resolve to a.ts/a.tsx/dir/index.ts).
    A TS file no test reaches is UNTESTED: it still runs the full suite, and a survivor there
    is reported as [UNTESTED], a survivor-class finding. (CMX-174)

  • The judge and the self-check grade TypeScript. chela judge run, chela judge self-check and task-finished --self-check-experiments apply experiments to .ts/.tsx
    files with the sweep's parse proof and Vitest test selection, within the same budgets. A
    TS-only PR gets blocked or clean like any other. If the repo has no typescript (or no
    node) to prove a TS file parses, the run is cannot-verify, never clean. Vitest's
    Test Files summary line no longer counts as tests run. (CMX-175)

  • A React + TypeScript frontend scaffold for the dashboard in chela/dashboard/frontend/
    (Vite, strict TypeScript, Vitest, React Testing Library, ESLint), installed by the root
    pnpm install as a workspace package. dependency-cruiser rules keep each src/features/*
    and src/shared/* module reachable only through its root entry files and forbid cycles. CI
    gains a frontend job (typecheck, lint, module boundaries, test), and the Vitest suite runs
    inside pytest too, as do tests that feed each boundary, type and lint rule a deliberate
    violation. Nothing is wired into the dashboard page yet; users still need no Node.
    (CMX-176)

  • The dashboard frontend now builds into Flask's static dir, and the build is committed.
    pnpm -C chela/dashboard/frontend build writes hashed files plus manifest.json to
    chela/dashboard/static/app/, served under static/v/<version>/app/ with the same
    cache headers as every other asset. Because the output is tracked, a wheel, an sdist and
    a plain git clone + uv sync all serve it with no Node installed. CI and pytest fail
    when it differs from a fresh build of src/, and chela release rebuilds it before it
    commits. chela release --dry-run runs every release step (frontend build included) and
    leaves no commit, tag, request or push. (CMX-177)

  • The dashboard loads its first React island. The page now loads the built frontend
    (named by the Vite manifest) before the legacy modules, and the sidebar foot shows the
    dashboard build version, rendered by React from /api/version. Legacy code publishes the
    agents list and the sidebar layout to a small shared store React reads, so components can
    be ported one at a time. docs/FRONTEND.md documents the stack, islands, the store, the
    dev loop (pnpm dev) and how the judge grades TypeScript. (CMX-178)

  • chela's status line as an opt-in Mod. On Claude Code 2.1.301 or later,
    mods/chela-hooks/ draws a status line with the context bar, the window id and an
    orchestrator or dispatched-run marker, through $.ui.status. After every turn it also
    writes the context cache the dashboard reads, through the same
    scripts/cache-statusline.sh, so loading the Mod needs no settings.json edit.
    chela install-statusline is unchanged and stays the default. The Mod's event posting
    now runs only when CHELA_MOD_HOOKS_URL is set, so a chela pane that loads the Mod does
    not file each event twice. (CMX-181)

  • A "needs you" band in the opt-in Mod. With mods/chela-hooks/ loaded (Claude Code
    2.1.301 or later), the orchestrator session shows one line above the prompt with the
    pending override approvals, access requests and runs in needs_human: counts plus the
    oldest, each linking to the dashboard. It polls the new read-only GET /api/needs-you
    every 10 s, shows nothing when the daemon is down, and hides a reading older than 30 s.
    Approving stays in Telegram, the dashboard or a plain terminal. (CMX-183)

  • Classifier denials reach the inbox straight from the opt-in Mod. With
    mods/chela-hooks/ loaded, a classic.PermissionDenied is POSTed to the new
    POST /api/inbox/denial, which files CMX-155's orchestrator-inbox notice (tool, one-line
    reason, session, window) without tailing the event log. With plugin/ loaded too, one
    denial is still one notice: both copies are keyed on its tool_use_id. A dead daemon
    never affects the tool call. Under --permission-mode dontAsk no denial fires in either
    layer. plugin/ drops nothing yet (docs/MODS_SPIKE.md §8.0). (CMX-184)

  • Settings > Telegram topics. "Telegram: delete a topic when its window closes" (off by
    default) makes the bridge delete a dead window's topic instead of archiving it, falling
    back to archive if the delete is refused. "Delete archived topics (N)" deletes every
    remembered archived topic after a confirm, never a bound one or General. Both apply on
    the bridge's next tick, no restart. (CMX-204)

  • Risk tiers for the merge gate. A PR whose every changed file is prose (text under
    docs/, a changelog.d/ fragment, a top-level *.md other than WORKFLOW.md/CLAUDE.md/
    AGENTS.md, or an image or video) gets no judge round. The dispatcher records the verdict
    prose_only on its head, and chela merge accepts it once CI is green and GitHub says
    MERGEABLE. A PR that touches an always-human path (WORKFLOW.md, .github/workflows/**,
    plugin/**, the merge gate and release code, plus any workflow's merge.always_human)
    needs an operator-approved --override even with a clean judge, and both the refusal and
    the override question name the path. If the file list cannot be read, the PR is never
    treated as prose. (CMX-208)

  • chela dispatch --pause --all quiets the whole fleet. It holds the queue, then
    presses Escape in each dispatched agent's window and tells it to start nothing until
    "resu...

Read more

v0.17.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 07:00

Added

  • chela judge sweep --github publishes a sweep to GitHub Actions with only
    GITHUB_TOKEN: it writes the job step summary and the verdict / survivors step
    outputs, and keeps ONE sticky PR comment — found by an HTML marker and edited in place, a
    new one posted only when the result is not clean. --no-comment skips the comment. A
    comment list that cannot be read is an error, never a duplicate post, and publishing never
    changes the sweep's exit code: every failure is a ::warning:: line. (CMX-97, CMX-132)

Changed

  • The README shows how to try the judge on your own repo with no setup: the
    uvx … chela judge sweep --base origin/main one-liner, a real sample run, the GitHub Action
    snippet and the exit codes. Without --base or a workflow, chela judge sweep now diffs
    against the remote's default branch (origin/HEAD), else origin/dev. A standalone run no
    longer suggests chela task-finished --sweep-equivalent, which only exists in a chela-managed
    run (one with a WORKFLOW.md). (CMX-134)

Fixed

  • chela watch @N from a side session no longer steals a live orchestrator's pin. It now
    claims the role only when the pin is empty, undeliverable (dead window, new tmux epoch) or
    already the caller's; otherwise it adds the watch routed to the current orchestrator and
    prints watching @N — events go to the orchestrator @M (live). A bare chela watch from a
    non-orchestrator gets the --if-unclaimed refusal plus a hint. Taking the role from a live
    holder is explicit: chela watch [@N] --take-over. (CMX-138)

  • The landing deploy's "production serves this commit" check no longer fails on every
    deploy since Cloudflare Web Analytics was switched on.
    Cloudflare injects its beacon
    <script> into every page chela.pages.dev serves, so the served HTML never matched
    landing/index.html again and each deploy went red with a misleading "most likely a
    PREVIEW deployment" error. The comparison now lives in scripts/landing_verify.py, which
    strips only that beacon element before the whitespace-normalised token diff. Any other
    difference still fails, down to one character of body text. The workflow's verify step is
    tested by running its own run: block with curl/sleep stubbed. (CMX-149, #692)

  • A PR queued behind another judge no longer gets the "NO judge round has started" stall
    notice.
    With judge_max_concurrent saturated by its workflow's running judges, a run in
    awaiting_review is a queue, not a stall: it stays silent for three
    stall bounds (CHELA_JUDGE_NOT_STARTED_ALERT_S × 3, 1h by default), then says once that it is queued behind which
    run, judging since when, and how many slots there are. The stall clock restarts from the
    last tick that saw it queued, so a freed slot gets the full 20m before the stall notice
    fires. The stall notice no longer tells the operator to run chela judge run CMX-N, which
    needs the judge agent's --experiments and is not how a round starts. It now says the
    daemon starts the round on its next dispatch tick and points at chela dispatch --hold-status. A test runs that argv through the real CLI parser. (CMX-150)

v0.16.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 15:14

Added

  • Hold Alt to number the sidebar sessions; Alt+1..9 opens that row — like the Claude
    desktop app's Ctrl-held numbers (Ctrl+1..9 is the browser's tab switch). The numbers go on
    the first nine visible session rows, top to bottom; collapsed groups and the Archived
    section are skipped. Alt+N now works in every mode, not only the wall, and from inside a
    focused terminal pane. The wall panes' corner numbers are gone — the sidebar's is the one
    numbering. (CMX-85, #657)

  • Pending approvals on the dashboard — no Telegram needed. A ⚖️ pill on every view counts
    the override (and, once CMX-87 lands, release) requests waiting on you; its sheet shows
    what, who asked, why, age and time to expiry. Approve (a second tap confirms) and Deny go
    through the same decide path as /override/<id> and re-check head / CI / mergeable /
    expiry; a request that can't be approved says why instead of offering the button. Live over
    SSE, with an opt-in browser notification (Settings → Notifications, off by default).
    chela merge --override now prints the dashboard's absolute URL (CHELA_DASHBOARD_URL,
    else the bind address) and chela merge-approve, and names Telegram only when a bridge is
    configured. A dashboard decision is recorded as decided_by: dashboard (or
    dashboard:<tailnet login>). (CMX-103, #667)

  • The orchestrator hears every override and release decision. Approving or denying an
    override or a release now sends one inbox notice: merged (the commit, who approved and
    where, and the deploy step: which chela services to restart, or the changed paths in
    another repo), released (version, tag, what was pushed), or denied (by whom, where, the
    reason). (CMX-123, #676)

  • chela task-finished refuses an oversized diff. It measures the branch's changed lines
    against its base (code + tests; docs, media and changelog excluded via
    hand_in.size_exclude) and refuses over hand_in.max_pr_lines (default 300) BEFORE the
    sweep runs, printing the measured count. A diff that truly cannot be split may pass
    --oversize-reason '<why>', which is recorded on the run, shown to the judge and appended
    to the PR body. An unmeasurable branch refuses rather than passing. (CMX-126)

  • chela task-finished --split-plan <file> hands in slice 1 of an oversized task. With a
    plan ({"slices": [{title, files, behaviour, tests}, …]}, slice 1 = this PR, the rest
    parked on the branch <task>-rest), the hand-in passes the size gate only once this branch
    is back under hand_in.max_pr_lines. chela then files one Linear sub-issue per remaining
    slice under the run's issue, chained so they run in order, and posts a split_filed inbox
    notice. A failed tracker write refuses the hand-in and names what was already filed.
    (CMX-140, #683)

  • chela task-finished --handoff "<note>" leaves a note for the reviewer. The agent's
    note (what changed, what to look at first, what it assumed) is stored on the run
    (runs.handoff) and shown, sanitised, in the orchestrator's inbox notice and on the
    Dispatch board card. It is informational only and never gates anything. (CMX-52, #653)

  • A claude you type by hand into a chela window gets Remote Control too. While the
    Remote Control setting is on, every window chela opens for a human ("+ New shell", the
    dashboard launcher, Telegram /new, a resume) defines a claude shell function, so a
    hand-typed claude … starts with --remote-control <window name> and shows up in the
    Claude desktop app under the window's current name. -p/--print, help/version, a
    prompt or subcommand, or an explicit --remote-control are passed through unchanged.
    Dispatched agents and judges never get it. Turning the setting off affects windows opened
    afterwards; an already-open shell keeps its function until it exits. (CMX-84, #646)

  • The orchestrator now hears what it used to find by hand. One inbox notice each, never
    repeated on a later tick: a run waiting in review with no judge round started past
    CHELA_JUDGE_NOT_STARTED_ALERT_S (default 20m); a dispatch hold that lapses by its TTL or
    is released by someone else; CI red on the PR's head, naming the failing job, the first
    failing line and the gh run rerun … --failed command (the rework the CI loop sends is no
    longer a second notice); a PR that turns CONFLICTING with its base (once per head); a
    running agent with no commit and no transcript activity past CHELA_STUCK_AGENT_ALERT_S
    (default 20m); and a task-finished / push-request marker left unapplied past
    CHELA_MARKER_UNREAD_ALERT_S (default 5m), with the daemon's uptime. (CMX-86)

  • chela release X.Y.Z --theme "…" — the orchestrator prepares a release, the operator
    approves it with one tap, chela pushes.
    In its own worktree off origin/dev (refused
    unless origin/main is an ancestor) it runs the release steps — the notes, the
    pyproject.toml bump, uv lock (which must re-record the new version), the release tests —
    then commits and tags LOCALLY and files an approval request. It pushes nothing; a failed or
    dirty step refuses with the reason and leaves nothing behind. The request is a Telegram card
    (version, theme, the dev commit, the task ids in the new CHANGELOG section, Approve / Deny),
    the dashboard's /override/<id>, or chela merge-approve <id>. Approving re-checks that
    origin/dev is still the commit the release was cut from and origin/main an ancestor,
    then pushes main, the tag and dev — fast-forward only, once — and the card ends on the
    released commit and, once the Release workflow built it, the GitHub Release's URL. A push to
    dev expires the request; deny, expiry or a failed check push nothing and remove the local
    tag. The approval stays human-only: the merge-gate hook (plugin 0.2.9) also denies a
    session's python call into chela.release.complete. A staged file outside the release
    paths (CHANGELOG.md, pyproject.toml, uv.lock, changelog.d) refuses the release too. See
    "Releasing" in CONTRIBUTING.md. (CMX-87)

  • chela judge sweep: a mechanical mutation pass over every conditional the branch
    changed.
    For each changed line in .py/.js/.mjs (not tests) vs --base (default
    origin/<workspace.base_branch>), it rewrites if X to if False and (X) (plus the
    True or (X) twin when there is an else), swaps a return/CompletedProcess code
    0↔1, swaps return None↔return <expr> in a function that has both, and flips
    ==↔!=, <↔>=, >↔<=. Each mutation goes through the judge's own
    apply/read-back/parse/per-mutation-selection mechanics and is restored in a finally. A
    recovery marker (.chela-sweep-recovery.json) means a killed sweep is undone on the next
    start. It exits 1 on any survivor. A mutation that does not parse is reported as skipped,
    never as killed.

  • chela task-finished runs the sweep and refuses while a survivor remains. To accept a
    survivor, the agent names it with a reason:
    --sweep-equivalent '<file:line>: <why no outcome can change>'. The note is stored in
    runs.sweep_equivalents, shown to the judge and appended to the PR body (one line per note — a newline in the reason cannot forge an
    extra bullet). A note that names
    a line which did not survive is refused. The judge itself is unchanged. (CMX-88)

  • chela judge sweep runs on any git repo, with no chela setup. No WORKFLOW.md, run
    record, daemon, tmux or tracker: the suite is --test-cmd, else the workflow's
    judge.test_cmd, else detected (pytest / npm test / go test / cargo test); --json
    prints the result as JSON. A changed file in a language the sweep cannot mutate (anything
    but Python and .js/.mjs) is reported as UNKNOWN (exit 3), never as a pass. Run
    standalone, the sweep does not provision the repo's environment. (CMX-97)

Changed

  • Docs: the README / landing demos are re-recorded for the new sidebar. The desktop clip
    now shows the sidebar as it is today — folder groups with "Needs you" on top, the
    orchestrator badge, hold-Alt row numbers, and a finished session archived (its window
    closes) and unarchived (resumed in a new window) — before the Work view and Settings; the
    phone clip is re-shot on the current pill switcher. Same file names. The demo recorder
    gained what that needed: the synthetic fleet registers an orchestrator, its fake claude
    answers claude --resume <id> by re-running that scripted session, and record.mjs can cut
    over a transient instead of freezing on it. (CMX-100)

  • chela judge sweep has a budget, so task-finished can no longer block for as long as
    the diff is big.
    The sweep's cost was mutations × suite time; one hand-in ran 24+
    minutes. It now stops at a wall-clock budget (judge.sweep_budget_seconds, default 900)
    or a mutation cap (judge.sweep_max_mutations, default 60), whichever comes first; 0 or
    unset means the default, and chela judge sweep --budget-seconds/--max-mutations override
    both. Mutations run conditionals first, then return codes and values, then comparison
    flips, round-robin across files. Each mutation the budget did not reach is listed as
    [UNRUN]: CANNOT VERIFY, never counted as killed. chela judge sweep exits 2 on a partial
    sweep with no survivor. task-finished still refuses a measured survivor, but proceeds past
    the unrun remainder and reports it as "PARTIAL", never as a pass. (CMX-111)

  • The agent brief now has a guard-category checklist to walk before hand-in. Step 3 of
    WORKFLOW.md's agent prompt lists seven categories the agent checks its own diff against
    (except clauses, subprocess calls, literal values and regexes, kind branches, fail-closed
    reads, flag precedence, the authoritative record), writing one test per item that applies
    and naming the rest in the PR body. CMX-87 took 13 judge rounds because each round found
    the next gap in a category the tests co...

Read more

v0.15.0

Choose a tag to compare

@github-actions github-actions released this 11 Oct 17:50

Added

  • chela close <run> --requeue makes a closed task claimable again. The next tick claims
    a fresh attempt (attempt 1) on its own -r<N> branch and worktree, never the closed run's.
    The old PR is closed, the requeue is recorded in the review history, and a run whose PR
    merged is refused. Without --requeue, close stays terminal, and it now says so: it
    prints that the task will not be re-dispatched and the command that requeues it. Moving
    the issue back to Todo by hand does not requeue it. (CMX-65, #627)

  • The Cost tab has a Usage view: what is eating the plan limits, in tokens. A "Cost |
    Usage" toggle shows the 5h and 7d limit bars (from the freshest statusLine rate_limits,
    with a %/h burn rate and whether it reaches 100% before the reset; missing, stale or reset
    data shows as unknown, never 0%) and the top token consumers over the last 30 min and today
    (UTC) across EVERY Claude Code transcript — judges, subagents, background sessions and, on
    WSL, Windows-side ones (/mnt/c/Users/*/.claude/projects by default; set usage_extra_roots
    in ~/.chela/config.json or CHELA_USAGE_EXTRA_ROOTS, [] for none — the view shows the
    roots read-only). Each row has its requests, token kinds, a weighted total and cache-hit
    rate; a session under 50% hit over ≥10 requests and >1M tokens is flagged ▲ CACHE BROKEN. (CMX-38, #618)

  • A held decisions inbox, or a clean PR nobody merges, now pushes one ntfy alert. When the
    inbox queue's oldest event has waited past CHELA_INBOX_HELD_ALERT_S (default 30m), the
    announcer pushes once per held episode, naming the orchestrator's status and the last failed
    delivery (an open prompt, an unreachable socket, an adverse receipt). A run that stays
    judge-clean on its current head and unmerged past CHELA_CLEAN_UNMERGED_ALERT_S (default
    30m) pushes once. chela doctor gains an inbox.held fact that goes red while the queue is
    held. (CMX-26, #610)

  • chela spawn <cwd> [--command CMD] opens an agent window from the terminal. It runs
    through the same spawn_window path as the dashboard launcher and Telegram /new. With no
    --command it launches $CHELA_AGENT_CMD (default claude), not the bare shell that
    spawn_window opens on its own. It prints the new @N and cwd, and adds the cwd to the
    launcher's Recent list on a best-effort basis. (CMX-13)

  • chela close @N kills one chela window, so nobody has to reach for raw
    tmux kill-window.
    It refuses a window that is not live in the chela session, the
    orchestrator's own window unless --orchestrator is passed, and a window that an
    in-flight dispatched run still claims unless --force is passed. For that last case,
    chela close <task> --reason … is the right tool. Closing a run by id works as before.
    (CMX-13)

  • A profile of chela's services under normal load (docs/perf/2026-10-profile.md).
    The dashboard spends most of its CPU starting processes. About 90% of its tmux and pgrep
    spawns come from /api/agents and /api/agents/context, which probe each window
    separately every 4 s per open Wall tab. The cost grows linearly: 7% of a core at 5
    windows, 73% at 40. The report ranks three fixes: batch the probes through the existing
    sessions.panes(), remove a 36-second CPU burst in chela doctor, and have Telegram
    capture only panes that changed. It also lists what is not worth doing, including
    swapping Werkzeug for waitress. The isolated harness that produced the numbers is in
    docs/perf/2026-10-harness/. No production code changed. (CMX-15)

  • chela doctor fact tmux.leaked_env. It goes red when tmux's global env holds a
    proxy aimed at a dead localhost port, a Claude session marker, or a TMUX_TMPDIR whose
    dir is missing. The daemon's doctor sweep pushes it once through the notify path.
    (CMX-21)

  • The perf harness (docs/perf/2026-10-harness/) now passes tmux -L chela-perf on every
    call. It refuses kill-server on the default socket, starts the ttyd supervisor under
    env -i with an allowlist inside its own process group, and stop.sh reaps that whole
    group. (CMX-21)

  • A push when an agent is blocked by its permission classifier. An auto-mode denial
    never shows a prompt, so the session never reaches waiting and the needs-input push
    stayed silent. notify.DeniedWatch now tails hook.permission_denied events and sends
    one ntfy push per window per 10 minutes, from whichever process holds the announcer
    lease (daemon or dashboard). (CMX-25, #609)

  • A judge window shows its detached mutation battery's live progress instead of
    "idle".
    While chela judge run --detach runs, the judge pane's state pill reads
    ⚖️ testing · 3/6 · 15m (its status dot reads working), and chela peek on the window adds a
    judge: line with the KILLED/SURVIVED/INVALID tally and the experiment running now (a held-out one
    is never named). A run whose process died before a verdict reads ⚖️ judge run died — no verdict, never idle. The badge disappears when the run finishes. (CMX-40)

  • chela close <run> --reason "…" — walk away from a run on purpose, with a visible
    reason.
    A run that was superseded or abandoned used to stay on the Work view as a
    misleading FAILED card (for example "tmux window disappeared" after its idle window was
    closed). Now it is marked closed: it is never claimed or retried again, its slot is
    freed, the reason goes in its review history and a run_closed event, and the card reads
    "Closed — ". It accepts failed, needs_human, changes_requested,
    awaiting_review, running and claimed runs. A running or claimed run is refused
    unless its window is gone or its agent is idle; --force overrides that. An open PR gets
    the reason as a comment, --close-pr also closes it, and on a terminal with neither
    --close-pr nor --keep-pr you are asked. The branch is never deleted, and the worktree
    is kept unless you pass --remove-worktree. Restart chela-daemon and
    chela-dashboard to pick it up. (CMX-406)

  • chela judge eval — an offline eval of the judge's experiment DESIGN. It runs only
    the design step (the live judge prompt at each risk level, and the model returning its
    experiments JSON) against a read-only copy of a PR's tree. It never runs a test suite.
    Per risk level it reports recall on seeded and historical real regressions, the share of
    proposed experiments that are contrived, validity (each experiment is checked with the
    judge's own apply_mutation / parse_check), grader flips, and a decoy negative control.
    Every score has a 95% interval. The dataset is 85 historical cases mined from this repo's
    judge verdicts plus 4 seeded regressions. It is split train/test by PR, and a test-split
    run prints aggregates only. The cost estimate prints before any model call, and nothing is
    spent without --yes. --limit, --max-cost and a per-call budget cap the rest.
    Subcommands: mine, label, controls, spot-check, agreement. It makes no change to
    how the judge behaves. See docs/JUDGE_EVAL.md. (CMX-408)

  • Drop or paste a file into a Wall terminal. Dropping a file on a terminal pane, or
    pasting one (a screenshot, "Copy image"), now saves it to that session's
    uploads/ folder and types @uploads/<name> into the prompt, without sending it, so
    Claude can read a file that lives on your phone or laptop and not on the host. A small
    toast names the saved file, and an error toast explains a refusal. Files are never
    overwritten (-1, -2… suffixes). A name with a path separator, .., or a leading dot
    is refused, and so is a symlinked uploads/. Uploads are capped at 25 MB each
    (CHELA_UPLOAD_MAX_MB) and 20 per minute (CHELA_UPLOAD_PER_MINUTE). Every upload
    records an upload.saved or upload.refused event. It is for the operator only: a
    share guest's request is refused whatever Guest typing says. Turn it off in
    Settings → General → File drop into terminals (on by default, or CHELA_FILE_DROP).
    Restart chela-dashboard to pick it up. (CMX-412)

  • Every pane shows its tmux window id (@N). The orchestrator, chela peek @N, inbox
    notices and peer messages all address agents by @N, and the dashboard now shows it too:
    as a small, dim @N at the start of each pane's bottom bar (click it to copy, with a
    "Copied" toast), on each sidebar session row's second line and in its tooltip, and in the
    ⌘K / "Jump to session" palette, where typing @32 jumps straight to that window. The id
    stays on one line at desktop and phone widths, however long the session or branch name.
    It is the same id chela status prints and renumbers after a tmux restart, as tmux does.
    Restart chela-dashboard to pick it up. (CMX-417)

  • Web access for a sandboxed share session (opt-in, per session). Start one with
    New session → Sandboxed session · allow web access… or
    chela share-session <project> --web. The default is still no network. The guest
    container stays alone on its --internal network with no host address. One more
    sidecar joins it: chela/share_web_proxy.py, a filtering HTTP(S) forward proxy that
    carries all the guest's web traffic.

    • The proxy resolves each name itself and refuses any private, loopback, link-local,
      CGNAT, host or embedded-IPv4 address after resolution. It then connects to the
      address it checked, so there is no DNS-rebinding window.
    • It allows ports 80 and 443 only.
    • It rate-limits each site (1 request/s by default) and all sites together.
    • It logs every request to ~/.chela/share-web/<session>.log.
    • Optional CHELA_SHARE_WEB_DENY / CHELA_SHARE_WEB_ALLOW lists. Unset means any
      public host.

    The guest runs a locally built chela-share-web image with headless Chromium and a
    browse command. Its WebFetch, curl and pip go through the proxy. The pane shows
    🌐 web. The live share check now ...

Read more

v0.14.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 14:43

Added

  • --remote-control on the sessions chela opens for a human. Windows started by
    spawn_window (Telegram /new, the dashboard launcher, resumed sessions) and the
    orchestrator persona now launch with Claude Code's --remote-control <project>, so they
    are reachable from claude.ai too. Dispatched agents, reworks and judges never get it.
    Default on; set CHELA_REMOTE_CONTROL=false to disable. (CMX-375, #526; guards hardened
    in CMX-376, #527)

  • A warm dashboard theme (Settings > Appearance): warm greys, an off-white accent,
    Okabe-Ito sky/orange status colours, and a matching warm terminal palette whose 16 ANSI
    colours each keep their own hue family. (CMX-381)

  • Settings → General → Remote Control: an on/off switch for --remote-control on new
    sessions.
    CHELA_REMOTE_CONTROL was an import-time constant, so turning it off meant
    editing the env and restarting the dashboard, Telegram bridge and daemon. It is now
    config.remote_control_enabled(), read per call through dashboard_setting() — env beats
    ~/.chela/config.json (remote_control) beats the unchanged ON default — so a toggle in
    the dashboard reaches every process without a restart. It applies to NEW windows only
    (dashboard launcher, Telegram /new, the orchestrator persona); a running session keeps
    the flag it launched with, and dispatcher agents/judges still never get it. When
    CHELA_REMOTE_CONTROL is set, the switch shows the effective value, is disabled, and says
    so. examples/chela.env no longer exports it by default, since an exported value locks the
    switch. (CMX-382)

  • A real-browser suite for the dashboard's layout, status shapes and fonts
    (tests/browser/, Playwright + headless Chromium). jsdom has no layout engine, so the
    CMX-377 restyle's cascade-emulating guards kept missing one more CSS way to break the look
    (grid-row: 2, .app not a grid, opacity: 0, scale(0), a transparent ring border,
    an inset shadow filling the idle ring). The new suite boots the real index.html shell
    against stubbed API fixtures (no daemon, no tmux, no network) and asserts what a user
    sees: bounding boxes at 1440×900 and 390×844, each status mark's greyscale painted
    silhouette (same in the sidebar row and the pane header, four states pairwise different,
    never empty), and that Geist actually loads. npm run test:browser locally; CI caches
    Chromium and runs it inside pytest, where CHELA_REQUIRE_JS_TESTS=1 makes a browser
    that cannot launch a failure. (CMX-383)

  • Ctrl+, (⌘, on macOS) opens and closes Settings, like VS Code — from the dashboard
    and from inside a focused terminal pane (the same pane shim that carries Ctrl+K), and
    Esc now closes Settings too. Listed in the keyboard-shortcuts cheatsheet. macOS Chrome
    may keep ⌘, for its own preferences. Needs pm2 restart chela-dashboard to pick up the
    pane shim. (CMX-385)

  • The merge rule is enforced inside chela: a direct gh pr merge is denied in every Claude session. chela's plugin (0.2.5) carries a second PreToolUse hook on Bash (chela/mergegate.py, a stdlib-only command hook that decides locally from $CHELA_DIR/mergegate.json — no dashboard needed) that DENIES gh pr merge, gh api …/pulls/N/merge and a git push to the workflow's base branch or main in any repo with a chela workflow, pointing the session at chela merge cmx-N. The command is parsed (chains, bash -c, cd x &&, -R, worktrees), and the hook fails open — logged to $CHELA_DIR/mergegate.log — when it cannot decide. ⚠️ Restart every agent and orchestrator session (and claude plugin update chela@chela) to load it; the orchestrator's own git push origin dev is now denied too. (CMX-389)

  • chela merge cmx-N --override --reason "…" — the one way past the judge, operator-approved and audited. It waits CHELA_OVERRIDE_WAIT_S (default 300s) for the operator to approve on the dashboard (/override/<id>) or with chela merge-approve <id> in a plain terminal; a timeout is a deny. The approval (who, head sha, overridden judge state, reason) goes to the event log and the run's review history before the merge, the merge is pinned to the approved head, and the reason rides in the squash body. CI red or not-mergeable still refuse. (CMX-389)

  • Out-of-gate merges are flagged. When the reconcile finds a chela PR merged without a clean judge on its merged head and without an approved override, it sets runs.merged_outside_gate and the inbox pushes ⚠️ cmx-N was merged outside chela's gate — once. (CMX-389)

  • The judge holds some experiments out, and runs a sample twice. The judge agent now
    tags about 30% of its mutation experiments "held_out": true (at least one once it
    proposes three or more; set per workflow with judge.held_out_fraction). They run like
    any other, and a held-out survivor still sends the PR back. The PR comment, the review
    history and the rework prompt only ever say how many held-out guards survived, never
    which, so a rework has to fix the guard itself instead of patching the listed cases.
    Separately, up to two experiments (survivors first; judge.consistency_sample) are run a
    second time. One whose KILLED/SURVIVED outcome flips is marked flaky in the comment and
    does not count toward blocking. If nothing else blocks, the verdict is cannot_verify and
    the operator decides. Each round is recorded privately under
    $CHELA_DIR/judge-heldout/. The new chela judge show <run> prints rounds-to-clean,
    visible vs held-out survival rates and the flip rate; add --held-out to see each
    held-out experiment in full. Needs a chela-daemon restart to pick up. (CMX-395)

  • Search in Settings. A search box at the top of the Settings drawer filters every
    setting across all tabs, VS Code style. It matches a setting's label, its help text and
    hidden keywords (for example, "phone" or "mobile" finds Remote Control). While you
    search, the matching rows are grouped under their tab name and no tab is selected.
    Clearing the search returns you to the tab you were on. Ctrl/⌘+, opens the drawer with
    the search box focused. Esc clears the query first, and a second Esc closes the drawer.
    A query with no results says "No settings match '…'", and screen readers hear the live
    count ("3 settings"). No setting's behaviour, storage or API changed. Needs a
    chela-dashboard restart to pick up. (CMX-396)

  • chela watch --if-unclaimed — a SessionStart hook can re-claim the orchestrator slot
    after a reboot without ever stealing it.
    It registers the calling session exactly like
    chela watch only when the pin is empty, undeliverable (dead window or stale tmux epoch —
    the inbox's own address_state), or already the caller's; otherwise it changes nothing,
    exits 0 and prints orchestrator is @N (live) — not claiming. Plain chela watch is
    unchanged. The recommended hook line is in docs/HOOKS.md. (CMX-399)

  • "Settings" is now a row in the Ctrl/⌘+K palette — and so in the phone sidebar's
    "Jump to session" box, which drives the same list.
    On a phone there is no Ctrl+,, so
    this was the missing way into Settings. Type "sett" (or "settings") and pick it; it
    opens the Settings drawer. Restart chela-dashboard to pick it up. (CMX-401)

  • scripts/demo/record.sh re-records both demos. It runs scripts/demo/fleet.py, a throwaway fleet on its own tmux server (tmux -L chela-demo) with its own HOME and CHELA_DIR under a fixed demo root (/tmp/demo) and a from-scratch environment. The fleet's panes are scripted, and its claude only reports their statuses. It looks like a healthy install on camera: chela runs from a git checkout with an upstream (Update reads "up to date"), the demo's own daemon is running, and the "+" menu's Favorites and Recent list the demo projects. Playwright then records the fleet, and ffmpeg writes the MP4s and GIFs over the published files. tests/test_public_media.py fails if a README or landing page names a removed view, if any file under docs/img/ or landing/ contains the operator's user name, a private window name or a /home/ path, or if the recorder's environment stops being isolated. (CMX-402)

  • Shared terminals are view only by default; guests can type only into a verified
    sandboxed session.
    A new Guest typing setting (Settings → Collaboration,
    CHELA_SHARE_TYPING, off by default) gates all guest input on the host. With it on, a
    share can allow typing only into a window that verifies LIVE as a sandboxed session,
    checked from the process tree and docker inspect at least every 2 s. Refused input
    is dropped, and the guest gets a rate-limited "view only" notice. Start a sandboxed
    session from New session → Sandboxed session… (desktop and phone) or with
    chela share-session <project>. The new share dialog defaults to View only and
    shows why Allow typing is disabled. The share pill shows 👁 or ⌨. See
    docs/SHARE_SANDBOX.md. (CMX-403)

  • Trusted-peer "Full access — UNSANDBOXED" override, one share at a time: you type
    the window name to confirm it, it expires after CHELA_SHARE_UNSANDBOXED_MINUTES
    (default 30), it's bound to one joiner, it shows a red banner on the pane and the
    share pill, the share kill switch revokes it, and the event log records the grant,
    expiry and revocation. (CMX-403)

  • Per-task risk levels (high / normal / low) that size the judge's search and the
    rework budget.
    Mark a TODO bullet <!-- risk: low --> (or give a GitHub issue a
    risk:low label); unmarked tasks are normal, or high when the brief's BOUNDARIES touch
    the dispatcher, judge, merge gate, sandbox, inbox or secrets. The judge runs at most 12 / 8
    / 4 experiments and a run gets 5 / 4 / 3 rework rounds by level — all Settings → Dispatch
    knobs. The judge still always runs, and a surviving mutation still blocks at every level.
    The level shows on the Work card, th...

Read more

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 05:24

Added

  • The sandbox boundary for coding and rework agents ships, off by default (issue #502).
    docs/SANDBOX_BOUNDARY.md §6.3's frozen config — sandbox.enabled, a narrow
    filesystem.allowWrite, network.strictAllowlist with a measured allowedDomains list,
    and deny on the GitHub token file — is now written to a chela-owned
    <claude config dir>/chela-sandbox.settings.json and passed as --settings <path> to a
    dispatched coding or rework agent whose workflow opts in with agent.sandbox: true in its
    WORKFLOW.md. The judge is excluded unconditionally (role="judge" short-circuits before
    the opt-in check), since it runs a fixed command in a throwaway tree and never pushes,
    while a coding agent takes an arbitrary task description and pushes to a public repo.
    CMX-366 (the completion hop) and CMX-368 (the push/PR-open) already moved the two
    privileged effects that would have made this cosmetic onto the daemon; this lands the
    config itself. A workflow that has not opted in launches byte-identically to today.
    (CMX-369, #518)

Changed

  • The completion hop is now the daemon's write, not the dispatched agent's (issue #502).
    chela task-finished used to run UPDATEs against ~/.chela/scheduler.db and kill the
    agent's tmux window inside the agent's own process — the single thing blocking OS-level
    sandboxing of dispatched agents, since an agent that can write the runs DB can rewrite every
    other run's row, and one that can reach the tmux control socket can send-keys into any
    window, including the operator's, with no from attribution. chela task-finished now
    writes a request marker into the run's own worktree (a path the agent can already write,
    sandboxed or not); tick(), unsandboxed and already polling every run, picks the marker up
    and performs the transition to awaiting_review and the window kill on the daemon's side. A
    request the daemon cannot apply for an unexplained reason (not: someone else already settled
    the row) now escalates the run to needs_human and keeps the marker as evidence, instead of
    logging a warning nobody reads and leaving the row running forever.
    (CMX-366)

  • The push and the PR open are now the daemon's, not the dispatched agent's (issue
    #502).
    Done Criteria step 5 used to run git push then gh pr create inside the
    agent's own process
    — the second blocker (after CMX-366's completion hop) standing
    between OS-level sandboxing and being cosmetic. Measured with a matched unsandboxed
    control (docs/SANDBOX_BOUNDARY.md §5 B2): masking the GitHub token keeps gh working
    (it sends Authorization: token <token> verbatim, so the sandbox's substring
    substitution catches it) but not git push (it sends Authorization: Basic base64(user:token), so the token is never present verbatim and nothing is substituted —
    GitHub rejects the push). chela request-push <task_id> [--pr-title T --pr-body-file P]
    now writes a request marker into the run's own worktree instead (a path the agent can
    already write, sandboxed or not); tick(), unsandboxed and already polling every run,
    picks the marker up, pushes the branch, and — only when the run has no pr_url yet —
    opens the PR, so the token never has to enter the agent's process at all. A request that
    keeps failing gets 5 ticks to succeed before it escalates to needs_human, preserving the
    marker as evidence, instead of retrying forever and pinning the run's concurrency slot.
    (CMX-368)

Fixed

  • npm-shared-install.sh now checks whether the declared npm packages actually resolve,
    not whether the shared node_modules is empty.
    The previous freshness check
    (-z "$(ls -A ...)") still read a partially-unpacked shared install — e.g. one left by an
    npm ci interrupted mid-unpack — as "already installed" if it had any content at all, so a
    worktree could symlink into a shared node_modules still missing jsdom. The check now
    reuses chela.judge's own declared_npm_packages / _unresolvable helpers. (CMX-367, #510)

  • ensure_schema now names the missing column and the underlying sqlite reason when a
    migration genuinely fails
    (a read-only connection whose schema is missing a column,
    e.g. a sandboxed agent's denied ~/.chela write). Anyone running chela against a database
    they cannot write now gets a loud, specific SchemaMigrationError instead of an inscrutable
    no such column surfacing later, elsewhere. (CMX-370, #519)

  • context.ensure_schema now names the missing column and the underlying sqlite reason when
    a migration genuinely fails
    (a read-only connection whose context_snapshots schema is
    missing a column, e.g. a sandboxed agent's denied ~/.chela write). Anyone running chela
    against a database it cannot write now gets a loud, specific SchemaMigrationError instead
    of an inscrutable no such column surfacing later, elsewhere. (CMX-371, #520)