Rank 3 on the docket. RuntimeConfig carries 32 boolean fields and nothing in
the repository stated that set, distinguished a capability flag from a posture
flag from a deployment knob, or recorded what evidence would flip any of them.
Two independent counts of the same set differed by eleven — the assessment said
"seventeen capability flags," N-7 measured 28 default-off plus 4 default-on —
and neither was wrong about what it looked at. They counted different things
because nothing declared what the set was.
R-3 (ruled A — incomplete flag set). accrue_realized_knowledge joins
CONTINUOUS_LIFE_CONFIG_FLAGS. Step D consolidates the realized facts Step B
writes, and the daemon forced the consumer without the producer: a continuous
life consolidating an empty set, which the mastery framework calls "garbage at
high speed." It shipped that way for six weeks.
The decisive evidence was in the code, not the documents. BOTH flags' comment
blocks in core/config.py already described the corrected profile —
accrue_realized_knowledge says "the production L10 process enables it alongside
persist_session_state", consolidate_determinations says "...alongside
accrue_realized_knowledge + persist_session_state". Three records (two
docstrings and the 07-25 verification doc) agreed with each other and disagreed
with four lines of code. Dormancy was a coherent ruling and would have cost
correcting three records that were RIGHT about the design; incompleteness cost
one line.
So the planned "N-6 docstring correction" deliverable DISSOLVED rather than
shipping: adding the flag made both docstrings true. Recorded explicitly,
because a PR that quietly ships less than it promised is the same divergence
class this arc exists to close.
R-4 (ruled A) — docs/specs/flag_register.md. All 32 booleans with class,
governing ADR or an honest "none", recorded rationale, and what evidence would
flip it. The classification is the load-bearing half and is stated as a
taxonomy rather than a label:
CAPABILITY — what CORE can do; engineering may flip it on named evidence
POSTURE — what CORE serves or refuses AS TRUE; the wrong=0 boundary,
ruling-only
DEPLOYMENT — cost, lifecycle, process shape; per-deployment
estimation_enabled and composed_surface are both "= False" and are not the same
kind of thing. Nothing said so before.
Profiles are declared as the UNIT of decision (R-4's mechanism): one-shot/eval
and continuous-life. The absence of a SERVING profile is stated deliberately —
the four POSTURE serving flags each ride their own license, and grouping them
before the ledger is sealed would license by association, the outcome R-8
exists to prevent.
tests/test_flag_register.py, on the gate. BIDIRECTIONAL, because either
direction alone would have passed through both failures it exists to prevent:
a flag added to config.py and not registered fails; a register row outliving
its flag fails. The counts the register's prose states (32 / 4 ON) are pinned,
since a stale count is how this register came to be needed. Four sabotages
observed red — new unregistered flag, deleted row, orphaned row, and a flag
silently flipped ON, the last caught by two independent pins.
THREE FINDINGS THE ITEM DID NOT ANTICIPATE, all from measuring rather than
reading.
1. Accumulated permissiveness, not just hesitancy. Of the four default-ON
flags, ONE has a governing ADR (deduction_serving_enabled / ADR-0256) and
TWO have no recorded reason of any kind — allow_cross_language_recall and
use_salience carry no comment block, no ADR, no criterion. In an
architecture built on earned licenses, two permanently-on capability flags
with no recorded decision is G-8's question inverted. Registered and
deliberately NOT fixed: writing a rationale after the fact would invent a
decision nobody made, which is worse than recording the absence.
2. A hollow gate inside the governance pin itself.
test_default_on_flag_is_not_governed_by_a_proposed_adr is parameterized over
(default-ON flag x cited ADR), so the three uncited ON flags contribute ZERO
cases — it covered exactly one flag. Its non-vacuity guard asserted only
that SOME flag cites an ADR, which default-off flags satisfy in abundance.
One reformatted comment away from zero coverage with everything green: the
failure state indistinguishable from the success state, inside the module
written to prevent exactly that. Guard tightened to require a non-empty
parametrization, and observed red by stripping ADR-0256's citation.
3. The daemon's own tests ran on no gate. R-3 changes what the always-on
process does every beat, and tests/test_l10_always_on_daemon.py was in no
curated suite — so this behaviour change would have shipped guarded solely
by tests no gate invokes, which is the hollow-gate pattern landing on the
exact file covering the change. Promoted to smoke WITH the change it guards.
Measured +9.4s, +4.3% of smoke — paid deliberately, not estimated. Removed
from full_only_baseline.txt in the same edit (both-directions ratchet; the
baseline shrank 747 -> 746).
Red before green throughout: the assertion cfg.accrue_realized_knowledge is
True was added to the daemon test and OBSERVED FAILING before the flag existed.
H-8(c) closed by AMENDING the correction note rather than deleting it, and the
amendment is the more useful artifact. PR-1's note said the 07-25 document's
claim was false; R-3 then made that claim TRUE, so the correction note itself
went stale — in the direction nobody guards, a correction made wrong by its own
subject being fixed. Both notes now carry the commit window they apply to. A
dated correction survives its subject changing; an undated "this is false" does
not.
Also delivered, from the 2026-07-28 external-assessment triage: §5, the
declared-table index. Eight single-source-of-truth tables and the pin that makes
each true. A reader's aid, explicitly NOT a central contracts.toml — that would
add a fifth copy needing agreement with four generators and put four different
authorities in one merge surface.
Handed forward to PR-11: the 5000-beat soak recorded in
evals/l10_always_on/contract.md (2026-07-19) predates this change and therefore
describes a configuration that NO LONGER SHIPS. PR-11's re-run is the first soak
of the corrected profile, and Step B feeding Step D is exactly what a long
horizon stresses.
Closes G-6, G-8, H-6, H-8(c), H-8(d) — and H-8 in full. Wave 4's F-6 half-gate
is lifted; the Track C half is untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
211 lines
9 KiB
Python
211 lines
9 KiB
Python
"""The always-on daemon shell — the thin process that RUNS the continuous-life heartbeat.
|
|
|
|
``chat/always_on.run_continuous`` is the reusable loop; this module is the daemon a
|
|
``core always-on`` invocation drives. It resolves the engine-state dir, takes a
|
|
single-instance lock (ONE life per engine-state dir — two daemons would corrupt the one
|
|
continuous life), installs SIGINT/SIGTERM handlers for a graceful stop, builds the
|
|
continuous-life ``ChatRuntime``, and runs the heartbeat unbounded until a signal — writing
|
|
the ``lived_life.json`` evidence the workbench reads and checkpointing so the next start
|
|
resumes the SAME life.
|
|
|
|
No new authority (CLAUDE.md): the heartbeat only ticks the proposal-only ``idle_tick`` and
|
|
READS ``versor_condition`` (no hot-path repair). It is FOREGROUND and explicit — there is
|
|
no hidden background execution; an operator backgrounds it with their shell / service
|
|
manager. The only writes are to the engine-state dir it was pointed at (the lock, the
|
|
manifest checkpoint, ``lived_life.json``).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import fcntl
|
|
import os
|
|
import signal
|
|
import threading
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any, Callable
|
|
|
|
from chat.always_on import (
|
|
LIVED_LIFE_FILENAME,
|
|
AlwaysOnReport,
|
|
HeartbeatRecord,
|
|
run_continuous,
|
|
)
|
|
from chat.runtime import ChatRuntime
|
|
from core.config import RuntimeConfig
|
|
from engine_state import EngineStateStore
|
|
|
|
LOCK_FILENAME = "always_on.lock"
|
|
|
|
# The continuous-life config: ACCRUE knowledge from each turn (Step B), persist the lived
|
|
# field/vault across reboot (Shape B+), learn from determined facts each beat (Step D), and
|
|
# REFUSE to resume a different-identity checkpoint (the load-time identity guard). So a
|
|
# daemon restart is the SAME life or it stops — never a silent fork.
|
|
#
|
|
# ``accrue_realized_knowledge`` joined this set on 2026-07-28 (R-3, ruled A). It was absent,
|
|
# and its absence was the assessment's F-6: Step D consolidates realized facts back into the
|
|
# held self, and Step B is the ONLY turn-path writer of the facts Step D consolidates. A
|
|
# daemon forcing D without B is a continuous life that consolidates an empty set — capability
|
|
# machinery running over nothing, which the mastery framework calls "garbage at high speed."
|
|
#
|
|
# The ruling was between "incomplete flag set" and "intended dormancy," and the evidence for
|
|
# incompleteness was in the code rather than the documents: BOTH flags' comment blocks in
|
|
# core/config.py already described this exact profile — accrue_realized_knowledge says "the
|
|
# production L10 process enables it alongside persist_session_state," and
|
|
# consolidate_determinations says "...alongside accrue_realized_knowledge +
|
|
# persist_session_state." Two flags' own documentation asserted a production configuration
|
|
# the production configuration did not have. Adding the flag makes both true; dormancy would
|
|
# have required correcting two docstrings that were right about the intent.
|
|
CONTINUOUS_LIFE_CONFIG_FLAGS: dict[str, Any] = {
|
|
"accrue_realized_knowledge": True,
|
|
"persist_session_state": True,
|
|
"consolidate_determinations": True,
|
|
"strict_identity_continuity": True,
|
|
}
|
|
|
|
|
|
class AlwaysOnLockedError(RuntimeError):
|
|
"""Another live always-on daemon already holds the lock for this engine-state dir."""
|
|
|
|
|
|
class _SingleInstanceLock:
|
|
"""Single-instance lock backed by an advisory OS lock (``fcntl.flock``).
|
|
|
|
The kernel holds the lock for the lifetime of the open fd and releases it AUTOMATICALLY
|
|
when the process dies — so there is no stale-lock window, no PID-reuse ambiguity, and no
|
|
empty / half-written-file race (a pid-file scheme has all three: a loser of the create
|
|
race can read a not-yet-written file and mistake a live holder for stale, then reclaim,
|
|
and two daemons proceed over one life). The lock FILE is intentionally never unlinked —
|
|
unlinking it would let a waiting peer flock a different inode — so the persistent marker
|
|
also carries the holder's pid for a human-readable refusal message.
|
|
"""
|
|
|
|
def __init__(self, path: Path) -> None:
|
|
self._path = path
|
|
self._fd: int | None = None
|
|
|
|
def _holder_pid(self) -> int | None:
|
|
try:
|
|
return int(self._path.read_text(encoding="utf-8").strip())
|
|
except (OSError, ValueError):
|
|
return None
|
|
|
|
def acquire(self) -> None:
|
|
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
fd = os.open(self._path, os.O_CREAT | os.O_RDWR, 0o644)
|
|
try:
|
|
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
except OSError as exc:
|
|
# The lock is held by another LIVE process (the kernel released it if the holder
|
|
# died). Read the marker pid for the message; it is informational only.
|
|
holder = self._holder_pid()
|
|
os.close(fd)
|
|
suffix = f" (pid {holder})" if holder is not None else ""
|
|
raise AlwaysOnLockedError(
|
|
f"another always-on daemon{suffix} holds {self._path}"
|
|
) from exc
|
|
# We hold the kernel lock; stamp our pid as a human-readable marker (best-effort —
|
|
# exclusion is the flock, not this content).
|
|
try:
|
|
os.ftruncate(fd, 0)
|
|
os.write(fd, f"{os.getpid()}\n".encode("utf-8"))
|
|
except OSError:
|
|
pass
|
|
self._fd = fd
|
|
|
|
def release(self) -> None:
|
|
if self._fd is None:
|
|
return
|
|
try:
|
|
fcntl.flock(self._fd, fcntl.LOCK_UN)
|
|
except OSError:
|
|
pass
|
|
os.close(self._fd)
|
|
self._fd = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class DaemonResult:
|
|
"""The outcome of one daemon run — the heartbeat report + how it ended."""
|
|
|
|
report: AlwaysOnReport
|
|
engine_state_path: Path
|
|
stopped_by_signal: bool
|
|
|
|
|
|
def continuous_life_config(base: RuntimeConfig | None = None) -> RuntimeConfig:
|
|
"""``base`` (or a default config) with the continuous-life flags forced on.
|
|
|
|
Forced, not defaulted: the daemon's whole purpose is ONE continuous life, so persistence
|
|
+ consolidation + the strict identity guard are not optional knobs here."""
|
|
cfg = base if base is not None else RuntimeConfig()
|
|
return dataclasses.replace(cfg, **CONTINUOUS_LIFE_CONFIG_FLAGS)
|
|
|
|
|
|
def run_daemon(
|
|
*,
|
|
config: RuntimeConfig | None = None,
|
|
engine_state_path: Path | None = None,
|
|
interval: float = 1.0,
|
|
max_beats: int | None = None,
|
|
no_load_state: bool = False,
|
|
on_record: Callable[[HeartbeatRecord], None] | None = None,
|
|
install_signals: bool = True,
|
|
stop_event: threading.Event | None = None,
|
|
) -> DaemonResult:
|
|
"""Run the always-on heartbeat as a daemon until a signal (or ``max_beats``).
|
|
|
|
Resolves the engine-state dir, takes the single-instance lock (raising
|
|
``AlwaysOnLockedError`` if a live daemon already owns this life), installs
|
|
SIGINT/SIGTERM handlers (unless ``install_signals=False`` — tests drive ``stop_event``
|
|
directly), then runs ``run_continuous`` with the continuous-life config forced on and
|
|
persists ``lived_life.json`` beside the checkpoint.
|
|
|
|
``install_signals=True`` must be called from the main thread (a Python signal-handler
|
|
constraint); tests pass ``install_signals=False`` with their own ``stop_event``.
|
|
"""
|
|
resolved = EngineStateStore(engine_state_path).path
|
|
cfg = continuous_life_config(config)
|
|
stop_event = stop_event if stop_event is not None else threading.Event()
|
|
signalled = threading.Event() # distinguishes a signal stop from a max_beats / test stop
|
|
|
|
lock = _SingleInstanceLock(resolved / LOCK_FILENAME)
|
|
lock.acquire() # fail fast BEFORE any signal/runtime setup if another life is live
|
|
|
|
previous_handlers: list[tuple[int, Any]] = []
|
|
try:
|
|
if install_signals:
|
|
|
|
def _handle(_signum: int, _frame: Any) -> None:
|
|
signalled.set()
|
|
stop_event.set()
|
|
|
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
old = signal.getsignal(sig)
|
|
signal.signal(sig, _handle) # record only AFTER it actually changed
|
|
previous_handlers.append((sig, old))
|
|
|
|
runtime = ChatRuntime(
|
|
config=cfg, engine_state_path=resolved, no_load_state=no_load_state
|
|
)
|
|
# An ephemeral (no-load-state) life persists nothing — including no durable
|
|
# lived_life.json, which would otherwise overwrite the persistent life's artifact.
|
|
report_path = None if no_load_state else resolved / LIVED_LIFE_FILENAME
|
|
report = run_continuous(
|
|
runtime,
|
|
heartbeats=max_beats,
|
|
sleep_seconds=interval,
|
|
stop=stop_event.is_set,
|
|
on_heartbeat=on_record,
|
|
report_path=report_path,
|
|
)
|
|
finally:
|
|
for sig, handler in previous_handlers:
|
|
# getsignal can return None for a non-Python handler; restore SIG_DFL then.
|
|
signal.signal(sig, handler if handler is not None else signal.SIG_DFL)
|
|
lock.release()
|
|
|
|
return DaemonResult(
|
|
report=report, engine_state_path=resolved, stopped_by_signal=signalled.is_set()
|
|
)
|