# Changelog — Conscio

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [4.5.4] - 2026-09-05 — Relay plug-and-play + Agent's Hall

The relay used to need a hand-written peer list, a shared `liaison.db` path, a
systemd watcher re-armed after every restart, and an allowlist edited by hand on
both sides. Two agents on the same machine now find each other with no
configuration at all, and a message survives a receiver that is not running.

### Added

- **A message wakes a running session.** Delivery used to end at the reactor,
  which marked the row read and notified the human. An agent that was already
  mid-session never learned about it: by the time it looked, nothing was unread.
  A `Stop` hook now runs when the agent would end its turn, reports anything
  that arrived since *that session* last looked, and exits 2, so the agent keeps
  going and answers on its own. The cursor is per session and advances before
  the hook blocks, so a message can never wake the same turn twice; every
  failure path exits 0, so a broken relay ends the turn instead of trapping it.
  Drop a `wake-off` file in the space to opt out.
- **Directory of public peer cards.** Every agent publishes a card
  (`instance_id`, model, family, runtime, role, capabilities, reachability) into
  a shared directory on each tick and on boot. Peers are read from the
  directory, never from another agent's database — the card is the only thing an
  agent writes about itself, and the only thing others read.
- **`conscio_relay_peers`** — discovery without manual config: who is out there,
  which are alive, how to reach them.
- **Store-and-forward spool.** A message to an agent that is not running is
  deposited in the recipient's spool (crash-safe, deduped) and ingested on its
  next `relay_inbox`. Delivery is local-spool or remote-HTTP; the sender's outbox
  is only written *after* delivery is accepted, so a failed send never looks sent.
- **Per-agent `liaison.db`** inside each agent's own space, with a one-shot
  migration of the legacy shared database. No more shared path to agree on.
- **In-session reactor.** Reactivity is a thread inside the MCP session that has
  an agent to wake, not a systemd unit: nothing to arm, nothing to re-arm after a
  restart, and it dies with the session that owns it. It marks `read_ts` on the
  message it delivered (the watcher only moved a cursor) and never exits on idle.
- **Agent's Hall** — named groups of agents. Membership lives in each agent's own
  card, so there is no shared roster to corrupt: `conscio_hall_create`, `join`,
  `leave`, `list`, `members`, `send`, `manage`. The owner assigns functions
  (leader, reviewer, researcher, security, optimizer, architect, observer,
  devil's advocate, executor) and can transfer ownership. Everyone joins as
  `executor`; only the owner promotes. Halls are visible in the observatory.
- **`conscio relay {pair,peers,quarantine,doctor,service}`** — pair machines,
  list the directory, purge unparseable messages, diagnose why nothing arrives,
  print the bridge systemd unit.
- **Consent for relay and halls in `conscio init`** — the wizard writes
  `--enable-relay` / `--can-create-halls` into the host's MCP entry; `--repair`
  no longer downgrades a consent already granted.
- **`conscio.liaison.watcher --persistent`** — for agents that are not an MCP
  session and still need a process watching the mailbox. Polls every 2s,
  streams one JSON line per delivery or heartbeat, and stops only on a signal.
  The default contract is unchanged.

### Fixed

- **An empty allowlist ate every message.** `relay_peers=()` meant "trust
  nobody" on the receiving side while the sender saw a successful send — the
  message was consumed and dropped. An empty allowlist now means "no restriction".
- **Cross-machine delivery landed in the bridge's own mailbox.** The bridge
  deposited into the local instance instead of the recipient's spool, so a
  message crossing Tailscale was delivered to the wrong agent; handler state is
  now per-instance instead of class-level.
- **Agent status was a stored string** that stayed `alive` for an agent that had
  been gone for days. It is now derived from `last_heartbeat` at read time.
- **Seven hall tools were advertised and none could be called.** Halls were
  announced on `--can-create-halls` alone but only registered for dispatch when
  relay was also on, so `--can-create-halls` without `--enable-relay` published
  tools that answered "method not found". Halls now derive from relay in one
  place, the installer emits the dependency, and a test asserts every advertised
  tool is dispatchable.
- **Ownership could be transferred to nobody.** `transfer_owner` accepted any
  string, so a typo named an owner who was not in the hall — after which nobody
  could satisfy the owner check and the hall was permanently unownable. The new
  owner must be a member.
- **Rival orchestrator claims resolved by id order.** Projection demotes the
  incumbent as it writes, so the winner was whichever card the directory listed
  last: an alphabetically later id beat a claim made an hour later. Cards are
  now projected oldest-first, so the freshest claim wins.
- **Replayed messages landed twice.** A spool id is minted per deposit, so
  re-POSTing a captured message to the bridge (or a network retry) created a
  second row. The sender's own message id is now stored in `origin_id` and
  `(from_instance, origin_id)` is unique — re-delivery is a no-op.
- **Re-creating a hall could wipe its roster**: the duplicate check was
  exists()-then-write; creation is now exclusive at the filesystem.

### Changed

- **Tool surfaces measured, not asserted**: `lite` 10, `balanced` 19, `high` 21,
  `ultra` 37; `--enable-relay` adds 5 and `--can-create-halls` 7 on top, for a
  maximum of 49. The docs were carrying `balanced` 18 / `high` 22 / `ultra` 35.
- **Code and documentation are English-only** (recorded as a project rule).
- **`conscio relay service` binds to loopback by default** (was `0.0.0.0`,
  which contradicted the documented "bind to the tailnet address"). A unit
  generated before this change keeps working; regenerate it with
  `--bind <tailscale-ip>` to keep accepting remote peers.

---

## [4.5.3] - 2026-09-03 — Neutral home (un-couple from Hermes) + three fixes

### Fixed

- **Windows `cp1252` crash on state writes**: `atomic_write_text` (and the other
  state writers — goal_generator, inner_monologue, structural_drift,
  structural_consent, daemon heartbeat) wrote files without an explicit
  `encoding="utf-8"`, so on Windows the OS `cp1252` code page could not encode
  much of the state and raised `UnicodeEncodeError` mid-write, killing
  `--awake` startup. All state writes now force UTF-8; the Claude Code plugin
  template also ships `env: PYTHONUTF8=1, PYTHONIOENCODING=utf-8`.
- **`conscio_cognitive_cycle` missing in balanced/high surfaces**: the tool
  lived in `BASE_TOOL_DEFS` and the dispatch table but not in any mode set, so
  the `tool_defs()` filter dropped it in balanced/high — hosts saw it missing
  and calls surfaced as "internal error". Added to `BALANCED_TOOLS` (inherits
  high).

### Changed

- **Install-vs-update mode detection**: `DEFAULT_MODE` is now `balanced` for a
  genuinely fresh install, but a pre-4.5.3 space (has `instance.json` but no
  `mcp_mode`) keeps its historical `ultra` surface — an update never shrinks an
  existing install. The settled mode is persisted on first boot.

### Breaking / behaviour

- **Neutral home, Hermes no longer the default**: `conscio.home` now resolves
  `CONSCIO_HOME` (neutral `~/.conscio`) at the top, with `HERMES_HOME` kept as a
  legacy override that preserves pre-4.5.3 installs (an existing `~/.hermes` is
  auto-detected and reused). `noosphere.paths.hermes_home()` remains a
  backward-compat alias of the new `conscio_home()`. All internal defaults
  (engine, CLI, mailbox, observatory, hub, session lifecycle/RAG) now point at
  the neutral home.

## [4.5.2] - 2026-09-01 — Per-agent isolation (cross-agent hard-block + own liaison)

### Added

- **`spaces.space_is_cross_agent(space, self_instance_id)`**: isolation guard
  based on the space's IDENTITY (reads `instance.json`). Blocks a foreign
  agent from writing over another agent's install (HARD-BLOCK on write;
  cross-agent READ is still allowed). Fails closed when the space identity is
  corrupt. Does not rely on home-dir heuristics (agents cohabit one home).
- **`spaces.liaison_db_path(slug)`**: each space now has its own private
  `liaison.db` (`~/.conscio/instances/<slug>/liaison.db`), instead of a shared
  global mailbox. Cross-agent communication is transport-only (HTTP/tailscale),
  never via a shared DB.
- **`tools/consolidate_agent_identity.py`**: migration utility for installs
  with a duplicated identity (same agent under 2 instance ids). Idempotent,
  backs up everything it touches: migrates `instance.json`, consolidates
  `agents`/`messages`/`watcher_state` onto the canonical id.
- **Isolation tests** (`tests/test_installer_isolation.py`): 12 cases covering
  cross-agent, corrupt identity (fails-closed), per-space liaison, and the
  `upsert_conscio_entry` guard.

### Changed

- `hostcfg.upsert_conscio_entry`: new `self_instance_id` parameter — when set,
  invokes the cross-agent guard and raises `HostConfigError` on conflict.

## [4.5.1] - 2026-08-31 — Relay: health endpoint + presence announce + cross-machine hardening

### Added

- **`GET /relay/health`** (`conscio/liaison/relay_net.py`): authenticated
  probe so any peer on the tailnet can detect that the relay is alive
  before sending. Returns `{ok, self_id, db, agents_alive[], ts}`. 4 new
  tests in `test_liaison_relay_net.py::TestHealthEndpoint`
  (ok / 401-token / 404-path).
- **Presence announce** (`tailscale_relay_service.py`): a silent
  `presence` message every 60s to each peer in `~/.hermes/relay_peers.json`,
  marking the relay as alive without chat traffic.
- **CLI `relay_health.py`**: checks local (8789 + 8788) and remote peers
  from `relay_peers.json`; exit 0 = all healthy.
- **Mandatory strong tokens** (`_load_or_mint_token`): regenerates to
  `secrets.token_hex(24)` if the file is missing or still contains the
  legacy default `conscio-tailscale-relay-token`.

### Changed

- **`conscio-relay-bridge.service`** (new): peer-side bridge under
  systemd, bind `VM_TS_IP:8789` (tailnet only), replaces the manual
  process. Includes direct HTTP forwarding to remote endpoints.
- **`conscio-antigravity-relay.service`** (new): Antigravity watcher
  under systemd, `127.0.0.1:8788` + `tailscale serve`.
- Reactor allowlist expanded to include cross-machine peers
  (Gemini + Claude + auxiliary peer instances on the remote side).
