v4.8.0 — The space survives, the machine has a board, the council can be measured
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=Truesignals the caller to create or update the
pointer):- B0 (Legacy): unmigrated plugin space; prints stderr announcement to run
conscio space migrateand uses the legacy path byte-for-byte. - B1 (Bound): valid
space-pointer.jsonpointing 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 ifmigrated-from.jsontombstone 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 withrepair_pointer=True.
- B0 (Legacy): unmigrated plugin space; prints stderr announcement to run
- Pointer minting with exclusive flock (
conscio/installer/spaces.py).
Concurrent boots serialize via an exclusiveflockon
~/.conscio/instances/.minting-<slug>(5 s timeout) with a mandatory re-read of
instance.jsonupon acquisition to prevent ID divergence. Degrades safely with
a warning on filesystems without full lock support (ENOLCK/EOPNOTSUPP). - Refusal markers (
space-refused.json). Whentarget is None
(B3/B4/B5/lock), the server writes an atomicspace-refused.jsonmarker 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:- acquire
.migrating-<slug>lock; - create timestamped backup in
~/.conscio/backups/pre-migrate-*(keeps 2
generations); - move content of
space/while preserving the folder; - write
migrated-from.jsontombstone in durable root; - atomically write
space-pointer.jsonin plugin directory; - remove
space-refused.json; - release migration lock;
- 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.
- acquire
- 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.jsontombstones. - 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.jsonrefusal markers with state and age.
- D1: lists orphan spaces in
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
shareddecision_adaptertransport (config: an emptyjudge
marker block plus adecision_adapterblock — seeUSAGE.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'squestion,
contextand, when present,optionsleave 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 inUSAGE.md). The per-voice weight
table inconscio/gates.pyis commented with the dev round that
produced each value. - Readiness gate. A
proceedonly leaves the council when the
engine is ready — not inaction_lockdown, not in a critical
metabolic state, and, when a coherence score exists, coherence ≥
0.5. Not ready ⇒ lowered tohold, reasons ingate_reason; the
gate never promotes. New additive result fields:mode,
judge_status,gate_reason, and thejudgereport 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); theconscio_councilentry
indocs/guides/mcp.mdnow 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_boardMCP tool +conscio ambientCLI. 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, andwake <id> --dry-run. The MCP tool is a single
conscio_boarddispatch; the actor is always the server's own identity —
there is noas=argument.- The ambient node rides the reactor tick. While enabled it notices
assigned work over the relay (atask_dispatchcarrying only the task id),
re-notifies a stalled reviewer on a cap, and — through the connector gate —
may wake a non-live agent. A brokenconscio.ambientpackage costs the node
and never a relay delivery. - Off by default. Without the
<relay_root>/ambient/enabledflag file the
node gives its sweep back and touches nothing (conscio ambient enable
creates it). The board and CLI keep working while disabled. board.proposetravels over the relay. The one board write a remote
machine may perform: a peer asks for work and it lands as aproposedtask
withcreator = senderandorigin = <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'swake_budget_per_day
defaults to 0, so nobody is woken until the owner opts them in. - The
claude-bgconnector. The one connector in this release. A wake
runsclaude --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 thebackgrounded · <id>line. A
spawn that exits 0 with an id is a success; otherwise a429/rate-limit
message is recorded asrate_limitedand anything else asspawn_error.
Liveness readsclaude agents --json:workingis live;done,failedor
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 failedclaude stopis logged and the session is
still recorded as stopped. The wake environment is built from scratch and now carries
XDG_RUNTIME_DIRalongsidePATH,HOMEandLANG(systemd-run --user
needs it). doctorreports the machine side too:units:(the
conscio-relay-reactor*user units) andclaude:(claude --version). A
command that fails readsunknown (…); 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.pydefaultmax_bytes, the vendored copy in
Claude Code assets, and theRETENTION_BYTESconstant in
conscio_deepminer.py). Retention age remains unchanged at 30 days.
Fixed
- Host identity fallback keys demoted to last resort (commit
d98a9ee).
ZCODE_FALLBACKandANTIGRAVITY_FALLBACKkeys 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 migratevalidates
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 observatorydispatch
block incli.pythat 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 manualrmwhen 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--storageis omitted (51cf0c5). - Remote card handling in B4: skip remote relay cards when checking local
residual evidence; report corruptinstance.jsonas unreadable instead of
crashing (5015843). - Closed B4 refusal on directory read error: fail closed with clear refusal
message ifdirectory.peers()errors during evidence lookup (d622b3e). - Non-blocking flock fallback: warn on
ENOLCKorEOPNOTSUPPduring 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).
- Upfront plugin pointer validation:
- 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).
- Reactor survives broken ambient package at boot (
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 singleclaude --bgwake once: a peak RSS of 917 MB for
the whole process tree, under the 1500 MB floor. Itsload1reading was taken
with other agents running and cannot calibrateLOAD1_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.