Skip to content

v4.8.0 — The space survives, the machine has a board, the council can be measured

Choose a tag to compare

@github-actions github-actions released this 28 Sep 10:50
· 48 commits to main since this release

This release decouples agent storage from disposable plugin paths into durable,
host-bound spaces (~/.conscio/instances/<slug>), introduces a machine-level
ambient task board swept by the relay reactor, and delivers a calibration harness
with an opt-in typed decision judge for the four-voice council.

Added — Durable space (v4.8 S1)

Decouples agent state from disposable plugin directories (which are wiped on
updates) by binding spaces per host identity under ~/.conscio/instances/<slug>.
Existing plugin-bound spaces remain untouched until migrated; fresh installations
mint into durable space directly.

  • Pure space resolver (conscio/installer/durable.py). resolve_space(storage, env)
    classifies the environment across states B0–B6 and migration locks without
    side effects (repair_pointer=True signals the caller to create or update the
    pointer):
    • B0 (Legacy): unmigrated plugin space; prints stderr announcement to run
      conscio space migrate and uses the legacy path byte-for-byte.
    • B1 (Bound): valid space-pointer.json pointing to an existing durable
      target; follows the pointer.
    • B2 (Adopted): plugin directory wiped without local identity, but durable
      space exists; adopts the durable space (repair_pointer=True) and warns to
      re-arm the relay reactor if migrated-from.json tombstone is present.
    • B3 (Conflict / Divergence): two copies found (plugin and durable);
      refuses boot with a message distinguishing identical IDs (copies may have
      diverged) from distinct IDs (identity conflict).
    • B4 (Residual evidence): previous identity evidence found (tombstone or
      local relay card) but no live space exists; refuses boot without minting
      silently. Fails closed with an explicit error if the relay directory cannot
      be read.
    • B5 (Dangling pointer): pointer exists but target is missing; refuses
      boot.
    • B6 (Fresh mint): neither space nor evidence exists; mints into durable
      space under exclusive flock with repair_pointer=True.
  • Pointer minting with exclusive flock (conscio/installer/spaces.py).
    Concurrent boots serialize via an exclusive flock on
    ~/.conscio/instances/.minting-<slug> (5 s timeout) with a mandatory re-read of
    instance.json upon acquisition to prevent ID divergence. Degrades safely with
    a warning on filesystems without full lock support (ENOLCK / EOPNOTSUPP).
  • Refusal markers (space-refused.json). When target is None
    (B3/B4/B5/lock), the server writes an atomic space-refused.json marker in the
    plugin data directory and exits 2 without touching storage or minting. Cleared
    automatically upon successful resolution or migration.
  • Stdlib hooks cascade ("when in doubt, do not write"). Pure stdlib hooks
    (deepminer, obsstore, honesty, wake) run in isolated processes and
    evaluate files locally: pointer → migration lock → refusal marker → legacy B0
    → silent exit 0. Hooks never block agent turns.
  • Atomic migration command (conscio space migrate). Migrates legacy
    plugin-bound spaces to durable instances via 8 strict atomic steps:
    1. acquire .migrating-<slug> lock;
    2. create timestamped backup in ~/.conscio/backups/pre-migrate-* (keeps 2
      generations);
    3. move content of space/ while preserving the folder;
    4. write migrated-from.json tombstone in durable root;
    5. atomically write space-pointer.json in plugin directory;
    6. remove space-refused.json;
    7. release migration lock;
    8. print regenerated systemd user unit definitions for the durable path.
      Protected by two pre-flight gates: process-zero (inspects /proc/*/cmdline,
      checking launcher names and open file descriptors to safely exempt parent
      shells while blocking active legacy processes) and quiet-minutes (verifies no
      file modifications within --quiet-minutes). Supports idempotent item-by-item
      resumption with tree and file equality checks.
  • Space checks in relay doctor (D1–D5). Read-only
    diagnostics:
    • D1: lists orphan spaces in instances/ (ignoring hidden dotfiles and lock
      files).
    • D2: flags downgrade ghosts and suggests conscio relay forget.
    • D3: identifies orphan migrated-from.json tombstones.
    • D4: reports deferred migrations with active process PIDs.
    • D5: detects stale migration locks with dead PIDs and suggests resuming via
      conscio space migrate --slug <slug> (never deletes automatically).
    • Lists active space-refused.json refusal markers with state and age.

Added — Council calibration (v4.8)

A calibration round for the four-voice council: an opt-in judge, trait-
sharpened deterministic votes, a readiness gate, and an offline
benchmark. The voices' contract — they never call an LLM or an
attached adapter — is intact; the votes now also read a 9-trait
extraction of the question text, and the new result fields are
additive.

  • Optional council judge (conscio/judge.py). One canonical
    decision question, answered by your typed decision API through the
    shared decision_adapter transport (config: an empty judge
    marker block plus a decision_adapter block — see USAGE.md). The
    judge is off unless configured, and no env var alone turns it on.
    Any failure is a status string, never an exception: ok, off,
    no_key, bad_config, no_adapter, timeout, network,
    http_<code>, malformed, internal_error — the last two mark the
    judge boundary: a response that fails validation, or an unexpected
    error, is logged and the council falls back to deterministic mode.
    Only the council's question,
    context and, when present, options leave the machine.
  • Trait-sharpened deterministic votes. All four voices stay
    deterministic (the pinned contract: they never call an adapter) and
    now also read a 9-trait extraction of the question text (English
    only — see the limitation in USAGE.md). The per-voice weight
    table in conscio/gates.py is commented with the dev round that
    produced each value.
  • Readiness gate. A proceed only leaves the council when the
    engine is ready — not in action_lockdown, not in a critical
    metabolic state, and, when a coherence score exists, coherence ≥
    0.5. Not ready ⇒ lowered to hold, reasons in gate_reason; the
    gate never promotes. New additive result fields: mode,
    judge_status, gate_reason, and the judge report in judged
    mode.
  • Calibration bench + relabel tool. An offline harness
    (tests/test_council_calibration.py + tests/council_bench.py)
    measures agreement with the frozen judge labels — Cohen's kappa
    plus a confusion matrix, per origin — on a 114-case corpus
    (61 dev + 53 heldout, hash-pinned in
    tests/fixtures/council_bench/MANIFEST.json). Tuning never reads
    the heldout half; only the harness and the relabel tool open it.
    scripts/council_bench_relabel.py
    re-labels the heldout against a live judge and reports drift
    without writing to the fixture. The harness prints its headline
    literally as agreement with the judge labels, not ground-truth
    correctness.
  • Docs. The optional judge, its config keys, statuses, result
    fields, the gate, and the English-only limitation are documented in
    USAGE.md (root and packaged copies); the conscio_council entry
    in docs/guides/mcp.md now mentions the judge.

Added — Ambient: one task board per machine (v4.8 S3)

One task board per relay root (<relay_root>/ambient/board.db, SQLite/WAL,
user_version=1, busy_timeout=5000), swept by the relay reactor's own tick —
no new daemon. All four surfaces resolve the same board (invariant I1): the
conscio_board MCP tool, the conscio ambient CLI, the ambient node, and the
doctor.

  • conscio_board MCP tool + conscio ambient CLI. The board's write
    surface: task {propose,create,assign,list,show,claim,renew,submit,review, release,block,cancel}, orchestrate {acquire,renew,release}, status,
    report, doctor, and wake <id> --dry-run. The MCP tool is a single
    conscio_board dispatch; the actor is always the server's own identity —
    there is no as= argument.
  • The ambient node rides the reactor tick. While enabled it notices
    assigned work over the relay (a task_dispatch carrying only the task id),
    re-notifies a stalled reviewer on a cap, and — through the connector gate —
    may wake a non-live agent. A broken conscio.ambient package costs the node
    and never a relay delivery.
  • Off by default. Without the <relay_root>/ambient/enabled flag file the
    node gives its sweep back and touches nothing (conscio ambient enable
    creates it). The board and CLI keep working while disabled.
  • board.propose travels over the relay. The one board write a remote
    machine may perform: a peer asks for work and it lands as a proposed task
    with creator = sender and origin = <message id> (a redelivery dedupes on
    origin). It is a reserved type the generic relay refuses; only
    conscio_board op=propose to=<peer> sends it.
  • Wake registry with a zero default budget. <relay_root>/ambient/agents.json
    names the connectors a machine may use; every agent's wake_budget_per_day
    defaults to 0, so nobody is woken until the owner opts them in.
  • The claude-bg connector. The one connector in this release. A wake
    runs claude --bg [--model M] <prompt> inside its own
    systemd-run --user --scope, so the session outlives a reactor restart; its
    output goes to temporary files, stdin is /dev/null, and the call times out
    after 60 s. The session id is read from the backgrounded · <id> line. A
    spawn that exits 0 with an id is a success; otherwise a 429/rate-limit
    message is recorded as rate_limited and anything else as spawn_error.
    Liveness reads claude agents --json: working is live; done, failed or
    a session missing from the list is not; blocked, an unrecognised state, a
    non-zero exit or output that is not the known JSON shape is unknown — and
    unknown never wakes. A failed claude stop is logged and the session is
    still recorded as stopped. The wake environment is built from scratch and now carries
    XDG_RUNTIME_DIR alongside PATH, HOME and LANG (systemd-run --user
    needs it).
  • doctor reports the machine side too: units: (the
    conscio-relay-reactor* user units) and claude: (claude --version). A
    command that fails reads unknown (…); the doctor never raises.

Changed

  • Universal observation retention cap raised to 3 GB (commit 0dcdff0). The
    observation store retention cap is increased from 2 GB to 3 GB across all
    producers (conscio/obsstore.py default max_bytes, the vendored copy in
    Claude Code assets, and the RETENTION_BYTES constant in
    conscio_deepminer.py). Retention age remains unchanged at 30 days.

Fixed

  • Host identity fallback keys demoted to last resort (commit d98a9ee).
    ZCODE_FALLBACK and ANTIGRAVITY_FALLBACK keys no longer override
    plugin-scoped primary signals when processes inherit ambient environment
    variables from host IDE sessions (such as Claude or Hermes running inside
    ZCode). Evaluation order is now: ZCode primary → Antigravity primary → Claude
    → Hermes → OpenCode → ZCode fallback → Antigravity fallback → none.
  • Space migration and resolver fixes:
    • Upfront plugin pointer validation: conscio space migrate validates
      pointer resolution before any destructive step or writing tombstones,
      exiting 3 cleanly if the legacy space is not plugin-bound (a859957).
    • Restored observatory CLI routing: restored conscio observatory dispatch
      block in cli.py that was displaced during space CLI addition (32a6f88).
    • Ancestor process chain filtering: migration process-zero gate inspects open
      file descriptors and launcher names (bash, sh, dash, zsh, fish,
      env, timeout, nohup), allowing shell wrapper invocations without
      false-positive blocks (08affe5, 4195846).
    • Quiet minutes logging: quiet minutes check is only logged when
      quiet_minutes > 0 (4c8f05b).
    • Resumption on stale lock: doctor suggests conscio space migrate --slug <slug> resumption rather than manual rm when encountering stale locks
      (0ac7d81).
    • Plugin-bound refusal cleanup: marker cleanup only touches subpaths for
      plugin-bound storage (1b2a3fb).
    • Resolver slug and runtime consistency: server uses resolved slug and
      runtime for pointer repair and minting locks (58cd4e2).
    • Skip space resolution without --storage: MCP server skips durable space
      resolution when --storage is omitted (51cf0c5).
    • Remote card handling in B4: skip remote relay cards when checking local
      residual evidence; report corrupt instance.json as unreadable instead of
      crashing (5015843).
    • Closed B4 refusal on directory read error: fail closed with clear refusal
      message if directory.peers() errors during evidence lookup (d622b3e).
    • Non-blocking flock fallback: warn on ENOLCK or EOPNOTSUPP during minting
      lock on filesystems lacking lock support (4fad1d7).
    • Resumption tree comparison: item-by-item migration resumption verifies tree
      and file equality checks (f4ea2cb).
    • Variable expansion in known dirs: ignore unexpanded variables and relative
      paths in known plugin data dirs (97adfbd).
    • Typed resolved slug variable: explicit typing of resolved slug variable in
      migrate command (d47fbdc).
  • Ambient reactor and decision adapter fixes:
    • Reactor survives broken ambient package at boot (2ac2183).
    • SQLite WAL journal mode retry on busy during board node connection
      (0417e4b).
    • Connector spawn prioritizes exit code over rate limit warning, and stop
      raises RuntimeError on non-zero exit (35df025).
    • Decision adapter transport enforces ASCII api_key, strict key resolution
      from environment, and suppresses __context__ leaks on network exceptions
      (b1e6ce1, ce736fc, 436e86f).

Provisional constants (to be calibrated)

Seven constants in conscio/ambient/node.py are provisional; the values are
unchanged and their calibration is deferred to the next patch, together with
probe S4:

  • Admission gate (spec §7.3): WAKE_FLOOR_MB=1500,
    LOAD1_DELTA_TOLERANCE=1.5, ADMISSION_WINDOW=36, ADMISSION_MAX_AGE_S=360.
    Probe S3 measured a single claude --bg wake once: a peak RSS of 917 MB for
    the whole process tree, under the 1500 MB floor. Its load1 reading was taken
    with other agents running and cannot calibrate LOAD1_DELTA_TOLERANCE.
  • No associated probe (spec §7.4): WAKE_GRACE_S=600, RENOTIFY_MAX=3.
  • MAX_CONCURRENT_WAKES=1: probe S4 (two simultaneous wakes) did not run,
    so it stays at 1.

Tests

The full suite was run one test file per process (359 files). Measured at
close: 4708 tests across 359 files — 4703 passed, 2 xfailed, 3 skipped;
0 failures, 0 collection errors.
The two xfails are documented limits: the
council calibration baseline sits below the acceptance criterion, and the trait
extractor leaves a second mitigator unlit when an absence word sits just before
it in the same sentence (the conservative side). Two tests check that a
shipped file is tracked by git (test_cc_materialize, test_plugin_hook_paths);
they fail by design outside a checkout, so they were run in the git worktree. The
five guard/contract/invariant/no_ suites
(test_agency_contracts, test_agency_no_network, test_assets_no_phantom_tools,
test_durable_guards, test_mcp_dispatch_contract — 41 tests) were run
nominally and pass.