v4.8.1 — Awake stops paying to confirm that nothing happened
A field run of Awake Mode drained its provider quota: the maintenance goal
re-proposed host_health on every heartbeat (~300 LLM calls/day to re-confirm
"all normal", measured on the field ledger), a 429 storm cascaded through every
gateway tier, and the ledger recorded the failures with an empty error. This
patch caps the call volume, makes the gateway stop on rate limits, and fixes the
smaller defects the same report and the follow-up audit surfaced. No schema
change: the ledger gains no column, so existing databases open unchanged.
Fixed — Awake Mode call volume (conscio/awake/calibration.py)
- Maintenance cooldown. The
daemon_checkmaintenance goal expires after
its act and is regenerated only afterMAINTENANCE_COOLDOWN_MIN = 60minutes,
read from the action ledger (the last attempt for that goal fingerprint), so
the cooldown survives daemon restarts. The reflect cycle still runs on every
heartbeat; only the LLM-backed act is gated. - One maintenance cycle per heartbeat. When the maintenance goal is the only
active goal, the loop runs it once and stops (stopped: "maintenance_cycle_cap")
instead of repeating it for the wholemax_cyclesbudget. A failed attempt
still counts as the one cycle; a heartbeat that also holds another goal is
never capped. - Rolling 24h attempt ceiling.
DAILY_LLM_CEILING = 120act attempts by the
autonomous loop per rolling 24h window (tier != 'host', so host-approved
acts never consume it). Failed attempts count whatever their token count: a
429 storm writestokens = 0rows and still burns quota. When the ceiling
trips, awake degrades to perceive + reflect, emits the trip on the event bus,
and the heartbeat'slast_run.stoppedreadsdaily_cost_ceiling. - No active goals is IDLE, not a failure. A heartbeat with no executable goal
returnsActStatus.IDLE(stopped: "idle"); it no longer feeds the failure-rate brake, which
used to stop a healthy idle daemon after a few heartbeats. The IDLE break runs
after dream housekeeping, so an idle daemon still prunes its ledgers.
Fixed — Gateway failure handling (conscio/agency/)
- Typed HTTP status. HTTP errors from inference backends raise
AdapterHTTPError(a subclass ofAdapterBadResponse) carryingstatus.
FailureGovernor.classifymaps 429 →RATE_LIMIT, 401/403 →PERMANENT,
5xx →PROVIDER_OUTAGE, any other status →MALFORMED_STREAM. - Fast-fail on rate limit and outage. On
RATE_LIMITorPROVIDER_OUTAGE
the gateway stops instead of falling back to a lower tier: every tier calls
the same provider, so the fallback only repeated the 429.TIMEOUTstill
falls through (local grammar decoding can be slow and T3 may succeed). - Failure reason in the ledger. Failed act rows persist the reason in the
existingerrorcolumn (gateway: …,decode failed: …) instead of''. - Tool-name normalization. A proposed tool name is stripped of whitespace,
wrapping quotes/backticks and a trailing()before lookup (host_health()
resolves tohost_health); no lowercasing and no fuzzy matching. - Instructions without value slots. The JSON and key-value format
instructions describe each field in prose instead of a"<tool name>"
placeholder the model echoed back verbatim; tool names are listed sorted, and
the dead instruction constants were removed.
Fixed — Recall and council
conscio_recall_observationssession scope. The defaultscope="session"
filtered on the MCP server's own session id, which no capture hook ever
writes, so it silently matched nothing. It now resolves the session in order:
an explicitsession_id(exposed in the tool schema), the session the
platform wired into the engine, the most recent session recorded for the
project (the current repository by default) and, when noprojectwas
given, the most recent session among observations recorded without one. If
none exists it answersINVALID_PARAMSsaying so. The project path is
canonicalized (~expanded and, when the path exists, resolved to the
enclosing repository root) so a subdirectory or a tilde path matches the
stored project.- Council trait negation. A post-trigger absence predicate ("backups are
missing") is consumed by the trigger it negates and can no longer also act as
a pre-trigger negator for a later trigger in the same sentence.
Security and hygiene
- Bandit HIGH findings: 3 → 0. The two non-security
sha1digests (short
ids inintegrations/neurata.pyandoutcomes.py) pass
usedforsecurity=False; the relay reactor'sshell=Truecall
runs the operator's own notify pipeline with the payload on stdin, never
interpolated, and carries a# nosec B602annotation that says so. - Dead code removed.
OutputGatewayloses the unused_failure_gov
attribute andfailure_governorparameter, and the vulture whitelist entry
that hid them. - Internal plans out of the repo and the site. The v4.5 relay-halls
implementation plans (docs/plans/) are no longer tracked or published on
the docs site;.gitignoreand mkdocsexclude_docsnow cover them, along
with local per-agent harness files. uv.lockback in sync. The lockfile was missing thebuilddev
dependency thatpyproject.tomlhas declared since v4.0.0, so
uv lock --checkfailed on a clean checkout.