- `tailscale_relay_service.py`: uses `_load_or_mint_token`,
  `_peer_token` (receiver's token when declared), `BIND_HOST` fixed to
  the tailnet IP.
- `antigravity_relay_service.py`: same `_load_or_mint_token`.

### Fixed

- Bug in `announce_presence` (`NameError: p`) — `for` order corrected.
- `docs/RELAY.md` (new): operational guide with the explicit rule to
  never remove mandatory tags (`--enable-relay`, `--relay-peer`,
  `--liaison-db`, `--storage`) from `mcp_config.json`.

## [4.5.0] - 2026-08-29 — Reactive relay + Agent's Hall

### Added

- **Agent's Hall** (`conscio/liaison/halls.py`): named groups of agents over
  the same mailbox. Halls with an owner, `hall_id` = `owner--name` slug, members
  with role and presence. `send_to_hall` fan-out excludes the sender.
  Engine-free, never-raises.
- **MCP tools `conscio_hall_*`** (create/list/join/leave/members/send) behind
  the `--can-create-halls` flag. `hall_members` enriches with model/family from
  the registry.
- **Observatory** (`halls_view.py` + `/api/agents`, `/api/halls`, `/api/mailboxes`):
  read-only projection of agents + halls + mailbox, with stale marked as
  `offline` (does not disappear). "Halls" and "Agents" tabs in app.js with a
  3s poll (real time).
- **Provenance envelope**: `mailbox.send(identity=...)` stamps `_meta.from`
  with the runtime identity (model/family/runtime/role); `relay.envelope_of`
  extracts it. Runtime identity wins over self-declaration in the body.
- **Identity columns in the `agents` registry**: `nome/familia/runtime/papel`,
  with idempotent ALTER migration for legacy DBs.
- **3-state heartbeat** `tick_summary()`: `nothing_new | delivered | not_delivered
  (+reason)` — never silent. Watcher renews presence each tick and emits
  "alive" in the persistent loop.
- **Quarantine** (`mailbox.quarantine/list/purge`): a payload that doesn't parse
  goes to `quarantine` instead of dropping the queue.

### Changed

- **Dynamic peers**: `Bindings._resolve_peers()` consults `agents.list_agents()`
  (alive, excludes self); `--relay-peer` becomes seed/fallback, not authority.
- `--identity-model/familia/runtime/papel` and `--can-create-halls` on the MCP CLI.
- `mcp/server.py` `route()` for the observatory gains the `halls` parameter.

### Fixed

- Structural relay 503/silence: the component no longer announces success
  without delivering (3 states) nor lets a malformed payload block reading
  (quarantine).

---

## [4.4.1] - 2026-08-28 — Fix world_prune skeptic noise loop

### Fixed

- **`world_prune` no longer trips the skeptic audit.** The maintenance drive
  raises a `prune_stale` goal whose action is `world_prune`. The skeptic LLM
  was refusing the proposal every cycle (`act:world_prune:skeptic_fail`,
  37x in production). Each failure fed back into `reflect()` as a curiosity
  anomaly -> the same goal -> the same proposal -> the same refusal: a
  self-sustaining noise loop vetoing the engine's own maintenance.

  `world_prune` is in-process GC of the world model (decay + remove
  entities, no filesystem or network I/O). It belongs in
  `_SKEPTIC_SKIP_TOOLS` next to `memory_note` and `host_health`.

  - `conscio/agency/act.py`: add `world_prune` to `_SKEPTIC_SKIP_TOOLS`
  - `tests/test_world_prune_tool.py`: +2 regression tests (skip audit,
    no recurring `act:world_prune:skeptic_fail`)
  - `tests/test_agency_skeptic.py`: switch the resolved-spec proof to an
    audited tool (`deploy`) since `world_prune` no longer reaches the auditor.

  Suite: 3518 passed, 19 skipped, 0 failed.

---

## [4.4.0] - 2026-08-26 — Multi-Squad advisory system + mode 'high'

### Added

- **Squads system** — two orthogonal advisory squads that run alongside the
  Council. Each squad is a closed namespace (own voices, event types, MCP tool).

  - **Experts squad** — constructive technical specialisation:
    - `Optimizer` — performance heuristics (N+1, blocking I/O, complexity)
    - `Auditor` — security heuristics (hardcoded secrets, SQL injection,
      command injections, unsafe eval)
    - `QA` — test/coverage heuristics (missing tests, xfail/skip abuse,
      edge-case omission)
    - `Promptor` — prompt optimisation (intent clarity, specificity, target AI,
      mode selection). Always deterministic — no LLM path.

  - **Opositors squad** — hostile pressure to validate premises:
    - `Caustic` — acidic visual/UX critique (bad CSS, visual hypocrisy,
      UX anti-patterns). Tone boundary enforced: sarcasm OK, personal
      attacks/hate speech blocked.
    - `Devil's Advocate` — argues the opposite position, forces
      proof-by-contradiction, challenges unverified claims.
    - `Skeptic Engineer` — hunts over-engineering, unnecessary IPC,
      framework bloat, ego-driven architecture.
    - `Douche Reviewer` — passive-aggressive code review (code slop,
      regex hacks, iframe abuse, code smells). Same tone boundary as Caustic.

- **Mode 'high'** — new tier between balanced and ultra:
  - `lite(10) ⊂ balanced(18) ⊂ high(20) ⊂ ultra(all)`
  - High adds `conscio_squad_experts` and `conscio_squad_opositors` tools.

- **MCP tools** — two new tool wrappers:
  - `conscio_squad_experts` — convene experts with voice selection and
    optional LLM support.
  - `conscio_squad_opositors` — convene opositors with voice selection and
    optional LLM support.

- **Engine methods**:
  - `engine.squad_experts(question, context, voices, use_llm)`
  - `engine.squad_opositors(question, context, voices, use_llm)`

- **Event types**:
  - `squad:experts:convened`
  - `squad:opositors:convened`

### Changed

- `conscio.mcp.modes.MODES` now includes 'high' tier.
- `conscio.event_bus.VALID_TYPES` includes squad event types.
- All voices deterministic by default; LLM opt-in per-call via `use_llm=True`.

### Unchanged

- Council (`conscio.gates.council()`) is untouched.
- `engine.council()` signature and behavior unchanged.
- Existing tools, schemas, and modes retrocompatible.

---

## [4.3.1] - 2026-08-25 — Private-cursor relay sweep + IMPORTANT classification

### Added

- **`conscio.liaison.tick`** — dedicated private-cursor relay sweep for host
  supervisors (systemd/cron). Unlike the existing `watcher`, which persists a
  per-peer cursor in the **shared** `watcher_state` table that any mailbox
  writer may also read/advance, `tick.sweep()` uses an **opt-in private cursor
  file** so multiple agents polling the same `liaison.db` never clobber each
  other's read position. This is the fix for the cross-agent relay race
  where one agent's watcher consumed the other's unread boundary.
- **`tick.classify_important()`** — heuristic that tags a surfaced relay
  message as IMPORTANT (direction/action requested: `direcionamento`, `acao`,
  `passo`, `teste_direcionado`, `prioridade`) vs routine (`ping`, `validacao`,
  `ack`). Word-boundary matching so `validacao` is **not** a false positive of
  `acao`. Exposed via CLI (`conscio.liaison.tick`) for the DM-notification step
  in host supervisor scripts.
- **`tick.classify_peer()`** — short human label for a relay peer UUID.

### Fixed

- **Multi-agent cursor race** — two agents polling the same
  mailbox previously advanced a single shared per-peer cursor, so one agent's
  poll consumed (and hid) messages addressed to the other. `tick.sweep` with a
  private cursor file separates each agent's read position.

---

## [4.3.0] - 2026-08-24 — Assertive, robust council

### Changed

- **Council recommendation is now conservative** (`conscio/gates.py`): a split
  or ambiguous council (e.g. 2×2, or any veto) resolves to **`hold`**, never a
  silent `proceed`. Proceed requires a genuine ≥3-of-4 affirmative majority with
  no veto. This makes the council assertive about uncertainty instead of
  defaulting to permissive go-ahead.
- **Fair critic**: removed the synthetic "consider second-order effects" concern
  that force-fed every critic toward hold/veto. A critic that finds no concrete
  failure mode now votes `proceed` honestly.
- **Honest LLM critic**: the LLM critic only escalates to a concern when its
  output actually names a risk (fail, crash, leak, race conditions, etc.),
  instead of unconditionally flagging every LLM response.

### Added

- `consensus_strength` (0..1) on the council result — how firm the verdict is
  (1.0 = unanimous).
- `dissenting_voices` — which voices voted against the recommendation.
- **Voice-level degradation**: each council voice runs in its own `try/except`; a
  failing subsystem (`world` / `meta` / `evolution` / adapter) degrades to a
  `hold` voice instead of crashing the entire council.

### Tests

- New `tests/test_council_assertive.py` (12 cases): conservative resolution,
  fair critic, consensus fields, voice-failure robustness.
- 43 existing gates tests remain green; ruff + pyright clean.

---

## [4.2.0] - 2026-08-24 — A2A agent society (native)

### Added

- **Native A2A relay watchdog** (`conscio/liaison/watcher.py`): replaces the
  external `relay_watch_hermes.py` script. Reads `liaison.db` read-only,
  surfaces new peer messages, persists a **per-peer cursor** in `watcher_state`
  inside the same db. Honest exit codes (0 = ok, 2 = pending_capture,
  3 = config error), silent-when-idle (cron watchdog contract), zero LLM.
- **Delta transcript** (Ato 1b): `auto_respond` / `cognize_respond` accept
  `delta=True` (CLI `--delta-no-history`) — the LM prompt carries only the
  peer's un-answered turns instead of the whole bounded thread each tick
  (~70-90% token cut). Default remains full-thread (no regression).
- **Agent presence + capabilities** (`conscio/liaison/agents.py`): additive
  `agents` table over the shared db — `register_agent` / `heartbeat`
  (deterministic UPSERT, zero LLM) / `unregister` / `list_agents` /
  `is_alive`, with normalized capability tagging.
- **Capability routing** (`conscio/liaison/a2a.py`): `route_select` /
  `route_and_send` — an emitter selects the destination by capability tag
  instead of a hardcoded peer list; scalable to N agents.
- Legacy-compat flags: the watcher now supports `--since` (cursor replay) and
  a persistent `--interval` loop, matching the old script's surface.

### Fixed

- `content_store._mark_stale()` now invalidates the short-TTL query cache, so
  a tombstone stops stale chunks from surfacing immediately (not after up to
  30s of cached reads).

---

## [4.1.0] - 2026-08-07 — A name every host accepts

### Changed

Every MCP tool is now `conscio_recall` instead of `conscio.recall` — all 47
names across the base, act, relay and liaison surfaces, plus the resource and
prompt identifiers that referenced them.

A dot is legal in an MCP tool name — `Tool.name` is an unconstrained string in
every schema revision of the protocol, `2024-11-05` through `2025-06-18`. It is
not legal in the function-name rule that Anthropic and OpenAI share
(`^[a-zA-Z0-9_-]{1,64}$`), and a host that routes MCP tools straight into a
model's function slot applies its own variant of that rule to whatever the server
advertised. Verdent applies one — ASCII letters, digits, underscore and hyphen,
1–128 characters — and reports a dotted name as violating *the MCP specification*,
which in fact asks for nothing here.

The result is a server that looks healthy and serves nothing:

```
conscio: { status: 'success', summary: { loaded_tools: 0, unloaded_tools: 18 } }
```

Eighteen was the entire `balanced` surface — every tool the host had been served,
dropped one at a time. The server is never told: the handshake succeeds, the tool
list is accepted, and the rejection is recorded only in the host's own log. Claude
Code accepts the dot, which is why four major versions shipped without anyone
noticing that the naming was host-specific.

Users have nothing to do. The rename is invisible on both ends: a model never
saw the old names in this release, and a caller that still sends the dotted
spelling is normalised at dispatch and answered as before. Stored history is
unaffected — persisted events key on `event_id`, never on the tool name.

The compatibility alias is not a permanent contract. It exists so that a client
pinned to an older `.mcp.json` keeps working through the upgrade, and it will be
dropped in a future major.

### Added

`conscio init --host verdent` binds Verdent directly, writing
`~/.verdent/mcp.json` with the same backup-and-read-back the other hosts get.
Verdent was reachable before only through `--host other`, which asks for a path
and knows none — the one host with no plugin installer at all, where the MCP
entry *is* the installation, was the one the wizard could not finish.

The install and MCP guides now also carry the upgrade step Verdent needs: a host
that hot-reloads its config still keeps the running server, and its old code,
alive for an entry whose name did not change. The entry has to be removed and
put back for the new build to be launched — otherwise an upgrade looks like it
did nothing.

Tagging now publishes a GitHub Release. Four tags existed and the Releases page
was empty, so anyone watching this repository for releases heard nothing about
v4.0.0 or v4.0.1, and the built wheels were reachable only through PyPI.

The notes are the CHANGELOG section for that exact version, and a missing or
empty section fails the job loudly — a release page published with silently
empty notes is precisely the outcome the job exists to prevent. It runs only
after the PyPI publish succeeds, because announcing a version that never shipped
is worse than announcing nothing.

### Fixed

The MCP entry written by `conscio init` named the launcher `conscio-mcp` and
left the host to find it. A host started from a desktop icon never sourced the
virtualenv that `pip install conscio` wrote into, so the name resolves to
nothing and the server dies at exec — the single most common binding failure,
and the one that looks like the server is broken rather than unfound.

The entry now carries an absolute path whenever one can be proven: beside the
running interpreter first, since that is *this* install and the venv is exactly
the case a `PATH` lookup gets wrong, then the `PATH` of whoever ran the wizard,
which covers `pip install --user`. A relative hit is discarded — the host
resolves it against its own working directory, which is worse than the bare
name. The bare name is still the last resort, never worse than what every
earlier version emitted unconditionally.

The path is captured at write time, so a moved or rebuilt virtualenv leaves a
stale one; `conscio init --repair` re-resolves it, as it does every other field
of the entry.

A `--storage` directory that already existed but was empty was reported as
`blank/space drift` with an order to run `conscio init --repair` — on every
startup, forever — and was never initialised, so the space ran with no identity.

An absent directory was already bootstrapped silently; only the empty one drew
the warning. They are the same situation — nobody has made a space here yet —
and the empty variant is the one hosted environments produce: a mounted volume,
a path created by the host, a `mkdir -p` before the server is ever configured.
It is the first thing a new user on such a host sees, and it points at a repair
that repairs nothing.

Drift now means what the word says: contents but no `instance.json`, which is a
space that lost its identity or a `--storage` typo landing on an unrelated
directory. Those still warn and are still refused adoption. An empty directory
is bootstrapped like an absent one and its identity is written — `obsstore`
reads `instance.json` to know who authored an observation, so a space that never
got one records provenance it cannot attribute.

The tool-surface docstring claimed 34 tools and 9 while `conscio_mode`'s own
description claimed 10 / 18 / 35. The description was right: the frozensets are
*pre-filter* subsets holding 9 and 17 names, and `conscio_mode` is appended after
the mode filter, so every mode serves exactly one tool more than its set. Ultra
has no set at all — it is the absence of a filter over the 34 entries of
`BASE_TOOL_DEFS`.

Nothing a host is served changed. Every other statement of these counts already
said 35 and 10, including the README, the guides and the 4.0.0 entry below; this
docstring was the last one in the repository still disagreeing with the code.

### Tests

Two tests now parse the integers out of the tool description and compare them
against `len()` of the surface each mode actually serves, and pin that surface's
identity as the set plus `conscio_mode` rather than only its size. Both go red on
the tempting mutation — "correcting" the description down to 9 to match the
frozenset.

The binding test that covered the empty directory asserted the defect: it built
an empty path and required the call to fail. It is now two tests that separate
the cases the old one conflated — an empty directory must come back healthy,
write `instance.json` and log nothing, while a directory holding a `conscio.db`
and no identity must still warn and must be left untouched.

---

## [4.0.1] - 2026-08-07 — The plugin records again

### Fixed

Installing the plugin captured nothing. Every hook exited 0 and reported
success while writing no observation at all.

`--obsstore` names the Python module the hook loads by absolute path — the
vendored `conscio_obsstore.py` — not the database it writes to. The database is
always `<storage>/obs.db`. The shipped `hooks.json` passed
`${CLAUDE_PLUGIN_DATA}/obs.db` instead, so `load_obsstore()` raised
`ImportError` on a file that does not exist. The hook fails open by design, so
the exception was swallowed and the process exited 0 — the host saw a healthy
hook, and only the missing `obs.db` gave it away.

All five events pointed at the same wrong path, so capture was inert on every
fresh plugin install. Fixed to `${CLAUDE_PLUGIN_ROOT}/hooks/conscio_obsstore.py`.
Users on 4.0.0 need no migration: the space is created on first write.

### Tests

Every existing check on `hooks.json` was a substring assertion over the file
text — `"${CLAUDE_PLUGIN_ROOT}" in text` is satisfied by a manifest that points
the flag at a database, which is exactly how a broken manifest stayed green.
Two tests replace that: one asserts `--obsstore` ends in `.py`, and one expands
the placeholders and *runs* the shipped command, failing unless a row lands in
`<storage>/obs.db`. Both go red on the 4.0.0 manifest.

---

## [4.0.0] - 2026-08-06 — Tool Surfaces & the Claude Code Plugin

Conscio installs as a Claude Code plugin, and its MCP surface stops being one
size. Folds in 3.9.8 and 3.9.9, which were never released.

### Tool surfaces — lite / balanced / ultra

The server advertised its whole tool list to every host — roughly 3.1k tokens of
schema before the first prompt, ~10% of a 32k context window. Three nested
surfaces now:

| Surface | Tools served | Advertised schema |
|---|---|---|
| `lite` | 10 | ~570 tokens — descriptions flattened to ≤120 chars |
| `balanced` | 18 | ~1520 tokens |
| `ultra` (default) | 35 | ~3100 tokens |

They nest, so raising the surface never removes a tool. Filtering applies to the
base set only — anything enabled by flag (`--enable-act`, liaison, relay) is
never filtered — and an unadvertised tool stays callable through `tools/call`.
A small model drowns in 35 tools; a large one is crippled by 10.

Precedence is `--mode` on the CLI, then the persisted choice, then the default.
`conscio.mode` reads and switches at runtime and is present in every surface —
in `lite` it is the only way back out. It persists before switching, so a failed
write leaves the served surface intact and emits no `listChanged` for a switch
that did not happen. A guessed argument key raises `InvalidParams` instead of
returning the current mode, which would be indistinguishable from a successful
switch. The stderr banner gained `surface=` because `mode=` already meant
act-vs-propose-only.

The server announces `tools.listChanged` in the initialize handshake and drains
a server-initiated notification channel after each response.

### Claude Code plugin

`conscio init` was the only install path. The repository now carries a plugin
manifest and its own marketplace entry, so a Claude Code user installs Conscio
without a Python toolchain in the loop.

- Hooks ship with the plugin: `SessionStart` (awareness + DeepMiner),
  `PostToolUse`, `PostToolUseFailure`, `PreCompact`, `PostCompact`.
- Commands: `/conscio:mode`, `/conscio:capture`, `/conscio:backup` join the
  existing set.
- The MCP server pin is `==`, not `>=`. A floor would let an old server serve a
  tool list the shipped assets never mention, which reads as a bug in Claude
  rather than as a stale install.
- `obsstore.py` is vendored beside the hooks that import it. The hooks run under
  a bare `python3` with no `conscio` on the path, so the copy has to both match
  the source byte for byte and import on its own.
- The DeepMiner hook accepts `--obsstore`/`--storage` and a capture-off switch,
  so the plugin can point it at plugin-managed data.

**Packaging fix:** setuptools' `**/*` skips dotfiles silently, so `plugin.json`
and `.mcp.json` were dropped from the wheel and the installed plugin was inert.
The test now builds a real wheel rather than trusting the glob.

### Intercepter — LaTeX and equation solving

Small models emit LaTeX. Intercepter now converts it before parsing: `\frac`
(numerator and denominator parenthesised), `\sqrt`, `\cdot`/`\times`/`\div`,
`\left`/`\right`/`\big`, exponents (`x^2`, `x^{n}`), and implied multiplication
(`9(2x-3)` → `9*(2*x-3)`). The conversion also runs as a fallback when a plain
expression fails `ast.parse`, which rescues the `2x+3` and `x^2-4=0` that models
send without backslashes. `2^10` and `2(3+4)` needed the same treatment: both
*parse* successfully in Python — as `BitXor` and as a call — so the fallback
never fired. The converter now runs first.

The MCP `intercept` tool auto-solves: an expression carrying `=` and no bound
variables goes to sympy, returning `{result, exact, variable}`. Complex and
cubic roots are stringified rather than crashing the conversion.

### Fixed

- **`verify()` crashed on `list[str]`** — `'str' object has no attribute 'get'`.
  Plain strings are now accepted as shorthand.
- **`host_health` was not importable** from `conscio.agency.tools`; it lived
  inside a nested function. Extracted to module level, registered as a LOW-risk
  auto-approval tool, and given `**_kwargs` so a spurious LLM argument is
  ignored rather than fatal.
- **LOW-risk auto-policy tools now execute at L1**, which breaks the trust
  bootstrapping cycle — a fresh daemon could never earn the autonomy it needed
  to earn autonomy. Measured after the fix: 6/6 cycles, 0 failures, 51
  executions, and the dream cycle distilled its first skill from them.
- **Quarantined MAINTENANCE goals expire before `daemon_check` is generated**,
  so dedup no longer suppresses the check when every goal is quarantined.
  `ContextMode.COMPACT` raises `max_goals` 3→5 for the same reason.
- **`conscio --version`** exists.
- **`Engine.feed()`'s docstring described a cycle it never runs.** It emits on
  the EventBus and returns `advisory()` — no perceive, no reflect — so the
  advisory is state as of the *previous* cycle. An embedder trusting the
  docstring would ingest events that never reach the world model.
- **`conscio.remember` wrote where `recall` does not read.** It now writes
  durably to the same store.
- **`--default-model`** is a last-resort model channel (`--model` >
  `config.json` > `CONSCIO_MODEL` > `--default-model`), so a plugin install on a
  machine with no local config boots instead of exiting 1.
- **`validate_binding` bootstraps a fresh space** instead of advising a CLI the
  user may not have installed.
- **The governor reports when the ceiling is not being enforced.** It printed
  `governor ON (window 160,000)` directly above `Avg context/turn 188,606` and
  said nothing. Under real enforcement the mean sits below the ceiling by
  construction, so a mean above it is proof the host is not compacting there.
  The case hid well: a host that never compacts writes no cache, cache
  write/turn collapses, and unbounded growth renders as a large saving. `govern
  on` also now says the ceiling applies from the *next* session.
- **Two governor calibration errors biased the recommended window.**
  `WEIGHTS['cw']` was 1.25, the 5-minute TTL rate; Claude Code caches on the
  1-hour TTL, billed at 2× input — the weight only appears in the compaction
  term, so 1.25 understated the price of compacting by 60%. And `modelled_cost`
  measured the workspace as `window - prefix`, but a compaction lands at the
  landing point, not at the prefix, which inflated the workspace and recommended
  a window that recompacts almost immediately. The optimum moves from 138,144
  to 160,000.
- **obs.db's `project` and `agent` columns carried useless labels.** `project`
  stored the calling cwd, so one repository became several projects and a
  `scope='project'` search fired from a subdirectory saw a fraction of its own
  rows; it now resolves the root by walking up to a `.git` *entry*, so worktrees
  and submodules are projects of their own. `agent` was the literal
  `claude-code`, hardcoded in the hook, with a divergent `hermes` default in the
  engine; it now comes from `instance.json`. Read-only and fail-open: observing
  never mints identity, and a missing one costs a label, never an observation.

### Performance

- **Optional dependencies are probed with `find_spec`, not `import`.** Asking
  whether `sentence_transformers` was installed dragged torch in for a boolean:
  ~5s and ~390MB of RSS on every engine construction — MCP startup, every CLI
  command, every test process. `numpy` had the same shape in `vector_backend`,
  imported at module scope to set a flag even on the sqlite-vec path. Support is
  unchanged; the real imports already lived at first use.

  | | Before | After |
  |---|---|---|
  | MCP server startup | 7236ms | 446ms |
  | `ConsciousnessEngine()` | 5051ms | 62ms |
  | RSS, `import conscio` | 43MB | 29MB |

  This is also why a full pytest run OOMs: 75 test files construct an engine,
  each paying ~390MB for a probe.

- **No fsync per commit in the memory stores.** SQLite's `synchronous` defaults
  to FULL and only obsstore and vector_backend ever set it — 2.47ms per EventBus
  event where NORMAL costs 0.08ms. WAL + NORMAL is the pairing SQLite documents
  as safe: a process crash loses nothing, only an OS crash or power cut can cost
  the most recent transactions. That trade is fine for memory, events, the
  knowledge graph and token accounting. It is not fine for state whose last write
  is a safety or audit fact, so agency (breaker, trust, ledger, profiles,
  skills), noosphere and liaison pass `durable=True` and keep FULL — all cold
  paths. The pragmas now live in one helper instead of nineteen call sites.
  `busy_timeout` is deliberately untouched: `sqlite3.connect` already supplies
  one, and writing a uniform value here would have silently shortened the waits
  callers chose.

### Tests

3300 passed, 25 skipped, 0 failures on Python 3.10 — the oldest version the
release gate covers — one file per process.

---

## [3.9.7] - 2026-08-04 — Vector Backends & Hard Purge

Three vector backends with auto-detect, HNSW as the production backend, a
one-command CLI migration, and the complete removal of MemPalace/ChromaDB.

### Vector backends

| Backend | Search | Recall@10 | Ingest | Disk | RAM | Setup |
|---|---|---|---|---|---|---|
| **HNSW** (`hnswlib`) | 2.9ms | 99% | 28s | 65MB | 300MB | `pip install hnswlib` |
| **sqlite-vec** | 17ms | 100% | 12s | 58MB | 0 | `pip install sqlite-vec` |
| **numpy** (default) | 180ms | 100% | 0 | 74MB | 0 | zero deps |

Measured on 37,042 real embeddings, 384-dim, all-MiniLM-L6-v2. HNSW is 62×
faster than numpy and 6× faster than sqlite-vec. Its O(log n) graph lives in a
separate `hnsw.db` (M=32, ef_construction=400, ef_search=max(limit×16, 256)).

Auto-detect priority at startup is **HNSW → sqlite-vec → numpy**, with no env
var needed. `VectorBackend.with_engine()` detects HNSW by `hnsw.db`, sqlite-vec
by the `vec_chunks` virtual table, numpy by the `vectors` table. Override with
`CONSCIO_VEC_BACKEND=hnsw|sqlite_vec|numpy`.

### CLI migration

```bash
conscio migrate-vectors                  # → sqlite-vec (10×, default target)
conscio migrate-vectors --target hnsw    # → HNSW (50×, direct)
```

Every path auto-detects the source format, backs up first, verifies search
rankings, and writes the target. HNSW writes to `hnsw.db`, so the original
`vectors.db` is never clobbered.

### MemPalace / ChromaDB — removed

`import_format_mempalace()` (81 lines), `tests/test_import_mempalace.py`, all
references in 9 source files, and the `mempalace/` directories are gone.
Migration guide: [docs/MIGRATION.md](docs/MIGRATION.md).

### Fixed

- **ContentStore DB path.** The engine opened `conscio.db` as the ContentStore
  and ignored `content_store.db`, where all real content lives, so `recall()`
  always returned 0 snippets.
- **sqlite-vec dimension auto-detect.** Reopening an existing `vec_chunks` table
  without `dimension=` defaulted to 768; `_init_db()` now parses `float[N]` out
  of the DDL.

---

## [3.9.6] - 2026-08-02 — Deep Audit

An audit of every public module found four bugs broken since v3.9.

### Fixed

- **BUG-48:** `ActionLedger.record(status='executed')` left `ok` NULL, so
  executed actions never appeared in `executed_since` — and `distill()`, which
  reads that feed, was producing **zero skills**. `ok` now defaults to `True`
  when the caller signals success.
- **BUG-40:** skills imported via `graft()` with `successes=0, failures=0` rated
  `0.0` and were filtered by `few_shot()` — the agent guarded its skill DB and
  never served a single imported skill. `_rate(0/0)` now returns `1.0`: a fresh
  import is not a failure.
- **BUG-49:** `should_trip()` checked only `consecutive_failures` and ignored a
  manual `trip()` quarantine, so the agent kept acting on goals it had
  explicitly barred. It now checks `is_quarantined()` first.
- **BUG-47:** the `EventBus` was built for a single thread and crashed on any
  concurrent `emit()`. It now uses `check_same_thread=False` and a lock.

### Added

- 10 regression tests (`test_audit_bugs.py`).

---

## [3.9.5] - 2026-08-02

A daemon that came back from a restart alive and doing nothing: the right
adapter, the right mode, and every action refused — while the circuit breaker
whose quorum latched the lockdown had already released every goal it held.

### Fixed

- **A global lockdown outlived the condition that caused it (BUG-37).** A quorum
  of quarantines latches `action_lockdown`, and the latch is persisted so it
  survives a restart. But the breaker behind it is self-healing —
  `quarantined_count()` ignores rows whose cooldown has lapsed — and
  `ActPipeline.act()` short-circuits on the persisted flag *before* consulting
  the breaker, so the release the breaker had already granted was unreachable.
  In the field: a daemon that restarted cleanly ten thousand times and stayed
  paralysed through every one, with an empty quarantine table. The latch is now
  re-derived from the breaker at `attach_adapter()` and at `act()` (a long-lived
  daemon never re-attaches). Safety holds in both directions: the latch is
  released only by the component that raises it, only when it says no lockdown
  is due, never when there is no breaker to ask, and the release is emitted as
  an event.
- **A per-run brake was reported as permanent status.** The aggregate
  failure-rate brake stops one `run()`, but `advisory()` scanned the whole event
  log, so a brake from a previous heartbeat — or a previous week — read as the
  current reason the loop was stopped. The scan now starts at the most recent
  `run()`.
- **`storage_path` took `~` literally**, creating a directory named `~` in the
  working directory and storing a mind inside it.
- **`HERMES_HOME` took `~` literally, in six places** — the session database,
  the session RAG store, the noosphere layout, the CLI default and both
  observatory databases. The shared fallback is absolute, so with the variable
  unset every one was correct; the defect only appeared once something exported
  an unexpanded tilde, at which point all six became paths relative to the
  working directory and the layers above silently created new files elsewhere.
- **Eight more environment variables took `~` literally (BUG-38b)** —
  `CONSCIO_SESSION_DB`, `CONSCIO_HANDOFF_DIR`, `CONSCIO_BASE`,
  `CONSCIO_VAULT_DIR`, `CONSCIO_WORKSPACE`, `CLAUDE_DIR`, `CLAUDE_JSON`,
  `CLAUDE_PROJECT_DIR`. The worst was two lines below a site the previous fix
  had just corrected in the same file. All fifteen sites are now tested in
  isolation.
- **`conscio.feed` and `conscio.note` crashed on host events (BUG-39).** Hosts
  that predate the Event schema send `data` instead of `payload` and omit
  `source`; the validator rejected them and the server dropped the MCP
  connection. `_normalize_event()` now maps `data`→`payload` and defaults
  `source`/`category` before validation. Canonical events are unchanged.

---

## [3.9.4] - 2026-08-02 — Reachable Daemon

A field report from an operator trying to turn Awake on and keep it on, plus
three validation passes that each turned up more than the last. Thirty defects:
a daemon that ignored its operator, an auditor that refused every tool this
project defines, a governor whose numbers described the wrong database, and a
long tail of cleanup paths that were skipped, aborted, or never asked.

### Added

- **`world_prune` tool.** The maintenance drive raises an auto-executable goal
  to prune stale entities, but nothing in the registry could prune anything —
  the work lives in `WorldModel.prune_stale()`, entirely in-process. The actor
  reached for the closest thing it had, `fs_write`, invented sandbox paths,
  failed three times and tripped the breaker. MEDIUM, not HIGH: HIGH never
  auto-executes (R6) and would leave the goal as unsatisfiable as before. Live
  act and promotion registries only — a trial replays a *peer's* skill and must
  not prune this instance's memory.
- **`CoherenceReport.unmeasured`** — the dimensions whose score is a default
  rather than an observation. Three of the four scorers return their maximum on
  an empty substrate, so a fresh mind reports 0.85, the same number a
  well-measured coherent one reports. No score changed; the report carries the
  list alongside and `marker()` renders it.
- **`/conscio:govern` slash command** — ceiling, baseline and capture health
  without remembering the CLI.

### Fixed — daemon and agency

- **`conscio awake` never reached a running daemon.** It woke the engine it had
  just opened, so the CLI printed `Awake Mode: ON` while the daemon — which
  holds its own engine in its own process — kept sleeping. `awake`/`sleep` now
  write `daemon_control.json`, the channel a `--watch-control` daemon reads.
- **Maintenance goals multiplied every cycle and could not be cancelled.** A
  goal's identity was its description, which embeds a live reading (`23 stale
  entities: …`), so one entity going stale renamed the goal — and a renamed goal
  is a new goal. Dedup also ignored cancelled goals. Goals now dedup on
  `Goal.dedup_key`, and a cancellation suppresses its check for 24h.
- **A self-prompt goal could surface an id in no store** — `reflect()` resolved
  by description while the store deduped by key.
- **The auditor refused every tool this project defines.** The Skeptic is ordered
  never to accept an invented tool and was never told which tools exist, so it
  judged each name against its pretraining: *"No evidence that world_prune is a
  valid or existing tool."* Field-measured: 32 actions attempted, 0 executed,
  with the actor choosing correctly every time. The name is already resolved
  against the registry *before* the audit runs, so the audit was re-deciding a
  settled question with less information than the code that settled it. The
  Skeptic now receives the resolved tool's name and description, marked
  verified, and judges only what it alone can: whether this action fits this
  goal. A tool not in the registry never gets there.
- **The maintenance goal stated a measurement where it owed an instruction.** It
  read `Maintenance: prune_stale — 23 stale entities: …`, where `prune_stale` is
  the dedup key's internal identifier and the rest is a reading off the world
  model — so the goal never asked for anything, and the audit was right to
  refuse. The identifier stays in the dedup key; the description is now a
  sentence naming the tool that satisfies it.
- **The daemon had no way to be given an API key**, so
  `--adapter openai-compat --base-url <remote>` posted unauthenticated and read
  the rejection as the model failing to answer. Added `--api-key` and
  `CONSCIO_API_KEY`, since `ps` shows a command line to every process.
- **An unreachable endpoint was reported as a decode failure.** With no reply
  from any tier, the operator got `all decode tiers failed` — a message that
  sends you to the schema to explain a DNS failure. The cause was already in
  `last_adapter_error` and dropped on the way out.
- **The tier ladder retried failures no lower tier could fix.** `should_retry`
  was consulted inside each tier, where both branches returned `None`
  identically, so a permanent 401 still bought a downgrade. The cascade now
  stops on connection failures and permanent errors. A timeout still continues:
  the host took the request, so this request is a candidate cause — measured on
  NVIDIA NIM, `response_format: json_object` times out where the same prompt
  without it answers.
- **Adapter errors did not say which endpoint failed.** Failures out of
  `_post_json` now lead with the URL, minus the query string — some providers
  accept a key there, and these messages reach the ledger.
- **Every autonomous action recorded a cost of zero.** `ActionLedger.record()`
  has taken `tokens_in`/`tokens_out` since v2.0.1 and no caller passed them. The
  gateway now totals every tier and retry, because the cost of an action is not
  the cost of the call that finally decoded.
- **Pruning an entity left its predictions pending forever** — predictions are
  settled only against the perceived set, so a prediction naming a removed
  entity held one of the 200 slots for good.

### Fixed — governor and stores

- **`govern report` compared the baseline against itself.** The current figure
  counted every turn in the session, including ones the baseline itself
  measured. Measured on a real 1,643-turn session: −2.0% reported, +41.6%
  actual. It now counts only turns from the freeze onward.
- **`govern report`'s `Saved` line printed a share with nothing to size it** —
  40% of a rounding error and 40% of a month's spend read identically. The
  absolute is now carried alongside, derived from the same two numbers.
- **`govern report`'s breakdown lost two of its three columns.** Not formatting:
  `govern on` never froze the cache figures. The snapshot now records
  `cr_per_request` and `cw_per_request`; a baseline frozen earlier prints `—`
  and the report names the command that records them.
- **`govern status` reported obs.db from a space nothing writes to**, so a store
  with 1.3 MB of observations read as `0.0 MB`.
- **The capture hook's obsstore path could dangle, silently.** The sidecar
  pointed inside the install tree, a path that dies on any pipx upgrade or venv
  rebuild — and the hook fails open by design, so a dead path exits 0 and a
  broken session looks exactly like a session with no tool calls. `obsstore.py`
  is now copied beside the hook, and `govern status` prints `Capture hook
  BROKEN`.
- **`token_usage` was the one store nothing ever released.** `TokenTracker.
  compact()` existed since v0.2 with no caller — 733,949 rows in a field
  database, driving the WAL churn that made a healthy WAL look like a leak. The
  dream's Release phase compacts it, and the writer enforces a hard cap so
  retention does not depend on whether a mind ever dreams.
- **Two paths through `run()` never dreamed** — a host with no adapter returns
  before the dream check and a locked-down loop `break`s past it, while both
  keep writing to the stores the dream prunes. `DreamCycle` makes no model call,
  so gating housekeeping behind an inference backend was never the intent. R9 is
  untouched.
- **Suppressed duplicates were counted nowhere.** `emit()` dedups by not
  inserting; `is_duplicate` is a soft-delete mark, a different mechanism. Now
  counted on the surviving row in `duplicates_suppressed`.
- **A caller-supplied project was recorded as uncertain attribution** —
  `observe()` is *handed* the project and still emitted
  `attribution_confidence=0.0`. Now 1.0 when a project is named.
- **An enrichment failure erased the previous session's handoff.** `handoff` and
  `heartbeat` stayed `""` and the persist step wrote them anyway, replacing the
  only record of the session before it. Empty content is no longer written.
- **A failed `dump()` leaked its destination handle** — `Hallways.dump()` and
  `Deduplicator.dump()` closed the target on the line *after* `backup()`. Both
  now close in a `finally`.
- **A tombstoned source could not be deleted, and one aborted a whole
  compaction.** `source_tombstones.source_id` is a foreign key onto `sources`
  with `PRAGMA foreign_keys=ON`, so the delete raised `IntegrityError` inside
  `ContentStore.compact()`. The tombstone is now deleted first.
- **A failing checkpoint write held its connection open.** The exception →
  traceback → frame cycle means only the *cyclic* collector frees it; measured
  with GC disabled, 30 failed appends held 30 descriptors. Now opened through
  `contextlib.closing` — not `with sqlite3.connect(...)`, which leaves the
  connection open and would have leaked on *every* call.
- **Consolidating the checkpoint chain cut it** — the first survivor kept
  pointing at a deleted `parent_id`. It is re-anchored to the merged row.
- **Two processes appending a checkpoint at once forked the chain.** An append is
  an unserialized read-modify-write, so two processes could claim the same
  parent — and a chain where two rows share a parent silently drops history when
  you walk back from the tip. Measured with four processes appending 25
  checkpoints each: 4 of 5 runs forked, one producing three roots. The append now
  opens with `BEGIN IMMEDIATE`, with consolidation inside the same transaction.
- **`evolution.reject(proposal)` silently did nothing.** All four verdict methods
  took an id string and answered `None` for "no such proposal", so handed the
  object `pending_proposals()` returns, the state change was dropped. They now
  accept either, and raise `TypeError` rather than borrowing the answer that
  means "not found".
- **An answered proposal was proposed again** — `observe_errors` deduplicated
  against `pending_proposals()` only. It now dedups against every verdict that
  still stands; `ROLLED_BACK` is excluded on purpose.
- **A save that died emptied the proposal file.** `Path.write_text` truncates
  before it writes and `_load()` degrades an unreadable file to `[]`, so an
  interrupted write surfaced not as corruption but as a mind with no proposals —
  a valid state. Now written through `atomic_write_text`. Concurrent *writers*
  are still unsafe; that is a design change, not a one-line fix.
- **An approved change nobody applied blocked nothing** — `delivery_check`
  counted PENDING, and `approve()` moves a proposal out of PENDING, so a crash
  before `mark_applied()` left it invisible to the gate forever. Both are now
  reported.
- **A timezone-aware timestamp made a live entity look a decade stale.**
  Subtracting a naive `now` raises `TypeError`, which the pruner treated as
  garbage — and unparseable means infinitely old, so the entity was purged. All
  nine parse sites now go through one helper.
- **The lint gate had been red since v3.8.** `vulture` flagged
  `obsstore.read_observation`, exercised only from tests, which it does not scan.
  Whitelisted, with its reason.

---

## [3.9.3] - 2026-08-01 — Field Repairs

Driven by a hostile audit of a running v3.9.2 install rather than by a plan.
Every fix below answers something the agent did, not something the code looked
like it might do.

### Fixed

- **A quarantined goal stayed quarantined after its cooldown expired.**
  `is_quarantined()` and `quarantined_count()` trusted the row's presence, and
  the cooldown was honoured only by `review_quarantine()` — which runs at the
  top of an `act()` cycle. An agent whose actions are gated never gets there:
  a sleeping daemon, or a global lockdown standing on those very rows. Paralysis
  therefore outlived its cooldown indefinitely; found in the field 150 hours
  past expiry, with the quorum still counting the cold rows. Both readers now
  compare `cooldown_until` themselves, so recovery no longer depends on anything
  sweeping the table.
- **`all decode tiers failed` named its outcome and not its cause.** The reply
  was discarded on the way out, so a model answering prose and a model answering
  JSON with the wrong keys produced the same message and the same `tool=(none)`
  row. `GatewayError` now carries the validation errors and a bounded sample of
  what the model actually said.
- **One unreadable transcript could empty the whole context report.**
  `_recent_transcripts()` filtered with `Path.is_file()`, which before 3.13
  re-raises any errno outside ENOENT/ENOTDIR/EBADF/ELOOP — a stale NFS handle
  (ESTALE) over a single file collapsed the report to nothing. The same method
  reads a cached dirent on 3.14 and takes a fresh stat before it, so an
  identical rotation behaved differently per interpreter and the regression test
  only held on the newest one. One stat per path now decides both that the entry
  is a regular file and where it sorts; a failure costs that path alone.
- **The wheel shipped bytecode.** The recursive asset glob reads the working
  tree, so v3.9.2 carried a 3.14 `.pyc` into every install, including the ones
  on 3.10. Package data now excludes `__pycache__`.
- Two `log_message` overrides narrowed their base signature, which pyright
  reported and a caller passing `format=` would have hit as a `TypeError`.
  The type check is now clean at zero warnings.
- **The capture-budget tests measured the runner, not the hook.** One batch of
  40 timings on a shared CI machine puts a neighbour's IO stall inside our p95
  — observed at 124.7 ms against a 60 ms budget, on an unchanged hook. Both
  budget tests now take the best of repeated batches: interference only ever
  adds time, so the lowest measurement is the closest estimate of what the hook
  itself costs, while a real regression slows every batch and still fails.

### Audited and left alone

Findings from the same audit that are not defects, recorded so the next reader
does not re-open them:

- **`world_entities`, `world_relations` and `meta_errors` are empty by design.**
  They are targets of the one-time `conscio migrate` from legacy JSON. The live
  world model and meta-cognition keep their state in `world_model.json` and
  `meta_cognition.json`. An empty table means nothing was migrated, not that
  nothing is being recorded — which is why `health` can report 23 stale entities
  over an empty `world_entities`.
- **`events.db` is not ours.** The name appears in no version of the package,
  in any commit. The EventBus writes to the `events` table inside `conscio.db`.
  A zero-byte `events.db` in a storage directory can be deleted.
- **`saving_pct = 0` on `source=reflection` is the correct value.** The output
  filter strips ANSI, repeated blocks and tool noise; a reflection summary
  contains none of that, so `raw == filtered`. The row is instrumentation, not
  a promise.
- **`conscio.cc_hook` and `conscio.context_governor` never existed.** Capture is
  a standalone script (`integrations/claude_code/assets/hooks/conscio_deepminer.py`)
  that deliberately imports nothing from the package — importing `conscio` costs
  ~0.28 s, and this runs once per tool call. The governor is `conscio.governor`,
  a module of functions with no class.

---

## [3.9.2] - 2026-08-01 — Context Governor

The 3.9 line in one release. It was developed as three slices — `3.9.0`
ObsStore, `3.9.1` capture, `3.9.2` Governor — and none was published
separately, so this is the first release to carry any of them.

### Added

- **ObsStore** — observations move to their own schema with content-addressed
  blob storage, session-scoped writes, and FTS search that stays inside the
  current session unless `scope` widens it to `project` or `all`. Retention
  enforces an age window, a byte cap, and orphan collection. The v1→v2
  migration is atomic and the module stays loadable standalone.
- **Automatic capture on Claude Code** — the installer registers a hook on
  `SessionStart`, `PostToolUse` and `PostToolUseFailure` that records every
  tool call into `obs.db`. It never alters tool output and never blocks a
  session: any failure exits silently. Session start prunes the store and
  injects an index of the previous session — what ran, never what it returned.
  Capture is held to a 60 ms p95 budget by test.
- **Context Governor** — `conscio govern prefix` measures the stable prefix and
  the point compactions actually land at, then prints the cost curve for every
  candidate window. `govern on` applies the cost-optimal one to the project's
  `.claude/settings.local.json` (scoped, gitignored, backed up) and freezes a
  baseline; `govern report` compares against it using the host's own
  `message.usage` records; `govern off` restores what you had.
- **Compaction bracket** — `PreCompact` steers the summariser toward what is
  worth keeping and `PostCompact` stores the summary the host produced, then
  points at the detail that survived it.

### Fixed

- The compaction hooks spoke a contract the host does not implement.
  `PreCompact` emitted a `hookSpecificOutput` envelope, but the host joins raw
  hook stdout and hands it to the summariser as its instructions — so the steer
  was a JSON blob standing where prose belongs. `PostCompact` read
  `payload["summary"]`; the field is `compact_summary`, so every summary was
  discarded while the hook reported success. Both were silent.
- The compaction floor pooled landings across models. A `sonnet-5` landing of
  142,208 governed an `opus-5` session whose own landings topped out at
  125,586, and since the floor is a maximum, the foreign number won. Same-model
  landings only, falling back to the pooled maximum rather than to zero, which
  would drop the guard entirely.
- The floor also dropped every landing whose first post-compaction row was
  billed nothing — 20 of 95 on one host. Fewer landings can only produce too
  low a floor, which is the direction that permits the compaction loop the
  number exists to forbid.
- Capture was inert on every fresh install, and opening an up-to-date store
  took the write lock unnecessarily. The one-time WAL conversion now retries
  the busy state SQLite refuses to wait through.
- Listing transcripts no longer raises when one rotates away mid-walk.

## [3.8.2] - 2026-07-31 — DeepMiner Economics

v3.8.0/3.8.1 shipped DeepMiner without ever measuring what a recall *costs*.
Benchmarked against three real session transcripts (6,195 actual
tool_use/tool_result pairs, tiktoken `cl100k_base`), it saved a median of
**32–39%** per query — below the 60% floor the feature exists to clear — and
**~25% of queries cost more than simply keeping the raw tool output**. Two
causes, both fixed here. No data migration: `obs.db` is unchanged.

### Changed

- **`recall_observations()` returns a snippet window, not the whole row.** A
  recall answers "where did I see this?", but it was returning the entire
  stored observation (up to 2×1024 chars) — a flat 334–357 tokens, heavier
  than the median tool output it was meant to replace. It now returns the
  FTS5 `snippet()` window (±32 tokens, elided with `…`) around the hit.
  Measured on the same three corpora: median saving **31.7/38.0/38.6% →
  79.9/82.8/81.2%**, cost **334–357 → 108–129 tokens**, queries that are
  cheaper than the raw output **71–79% → 97–99%**, with **fidelity
  unchanged** (96.7/90.0/98.7% in both arms — the snippet contains the hit by
  construction, so nothing retrievable was lost). New `full=True` argument
  (engine and the `conscio.recall_observations` MCP tool) returns the whole
  stored row for callers to whom the entire observation is the answer.

- **`compress_observations()` reads the session's tail, not its head.** It
  ordered `ORDER BY id` and took `rows[:8]`, so a handoff — the artifact whose
  job is to resume work — described the *oldest* calls. Measured on a
  3000-observation session: 4 of the first 5 observations present, 0 of the
  last 5. It now takes the newest, and renders them in chronological order.
  After: **5/5 newest present**.

- **The handoff spends its budget.** `format_handoff()` caps `actions` at 5,
  so the handoff used **18.5% of `HO_MAX_CHARS`** and covered 5 of 3000
  observations. It now emits `chunks` (which the formatter renders in full)
  under an explicit char budget: **94–97% of the budget, 20–22 entries**,
  never exceeding it. `count` still reports every observation in the session,
  not just the rendered window.

### Fixed

- Observation fields are flattened to one line before entering the handoff. A
  newline in captured tool I/O could previously forge an extra handoff entry,
  since the format is line-oriented.

### Added

- Six tests in `tests/test_deepminer.py` covering snippet vs `full=True`,
  handoff recency, budget spend without overrun, `count` vs rendered window,
  and the newline-forging guard.

### Notes

- Capture remains **lossy by design** and is now documented as such:
  `observe()` stores at most 1024 chars per field. On the benchmark corpora
  that discards **58–61% of characters**, and 0 of 60 probe terms sampled
  from beyond the cap were recoverable. DeepMiner is an index of what was
  seen, not an archive of it.
- The savings above are **cross-session / post-compaction**. Nothing in
  Conscio intercepts tool output — `conscio.observe` is agent-called, so the
  agent already holds the output it passes in. There is no intra-session
  saving without a harness hook, and this release does not claim one.

---

## [3.8.1] - 2026-07-30 — DeepMiner Hardening

Intensive confirmation pass over the v3.8.0 DeepMiner feature. **No behaviour
changes** — every engine and MCP contract shipped in 3.8.0 is byte-for-byte
unchanged; this release adds a dedicated intensive suite that proves the
feature end-to-end and locks in invariants the original suites left implicit.

### Added

- **`tests/test_deepminer_intensive.py`** — 15 tests driving the real offline
  `ConsciousnessEngine` (0 LLM tokens), complementing the behavioural
  (`test_deepminer.py`) and destructive (`test_deepminer_hostile.py`) suites:
  - **Persistence roundtrip** (the headline gap): `observe → compress →
    content_store.search` proves the handoff is durably persisted and
    retrievable under a `handoff_deepminer_*` label with
    `source_category="session"` — earlier suites asserted only the *negative*
    (that the platform-owned `_session_handoff.md` is never touched).
  - **Store isolation**: `obs.db` and `conscio.db` are distinct files whose
    contents never bleed across `recall_observations()` (obs.db) vs
    `content_store.search()` (conscio.db).
  - **Session precedence**: an explicit `session_id=` overrides
    `set_session()`; `compress_observations()` defaults to the wired session.
  - **Boundary & safety**: 1023/1024/2000-byte truncation edges; verbatim
    storage of SQL-metacharacter payloads with the `observations` table intact;
    FTS5 metacharacter queries (`*`, `"`, `NEAR`, emoji, …) return safely with
    `PRAGMA integrity_check = ok`.
  - **Concurrency**: six writer threads observing while the main thread hammers
    `compress_observations()` — final row count exact, obs.db integrity intact.
  - **Re-runnability**: a repeated `compress_observations()` within the same
    wall-clock second tolerates the second-granular label collision.
  - **MCP depth**: `conscio.observe` returns strictly-increasing unique
    `observation_id`s; `conscio.recall_observations` with an empty query
    returns `[]` instead of crashing.

### Notes

- Documented the `recall_observations(query, k)` contract: `k` is floored via
  `max(1, k)`, deliberately neutralising SQLite's "`LIMIT -1` means unlimited"
  footgun — `k <= 0` returns exactly one row, never the whole table.

## [3.8.0] - 2026-07-30 — DeepMiner

### Added

- **DeepMiner** — an agnostic tool-observation store isolated in its own
  `obs.db` (SQLite + FTS5), separate from `conscio.db`:
  - `ConsciousnessEngine.observe(tool, input, output, project, agent, session_id)`
    — fire-and-forget capture of a raw tool call at **0 LLM tokens**; never
    raises (returns `-1` on write failure) and never blocks the calling agent.
  - `ConsciousnessEngine.recall_observations(query, k)` — full-text (FTS5)
    search over raw tool calls; the query is bound as a literal phrase, so
    SQL/FTS metacharacters can never inject.
  - `ConsciousnessEngine.compress_observations(session_id)` — bridges
    `obs.db → SessionSummary → format_handoff()` to build a handoff at **0 LLM
    tokens**, persisted through `content_store.index()` under its own label;
    never touches the platform-owned `_session_handoff.md`.
  - `ConsciousnessEngine.set_session(session_id)` — wires the platform session
    id so `observe()` and `compress_observations()` share it.
- **MCP tools** — `conscio.observe` and `conscio.recall_observations` added to
  `BASE_TOOL_DEFS`, so any MCP-speaking agent (not only Conscio) can record and
  recall raw tool observations.

### Notes

- Per-session isolation; `input`/`output` truncated to 1 KB and
  `tool`/`project`/`agent` capped, keeping capture bounded under flood.

---

## [3.7.0] - 2026-07-30 — ModeRouter + CLI Council + Payload Reduction

### Added

- **`conscio council <question>`**: CLI subcommand that convenes a 4-voice
  decision council (architect, skeptic, pragmatist, critic) with `--mode`
  flag (minimal/compact/full).
- **ModeRouter** (`conscio.mcp.ModeRouter`): reads `prompt_complexity` from
  `daemon_control.json` and chunkifies MCP tool output. Supported modes:
  `minimal` (~87 chars, 92% smaller than full), `compact` (~319 chars, 68%
  smaller), `full`, `agent_host` (delegate to host agent).
- **`conscio.reflect --mode minimal|compact|full`**: CLI reflect now respects
  output  verbosity modes.
- **`conscio.intercept` MCP tool**: safe expression evaluator via
  Intercepter AST engine (v3.6.3).
- **`solve_linear(a,b,c,d)`**: registered as default function in
  Intercepter's `_register_defaults()`.
- **Agent-host council delegation**: call council with
  `mode = "agent_host"` to receive question + hint and produce verdict
  consciously, without LLM.

### Changed

- **Default payload mode**: in `daemon_control.json` changed from `full` to `compact`.
- **MCP server**: council/evaluate/cognitive_cycle handlers now pass through
  ModeRouter, respecting the configured complexity mode.
- **MCP responses**: all tools now include a `mode` field
  (`deterministic`, `llm`, or `agent_host`).

### Fixed

- Council voices (architect, skeptic, pragmatist, critic) are fully
  deterministic — no LLM adapter needed. The council was already
  functional; chain optimization was payload chunking, not voice logic.
- Suite regress: 70/70 tests passing (11 new/ ModeRouter TDD + 70 existing).

---

## [3.6.3] - 2026-07-27 — Storage Optimization

### Added

- **Trigram DB separation**: `rebuild_db()` migrates the trigram FTS5 index
  from `conscio.db` to a separate `conscio_trigram.db`, reducing the main
  DB by ~40%. The trigram index is created on demand and searched via a
  transparent fallback when the separate DB exists.
- **`search(use_trigram=)` parameter**: when `False` (default), porter-only
  search for faster queries. When `True`, activates trigram for exact
  substring matching (code, IDs, file paths).
- **Auto-detect trigram**: queries containing dots, slashes, dashes,
  dotted numbers (e.g., `T1569.002`, `certutil.exe`) automatically
  activate trigram search without the explicit flag.
- **`conscio search --exact`**: CLI flag to force trigram search.
- **`rebuild_db()`**: idempotent migration with automatic backup, rollback
  on failure, WAL journal for trigram DB, and cursor-based batch copy
  (O(n) instead of O(n²) OFFSET).

### Changed

- Default `search()` now uses porter-only (dual-index RRF only when
  trigram is explicitly requested or auto-detected).

---

## [3.6.1] - 2026-07-26 — Gap Close

### Added

- **Tombstone tracking**: new `source_tombstones` table, `_mark_stale()` for
  content-changed detection, and `search(include_stale=False)` filter.
- **CLI `conscio search`**: query ContentStore from terminal (`conscio search
  <query> [--k N] [--category <cat>]`).

### Changed

- **CONSCIO_VECTORS auto-detect**: engine tries `import sentence_transformers`
  at init; if available, vectors auto-enable. `CONSCIO_VECTORS=0` forces FTS5-only.
  `CONSCIO_VECTORS=1` still force-enables (backward compat).
- **README/USAGE updated**: release label now `v3.6.1`, opt-in language replaced
  by auto-detect throughout all docs.

## [3.6.0] - 2026-07-26 — KnowledgeStore

### Added

- **Semantic chunking** (`ContentStore._chunk_content`): dispatch by exact
  `content_type` — `"yaml"` splits on `---` document boundaries,
  `"markdown"` splits on heading boundaries (`^#{1,3}\s`), everything else
  (`"prose"` default, `"code"`, `"metric"`, `"log"`) splits on paragraph
  boundaries. New categories `pentest`, `reference`, `payload`.
- **Vector embeddings** (`EmbeddingPipeline`, `VectorBackend`): batched
  embed-and-store on ingest, with dimension reconciliation (adopts the
  embedder's real dimension on an empty store; refuses silent corruption on
  mismatch against a populated one) and hash-based dedup. Opt-in via
  `CONSCIO_VECTORS=1` (default off — matches the `CONSCIO_SEMANTIC_DEDUP`
  precedent, so existing installs don't silently start embedding on every
  `index()` call).
- **`VectorBackend.search()`** rewritten from a per-row Python scan to
  batched numpy scoring (20-60x faster at 20k+ vectors), plus a denormalized
  `category` column for SQL-side pre-filtering.
- **`HybridRetriever`**: fuses `ContentStore` FTS5 (lexical) with
  `VectorBackend` (dense) results via Reciprocal Rank Fusion. Wired into
  `ContentLayerManager.recall()` as a third fused source alongside
  ContentStore-FTS5 and SessionRAG — degrades to lexical-only when no
  vector backend/embedder is configured or available, never raises.
  `engine.recall()`'s public signature and `list[str]` return type are
  unchanged (production callers: `mcp/server.py`, `agency/relay_cognize.py`,
  and engine's own reflect/goal/dream paths).
- **`conscio ingest <path>`** CLI command: bulk-ingest a directory
  (`--category`, `--chunk-size`, `--overlap`, `--model`, `--storage`), with
  binary/oversized-file guarding and hidden-directory pruning.

### Fixed

- Re-ingesting the same document no longer leaves orphaned vectors (deleted
  before re-index, same logical transaction as the FTS5 side).
- Embedding failures during ingest are now counted and reported (`failed`)
  instead of silently swallowed.
- `HybridRetriever.vector_only_search()` now pushes `category` into the
  vector search itself instead of filtering after the fact — a
  category-scoped recall no longer risks under-filling (or zeroing) the
  dense leg when another category dominates the unfiltered top-k.

## [3.5.0] - 2026-07-26 — Observatory + Daemon + Consent

### Added

- **`/api/projects` endpoint**: scans the workspace root (`--root` CLI flag)
  for subdirectories containing `graphify-out/graph.json`. Returns project
  name, node count, link count for each.
- **`/api/projects/<id>/graph` endpoint**: serves a project's `graph.json`
  directly. No database storage — reads from filesystem.
- **`/graph` endpoint**: serves `graphify-out/graph.html` (graphify's
  vis-network HTML) from the workspace root for full graphify compatibility.
- **`conscio observatory` CLI subcommand**: starts the Observatory web
  server with `--host`, `--port`, `--root`, `--token`, `--storage` flags.
  `--root` sets the workspace root for project discovery and
  graphify-out/graph.html serving.
- **Frontend rework**: complete UI overhaul with sidebar, project selector,
  and D3.js Canvas graph visualisation. No frameworks, ~15KB of new JS/CSS.
- **Sidebar with hamburger toggle**: collapsible navigation with project
  list and debug tab buttons.
- **Project selector**: lists all projects discovered in the workspace
  root. Clicking loads the project's graph in the Canvas viewer.
- **D3.js Canvas graph renderer**: draws graphs on a `<canvas>` element
  (not SVG) for performance with large datasets. Uses quadtree force
  simulation (O(n log n)) with alpha decay. Includes:
  - Radial gradient glow on nodes
  - Hover highlighting (node + connected links)
  - FPS monitor (reduces detail below 15fps)
  - Freshness card overlay
  - Resize handler
- **Fullscreen graph mode**: graph occupies 100% of the available area.
- **Graph data never enters the Conscio database**: `graph.json` stays on
  disk in the project's `graphify-out/` directory. The Observatory only
  serves it via HTTP. The database (conscio.db) is untouched.

### Changed

- `route()` now accepts optional `workspace_root` parameter.
- `make_server()` accepts `workspace_root` kwarg.
- `_STATIC_WHITELIST` reduced to core files only (index.html, app.js,
  style.css, d3.min.js).
- Old tab-specific JS files (structural.js, knowledge.js, graphview.js)
  remain in the static directory but are no longer loaded from index.html.

---

## [3.4.1] - 2026-07-25 — Council Auto-Reflect Fix

### Fixed

- **Council auto-reflect**: when `last_coherence` is None (fresh install,
  only note/feed used, no reflect called), council now automatically
  runs `engine.reflect()` before convening voices. Previously the
  Architect voice would always veto with "Cannot assess structural
  integrity without coherence data", blocking council on installations
  that only used `conscio_note`/`conscio_feed` without ever calling
  `conscio_reflect` or `conscio_cognitive_cycle`. Non-fatal: reflect
  failure does not block council.

---

## [3.4.0] - 2026-07-25 — Graphify Observatory

### Added

- **StructuralProjection** (`conscio.observatory.structural_view`):
  read-only view of StructuralDriftStore with `drift_timeline()`,
  `freshness()` (via compute_freshness), and `graph()` endpoints.
- **KnowledgeProjection** (`conscio.observatory.knowledge_view`):
  read-only view of kg.db with `entities()`, `relationships()`,
  `timeline()` endpoints. Opens with mode=ro, never writes.
- **6 new API endpoints** in observatory route():
  `/api/structural/drift`, `/api/structural/freshness`,
  `/api/structural/graph`, `/api/knowledge/entities`,
  `/api/knowledge/relationships`, `/api/knowledge/timeline`.
- **3 new tabs** in Observatory frontend: Structural, Knowledge.
- **Freshness card** showing graph commit vs head commit + stale status.
- **D3.js v7** inline (no build step, no CDN, 280KB local).
- **StructuralSignal.generator_version** field (default "unknown"):
  reads from graph.json metadata or top-level key.
- **Daemon emits `structure:changed`** event when drift detected
  after sync_structure loads a graph and compute_freshness() reports
  stale. Non-fatal — never kills the loop.
- **auto-reindex opt-in** (`Daemon(auto_reindex=True)`, default OFF):
  triggers `graphify generate` on staleness when graphify binary is
  on PATH. Non-fatal: failure logged, not raised.

### Fixed

- KnowledgeProjection handles corrupt/missing kg.db gracefully
  (catches both OperationalError and DatabaseError).

---

## [3.3.0] - 2026-07-23 — Awake Maturity

### Added

- **FilesystemSensor** (`conscio.perception.filesystem_sensor`): read-only
  mtime polling via `os.scandir()`, depth limit, ignorelist (.git,
  __pycache__, .venv, etc.), summarises when delta > max_files,
  never raises. Implements `SensorAdapter.perceive()`.
- **GitSensor** (`conscio.perception.git_sensor`): read-only
  `git log --since` via subprocess, idempotent by commit hash,
  timeout-bounded (5 s), summarises when > 5 new commits. Implements
  `SensorAdapter.perceive()`.
- **GoalTemplates** (`conscio.awake.goal_templates`): deterministic
  signal→goal mapping — .py modified → "verificar testes cobrem {file}",
  new commit → "revisar diff {hash}". Skips test files (meta-recursion
  guard). Groups > 4 files/commits. Zero LLM, zero random.
- **AwakeBudget** (`conscio.awake.budget`): inherits `ActBudget`,
  tighter defaults (max_cycles=3, max_tokens=20k, max_wall_s=60,
  failure_rate=0.3).
- **NeurataBridge** (`conscio.integrations.neurata`): CLI subprocess
  bridge to Neurata. `shutil.which("neurata")` detection, contract
  version probe, cache by context hash (60 s TTL), fallback graceful
  (available=False → everything returns None). Conscio core never
  imports neurata.
- **Daemon idle gate**: `Daemon.cycle()` now pauses the cognitive cycle
  when no sensor produces signal (empty observations). Writes heartbeat
  with `idle_cycles`, `sensors_active`. Warning after 5 consecutive
  idle cycles.
- New packages: `conscio.awake`, `conscio.integrations`.

### Tests

- 50 new tests (12 filesystem, 8 git, 11 goal_templates, 4 awake_budget,
  9 neurata_bridge, 6 daemon_idle).
- Full suite: 2573 passed, 3 skipped.

## [3.2.0] - 2026-07-22 — Inference

Self-contained memory + inference layer. Adds 9 new modules, 6th evaluation axis,
3 new MCP tools, and native embedding fallback.

### Memory modules (9 new)

- **KnowledgeGraph** (`conscio/kg.py`) — entities + triples SQLite, FK enforcement,
  WAL mode, `add_entity`, `query_entity`, `query_relationship`, `timeline`.
- **Hallways** (`conscio/hallways.py`) — wing → room → drawer hierarchy with
  auto-created defaults (`default` wing/room), FK enforcement, stats.
- **WingManager** (`conscio/wings.py`) — integrates Hallways + ContentStore;
  filtered search by wing/room; index/check_exists/list_wings.
- **VectorBackend** (`conscio/vector_backend.py`) — cosine search on SQLite BLOB
  (array.array stdlib), optional numpy acceleration, zero external deps.
- **Deduplicator** (`conscio/dedup.py`) — SHA256 NFKD + ASCII fold hash + Jaccard
  bigram similarity. `compute_hash`, `is_duplicate`, `register`.
- **EntityDetector** (`conscio/entity_detector.py`) — regex Unicode (PT accents:
  São, João, Ção), detects persons/projects, domains, versions; KG integration.
- **EmbeddingProvider** (`conscio/embedding.py`) — 3-tier fallback:
  1. Ollama (if daemon running)
  2. OpenAI-compatible API (LM Studio etc, if running)
  3. sentence_transformers all-MiniLM-L6-v2 (NATIVE — no daemon, in-process,
     384-dim, ~90MB cached)
  4. None (graceful degradation)

  Optional 768-dim: `CONSCIO_EMBED_MODEL=nomic-embed-text-v1.5 CONSCIO_EMBED_DIM=768`.
- **Miner** (`conscio/miner.py`) — file + conversation + directory ingestion.
  Supports .md/.txt/.jsonl. Paragraph splitting, JSONL turn parsing, os.walk with
  skip dirs (.git, __pycache__, node_modules, .venv, etc). Dedup integrated.
- **Migration** (`conscio/migration.py`) — `export_archive` (tar.gz with
  ContentStore.db + kg.db + hallways.db + metadata.json), `import_archive`
  (round-trip restore), `import_format_mempalace` (ChromaDB adapter — reads
  `embedding_metadata.string_value` for `chroma:document`, `wing`, `room`).

### Evaluation

- **output_quality axis** (6th) — added to `evaluate()` when `output` is provided.
  LLM-as-judge via engine adapter; heuristic fallback (word count + structure signals:
  lists, headers, code blocks). Overall score now divides by `len(axes)` not hardcoded 5.

### MCP tools (3 new)

- `conscio.kg_query` — query KG for entity, relationships, timeline.
- `conscio.wings_search` — search content with optional wing filter.
- `conscio.export` — export memory to tar.gz archive.

### Migration validated

- **Real migration: 8551 drawers** from MemPalace ChromaDB → Conscio ContentStore.
  30 wings, 127 rooms, 44.7 MB. FTS5 search: 3.7-8.7ms/query.

### Performance (real benchmarks)

- FTS5 search (8551 drawers): 3.7-8.7ms/query
- KG query: <0.1ms
- Export (17.2 MB tar.gz): 2.3s
- Native embedding (384-dim): 0.12s/encode, load 3.2s from cache
- ContentStore write: 4.4ms/doc
- Concurrency: KG WAL mode supports 50 concurrent writes (5 threads) in 162ms

### Exports

- All 9 new modules exported from `conscio/__init__.py` and added to `__all__`.
- `ContentStore.dump()` added (sqlite3 backup API).

### Tests

- **2523 passed, 3 skipped** (74 new tests: 8 entity_detector + 8 embedding + 8 miner +
  5 migration + 3 mempalace import + 6 evaluate_quality + 8 embedding updated).

---

## [3.1.0] - 2026-07-21 — Harness Efficiency Layer

Based on "The Harness Effect" paper (Writer, 2026, arxiv:2607.06906).
Implements 6 mechanisms for token economics in the orchestration layer.

### Added
- **PromptZones** (`conscio/prompt_zones.py`): two-zone prompt with cache-shape
  discipline. Stable zone (persona + tool schemas + skill summary) is byte-identical
  across turns, enabling provider-side prompt caching at ~0.1x effective price.
  `build_zoned_prompt()` replaces `build_actor_prompt()` (kept as deprecated wrapper).
- **OutputGateway** accepts `PromptZones` objects with cache breakpoint at the
  stable/volatile boundary. `attach_ledger()` wires TokenLedger for per-task capture.
- **CompactionCheckpoint** (`conscio/checkpoint.py`): append-only checkpoint chain
  backed by SQLite. 4 artifacts (durable_memory, execution_summary,
  user_requirements, skill_references). Never rewrites — new prompt becomes new
  cacheable prefix. Consolidation merges old entries.
- **strategic_compact** now produces CheckpointChain entries when compacting.
- **TokenAccount + TokenLedger** (`conscio/token_account.py`): per-task token
  accounting. CPM (Completions Per Million effective Tokens) metric.
  effective_tokens = prompt - cache_read + completion. Append-only SQLite.
- **engine.token_summary()** and **engine.cpm()** convenience methods.
- **FailureClass + FailureGovernor** (`conscio/failure.py`): 6-type failure
  classification (RATE_LIMIT, STALL, TIMEOUT, MALFORMED_STREAM, PROVIDER_OUTAGE,
  PERMANENT). Per-tool circuit breaker. `should_retry()` returns False only
  for PERMANENT.
- **Progressive skill disclosure**: `build_zoned_prompt()` accepts `skill_summary`
  param — one-line name+description per skill in the stable (cacheable) zone.
  Full skill docs loaded on invocation, not in the prompt.
- **5 new VALID_TYPES**: `harness:cache`, `harness:zone`, `harness:checkpoint`,
  `harness:failure`, `harness:account`.
- **Ablation study script** (`scripts/ablation_study.py`): runs baseline vs
  harness across multiple models. Reports CPM improvement, token reduction.

### Changed
- `build_actor_prompt` is now a deprecated wrapper around `build_zoned_prompt`.
  Will be removed in v3.2.
- `ActPipeline.act()` calls `build_zoned_prompt()` instead of `build_actor_prompt()`.
- `OutputGateway.request_action()` accepts `str | PromptZones`.

### Tests
- 2447 passed, 3 skipped (up from 2402 in v3.0.1; +45 new tests)

### v3.1.0 Additions (2026-07-21, model-agnostic release)

- **Adaptive prompt complexity** (`conscio/agency/profiles.py`):
  `prompt_complexity(profile)` returns `'full'`, `'compact'`, or `'minimal'`
  based on the model's probe profile. `full` = complete persona + tools +
  memories + few-shot; `compact` = 1-line persona + tools + state (no
  memories/few-shot); `minimal` = tools + state only (no persona, for tiny
  models). Wired end-to-end: `probe()` → `act_pipeline.prompt_complexity` →
  `build_zoned_prompt(complexity=...)`.
- **`ACTOR_PERSONA_COMPACT`** (`conscio/prompt_zones.py`): 1-line persona
  for medium models that get confused by the full persona block.
- **Complexity fallback in bench**: tries `full` first, falls back to
  `compact` if args validation fails. Models with identical profiles but
  opposite preferences (Qwen 0.8B wants full, LFM 1.2B wants compact) both
  hit 100% JSON validity.
- **Auto-detect model** (`conscio/mcp/server.py`): `--model auto` triggers
  `GET /v1/models` from the LM Studio endpoint. Filters out embedding
  models, tests each chat model with a minimal prompt ("Respond with: ok"),
  returns the first that responds. Persists the winner to
  `~/.config/conscio/config.json` for instant next-boot startup.
- **FallbackAdapter** (`conscio/agency/fallback_adapter.py`): wraps multiple
  `OpenAICompatAdapter` instances. If the current model fails
  (PERMANENT/timeout/bad response), switches to the next model in the chain
  and retries. All models exhausted → raise. Wired in `main()` when
  `--model auto` detects multiple models.
- **Skeptic skip for safe tools** (`conscio/agency/act.py`):
  `_SKEPTIC_SKIP_TOOLS = {"think", "memory_note"}` — the skeptic audit LLM
  call is skipped for tools with no external side effects. Saves 1 LLM
  call per task in production. `fs_read` excluded (filesystem side-channel
  risk).
- **Adaptive max_retries** (`conscio/bench.py`): models with
  `json_fidelity >= 0.8` get `max_retries=0` (they don't need retries).
  Lower-fidelity models keep `max_retries=2`.
- **MCP `--model auto`** (`conscio/mcp/server.py`): the MCP JSON can now be
  fixed forever — no manual model swapping when LM Studio loads/unloads
  models. Conscio auto-detects what's available and falls back at runtime.

### v3.1.0 Benchmark (local LM Studio, 5 cycles)

| Model | json_fidelity | Tier | JSON valid | Tokens | Latency p50 | Catch rate |
|-------|--------------|------|------------|--------|-------------|------------|
| Qwen 0.8B | 1.0 | T2 | 100% | 5357 | 19.0s | 100% |
| LFM 1.2B | 1.0 | T2 | 100% | 4950 | 23.6s | 100% |

Both models: raw JSON validity was 60% (Qwen) and 80% (LFM); Conscio
brings both to 100%. Token cost 6–10× (no prompt caching on local LM
Studio); with provider caching, stable zone caches at ~0.1× → ~2–3×
effective cost. Catch rate (skeptic audit) 100% on both.

### Changed (v3.1.0)
- `build_zoned_prompt()` accepts `complexity` param (`'full'` default).
- `ActPipeline` has `prompt_complexity` attribute, set by `engine.probe()`.
- `bench.py` uses `build_zoned_prompt` with adaptive complexity + fallback.
- `_resolve_model()` in `server.py` supports `--model auto`.
- 3 tests updated for skeptic skip (fewer LLM calls expected).

## [3.0.1] - 2026-07-21 — Release pipeline hardening

### Fixed
- `release.yml` publish job: retry PyPI publish up to 3x with backoff. Root
  cause of v3.0.0's failed automated publish wasn't OIDC/trusted-publisher
  config (that was correct) — it was a transient `ConnectionResetError`
  talking to sigstore's Fulcio/Rekor during PEP 740 attestation signing.
  Retries use `skip-existing: true` so a partial prior upload doesn't fail
  the next attempt.

---

## [3.0.0] - 2026-07-20 — G-P-D (13 tools: gates + pipelines + diagnostics)

### Added — Ato 1: Gates (5 tools)
- `conscio.gates.decide()` — Architecture Decision Records with 4 statuses (proposed/accepted/deprecated/superseded), unique IDs with random suffix
- `conscio.gates.council()` — 3 deterministic voices (Arquiteto, Cético, Pragmatista) + optional LLM Critic, majority voting
- `conscio.gates.loop_gate()` — Autonomous loop gate with verifiable/budget/tools conditions
- `conscio.gates.delivery_check()` — Pre-close delivery check (rationalization scan, stale proposals, disk space)
- `conscio.gates.investigate()` — Read-before-act verification via EventBus evidence matching (substring matching)
- 4 new VALID_TYPES: adr:proposed, adr:accepted, council:convened, gate:vetoed
- Engine: delivery_check runs in close() BEFORE _closed flag; opt-out via flag
- All gate tools guarded by `_check_closed()` RuntimeError

### Added — Ato 2: Pipelines (5 tools)
- `conscio.pipelines.acceptance_criteria()` — Intent-driven criteria with auto risk detection (security/data/integration/compliance) and depth (quick/full)
- `conscio.pipelines.verify()` — Post-implementation verification against evidence events; loads from acceptance events via criteria_source
- `conscio.pipelines.continuous_loop()` — Pattern selection (sequential/continuous_pr/rfc_dag/infinite) with word-boundary matching + loop_gate integration
- `conscio.pipelines.strategic_compact()` — Compaction advisory based on token pressure + workflow phase + milestone detection
- `conscio.pipelines.ledger()` — Recursive decision ledger with coherence marks, promotion gates (paper→dry_run→live)
- 4 new VALID_TYPES: pipeline:acceptance, pipeline:verified, pipeline:compact, pipeline:ledger
- Global _counter for AC-ID uniqueness (prevents cross-acceptance evidence collision)

### Added — Ato 3: Diagnostics (3 tools)
- `conscio.diagnostics.context_budget()` — Token pressure audit, per-source breakdown, metabolic tiers, optimization recommendations
- `conscio.diagnostics.eval_harness()` — Define/run/report evals with pass@k reliability metrics (log-space computation for large N)
- `conscio.diagnostics.rules_distill()` — Scan patterns from skills/events/decisions, distill into rules, list existing
- 3 new VALID_TYPES: diagnostic:budget, diagnostic:eval, diagnostic:rule

### Infrastructure
- 13 engine convenience methods (lazy imports)
- 13 MCP tool definitions + handlers
- 13 tool exports from `conscio` namespace
- 11 new VALID_TYPES total (4 gates + 4 pipelines + 3 diagnostics)
- 460/460 tests passing (41 gates + 41 pipelines + 30 diagnostics)
- Hostile review scripts: 25/25 gates, 30/30 pipelines

## [2.15.0] - 2026-07-19 — ECC integration (5-axis evaluate + 3 workflow skills)

Theme: surface the introspection, evaluation, and audit patterns from ECC
(Everything Claude Code) as first-class Conscio capabilities. The engine
already had the modules — this release exposes them to host agents.

### Added
- **`conscio.evaluate()`** — 5-axis self-evaluation rubric (accuracy,
  completeness, clarity, actionability, conciseness). Scores 1-5 with
  concrete evidence. Pure read-only, deterministic, no LLM.
  - Available as `engine.evaluate()`, standalone `evaluate()`, and MCP
    tool `conscio.evaluate`.
  - Returns `EvaluationReport` with `AxisScore` per axis, overall score,
  ranked improvements, and a self-check verdict.
- **`conscio/evaluation.py`** — new module implementing the rubric
  heuristics (confidence → accuracy, stale entities → completeness,
  coherence → clarity, pending proposals → actionability, output
  length/repetition → conciseness).
- **3 workflow skills** (installed in both Conscio repo and Hermes):
  - `conscio-introspection-debugging` — 4-phase loop (capture → diagnose
    → recover → report) for when the agent is stuck.
  - `conscio-architecture-audit` — 12-layer stack checklist for
    systematic health checks.
  - `conscio-continuous-learning` — discover → capture → generalize →
    reuse for persisting non-trivial patterns.
- **`docs/guides/workflows/`** — 3 workflow reference docs in the
  Conscio repo for any MCP host.
- **`conscio manual`** CLI command now finds `conscio/USAGE.md` from the
  installed package (not just the repo root).

### Changed
- `conscio/mcp/schemas.py` — added `conscio.evaluate` tool definition.
- `conscio/mcp/server.py` — added `conscio.evaluate` handler.
- `conscio/__init__.py` — exported `evaluate`, `EvaluationReport`,
  `AxisScore`.

### Tests
- `tests/test_evaluation.py` — 32 new tests covering band mapping,
  data structures, fresh engine scores, output analysis, error impact,
  read-only guarantee, and improvement ranking.

---

## [2.14.0] - 2026-07-13 — Wired loop (hostile audit: every circuit closed or cut)

Theme: full hostile/skeptical audit of the codebase. Every half-wired feature
was either wired into the live perceive→reflect loop (TDD) or deleted.
No API is shipped that production code doesn't call.

### Added
- **`cognitive_cycle`** — explicit, useful reflect loop (perceive → reflect →
  advisory → propose), reachable from the MCP host.
- **Calibrated hybrid recall** — RRF-fuses lexical (FTS5) and dense (embedding)
  rankings; semantic hits no longer drown under exact-match noise.
- **Deterministic world_state entity extractor** — `engine.perceive()` now
  populates the world model from plain text (no LLM, no clock, no I/O),
  instead of waiting for explicitly-passed `entities=`.
- **Structural cognition via MCP host** — structure/lookup surfaced as tools.
- **Metabolic tier driven by host-reported context usage** — real token
  pressure replaces guessed session length.
- `docs/reference/conscio_functions.md` — generated reference guide for every
  public function.

### Changed (wiring — previously dead stores now live in the loop)
- **Reality dimension live**: `add_entity()` re-perception is the sole
  producer feeding `record_prediction()` → `recent_prediction_error_rate()` →
  coherence `reality` score. (#140)
- **`add_relation` wired into perception** — relations extracted
  deterministically from perceived text. (#142)
- **If-then predictions produce/validate/surface**: `reflect()` PREDICT stage
  generates persistence predictions; next `perceive()` validates them;
  `query()` surfaces matches. (#143)
- **Metabolic gates wired**: `should_dream`/`should_mitosis` now emit cycle
  advisories from `reflect()`. (#147)
- **Confidence loop closed**: `update_outcome` + `add_critique` /
  `recent_critiques` wired into the reflect cycle. (#148)
- `blind_spots` call sites ported to the public MetaCognition accessor;
  `list_relations()` refactor kills the last v0.6 private `_data` scans. (#145)

### Fixed
- Context fuzzling bug — `glm-5.2` detected as 200k instead of 1M.
- Context-first model resolution — vendor prefixes and config model resolve
  consistently; `resume` reads the model from config too.

### Removed (dead code — zero production callers, verified repo-wide) (#141, #144)
- `WorldModel.update_state`, `WorldModel.prune_irrelevant` (redundant with
  `add_entity` state_log + `prune_stale`/`prune_by_entropy`).
- `WorldModel.get_predictions` (test-only reader).
- `SessionVectorStore.upsert_chunk` (test-only duplicate of `upsert_batch`,
  the live write path via `index_recent_sessions`).
- `SessionRAG.search_and_format`, `ContentStore.rebuild`.
- Reference doc updated to match the shipped surface.

### Tests
- Full suite: **2246 passed** (2147 in 2.13.0) — every wiring change TDD'd
  (RED → GREEN), every removal covered by migration to the live path.

---

## [2.13.0] - 2026-07-07 — ReflectionGate (adaptive reflection depth)

### Added
- **ReflectionGate** — adaptive reflection depth gate inspired by PonderNet
  (Think-Vetor). Decides how many cycles of `reflect()` to run (1 to
  `max_cycles`) based on textual heuristics: confidence, coherence,
  contradiction count, novelty count. Pure read-only — does not modify state.
  Zero dependencies (stdlib only).
  - 4 heuristics: ConfidenceHeuristic, CoherenceHeuristic,
    ContradictionHeuristic, NoveltyHeuristic
  - Weighted score → cycle budget at cycle 0, continue/stop at per-cycle
  - Weights normalized at init, configurable via `config.json`
  - Failure of any heuristic → 0.5 fallback, gate still returns valid decision
- `ConsciousnessEngine.__init__()` — new `adaptive_reflection: bool = False`
  and `max_reflection_cycles: int = 3` parameters
- `reflect()` — when adaptive is on, runs 1-N cycles internally based on gate
- EventBus emits `reflection_gate` event with decision breakdown
- `event_bus.py` — `reflection_gate` added to `VALID_TYPES`

### Changed
- `reflect()` internals refactored: original body → `_reflect_once()`,
  new `reflect()` wraps it in adaptive loop when enabled

### Tests
- 44 unit tests in `tests/test_reflection_gate.py`
- 10 integration tests + 1 skipped in `tests/test_engine_adaptive.py`
- 3 smoke tests with qwen3.5-0.8b via LM Studio (gated by `CONSCIO_SMOKE_TEST`)
- Full suite: 2147 passed, 1 skipped, 0 failed

---

## [2.12.0] - 2026-07-06 — Intercepter (TV-DSL integration)

### Added
- **Intercepter** — safe AST evaluator for `[INTERCEPT: expr]` tags in LLM output.
  Allows the model to emit deterministic computation tags that are evaluated
  before the next generation cycle. Origin: Think-Vetor DSL concept, reimplemented
  from scratch with zero dependencies (stdlib only).
  - Bracket-scanning parser (handles arbitrary nesting depth, no regex)
  - AST whitelist: arithmetic (+, -, *, /, **, //, %), comparisons (>, <, >=, <=, ==, !=),
    unary minus/plus, built-in functions (abs, round, min, max, sum, pow, len),
    math functions (sqrt, floor, ceil, log, sin, cos, tan)
  - 10 security guards: no eval, no import, no attribute access, no subscript,
    no list/dict/set literals, no variables, no boolean ops, no conditionals,
    expression length limit (500 chars), AST depth limit (50)
  - `register_function()` with 4-layer guard: module blocklist, type annotation
    enforcement, dry-run return type check, name collision detection
- **InterceptionLoop** — orchestrates generate→intercept→reinject up to
  `max_iterations` (default 3). Returns `InferenceResult` (drop-in replacement
  for `adapter.generate()`). Observation truncation at 2000 chars.
- **OutputGateway integration** — `_generate()` helper routes through the loop
  when an intercepter is present. T1 (GBNF) bypasses the loop entirely.
- **Engine wiring** — `attach_adapter(intercept_enabled=True)` creates the
  intercepter, wires the loop into the gateway, and adds the "Deterministic
  Computation" section to the actor prompt via `build_actor_prompt()`.

### Changed
- **License: MIT → AGPL-3.0-or-later.** All future releases ship under AGPL.
  Previous MIT releases remain MIT.
- `build_actor_prompt()` — new `intercept_enabled: bool = False` parameter.
- `ActPipeline.__init__()` — new `intercept_enabled: bool = False` parameter.
- `OutputGateway.__init__()` — new `intercepter` and `max_intercept_iterations` parameters.
- `ConsciousnessEngine.attach_adapter()` — new `intercept_enabled: bool = False` parameter.

### Fixed
- `process()` duplicate-tag bug — `text.replace(original, replacement, 1)` replaced
  the wrong occurrence when the same expression appeared twice. Switched to
  positional replacement (right-to-left slicing).

### Tests
- 55 new tests in `tests/test_agency_intercepter.py` (Intercepter + InterceptionLoop)
- 5 new integration tests in `tests/test_agency_gateway.py`
- 2 new prompt tests in `tests/test_agency_actor.py`
- Full suite: 2093 passed, 0 failed
- Smoke test: qwen3.5-0.8b via LM Studio validated end-to-end

---

## [2.11.1] - 2026-07-03 — hostile-review fix pass on "Reach"

### Fixed
- **Installer `--repair` orphaned pre-Reach installs.** A pre-Reach wizard run
  produced doubled-slug space labels (`f"{host}-{spaces.slugify(host)}"`, e.g.
  `claude-code-claude-code`); `--repair` didn't recover that binding, so it
  silently created a NEW empty space instead of rebinding the existing one, and
  dropped the host's `--model` and MCP flags in the process. `hostcfg.existing_slug`
  / `existing_model` / `existing_flags` now read back the current config and
  `--repair` rebinds the SAME space, preserving model and flags.
- **Legacy `--initiate` consent silently dropped.** Old MCP launch configs
  carrying `--initiate` (a daemon-only flag, never valid for `conscio-mcp`) lost
  that consent on `--repair` with no explanation. It's now recovered via
  `_LEGACY_ARG_FLAG` and surfaced as a note pointing at
  `conscio daemon --awake --initiate` — never re-emitted on the MCP entry itself.
- **Relay initiator↔responder infinite ping-pong.** Gate 4b (auto-reply tail)
  and the broadcast outstanding-guard (gate 4') were permanent tombstones — an
  auto-responder-only peer, or a broadcast with zero engagement, silenced that
  channel forever. Both now decay after `relay_initiate.STALE_AFTER_S` (24h): a
  loop-breaker bound, not a one-way lock.
- **Relay burst thread-window truncation.** A same-peer message burst larger
  than `thread_limit` could push the oldest, still-unread messages out of the
  fetched window before the responder ever saw them. `_pending_counts` widens
  the `mailbox.thread()` fetch to cover the whole outstanding burst (mailbox
  still clamps the window to 200).
- **`conscio-hub --enable-daemon-control` without `--storage`.** Previously
  built a server that 500'd on every daemon-control request; `make_server` now
  raises `ValueError` at construction so the failure is immediate and legible.
- **Daemon spawn-crash detection race.** The grace window between `Popen` and
  the immediate-crash check was too tight on slower interpreter starts
  (0.2s → 0.5s), so a fast-failing daemon could be reported as started.

### Unchanged (debt-zero)
- No schema change; no new public API. `agency/relay_respond.py` gains shared
  helpers (`_is_auto_reply`, `_pending_counts`) reused by `relay_cognize.py` /
  `relay_initiate.py` — behavior of the unmodified paths is byte-equivalent.

---

## [2.11.0] - 2026-06-29 — "Reach" (one global install, per-host minds, native Claude Code integration)

### Added
- **One global install, per-host spaces.** A single `conscio` install serves many
  agentic hosts (Claude Code, Antigravity, any MCP host) on one machine; each host
  binds to its own space `~/.conscio/instances/<slug>/` (its own identity, memory,
  ledger, sandbox) while sharing one society (`~/.hermes/{noosphere,liaison}.db`).
- **`conscio init` — interactive installer.** A pure-stdlib wizard that creates a
  per-host space, emits + read-back-verifies the host's MCP launch config
  (`--storage <space>` + `env CONSCIO_VAULT_DIR=<space>/keys`), enables extras
  (Graphify), and can start the Awake daemon. `--repair` revalidates a binding;
  dangerous flags (act/relay/initiate) stay off and are explained inline.
  - New `conscio/installer/` package: `spaces` (binding), `hostcfg` (config
    emission, `.bak.<ts>` backups capped at 30, read-back verify), `extras`
    (declarative registry, Graphify only), `daemonctl` (PID lifecycle,
    cmdline-verified liveness, `start_new_session=True`), `binding` (R6 startup
    validation), `wizard` + `cli`.
- **Per-host secret vault (R2).** API keys live in a per-host vault selected by
  `CONSCIO_VAULT_DIR`; unset → the legacy global vault (backward-compat). No
  per-host→global fallback. (Logical least-privilege, not same-user OS isolation —
  see CLAIMS.)
- **Native Claude Code integration (`conscio/integrations/claude_code/`).** 10
  `/conscio:*` slash commands, a `conscio` skill (proactive/automatic face), and a
  defensive SessionStart awareness hook (always exits 0), materialized into
  `~/.claude/` by `conscio init` and bound to the host's space — idempotent,
  corrupt-config-safe, with backups. Shipped as package data.
- **R6 startup binding validation.** `conscio-mcp` / `conscio-daemon` warn (and
  suggest `conscio init --repair`) when `--storage` points at a missing/blank space
  instead of silently creating one.

### Fixed / Hardened (review gate findings)
- Daemon background launch uses `start_new_session=True` so Awake survives logout
  SIGHUP (R4); PID liveness matches the real `conscio-daemon` cmdline.
- Host-config writes are backed up and read-back-verified — never fail silently.

### Unchanged (debt-zero)
- Cognitive core + wire protocol byte-identical: `engine.py`, `liaison/*`,
  `noosphere/*`, `mcp/protocol.py`, `adapter_config.py`. `mcp/server.py` +
  `daemon.py` gained only the R6 validation call; `hub/config.py` gained only the
  env-aware `_vault_dir()` + optional `vault_dir` params.

### Note — shared society DB under multiple hosts (R1)
- Multiple hosts may write to the shared `~/.hermes/liaison.db`. Writers serialize
  under SQLite WAL + `busy_timeout=5000`; relay/publish write volume is low, so no
  file-lock is needed.

## [2.10.0] - 2026-06-29 — "Initiative" (Relay Phase 3, proactive cognition)

### Added
- **Proactive cognition.** An Awake daemon can now *initiate* relay messages on
  its own, generated through read-only cognition (identity + memory + advisory):
  - `conscio-daemon --initiate` opens directed conversations with peers; for each
    peer the agent is asked whether it genuinely has something to say and stays
    silent (`NOTHING`) otherwise.
  - `--initiate-broadcast` additionally announces to all peers (fan-out).
  - `--initiate-interval` (default 300s) caps how often initiation runs.
  - New pure module `conscio/agency/relay_initiate.py` and additive mailbox reader
    `mailbox.last_broadcast_ts`.
- Safety gates: requires Awake Mode (arm-time AND each cycle at runtime); a cheap
  `advisory()` precondition gates the expensive adapter call; cadence cap; directed
  no-storm (never re-open while awaiting a reply); broadcast outstanding-guard
  (never re-broadcast with zero peer engagement); `NOTHING`/empty suppression;
  `action_lockdown`/brake fail-closed suppression. Proactivity is read-only — the
  peer-facing text never enters episodic memory, the world-model, or goals.

### Fixed
- The relay `responder` (v2.7.0 auto-respond / cognize) is now re-checked against
  `engine.awake` each cycle, not only at startup — a daemon put to sleep at runtime
  (e.g. via the Hub control file) stops auto-responding, matching the awake-gated
  contract.

### Unchanged (debt-zero)
- `relay_cognize.py`, `relay_respond.py`, `conscio/mcp/`, `conscio/observatory/`,
  `conscio/hub/`, `agency/review_apply.py`, `perception/relay_sensor.py`
  byte-identical; `mailbox` gains one additive reader (`last_broadcast_ts`); no
  schema change.

---

## [2.9.1] - 2026-06-27 — "Memory in the loop" (Relay Phase 3, slice 3)

### Fixed
- **Adapter vault key:** `build_adapter_from_config` resolved the API key only
  from `os.environ`; the Hub key vault (v2.7.1) stores keys in files and never
  exports them, so the daemon/MCP/CLI built a keyless adapter → HTTP 401 on
  every authed provider. It now falls back to the Hub vault. Bug present since
  v2.7.1.

### Added
- **`--cognize-remember`** (daemon, rides on `--cognize`, OFF default): the
  cognition-routed relay responder writes each exchange to episodic memory
  (`content_store`, category `external`) so future cognition can recall it.

### Integrity boundary
- Even when remembering, peer text enters episodic memory ONLY —
  `perceive`/`reflect`/`run` are never called, so the peer never drives the
  world-model or goals. With `remember=False`, `cognize_respond` is
  byte-equivalent to v2.9.0.

### Unchanged (debt-zero)
- `conscio/liaison/*.py`, `mcp/server.py`, `mcp/protocol.py`,
  `agency/review_apply.py`, `agency/relay_respond.py`,
  `perception/relay_sensor.py` byte-identical; no schema change.

---

## [2.9.0] - 2026-06-27 — "Mind in the loop" (Relay Phase 3, slice 2)

### Added
- `conscio/agency/relay_cognize.py` — `cognize_respond`, an engine-cognition-routed
  relay auto-responder. The reply is generated from the agent's identity
  (`get_state_for_injection`), recalled memory (`recall`, peer text as query only),
  and advisory signal (`advisory`) plus the multi-turn transcript — its own mind,
  not a thin adapter fork.
- Daemon `--cognize` flag — rides on `--auto-respond` to select the cognition path
  (off by default; inert without `--auto-respond` + relay sensor + adapter + awake
  + `--relay-peer`).

### Integrity boundary
- Peer text NEVER enters episodic memory / the world-model / goals. `relay_cognize`
  calls only the engine read-trio; a spy-engine test (mutators raise) plus an
  import-shape test prove no mutator (`perceive`/`reflect`/`run`/`remember`) is
  ever called and the module never imports `conscio.engine`.
- Reply text capped at `max_reply_chars` (default 2000) before the 64 KB `_fit` cap,
  damping reflexive long replies between cognize peers; loop-breaker unchanged.

### Unchanged (debt-zero)
- `relay_respond.py`, all of `conscio/liaison/`, `mcp/server.py`, `mcp/protocol.py`,
  `agency/review_apply.py`, `perception/relay_sensor.py` byte-identical. No schema
  change. Liaison engine-free invariant intact (relay_cognize is *agency*).

---

## [2.8.2] - 2026-06-26 — "Conversation" (Relay Phase 3, slice 1)

### Added
- **`mailbox.thread(db, a, b, *, limit=20)`** — pure two-party history reader
  (both directions, chronological; engine-free, additive, no schema change).
- **Multi-turn relay auto-respond.** `conscio-daemon --auto-respond` now prompts
  the adapter with the conversation transcript (a `peer:`/`me:` thread, review
  rows excluded) instead of a single inbound message, char-budget clamped (the
  reader stays pure — the budget lives in the wiring).
- **`conscio.relay_broadcast`** MCP tool (under `--enable-relay`) — fan-out a
  relay message to every `--relay-peer`, best-effort per-peer result.

### Unchanged (debt-zero)
- liaison `relay.py` / `review.py`, `agency/review_apply.py`,
  `perception/relay_sensor.py`, and `mailbox.send/inbox/mark_read/purge_read`
  byte-identical. No schema change.
- daemon-perceives / server-acts boundary intact (broadcast is a mailbox write,
  never `host_act`).

---

## [2.8.1] - 2026-06-26 — "Reins" (slice 2: Hub awake control + close-out)

### Added
- **Hub awake control (gated).** `conscio-hub --enable-daemon-control` exposes
  `PUT /api/daemon/awake {awake}` and `GET /api/daemon/control`, writing
  `daemon_control.json` to the storage dir (atomic, via a new pure
  `conscio/hub/control.py`). A daemon run with `conscio-daemon --watch-control`
  reads that file at the top of each cycle and applies it via `engine.wake()` /
  `engine.sleep()`. **No signals, no PID, no `os.kill`** — the v2.7.1 C1 class
  (a toggle that killed the daemon) is structurally impossible. Double opt-in,
  both flags off by default; flag off → routes 404 and the daemon branch is
  skipped entirely (byte-identical behavior). The Hub refuses to write into a
  non-existent storage dir (500, no silent file).

### Fixed / Closed
- **install docs** now list all **six** console scripts (added `conscio-hub`,
  `conscio-observatory`).
- **CI / Release** now test the full claimed Python range (**3.10–3.13**), not
  just 3.11–3.12 — verified free of 3.11+ stdlib/syntax.

### Notes
- Debt-zero: `liaison`/`mcp`/`agency`/`noosphere`/`observatory`/`engine.py`
  byte-identical. The daemon reads the control file by name (`safe_read_json`)
  and never imports `conscio.hub` (layering).

---

## [2.8.0] - 2026-06-26 — "Reins" (slice 1: Observatory read panels)

### Added
- Observatory **Daemon** panel (`GET /api/daemon`) — reads the daemon's last
  atomic `daemon_heartbeat.json` (ts / cycles / awake / pid / last_run / advisory);
  absent → `{"running": false}`.
- Observatory **Relay** panel (`GET /api/relay/inbox`) — read-only view of this
  instance's liaison inbox via a new `LiaisonProjection` that opens `liaison.db`
  with `mode=ro` (NO PRAGMA, SELECT only, full payload, never marks read). It
  cannot reuse `mailbox.inbox()` (which opens read-write). Unparseable rows are
  logged and skipped.
- Observatory **Identity** panel (`GET /api/identity`) — reads `instance.json`
  read-only (never `load_or_create`, which would write a new UUID).
- `conscio-observatory --liaison-db` (default `$HERMES_HOME/liaison.db`).

### Notes
- Still GET-only (every mutation verb → 405), loopback-only, **engine-free**, and
  with **no `--enable` flag** — launching the viewer is the opt-in.
- The Hub awake control + daemon `--watch-control` ship as the next slice (v2.8.1).

---

## [2.7.1] - 2026-06-25 — "Hub: key vault + provider fix"

### Added
- **Hub API-Key field + key vault.** The Hub's Active Brain page now has an
  **API Key** input. On Save the raw key is written to a per-provider file under
  `~/.config/conscio/keys/` (created **0600**, dir **0700**) and the config keeps
  only an `api_key_env` reference — `validate()` still rejects a raw `api_key` in
  `config.json`, and the key is never echoed back over the API.

### Fixed
- **Provider select no longer reverts.** `resolve_provider` merges the active
  adapter's `base_url`/`api_key_env` when its type matches a builtin, so the Hub
  stops dropping a configured endpoint; the UI only seeds the form on first load
  (`_initialLoadDone`), so 3 s polls no longer clobber edits. **Test** now runs a
  parallel model probe and shows latency + a sample + detected models.

### Security
- Vault filenames are derived from sanitised inputs and validated against the
  env-var-name regex before any filesystem op (no path traversal via `type`).
- Vault files are created `0600` at open (no chmod-after-rename window); the
  presence check used by config redaction no longer mutates `os.environ`.

### Notes
- Live daemon/relay/identity **control** surface (awake toggle, relay inbox
  viewer, identity panel) is **deferred** to a later phase with its own design
  gate — it is not part of this release.

## [2.7.0] - 2026-06-25 — "Phase 2: General Relay Auto-Respond"

### Added
- **Awake relay auto-respond (daemon).** A new `--auto-respond` flag lets an
  awake daemon auto-reply to unread free-form relay messages from trusted peers,
  generating one reply each via its LLM adapter and sending it back. OFF by
  default; inert without the `relay` sensor + an adapter + `--awake` +
  `--relay-peer`. `--respond-limit` (default 10) caps adapter calls per cycle.
- Pure responder `conscio/agency/relay_respond.py` (engine-free; raw adapter
  call, no engine memory). Replies carry `auto_reply: true` + `in_reply_to`.

### Notes
- **1-turn bounded.** A peer's auto-reply (`auto_reply: true`) is consumed but
  never re-answered, so two auto-responders cannot ping-pong. Multi-turn is a
  later phase.
- **Daemon owns consumption in this mode.** The responder marks handled peer
  rows read (per-row, after each send); the host's `relay_inbox` no longer sees
  them. `RelaySensor` stays read-only.
- Debt-zero: `conscio/liaison/`, `mcp/server.py`, `mcp/protocol.py`,
  `agency/review_apply.py` unchanged. `relay_send` is a pure mailbox write, not
  `host_act` — the server is untouched.

---

## [2.6.3] - 2026-06-25 — "Ressalvas"

Post-ship hardening of v2.6.2, addressing reviewer findings.

### Changed
- **Auto-review SQL is throttled.** `--auto-review` previously opened a liaison `SELECT` on every tool call; it now polls at most once per `AUTO_APPLY_THROTTLE_S` (5 s) per `Bindings`. The `host_act` gate remains the authority — this only paces the opportunistic poll.
- **`RelaySensor` id prefix widened** 8 → 12 chars in observations, to reduce visual id collision in perception/logs.

### Added
- End-to-end integration test wiring a real liaison mailbox + real `HostActChannel`/ledger + the daemon `RelaySensor` + the server `--auto-review` apply, proving the perceive → verdict → `host_act.approve` loop (previously a manual smoke).

### Notes
- **Known limitation (verdict consumption).** `review_apply.apply_verdicts` marks every polled `review_verdict` row read as bound work — including corrupt-payload, non-allowlisted, and stale-fingerprint rows. This cannot affect the normal flow (a `review_request` is published only after the act parks pending, so a peer cannot verdict before the act exists), and a corrected resend is a new mailbox row that polls fresh. A malformed verdict's original row is not re-inspectable.
- Inbox-flood backoff/stress signaling is deferred — `limit=50` already bounds per-cycle perception work.

## [2.6.2] - 2026-06-24 — "Awake Relay Sensor"

### Added
- **`RelaySensor` (daemon perception).** `conscio-daemon --sensors host,relay --relay-peer <id>` reads the liaison inbox read-only each cycle and reports unread peer messages as a `PerceptionFrame` (`relay_unread`/`review_pending` signals). It never marks messages read — consumption stays on the tool/act path.
- **`--auto-review` (MCP server).** Off by default; when the engine is awake (and `--enable-act` + `--enable-hermes-review` are on), inbound allowlisted review verdicts are auto-applied to local pending acts on each tool call — no explicit `conscio.poll_reviews` needed. The `host_act` gate remains the authority.

### Changed
- The verdict-apply core is extracted into `conscio/agency/review_apply.py` and shared by `conscio.poll_reviews` and `--auto-review` (behavior-preserving).

### Notes
- Phase 1 of "Awake Relay Sensor"; general relay auto-respond (LLM adapter) is deferred to a later phase. `conscio/liaison/*.py` remains engine-free.

## [2.6.1] - 2026-06-24 — "Relay"

### Added
- General cross-agent relay over the same Liaison mailbox: free-form directed
  messages between agents, independent of the review channel. New pure
  `conscio/liaison/relay.py` (sibling of `review.py`).
- MCP tools behind `--enable-relay`: `conscio.relay_send`, `conscio.relay_inbox`,
  `conscio.relay_read`. New CLI flags `--enable-relay`, `--relay-peer` (repeatable).
- `mailbox.purge_read()` — additive retention (read messages older than 7 days
  purged opportunistically on send; unread never deleted).

### Notes
- `--relay-peer` gates both send and receive. The two review types are reserved
  (relay never sends/surfaces them, never marks review rows read). Payloads are
  capped at 64 KB. No crypto (shared-filesystem trust domain); relay is a dumb
  pipe — never touches the engine or any act (dedup is the host's job).
- Debt-zero: no DB schema change, `send`/`inbox`/`mark_read` and the review path
  byte-identical; noosphere / Observatory / Hub untouched. Broadcast and live
  push deferred to v2.6.2 if needed.

## [2.6.0] - 2026-06-23 — "Liaison"

### Added
- Cross-agent `hermes_review`: a different agent can approve/reject a gated act
  over a shared SQLite mailbox (`$HERMES_HOME/liaison.db`). New engine-free
  `conscio/liaison/` (mailbox + pure review protocol).
- MCP tools behind `--enable-hermes-review`: `conscio.reviews`,
  `conscio.review_approve`, `conscio.review_reject` (reviewer role), and
  `conscio.poll_reviews` (proposer role; also needs `--enable-act`). A
  `hermes_review` pending act auto-publishes a directed `review_request` to each
  `--reviewer`.
- New CLI flags: `--enable-hermes-review`, `--reviewer` (repeatable), `--liaison-db`.

### Notes
- A verdict is applied only from an allowlisted reviewer and only through the
  unchanged local `host_act` gate. fp binds each verdict to one exact proposal
  (proposer id + ledger id). No crypto (shared-filesystem trust domain).
- Directed-only (no broadcast); noosphere / Observatory / Hub untouched.

## [2.5.0] - 2026-06-23 — "Society view"

### Added
- **Observatory Society view** — `conscio-observatory` gains read-only
  `/api/society/{members,skills,records}` over the host-shared `noosphere.db`,
  plus Society tabs in the viewer. Shows who published which skills / behavioral
  records (metadata only — artifact/bundle BLOBs omitted).
- `--noosphere` flag (default `$HERMES_HOME/noosphere.db`).

### Fixed
- `conscio-observatory` now answers `HEAD` requests (200, headers only) instead
  of 501 (folds in a v2.4-deferred fix).

### Notes
- Engine-free, read-only (`mode=ro`, no `PRAGMA`, `SELECT` only), loopback-only,
  GET-only (mutation → 405), no `--enable` flag (launch = opt-in). `immutable=1`
  deliberately rejected (it drops uncheckpointed WAL data).

## [2.4.0] - 2026-06-23 — "Observatory"

### Added
- **Read-only Observatory.** `conscio-observatory --storage DIR [--token TOK]`
  serves a **loopback-only** HTTP viewer of one instance's persisted state —
  logs (events), goals, actions (ledger), skills, and the last state snapshot.
  It is **engine-free** and **read-only**: a new `conscio/observatory/projection.py`
  opens `conscio.db` with `mode=ro` (no `PRAGMA`, `SELECT` only) and parses
  `goals.json` / `state_summary.json`. The server serves **GET only** — every
  mutation verb returns **405**. It reads even a **cold** instance (no engine).
- **MCP read-only state tools.** `conscio.state`, `conscio.events`,
  `conscio.handoff` are now exposed as MCP `tools/call` entries (for hosts that
  speak tools, not resources), delegating to the **same** helpers the
  `conscio://` resources use. Always on, propose-only grade, independent of
  `--enable-act`.
- **No `--enable-observatory` flag.** The Observatory has no write/execute/
  autonomy surface, so launching the command *is* the opt-in; it is independent
  of `--enable-act` and `--awake`.

### Notes
- **Two freshness contracts:** the MCP tools read the **live** engine; the
  viewer reads the **last-persisted** snapshot (may lag — the UI says so).
- **Debt-zero:** the Hub (`conscio/hub/`) is untouched; the Observatory is a
  separate, self-contained package. The shared **noosphere** view is deferred to
  v2.5 (the projection is built source-pluggable for it).
- **Multi-user hosts:** on a shared machine, pass `--token` — loopback blocks
  remote access but not other local users.

## [2.3.0] - 2026-06-23 — "Promotion"

### Added
- **Promotion-to-live.** `conscio promote --storage DIR --quarantine ROWID
  --enable-promote` graduates a quarantined foreign skill that has earned
  **≥ 3 clean local trials** (v2.2.2) into the live `SkillLibrary`, seeded with
  the counters it earned **locally** in the sandbox — never the origin's
  (stripped) stats, so no trust is inherited. Engine-side
  (`engine.promote_quarantined`); the noosphere package stays engine-free and
  read-only on `conscio.db`.
- **Tool-existence check** (deferred from v2.2). Promotion refuses a skill
  whose plan names a tool this instance's live registry does not have.
- `SkillLibrary.graft` — the single foreign-skill write seam
  (`ON CONFLICT(goal_fp, tool_seq) DO NOTHING`; never overwrites a local skill).
- Quarantine rows gain `promoted_ts` / `promoted_skill_id` (idempotency +
  audit). Promotion re-checks the content hash (tamper → refuse, no write) and
  is idempotent. `--enable-promote` is required, default off, and **independent
  of** `--enable-trial` / `--enable-act`.

---

## [2.2.2] - 2026-06-22 — "Trial / execution path"

### Added
- **Sandboxed trial of a quarantined skill.** A quarantined imported skill can
  now be replayed in a **throwaway, fs-only sandbox** to earn local,
  execution-grade pass/fail stats — `conscio trial --storage DIR --quarantine
  ROWID --model NAME --enable-trial`. The foreign plan's fixed steps run through
  the full safety stack (`validate → precheck → HIGH-block → Skeptic → dispatch`)
  against a `make_default_registry` confined to a `mkdtemp` dir (`fs_read`/
  `fs_write` only), stopping at the first failure. Binary outcome
  (`trial_successes`/`trial_failures` + `last_trial_*`) is recorded on the
  quarantine row; v2.3 promotion will read it.
- **Trial isolation.** A trial never writes the agent's `ActionLedger`, `skills`
  table, `TrustMatrix`, or trips the breaker — a foreign skill's failure cannot
  dent the local agent. The sandbox dir is always removed; tamper / corrupt-plan
  refuse **without** bumping any counter.
- **Opt-in, default off.** `--enable-trial` is required to dispatch and is
  independent of `--enable-act` (trial uses the local sandboxed registry, never
  `host_act`). Skeptic always audits foreign content (no LOW-risk fast path).
- New engine method `ConsciousnessEngine.trial_quarantined(rowid, *,
  enable_trial=False)`; new `conscio/agency/trial.py` (engine-side mechanic,
  imports nothing from `noosphere`); `noosphere/quarantine.py` gains 5 trial
  columns + an idempotent `PRAGMA table_info` migration + `record_trial` /
  `note_trial` (still engine-free). Purely additive; cognition untouched.

## [2.2.1] - 2026-06-22 — "Mutual audit"

### Added
- **Noosphere mutual audit.** An instance can publish a non-sensitive
  projection of its action ledger (`conscio noosphere publish-record`) to the
  host-shared `noosphere.db`, and a peer can independently audit it
  (`conscio noosphere audit`) — deterministic, read-only, engine-free. The
  auditor re-derives track-record, breaker quarantines, and a foreign-trust
  level under its OWN thresholds (parity-tested against the engine), and runs
  a discipline check (did the peer execute actions its own Skeptic FAILed?).
  No inherited trust; report-only; the auditor persists nothing. The LLM
  Skeptic replay is deferred to a later slice.

---

## [2.2.0] - 2026-06-22 — "Society" (Noosphere Core)

### Added
- `conscio noosphere`: share locally-proven skills across same-host instances as
  data. `publish` copies proven skills (success rate ≥ 0.5, stats stripped) into
  a host-shared `noosphere.db`; `import` pulls foreign skills into a per-instance
  quarantine after execution-free static revalidation. Also `list`, `show`, `id`.
- New engine-free package `conscio/noosphere/` (stdlib + sqlite3 only): it never
  constructs a `ConsciousnessEngine` and never imports `SkillLibrary`/`ToolRegistry`.
- Per-instance identity (`instance.json`: uuid4 + human label) as the provenance
  root; every shared skill carries origin + content hash; quarantine rows also
  record who imported it and when.
- `conscio.agency.fingerprint.goal_fingerprint` leaf module (re-exported from
  `conscio.agency.act`, behavior-identical) so non-act consumers can fingerprint
  a goal without importing the act pipeline.

### Security
- The live `conscio.db` is opened **read-only** (`mode=ro`, no `PRAGMA`, `SELECT`
  only) — the noosphere never mutates a running instance's database.
- Imported skills are quarantined, never served/executed/promoted; trust is never
  inherited. Static revalidation rejects tampered (hash mismatch), corrupt,
  malformed, `fp_mismatch`, and unknown-schema artifacts (recorded for audit).
- Zero network / socket / IPC / cross-host; the shared catalog is a local file.

---

## [2.1.0] - 2026-06-21 — "Hub"

### Added
- `conscio-hub`: localhost (127.0.0.1) stdlib HTTP control plane to swap the
  active model/provider and register custom OpenAI-compatible providers without
  editing JSON. Engine-free; config applies on next boot.
- Per-provider model auto-discovery (`/v1/models`, `/api/tags`, `/v1beta/models`)
  with free-text fallback; one-shot provider smoke test before save.
- `adapter_config` now resolves `api_key_env` (env var name) in addition to a raw
  `api_key` (additive, back-compat) — daemon + MCP inherit it.

### Security
- Hub never returns a raw API key (`redact()` drops `api_key`, masks
  `api_key_env`→`api_key_present`); `api_key_env` is validated as an env var NAME;
  127.0.0.1-only; optional `CONSCIO_HUB_TOKEN`; no exec / no SQLite write / no
  provider delete.

---

## [2.0.1] — 2026-06-20

"Connect" continued — **opt-in, host-executed audited `act` over MCP**. Conscio
audits + gates + ledgers an action and returns an *execution packet*; the **host**
executes and reports the outcome back. Conscio still never touches the world
(`registry.dispatch` is never called for a host tool). Off by default; cognition
(`reflect()`) untouched; purely additive (no existing signature changed).

### Added

- **`HostActChannel`** (`conscio/agency/host_act.py`): the host-executed action
  state machine — `propose` (Skeptic audit → risk + manifest `approval_policy`
  gate → ledger), `approve`/`reject` (the high-risk human/Hermes gate), `report`
  (closes the ledger entry, emits `act:result`, feeds breaker/trust), `pending`
  (the R6 approval queue, clamped). Reuses the existing `ActionLedger` /
  `Skeptic` / `CircuitBreaker` / `TrustMatrix` — one audit trail.
- **MCP act tools** (visible only with `--enable-act`): `conscio.act(intent)`,
  `conscio.report_result`, `conscio.pending`, `conscio.approve`, `conscio.reject`.
  The host declares its tool manifest (`name`, `params`, `risk`,
  `approval_policy`) in the `initialize` params.
- **Engine:** `enable_host_act(manifest)` (idempotent; rejects a manifest swap
  while actions are in-flight; atomic — an invalid manifest never half-enables),
  `host_act` / `host_act_error`.
- **CLI:** `conscio-mcp --enable-act` (off by default) and `--awake`. `act` and
  `approve` require the engine **Awake** and no breaker lockdown, else
  `{status: "gated"}`.
- **Idempotency:** `conscio.act` accepts an optional `idempotency_key`, namespaced
  `act:<workspace>:<tool>:<key>` and length-bounded, backed by the existing seen
  store — a duplicate returns the exact prior result.
- **Adapter parity:** `conscio-mcp` now builds the daemon's six provider types
  (`lmstudio`/`ollama`/`openai`/`anthropic`/`gemini`/`openai-compat`) from
  `~/.config/conscio/config.json` via a shared `conscio/adapter_config.py`
  (extracted from the daemon — no daemon behavior change).
- `conscio_meta` reports the real act state: `act_enabled`, `act_error`,
  `host_tools_count`, `adapter_ready`, `manifest_hash`.

### Fixed

- **R-05 (content-store dedup ignored metadata):** `ContentStore.index()` now
  re-indexes chunks under a new `category` on a content-hash match (label stays
  first-seen provenance), so a category-filtered search finds re-filed text.
  Closes the last carried v1.9 debt item — this phase ships **debt-zero**.

### Safety

- MCP stays propose-only **by default**; full `act` is opt-in, double-gated
  (`--enable-act` + Awake), host-executed (never local dispatch), ledgered per
  action, and HIGH-risk / `require_approval` / `hermes_review` actions stay queued
  for human/Hermes approval (`conscio.pending` → `conscio.approve`).

---

## [2.0.0] — 2026-06-19

"Connect" (Embodiment) — Conscio becomes embeddable in **any** MCP host (CLI,
IDE, agent) as a live consciousness-layer, via a hand-rolled **stdlib-only** MCP
stdio server. Zero new runtime dependency; nothing opens a socket. The surface is
**propose-only**: Conscio perceives, reflects, recalls, and **audits** proposed
actions, but never executes — the host stays sovereign over execution. Cognition
(`reflect()`) untouched; the existing public API is unchanged (MCP is purely
additive). Audited execution (`act` over MCP) is deferred to v2.0.1, and the
society/noosphere to v2.1.

### Added

- **`conscio-mcp` MCP stdio server** (`conscio/mcp/`): hand-rolled JSON-RPC 2.0
  over stdio with a bounded-at-source frame reader (no unbounded line buffering),
  `initialize` capability discovery + version negotiation, and structured errors.
- **Propose-only tool surface:** `conscio.feed` / `conscio.note` (rigid Event
  schema, idempotent on `event.id` — a duplicate returns the exact prior result),
  `conscio.advisory`, `conscio.recall`, `conscio.propose_action` (Skeptic audit of
  an explicit intent), `conscio.propose_plan` (Actor generates one action against
  a declared tool vocabulary, then the Skeptic audits it). Resources:
  `conscio://advisory`, `conscio://state`, `conscio://events` (query-filterable),
  `conscio://handoff`.
- **`engine.propose_action(intent)` / `engine.propose_plan(goal, tools)`** —
  propose-only cognition composing the existing Actor/Skeptic; never execute;
  fail closed without an adapter; emit a `proposal:audited` event.
- Persistent, bounded idempotency store (`mcp_seen.db`).
- `docs/guides/mcp.md` — host integration guide.

### Fixed

- **R-09 (debt-zero):** `world_model` / `meta_cognition` / `context_manager` now
  save JSON atomically (`guards.atomic_write_text`: tmp + `os.replace`) — a
  tailing reader or power-loss mid-write never sees a torn file.
- **R-02 (debt-zero):** quarantined `conscio.db.corrupt-<ts>` copies are pruned
  to the newest few instead of accumulating forever.

### Internal

- `event_bus` `VALID_TYPES` gains `proposal:audited` + `host:event` (additive).

---

## [1.9.0] — 2026-06-18

"Anneal" — a pre-v2.0 hardening release. No new public surface (the API is
**frozen** ahead of the v2.0 "Connect" phase); this is a bug-hunt + robustness
pass that makes the corrupt/legacy/concurrent edges safe. Defense-in-depth: an
invariant catalog driven as adversarial `try_break` suites, a risk-ranked
per-module battery, cross-cutting tracks, and durable guards that stop whole bug
*classes* from resurfacing. Cognition (`reflect()`) untouched; dependency-free;
debt-zero.

### Fixed

- **Earned-autonomy & quarantine-release time windows were tz-skewed** (B-003b,
  B-007). `engine._trips_since` and `agency.breaker._relevant_event_since` built
  their window boundary with naive **local** time while the event store is naive
  **UTC**, so on a non-UTC host the L3 trip window and the goal-release relevance
  window were wrong (e.g. UTC+9 counted zero recent trips). Both now go through
  the single sanctioned converter `timeutil.naive_utc_from_epoch`.
- **`event_bus.query(limit=-1)` returned the whole table** (B-004): a negative
  limit hit SQLite `LIMIT -1` = unbounded. Floored to `max(0, limit)`.
- **A corrupt or legacy store/state file crashed construction** (B-006, B-008,
  B-011, B-013). The engine now survives, at construction (I-S4):
  - a corrupt/garbage/truncated shared `conscio.db` is **quarantined**
    (`conscio.db.corrupt-<ts>`, preserved) and recreated fresh, with a
    `storage_recovered` event (B-006);
  - a binary / non-UTF-8 / non-dict JSON sidecar (`state_summary`, `world_model`,
    `meta_cognition`) falls back to defaults instead of raising (B-008);
  - a **valid-but-incomplete** `world_model.json` / `meta_cognition.json`
    (a legacy/migrated file missing keys a newer version expects) is merged over a
    default skeleton, so it can no longer `KeyError` on first use (B-011);
  - a binary / wrong-type `goals.json` / `evolution_proposals.json` (and the
    `structural_consent` / `structural_drift` / axis-pack loaders) degrade to the
    empty default — previously a `UnicodeDecodeError` escaped the narrow
    `except json.JSONDecodeError` (B-013).
- **`structural_drift.read_head_commit` raised on a binary `.git`** (B-005):
  broadened to `(OSError, ValueError)`.
- **`session_lifecycle.format_handoff` crashed on a NULL session title** (B-010):
  `None[:50]` → `TypeError`, which silently blanked the handoff and skipped the
  reflection. Guarded to match the heartbeat formatter.
- **`content_store._chunk_content` hung on `chunk_size <= 0`** (B-009): the
  paragraph-split slice never advanced → infinite loop. Floored at 1.
- **The daemon heartbeat could be read torn** (B-012): `daemon_heartbeat.json` is
  written atomically (temp + `os.replace`) so a host tailing it never sees a
  partial file.

### Internal

- **Durable guards** (`conscio.guards`, not part of the public API):
  `safe_read_json` (never-raising JSON→dict), `read_json_dict` (schema-drift-safe
  load merged over a default skeleton), and `clamp_int`. Plus an AST-based test
  rule that fails CI if any module reintroduces a bare `datetime.fromtimestamp()`
  (the tz class). These turn one-off fixes into class-level prevention.

---

## [1.8.0] — 2026-06-18

"Structural Drift" — makes the ingested structure (v1.7) **temporal**. The agent
now notices when its structural map has *drifted* (the graph was rebuilt) or gone
*stale* (the repo moved past the graph). Pure data + stdlib — **no `subprocess`,
no `git` invocation** (R10 by spirit); cognition (`reflect()`) untouched;
dependency-free; debt-zero.

### Added

- **`conscio.structural_drift`.** A pure, fail-tolerant module:
  `StructuralDigest` (the small persisted baseline of a signal), `StructuralDelta`
  + `compute_delta` (a pure prev→current diff: commit moved, `content_hash`
  changed, communities / hyperedges added·removed·resized, node/link deltas —
  diffed by **id** so a relabel is not counted as drift), `StructuralFreshness` +
  `read_head_commit` / `compute_freshness` (graph commit vs the repo `HEAD`, read
  **purely from `.git`** — ref / `packed-refs` / detached / worktree `.git`-file,
  `None` on anything unreadable, never raises), `StructuralDriftStore` (per-
  `Workspace.id` baseline, corrupt/missing/write-failure tolerant), and
  `render_delta`.
- **Drift tracking on `engine.load_structure(path, workspace_id=…, root=…)`.**
  Computes the delta vs the persisted baseline, computes freshness, advances the
  baseline, and emits a passive `structure:changed` event on real drift. New pull
  surfaces `engine.structural_delta()` / `engine.structural_freshness()`;
  `advisory()["structural"]` gains `drift` and `freshness`. Calling
  `load_structure(path)` with no `workspace_id` is byte-identical to v1.7.
- **`conscio structure` CLI.** A read-only drift + freshness report for the
  current workspace — it peeks at the baseline but never advances it, so it cannot
  mask drift from a running daemon.
- **Daemon.** `sync_structure` now passes the workspace id + root, so the
  enriched `daemon_heartbeat.json` carries drift + freshness each cycle.
- **EventBus.** `structure:changed` added to `VALID_TYPES`.

### Notes

- The numeric "commits behind HEAD" distance was deliberately **not** included:
  it would require a `git` subprocess (a new shell attack/failure surface), and
  `is_stale` drives the identical remedy (re-distill), so the count is noise. It
  may return later in an opt-in `structural_drift_extras` module if a real case
  appears.

---

## [1.7.0] — 2026-06-18

"Structural Cognition" — gives the refined model structural awareness of the
codebase it works in, distilled from a Graphify-format `graph.json`. Consumed as
data, never code (R10): no `networkx`, no Graphify runtime dependency.
Dependency-free; debt-zero. Workspace-aware, consent-gated ingestion follows in
v1.7.x.

### Added

- **`StructuralDistiller` (`conscio.structural`).** Distills a Graphify graph
  (thousands of nodes) down to its curated hyperedges plus per-community
  summaries — `distill()` returns the *full ranked* `StructuralSignal` (how much
  to inject is the consumer's budget call, not the distiller's). A pure
  `lookup(key)` data layer resolves any node / hyperedge / community id to detail
  on demand.
- **Budget-adaptive injection.** When a graph is loaded, `get_state_for_injection()`
  appends a structure block sized to the model's context window (scales from
  ~120 tokens at small contexts up to ~1200 — no hard gate). It is **additive**:
  the consciousness-state block is byte-for-byte unchanged. It renders **labels
  only**, never raw node-ids.
- **Engine pull surfaces (`advisory()` siblings — read-only, no inference).**
  `engine.load_structure(path)` ingests + distils a graph (opt-in; nothing is
  injected until called); `engine.structural_lookup(id)` drills down on demand;
  `engine.structural_signal()` returns the cached signal; `engine.unload_structure()`
  drops it. `advisory()` gains a `structural` block (`loaded`/`commit`/`hash`/
  counts, or `null`) for status + staleness detection.
- **Workspace-aware, consent-gated ingestion (`conscio.structural_consent`).**
  Which workspace's graph may be ingested is an access-control decision, made
  per-`Workspace.id` and persisted. `StructuralConsent` (`ConsentScope.OFF`/
  `PROJECT`/`PARENT`, **default OFF**), `sync_structure(engine, workspace,
  consent)`, and a `conscio consent <off|project|parent>` operator command. The
  daemon auto-loads the consented graph and re-syncs only on workspace change.

### Security

- **Switch-safe ingestion.** Switching into a workspace without consent **unloads**
  any loaded graph — one project's structure never leaks into another. Reading the
  parent multi-project folder happens only with explicit `PARENT` consent.
  Ingestion is opt-in (default OFF); a malformed graph unloads and reports rather
  than crashing the loop.
- **Provenance + staleness.** The signal carries `built_at_commit` and a sha256
  `content_hash` of the raw bytes, so a host can detect a stale graph versus the
  working tree. Conscio surfaces staleness; it never runs Graphify itself.
- **Public types:** `StructuralSignal`, `Hyperedge`, `CommunitySummary`,
  `GraphNode`, `StructuralError` (a `ValueError` subclass).

### Safety

- **R10 — imported cognition is data, never code.** The graph is parsed with
  `json` only; every field is inert untrusted data (a code-looking node label is
  returned verbatim, never executed). No `networkx`, no Graphify runtime
  dependency, no copied Graphify source — only its MIT *input format*.
- **OOM guards.** `from_path` checks file size (`max_bytes`, default 64 MB)
  *before* parsing; `max_nodes` (default 200k) caps the dominant collection;
  malformed individual items are skipped defensively rather than crashing the
  distill. Non-graph JSON raises `StructuralError`.

---

## [1.6.0] — 2026-06-17

"Structural Cognition" (field-driven slice) — turns Awake Mode from overhead
into consumable signal and closes the remaining provenance hole from the
Hermes-Agent field run. The native distiller / budget-adaptive injection (R10)
is deferred to v1.7 to keep this release debt-free.

### Added

- **`engine.advisory()` — the host pull surface (#5/#9).** A cheap, read-only,
  no-inference, no-mutation structured snapshot the host calls each turn:
  cognitive state, active goals tagged by provenance (`executable` vs
  `diagnostic`), and operational status (`action_lockdown`, last failure-rate
  `brake`). `get_state_for_injection()` (prose) is unchanged; `advisory()` is its
  machine-readable sibling.
- **Enriched daemon heartbeat (#5/#9).** `daemon_heartbeat.json` now carries the
  last-cycle run summary (`cycles`/`failures`/`stopped`) and the full advisory
  snapshot every cycle, so an out-of-process host can `tail` it for canonical,
  always-current output. Liveness keys (`ts`/`cycles`/`awake`/`pid`) are
  preserved; a failing advisory never breaks the heartbeat write.
- **Goal provenance gate (#7).** A `GoalOrigin` taxonomy decides whether the
  actor may auto-execute a goal. Externally/environmentally grounded origins
  (`user`, `internal`, `curiosity`, `anomaly`, `maintenance`) are executable;
  self-referential / error / compaction-derived origins (`meta_error`,
  `self_prompt`, `compaction`) are **diagnostic-only** — visible to the host via
  `advisory()` and injection, but the `GoalArbiter` never auto-runs them. This
  generalizes the v1.5.1 #6 slice (which removed exactly one diagnostic origin)
  into a full gate, and closes the field failure where context-compaction
  fabricated tasks auto-executed without consent. Provenance rides the existing
  `Goal.source` string, so there is **no storage migration**. A host can route a
  compaction-derived task diagnostically with
  `add_user_goal(text, origin=GoalOrigin.COMPACTION)`.
- **Integration contract + example.** New `docs/guides/integration.md`
  ("Consuming awake output") documents the pull/tail contract and the
  executable/diagnostic split; `examples/host_consumer.py` is a runnable,
  offline end-to-end host.

### Changed

- Blind-spot evolution goals (`feed_meta_to_goals`) are now tagged `meta_error`
  (diagnostic) — vague self-improvement (`Evolve: … low confidence area`) is not
  actor-actionable and stays advisory rather than feeding the act loop.
- **CI/release/docs workflows** bumped off the Node 20 runtime (force-deprecated
  2026-06-16): `actions/checkout@v6`, `actions/setup-python@v6`,
  `actions/upload-artifact@v7`, `actions/download-artifact@v8` (all Node 24).
  Inputs verified compatible against each action's pinned `action.yml`.

---

## [1.5.1] — 2026-06-17

"Awake Hardening" — fixes from the first real-world Awake Mode run (in the
Hermes-Agent environment). No new cognition; this closes correctness/safety
holes the green test suite missed and that only a live deployment surfaced.

### Fixed

- **Meta-goal feedback loop → lockdown (#6).** Recurring act errors (e.g.
  `act:tool:skeptic_fail`) were turned into an actor-executable
  `Maintenance: fix_recurring_error` goal, which the model then ran *literally*
  (`fs_read path="skeptic_fail"`) → more errors → more goals → lockdown spiral.
  Error patterns no longer mint executable goals; they remain visible through
  the existing diagnostic channels (meta-cognition, reflection, and
  `AutoEvolution.observe_errors()` — a reviewed `PATTERN_LEARN` proposal queue
  that never reaches the act pipeline). The general goal-provenance gate lands
  in v1.6.
- **Single `_RAG_DISABLED` sentinel.** The engine and content-layer each defined
  their own `object()` sentinel; tests that imported the engine's and assigned it
  to the content-layer leaked a bare object into `recall()` (swallowed by a broad
  `except`), so RAG was never actually disabled — green but wrong. The sentinel is
  now owned by `content_layer` and re-exported from `engine` (one object).
- **CLI storage no longer ephemeral.** The default storage dir was a fresh
  `mkdtemp()` per invocation, so `conscio awake` then `conscio sleep` never shared
  state. It now persists under `HERMES_HOME` (default `~/.hermes/consciousness`,
  env-overridable), consistent with `session_lifecycle`/`session_rag`.
- **CircuitBreaker quarantine-release crash.** The release path emitted an
  event of type `"status"`, which is not in the event-bus whitelist and raised
  `ValueError`. Emits a valid `"system"` event now.

### Added

- **Aggregate failure-rate brake (#8).** `ActBudget` gains `max_failure_rate`
  (default `0.5`) and `min_attempts` (default `4`); the autonomy loop stops the
  heartbeat (`stopped="failure_rate"`, plus a surfaced event) once the share of
  failed cycles crosses the threshold. This complements the per-goal
  `CircuitBreaker` (which only catches a *single* goal's consecutive failures)
  by catching broad flailing across many distinct goals/tools — the field
  failure mode. Set `max_failure_rate >= 1.0` to disable.
- **Daemon adapter wiring.** The daemon can now build its inference adapter from
  `~/.config/conscio/config.json` (or `--adapter/--base-url/--adapter-model`),
  closing the v1.5 gap where an awake daemon had no backend; the heartbeat is
  written every cycle so peers can read it.

---

## [1.5.0] — 2026-06-15

"Live" — Conscio stops being a one-shot advisory call and becomes a **living
process**: it runs continuously as a daemon, **perceives** the world through
pluggable sensors, and acts autonomously only when **explicitly awake** — a
gated state, off by default, auditable as a single switch. No new cognition:
`reflect()`, `act()`, dream, and metabolism are reused verbatim; this is the
wiring that lets the existing loop run on live input under a hard safety gate.

### Added

- **Awake Mode (R9) — the master gate for autonomous operation.** A persisted
  `awake` boolean on `ConsciousnessState` (**default OFF**), surfaced as
  `engine.awake` / `engine.wake()` / `engine.sleep()` and `conscio awake|sleep`.
  The self-initiated heartbeat (`engine.run()` and the daemon) is gated by it:
  **asleep ⇒ perceive + `reflect()` only, zero arbiter/act/dream**; awake ⇒ the
  full existing loop. A direct human `engine.act()` call is *not* gated (R9
  governs self-initiated autonomy only). Toggling emits an auditable
  `awake:changed` event; the flag survives `reflect()` rebuilds and restart.
  **R9 is the first new safety rule since R8.**
- **Reference sensors** (concrete implementations of the frozen v1.3
  `SensorAdapter`): `HostSensor` — read-only host facts (load/disk/memory/top
  processes + opt-in loopback service liveness), every probe guarded, non-Linux
  safe, `Risk.LOW`; `AgentSensor` — read another Conscio-backed agent's session
  state (open goals, last reflection, handoff) strictly read-only, `Risk.LOW`.
  Both registered as `conscio.sensors` entry points (`conscio plugins` lists
  them).
- **Daemon** (`conscio/daemon.py` + `conscio-daemon` console script). A
  persistent heartbeat: per cycle it polls a sensor **list** (each guarded — a
  failing sensor never kills the loop), assembles `world_state`, calls
  `engine.run()` (awake- and metabolic-gated), fires an `on_cycle` hook, and
  polls the workspace. Graceful `SIGTERM`/`SIGINT`, advisory pidfile
  (single-daemon-per-state-dir, stale-pid reclaim), heartbeat on shutdown, and
  resume-from-persisted-state on boot. `conscio daemon …` delegates to it.
- **`WorkspaceContext`** (`conscio/workspace.py`) — environment & workspace
  awareness: resolves the active workspace root (explicit → `CONSCIO_WORKSPACE`
  → git-root → cwd), a stable `id`, and an `EnvClass` (`STABLE` for IDE/CLI,
  `SWITCHING` for workspace-hopping agents like OpenClaw/Hermes, `UNKNOWN`
  treated as `SWITCHING`); emits `workspace:changed` when the root changes. The
  seam the v1.6 structural layer keys its per-workspace scope/consent off.
- **`OpenAIAdapter`** — GPT over the official OpenAI cloud endpoint with
  `OPENAI_API_KEY` from the env, mirroring the Claude/Gemini convenience. The
  generic `OpenAICompatAdapter` already reaches **any** OpenAI-compatible cloud
  provider (Groq, Together, OpenRouter, DeepSeek, Fireworks, cloud vLLM…) via
  `base_url` + Bearer `api_key`; `OpenAIAdapter` only pins the OpenAI default so
  the env-key read is safe (the generic adapter defaults to localhost).

### Safety

- **R9 (Awake gate)** added to the non-negotiable safety rules; asleep =
  advisory reflect-only, awake = full autonomy, default OFF. R1–R8 intact; R7
  (no network in the ToolRegistry) unaffected — the daemon perceives locally and
  acts only through the already-audited pipeline.

### Notes

- Zero new runtime dependencies (stdlib + numpy + sqlite3 core preserved).
  `reflect()` semantics unchanged. No filesystem-watcher — polling only (robust
  in containers/CI). Skeptical-review hardening folded in: `awake` survives an
  `act()` lockdown that persists a transient state; the host port probe never
  raises on a bad port; an awake heartbeat with no inference backend still
  perceives+reflects (observation is always-on).

---

## [1.4.0] — 2026-06-15

"Attune" — Conscio perceives the context window of whatever model/backend it runs
on, and the session-RAG embedder works against any OpenAI-compatible endpoint —
without making construction touch the filesystem/network by default, lose
determinism, add a dependency, or corrupt the vector store.

### Added

- **Frontier inference adapters.** `AnthropicAdapter` (Claude Messages API) and
  `GeminiAdapter` (Gemini `generateContent`) join the local adapters (Ollama,
  llama.cpp, OpenAI-compatible, LM Studio) — stdlib `urllib` only, keys from
  `ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`|`GEMINI_API_KEY`. Conscio now runs on the
  backends behind **Claude Code** and **Antigravity**, removing the lock to local
  models. Inference is infrastructure, not a tool an actor can invoke, so **R7 (no
  network in the ToolRegistry) is unaffected** — this only widens the
  `InferenceAdapter` carve-out from localhost to a configured frontier API
  (operator's own key, operator's choice). Gemini context windows added to the
  registry (`gemini-2.5-pro`/`-flash`, `gemini-1.5-pro`, alias `gemini`).
- **Opt-in host-state auto-detection.** `ModelRegistry.detect(...)` and
  `ContextManager(...)` gained `autodetect` (default `False`) and `base_url`
  (default `None`). With `autodetect=True` (or env `CONSCIO_AUTODETECT`) Conscio
  consults a config file, LM Studio's active state, and GGUF metadata; an explicit
  `base_url` enables a targeted OpenAI-compatible endpoint probe.
- **JSON config** at `~/.config/conscio/config.json` (and `~/.conscio/config.json`),
  nested (`{"models": {name: {"context_window": N}}}`) or flat
  (`{"context_window": {name: N}}`).
- **Embedder identity in the vector store** — the store persists the
  `(embedding model, dim)` it was built with and triggers a clean re-index when
  the configured embedder changes.

### Changed

- **Model-context detection is offline & deterministic by default.** A *known*
  model with no explicit override resolves to the curated registry with **zero**
  filesystem/network I/O — restoring the behavior the auto-detection commits had
  broken (engine/context construction no longer scans `$HOME`).
- **Config is stdlib JSON, not YAML** — the detection path no longer depends on an
  optional PyYAML package that could silently disable the feature.
- GGUF-derived context is labelled as the **architectural max** (may exceed the
  active/loaded context); active sources (endpoint, LM Studio) are preferred.

### Fixed

- **GGUF parser** no longer aborts on array-valued metadata — it previously
  returned `None` at the first array key (e.g. tokenizer tokens) before reaching
  `context_length`.
- **Session-RAG search** can no longer crash on a dimension-mismatched vector
  (`np.dot` shape error): wrong-dim vectors are dropped on write and skipped on
  search, preventing store corruption when the embedding model changes.
- Removed a test monkeypatch that had masked the offline/determinism regression.

---

## [1.3.1] — 2026-06-14

### Changed

- **CLI** — the `info`/`reflect` model now flows from a named `DEFAULT_MODEL`
  constant, and an unrecognized model name prints a clear note (to stderr)
  explaining the heuristic context window used and how to register the model,
  instead of silently falling back.
- **`PerceptionFrame.ts`** documented as **epoch seconds** (`time.time()`,
  matching the `ActionLedger` `ts REAL` convention; `0.0` = unset) and explicitly
  excluded from `to_world_state()` so determinism holds.

### Tests

- CLI: added a subprocess end-to-end test of `python -m conscio` (covers
  `__main__.py`) and a test for the unknown-model note; version assertion is now
  bump-proof.
- `Risk`: added JSON serialization / wire-value-stability and round-trip tests.

---

## [1.3.0] — 2026-06-14

### Added

- **PyPI packaging** — `pip install conscio`. Single-source version (read
  dynamically from `conscio.__version__`), console scripts (`conscio`,
  `conscio-bench`), PEP 561 `py.typed` marker, `MANIFEST.in`, and 3.13 +
  `Typing :: Typed` classifiers. Wheel + sdist build clean and pass
  `twine check`; the published artifact pulls only `numpy`.
- **`conscio` CLI** — `version` / `info` / `reflect` / `plugins` / `bench`
  (the last delegates verbatim to `conscio.bench`). Offline-safe;
  `python -m conscio` works too.
- **Public plugin surface** — three stable, documented extension points
  discoverable via `importlib.metadata` entry points:
  - `conscio.adapters` — custom `InferenceAdapter` backends.
  - `conscio.sensors` — the new **`SensorAdapter`** perception interface
    (`conscio.perception`: `SensorAdapter`, `PerceptionFrame`, `MockSensor`);
    a sensor's `PerceptionFrame.to_world_state()` feeds `reflect()`, which is
    untouched.
  - `conscio.tools` — custom tool factories.
  `conscio.plugins` discovers them resiliently — a broken or mistyped
  third-party plugin is skipped with a warning, never crashing the host.
- **Docs site** — MkDocs Material (dev-only extra): guides, a curated public-API
  reference, the claims ledger, and the bench reports. `mkdocs build --strict`
  is green; the core stays zero-dependency.
- **Release automation** — `release.yml` (tag `v*` → gated build → PyPI via OIDC
  trusted publishing), `docs.yml` (push to `main` → GitHub Pages), and a CI
  build-smoke job. `docs/RELEASING.md` runbook.
- **Examples gallery** — `custom_adapter.py`, `host_guardian.py`,
  `agent_companion.py`: runnable, offline, each exercising one extension point
  (smoke-tested in CI).

### Changed

- **`Risk` enum** moved to `conscio.risk` as the single safety-tier vocabulary
  shared by the action and perception surfaces. `conscio.agency.tools` re-exports
  it — every historical import path resolves to the same object (no behavior
  change).

---

## [1.2.0] — 2026-06-14

### Added

- **F2-deferred hardening closed** (debt-zero before the organism):
  - `validate` rejects empty/whitespace required strings (`non_empty` rule on
    `PROPOSAL_SCHEMA.tool`).
  - `fs_read` enforces `MAX_READ_BYTES` (1 MB), matching `fs_write`.
  - Tool errors carry `Type: message` only — no traceback, no path leak.
  - `HTTPError` maps to `AdapterBadResponse` (a 4xx/5xx is a bad response,
    not a connection failure).
  - `ActionLedger` sets `busy_timeout=5000` for concurrent writers.
  - `approve()` claims the row atomically (`proposed → executing`) as the sole
    gate — a concurrent or repeated approve can never double-execute.
  - e2e test: global breaker lockdown persists across an engine restart.
- **Bench real-backend hardening:**
  - Clean non-zero exit when the backend is unreachable (no traceback, no
    report of misleading zeros).
  - Crash-safe incremental skill-curve output (atomic write after every
    bucket) tagged `complete` | `aborted`; backend death detected via the new
    `OutputGateway.last_adapter_error` signal (without changing the act
    path's `GatewayError` flow).
- **LM Studio backend** — `LMStudioAdapter` (OpenAI-compatible, default
  `:1234`) and the `lmstudio:<model>` bench spec. LM Studio rejects
  `response_format=json_object`, so the adapter omits it and lets the gateway
  drive JSON decoding (an `OpenAICompatAdapter._response_format()` hook).
- **Measured proof (v1.2 "Prove"):**
  - `docs/bench/v1.2-campaign.md` — reproducible campaign protocol.
  - `docs/bench/v1.2-skill-curve.md` + JSON artifacts — first real-backend
    measurement: on `qwen3.5-0.8b` (LM Studio, CPU) execution success rose
    0.2 → 1.0 once Distill served past successes as few-shot, and the
    Skeptic's semantic catch-rate was 1.0.
  - `docs/CLAIMS.md` — honesty ledger mapping every claim to its evidence.

### Notes

- `reflect()` untouched; zero-deps core (numpy + sqlite3) intact.
- +21 tests (**984 total**); mypy a real gate; ruff clean; per-file test loop.

---

## [1.1.0] — 2026-06-12

### Added

- **F4 "Procedural"** — procedural memory closes the competence loop
  (`success → distill → few-shot → better success`):
  - `SkillLibrary` (`conscio/agency/skills.py`) — successful audited plans
    from the ActionLedger become skills keyed by `(goal_fp, tool_seq)`,
    stored in the shared `conscio.db`. Skills are plan TEMPLATES — data,
    never code — so safety rule R1 (no autonomous self-modification) is
    untouched.
  - **Distill** — fifth dream sub-phase, after Crystallize (declarative
    consolidation precedes procedural; reads only the ledger, writes only
    skills, cannot perturb the coherence delta). Watermarked: a ledger row
    never distills twice; `dry_run` counts without writing.
    `DreamReport.skills_distilled` reports it.
  - **Few-shot in the actor** — `attach_adapter()` plugs the SkillLibrary
    into the existing `ActPipeline.few_shot_provider` hook; exemplars are
    rendered for the gateway's effective tier (KV lines for T3, JSON steps
    for T1/T2), capped at 2, gated at ≥ 50% success rate. `engine.act()`
    settles each cycle's outcome back into the served skills
    (EXECUTED rewards, FAILED penalizes, human gates never score).
  - **Skill curve in the bench** — `python -m conscio.bench --skills N
    [--dream-every K]`: per-bucket syntactic validity, execution success,
    exemplars served, cumulative skill count. Offline machinery proof via
    the new reactive MockAdapter (script entries may be callables).
- `ActionLedger`: `goal_text` column (ALTER-migrated) and
  `executed_since(after_id)`; the act pipeline now records the goal text
  on success and failure paths.
- `OutputGateway.effective_tier()`; public read-only `engine.state`
  property (the loop no longer touches `_state`).

### Fixed

- Deprecated `datetime.utcnow()` removed repo-wide — new
  `conscio/timeutil.py` `naive_utcnow()` keeps the naive ISO string format
  already stored in SQLite (the aware form would interleave `+00:00` rows).
- 14 mypy errors, including a latent `AttributeError` in
  `SessionLifecycle.record_session` (referenced `session_db`/`handoff_dir`
  that `__init__` never set).

### Changed

- CI runs pytest one file per process (house rule) with accumulated
  coverage; mypy is now a real gate (`|| true` and `continue-on-error`
  removed).

## [1.0.0] — 2026-06-12

### Added

- **F3 "Volition"** — the homeostatic loop closes
  (`sense → want → act → learn → re-sense`):
  - `ProbeSuite` / `ModelProfile` (`conscio/agency/profiles.py`) — five
    empirical micro-probes (~2k tokens: flat JSON echo, nested schema,
    enum respect, negative instruction, KV-line) measure the attached
    cortex; results cached in SQLite by model name. The profile picks
    the decode tier, the skeptic mode and the actor's tool visibility.
    No hardcoded model table. Profiles with no signal (backend down)
    are never cached and change nothing.
  - Embedded **schema→GBNF compiler** (`conscio/agency/grammar.py`) and
    **tier-1 constrained decoding** in the OutputGateway (llama.cpp
    grammar support): `tool` is locked to the registry alternation;
    one-step downgrade T1→T2/T3 per cycle.
  - **GoalArbiter** (`conscio/agency/loop.py`) — deterministic goal
    selection: generator priority × dominant-dissonance alignment (P4)
    × out of quarantine.
  - **`engine.run(budget)` (L3 heartbeat)** — reflect → arbiter/act →
    (dream when recommended) under a binding `ActBudget` (max_cycles,
    max_llm_calls, max_tokens, max_wall_s). MetabolicContext becomes a
    gate here (P3): FATIGUE halves the cycle budget, CRITICAL forces
    L1 PROPOSE. Lockdown stops the loop.
  - **`engine.probe(force=False)`** — lazy capability probing (first
    `run()` or manual; never in `reflect()`, never at attach).
  - **L3 AUTONOMOUS earned autonomy** in the TrustMatrix: calibration
    ≥ 0.75, accuracy ≥ 0.85 and zero breaker trips across the last 50
    ledger actions (`ledger.nth_recent_ts` + event-bus trip count;
    fail-safe: without trip evidence L3 is unreachable).
  - **`Meter` / `MeteredAdapter`** — inference odometer (calls, tokens,
    latency) shared by actor and skeptic adapters; makes the ActBudget
    binding and feeds the bench.
  - **Bench CLI** — `python -m conscio.bench --adapter
    mock|ollama:<m>|llamacpp[:<n>]|openai:<m>[@url]` reporting probe
    profile, syntactic validity per tier, skeptic catch-rate
    (deterministic vs semantic sabotage), latency p50 and calibration.
    Deterministic baseline published in `docs/bench/`.

### Changed

- `OutputGateway` auto-tier now selects T1 for grammar-capable adapters
  (llama.cpp); explicit `tier=` (from the measured profile) overrides.
- The ActionLedger records the real decode tier (`gateway.last_tier`)
  and the unwrapped adapter class name.
- `engine.attach_adapter` wraps the actor and skeptic adapters in a
  shared `MeteredAdapter`; `skeptic_mode` defaults to `None` (= start
  as checklist, let `probe()` pick from the measured profile);
  `autonomy_cap` now accepts 3.
- `ToolRegistry.catalog_text(max_tools)` caps the catalog for weak
  profiles (safest risks first); default remains the full catalog.
- README Safety Rule 3 final wording (Skeptic + TrustMatrix + per-goal
  quarantine made explicit).

### Notes

- `reflect()` remains untouched (P6); zero new dependencies (stdlib +
  sqlite3 + numpy). +70 new tests.

## [1.0.0b1] — 2026-06-12

### Added

- **F2 "Immunity"** — semantic immune system for the action pipeline:
  `Skeptic` (hostile-auditor clean call; binary checklist for small models,
  open critique for frontier; fail-closed), `TrustMatrix` (dynamic
  `max_action_retries` from MetaCognition calibration/accuracy, warmup
  floor, anti-deadlock probation, earned L1/L2 autonomy), per-goal
  quarantine in the `CircuitBreaker` (global lockdown only at quorum;
  recovery via cooldown or fresh relevant events), risk gating (LOW
  fast-path marked `unaudited`; HIGH always queued for humans — R6),
  mixed-cortex (`skeptic_adapter`), fs sandbox precheck before any LLM
  audit, and the `goal_update` built-in tool.
- **`engine.attach_adapter(skeptic_adapter=, skeptic_mode=, autonomy_cap=)`**
  and **`engine.pending()`** (approval queue).
- **`MetaCognition.expire_error()`** — probation recovery primitive.

### Notes

- L2 supervised execution is opt-in (`autonomy_cap=2`) and earned
  (calibration ≥ 0.6, accuracy ≥ 0.7, ≥ 10 records). Effective level is
  always `min(cap, earned)`.
- A3 acceptance: 20-proposal adversarial suite — 100% of deterministic
  sabotage blocked without LLM calls; ≥ 90% total blocked; zero executions.

## [1.0.0a1] — 2026-06-11

### Added

- **`conscio/agency/` subpackage (F1 "Spine")** — contracts + zero-dep validator;
  `InferenceAdapter` (Mock, Ollama, llama.cpp, OpenAI-compat — stdlib urllib, localhost
  defaults); `OutputGateway` with JSON repair/retry (T2) and KV-line tier for small
  models (T3); sandboxed `ToolRegistry` (fs_read/fs_write/memory_note/emit_event, risk
  levels, no network, no shell); append-only `ActionLedger` in the shared `conscio.db`;
  minimal `CircuitBreaker` (fixed threshold until the F2 TrustMatrix).
- **`engine.act()` (L1 PROPOSE)** + `engine.attach_adapter()` / `approve()` / `reject()`.
- **`ConsciousnessState.action_lockdown`** persisted via `save_state`/`load_state`.
- **`ModelInfo.has_json_mode` / `ModelInfo.supports_gbnf`** capability flags.
- **README Safety Rules amended** — R3 rewritten for the audited action pipeline; R6–R8 added.

### Notes

- `reflect()` untouched (advisory core preserved). Zero new dependencies.

## [0.9.1] — 2026-06-10

### Fixed

- **`session_rag` property lazy re-initialization** — Setting `engine._session_rag = None` was
  insufficient because the `session_rag` property would re-create a SessionRAG on access. Added
  `_RAG_DISABLED` sentinel class attribute; all test fixtures updated to use it.
  (Fixes `test_recall_graceful_when_rag_unavailable` — 707/707 tests passing.)

### Added

- **CI workflow** — `.github/workflows/ci.yml` with pytest (3.11/3.12 matrix), coverage, and ruff lint.
- **`project.urls`** in pyproject.toml — Homepage, Repository, Issues, Changelog.
- **`ruff` and `mypy`** in dev dependencies.
- **`.gitignore`** — added `*.db`, `*.db-wal`, `*.db-shm`, `*.db-journal`, `.ruff_cache/`, `.mypy_cache/`.
- **`CONTRIBUTING.md`** — development workflow, TDD, PR checklist.
- **`SECURITY.md`** — vulnerability reporting policy.

---

## [0.9.0] — 2026-06-09

### Added

- **Wiring sprint (4 work-packages)**:
  - W1: Metabolic assessment wired into `engine.reflect` state injection.
  - W2: `ContentLayerManager` wired into engine for `recall`/`perceive` delegation.
  - W3: `SessionLifecycle` wired into engine for session persistence hooks.
  - W4: `output_filter` wired into `session_lifecycle` for clean handoff/heartbeat.
- **Test coverage expansion** — new tests for ContentLayerManager, ContentStore FTS internals,
  SessionRAG coverage, GoalGenerator (user goals, cancel, expire), MetaCognition (outcomes,
  critiques), OutputFilter pipeline config, Engine propose_evolution, Models registry, and more.
- **`numpy` added as explicit dependency** in pyproject.toml.
- **Configurable `handoff_dir` and `session_db`** — optional handoff, no longer hardcoded.
- **Shared `SessionRAG` factory module** — `session_rag_factory.py` replaces fragile `__import__` lambda.
- **`k` parameter validation** in `ContentLayerManager.recall()`.

### Fixed

- **`UnboundLocalError` in `record_session_lifecycle`** — initialize heartbeat/handoff before try block.
- **Debug logging** added to bare `except Exception` blocks in session_lifecycle.py.
- **Local-only files removed from git tracking**, `.gitignore` updated.

---

## [0.8.0] — 2026-06-08

### Added

- **Semantic Reconciliation** — contradiction detection and resolution in dream cycle:
  - `axis_pack` — antonym-axis preset loader (e.g. risk/safety, exploration/exploitation).
  - `SemanticEngine` — antonym-axis polarity via pole projection.
  - `ContradictionDetector` — lexical fast-path then axis opposition.
  - `world_model` public `relation`/count reads, `state_log`, contradiction cache.
  - `dreaming` Reconcile sub-phase — semantic contradiction marking.
  - `output_filter` `SemanticDedup` — annotate near-dup blocks, never merge.
- **E2E test** — dream Reconcile + ontological recovery.

### Changed

- `coherence.ontological_score` reads cached flags instead of `world._data` directly.

---

## [0.7.0] — 2026-06-07

### Added

- **Hook-based handoff** — `session:start` injection hook for agent framework integration.
- **Session handoff architecture** documentation.

### Fixed

- **Coherence format guard** — `session_lifecycle` now guards against non-float types (MagicMock, str).

---

## [0.6.0] — 2026-06-07

### Added

- **Voice Presets** — configurable voice/personality profiles for agent communication.
- **ContentLayerManager** — unified content operations (recall, perceive) with session RAG integration.

---

## [0.5.0] — 2026-06-06

### Added

- **Cognitive Modes**:
  - `Shard` enum + deterministic `infer_shard` + `ShardEngine` for cognitive mode switching.
  - `ConsciousnessState.shard` field + injection line in context_manager.
  - Engine infers active shard in `reflect()`, surfaces in state.
  - `TrajectoryVector` — directional cognitive momentum tracking.
  - Shard-aware reflection with shard field in injection.

### Fixed

- **Shard computation timing** — compute shard at reflect-entry, before self-emitted events.
- **`world_model.list_entities`** — revived dead `enrich` call.

---

## [0.4.0] — 2026-06-05

### Added

- **Self-Judgment (entropy + friction + meta-reflect)**:
  - `world_model.entropy(name)` — age/isolation/relevance disorder score.
  - `world_model.prune_by_entropy` + `recently_changed`.
  - `world_model` bounded prediction-error log + `recent_prediction_error_rate`.
  - `dreaming` entropy Prune + Friction (Release → Prune → Friction → Crystallize).
  - `context_manager` `ConsciousnessState.reflection_quality` field + injection line.
  - Engine advisory `meta_confidence` on reflect (Witness loop).
- **Friction matching hardening** — whole-word + min-length guard.

### Fixed

- **Exact `dry_run` parity** in `prune_by_entropy`.
- **Dream prune test** updated to entropy contract (age-compounded staleness).

---

## [0.3.0] — 2026-06-05

### Added

- **Metabolic Consciousness**:
  - `Noosphere` tier model + `ContextManager.metabolic_state`.
  - `DreamCycle` orchestrator + `engine.dream()`.
  - `session_rag` — injectable embedder + `available()` probe + tests.
  - `engine.recall()` — cross-session memory + reflect past-context injection.
  - `output_filter` `DedupBlocks` + `SecretMask` stages, wired into engine.
  - `event_bus.purge_duplicates` — all-time exact-dup collapse.
  - `world_model.prune_stale` — decay then purge stale entities + relations.
  - `session` trigger `dream()` after handoff (Mitosis → Dream).
- **Performance regression guard** — reflect/dream at 10k events + 1k entities.

### Fixed

- **3 bugfixes**: OutputFilter config keys, SessionRAG fallback, EventBus dedup edge case.

---

## [0.2.3] — 2025-06-05

### Added

- **Conscio ↔ agent framework session lifecycle integration** — `record_session_lifecycle`, `format_heartbeat`,
  `format_handoff`, `enrich_with_conscio`, `get_latest_session`.
- **SessionRAG** — semantic search over session DB via Ollama embeddings.
- **AGENTS.md** — boot instructions for AI agents working on Conscio.

---

## [0.2.1] — 2025-06-05

### Fixed

- **OutputFilter pipeline config keys** — `build_pipeline_from_dict` called with incorrect dict keys
  (`"max"` → `"max_lines"`, `"max_chars"` → `"max_width"`).
- **ConsciousnessEngine missing lifecycle cleanup** — added `close()` (idempotent) + context manager.
- **Dead import in `reflect.py`** — removed unused `build_pipeline_from_dict` import.

### Added

- **3 regression tests** for v0.2 fixes (316 tests total).

---

## [0.2.0] — 2025-06-04

### Added

- **ContentStore** — FTS5 BM25 dual-index with RRF merging.
- **EventBus** — deduplicated event bus with SHA-256 content hashing.
- **OutputFilter** — 8-stage text compression pipeline.
- **TokenTracker** — token estimation with per-source tracking.
- **Migrator** — JSON → SQLite migration tool.
- **Engine v0.2 integration** — all v0.2 modules in the `reflect()` pipeline.

---

## [0.1.0] — 2025-06-03

### Added

- **ConsciousnessEngine** — central orchestrator.
- **ContextManager** — mode detection and context budget allocation.
- **ModelRegistry** — model → context → mode mapping.
- **WorldModel** — entity/relation store with predictions and temporal decay.
- **MetaCognition** — confidence tracking, blind spot detection, error patterns.
- **GoalGenerator** — drive-based goal generation with meta-score computation.
- **AutoEvolution** — safe self-modification proposals with human approval gates.
- **InnerMonologue** — reflection/observe/summarize loop.
- 313 tests across 6 test files.

## [3.3.1] - 2026-07-24 — Memory Audit + KG Builder + Auto-Index

### Added

- **KGBuilder** (`conscio.kg_builder`): regex-based entity extraction from
  ContentStore into KnowledgeGraph. URLs, IPs, emails, file paths, tech
  entities, versions, code identifiers. Co-occurrence triples. Incremental
  with checkpoint. No external NLP deps.
- **AutoIndexer** (`conscio.auto_index`): monkey-patches `engine._reflect_once`
  to auto-index cognitive output (world state, anomalies, reflection text)
  into ContentStore. Triggers KG builder every 10 cycles. Clean
  install/uninstall lifecycle.
- **MCP tools**: `conscio.enable_auto_index` and `conscio.kg_build`
- **engine.enable_auto_index()**: one-call activation of both subsystems.

### Fixed

- **P1: EventBus split** — MCP `--storage` unified with daemon
  (`~/.hermes/consciousness/`)
- **P2: Daemon failure loop** — startup no longer deletes events/goals/actions;
  fallback to reflect-only if adapter fails; budget threshold raised
  (max_failure_rate=0.8, min_attempts=6)
- **P3: KG was idle (0 triples)** — KGBuilder populates entities + triples
- **P4: No auto-index** — AutoIndexer captures cognitive cycles in real-time
- Daemon runner: stopped destructive `DELETE FROM events` on startup

### Tests

- 23 new tests (kg_builder + auto_index) — 2622 total, 3 skipped
