mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 12:18:39 +00:00
tests/test_consciousness.py is rewritten for tick(now): every branch (disabled, live wake, live owner turn, not due, allowance unknown/exhausted, no owner chat, launch with the envelope and rendered message, refusal, finish -> chosen interval / pending reason / doubling backoff, notify debounce, the same-instant admission race, self-trigger exclusion, boot floor, the one-time inbox archive, graceful stop of a live wake, the snapshot keys). tests/test_consciousness_wake.py pins the renderer (placeholders, event truncation, fail-soft readers), the template's hints and the server status projection. Retired with the loop: test_consciousness_observations.py, test_observation_fold.py, test_background_model_wait_browser.py, the background twins in test_openai_chat_dispatch*.py, test_anthropic_native_custody.py, test_transport_death_retry.py, the background-role pins in test_owner_live_delivery.py / test_quiz_answer.py, the BG budget pins and the consciousness max_tokens row. The context harnesses (test_repo_read_limits, test_context, test_durable_learning_completeness) now assert the same governance and gap facts on the wake's Main context through build_llm_messages; test_background_model_wait.py keeps only the generic model-wait facts. The docs-sync identifier audit resolves the wake template against the full registry plus its render placeholders; the people/identity prompt pins follow the new wording; test_settings_honesty pins that the wake-up bounds apply without a restart; the persistence scan and the smoke tool inventory absorb the retired fold segments and the registered set_next_wakeup. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
805 lines
34 KiB
Python
805 lines
34 KiB
Python
"""The health invariants ouroboros.context builds, and where they must appear.
|
|
|
|
This module owns the cache hit-rate invariant, the remote context overflow it reports,
|
|
the hot-store growth it watches, the rest of the invariant coverage, and the rule that
|
|
the invariants come first in both the dynamic and the background-consciousness context.
|
|
|
|
The runtime section, the advisory review status, the memory/consolidation sections and
|
|
the drive-state projection were split verbatim into
|
|
``tests/test_context_runtime_section.py``, ``tests/test_context_advisory_review.py``,
|
|
``tests/test_context_memory.py`` and ``tests/test_context_drive_state.py``; the health
|
|
environment builder they share lives in ``tests/_context_shared.py``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from ouroboros.context import build_health_invariants, build_runtime_section
|
|
|
|
from tests._context_shared import _make_health_env
|
|
|
|
|
|
|
|
class TestCacheHitRateInvariant:
|
|
def _make_env(self, tmp_path, events_lines):
|
|
class FakeEnv:
|
|
def drive_path(self, p):
|
|
return tmp_path / p
|
|
def repo_path(self, p):
|
|
return tmp_path / "repo" / p
|
|
@property
|
|
def repo_dir(self):
|
|
return tmp_path / "repo"
|
|
@property
|
|
def drive_root(self):
|
|
return tmp_path
|
|
|
|
(tmp_path / "state").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "logs").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "memory").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "repo" / "docs").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "repo" / "VERSION").write_text("1.2.3", encoding="utf-8")
|
|
(tmp_path / "repo" / "pyproject.toml").write_text('version = "1.2.3"', encoding="utf-8")
|
|
(tmp_path / "repo" / "web").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "repo" / "web" / "package.json").write_text('{"version": "1.2.3"}', encoding="utf-8")
|
|
(tmp_path / "repo" / "README.md").write_text('version-1.2.3', encoding="utf-8")
|
|
(tmp_path / "repo" / "docs" / "ARCHITECTURE.md").write_text('# Ouroboros v1.2.3', encoding="utf-8")
|
|
(tmp_path / "repo" / "docs" / "DEVELOPMENT.md").write_text('# Dev', encoding="utf-8")
|
|
(tmp_path / "state" / "state.json").write_text('{"spent_usd": 0, "budget_drift_alert": false}', encoding="utf-8")
|
|
(tmp_path / "memory" / "identity.md").write_text('x' * 300, encoding="utf-8")
|
|
(tmp_path / "memory" / "scratchpad.md").write_text('x' * 300, encoding="utf-8")
|
|
(tmp_path / "logs" / "events.jsonl").write_text("\n".join(events_lines) + "\n", encoding="utf-8")
|
|
return FakeEnv()
|
|
|
|
def test_cache_hit_rate_good(self, tmp_path):
|
|
lines = [json.dumps({"type": "llm_round", "prompt_tokens": 1000, "cached_tokens": 600}) for _ in range(15)]
|
|
env = self._make_env(tmp_path, lines)
|
|
result = build_health_invariants(env)
|
|
assert "cache hit rate" in result.lower()
|
|
assert "60%" in result or "60.0%" in result
|
|
|
|
def test_cache_hit_rate_warning_below_30(self, tmp_path):
|
|
lines = [json.dumps({"type": "llm_round", "prompt_tokens": 1000, "cached_tokens": 200}) for _ in range(15)]
|
|
env = self._make_env(tmp_path, lines)
|
|
result = build_health_invariants(env)
|
|
assert "LOW CACHE HIT RATE" in result
|
|
|
|
def _emit_producer_rounds(self, tmp_path, reported, count=6):
|
|
"""Rounds in the PRODUCER's exact shape.
|
|
|
|
`loop_llm_call.call_llm_with_retry` is the only emitter of `llm_round`,
|
|
so a synthesized row proves nothing about what the health line reads:
|
|
the first version of this rule was inert because the producer stamped
|
|
`cached_tokens` on every event, including rounds where the provider had
|
|
reported no cache at all. `reported=None` is a provider that says
|
|
nothing about caching.
|
|
"""
|
|
from ouroboros.loop_llm_call import call_llm_with_retry
|
|
|
|
class _LLM:
|
|
def chat(self, **_kwargs):
|
|
usage = {"provider": "openrouter", "resolved_model": "m",
|
|
"prompt_tokens": 1000, "completion_tokens": 10, "cost": 0.0}
|
|
if reported is not None:
|
|
usage["cached_tokens"] = reported
|
|
return {"content": "ok"}, usage
|
|
|
|
for index in range(count):
|
|
call_llm_with_retry(
|
|
_LLM(), [{"role": "user", "content": "hi"}], "m", None, "medium", 1,
|
|
tmp_path / "logs", "cache-probe", index, None, {}, "task", False,
|
|
)
|
|
|
|
def test_no_provider_reported_a_cache_leaves_the_share_unknown(self, tmp_path):
|
|
"""Absence is not a measured zero: a run whose provider never reported a
|
|
cache rendered as an honest 0% and read as a caching regression nobody
|
|
had measured."""
|
|
from ouroboros.context_health import _compute_cache_hit_rate
|
|
|
|
env = self._make_env(tmp_path, [])
|
|
self._emit_producer_rounds(tmp_path, None)
|
|
assert _compute_cache_hit_rate(env) is None
|
|
assert "cache hit rate" not in build_health_invariants(env).lower()
|
|
|
|
def test_an_explicitly_reported_zero_is_still_a_real_zero(self, tmp_path):
|
|
from ouroboros.context_health import _compute_cache_hit_rate
|
|
|
|
env = self._make_env(tmp_path, [])
|
|
self._emit_producer_rounds(tmp_path, 0)
|
|
assert _compute_cache_hit_rate(env) == 0.0
|
|
assert "LOW CACHE HIT RATE" in build_health_invariants(env)
|
|
|
|
def test_a_window_with_fewer_than_five_reporting_rounds_stays_unknown(self, tmp_path):
|
|
"""The five-round threshold counts MEASUREMENTS, not rounds. Three
|
|
reporters beside three silent rounds are still too thin a sample to
|
|
publish a share, so the invariant says nothing rather than a number the
|
|
window cannot support."""
|
|
from ouroboros.context_health import _compute_cache_hit_rate
|
|
|
|
env = self._make_env(tmp_path, [])
|
|
self._emit_producer_rounds(tmp_path, None, count=3)
|
|
self._emit_producer_rounds(tmp_path, 600, count=3)
|
|
assert _compute_cache_hit_rate(env) is None
|
|
|
|
def test_silent_rounds_stay_out_of_the_reporters_denominator(self, tmp_path):
|
|
"""A mixed install reads the reporters' own ratio.
|
|
|
|
Charging a silent round's prompt tokens to the denominator turned a
|
|
provider that never measured a cache into measured misses: the share
|
|
collapsed and "LOW CACHE HIT RATE" fired for a regression no round had
|
|
measured. Five reporters at 600 cached of 1000 prompt are 60%, whatever
|
|
the silent rounds beside them spent."""
|
|
from ouroboros.context_health import _compute_cache_hit_rate
|
|
|
|
env = self._make_env(tmp_path, [])
|
|
self._emit_producer_rounds(tmp_path, None, count=3)
|
|
self._emit_producer_rounds(tmp_path, 600, count=5)
|
|
assert _compute_cache_hit_rate(env) == 0.6
|
|
|
|
@pytest.mark.parametrize("reports, expected_rate", [
|
|
([None] * 6, None),
|
|
([0] * 6, 0.0),
|
|
([600] * 6, 0.6),
|
|
([None] * 10 + [600] * 5, 0.6),
|
|
], ids=["unknown", "measured_zero", "measured_hit", "mixed"])
|
|
def test_nullable_adapter_cache_reaches_durable_and_live_rounds(
|
|
self, tmp_path, reports, expected_rate,
|
|
):
|
|
"""The real adapter uses a present null key for an unreported cache.
|
|
|
|
Omitted-key fixtures alone missed the producer turning that null into
|
|
zero. Both round events must preserve the measurement before health
|
|
computes its share over the reporting rounds.
|
|
"""
|
|
from queue import Queue
|
|
from ouroboros.context_health import _compute_cache_hit_rate
|
|
from ouroboros.llm_claudexor import _usage
|
|
from ouroboros.loop_llm_call import call_llm_with_retry
|
|
|
|
env = self._make_env(tmp_path, [])
|
|
events = Queue()
|
|
accumulated = {}
|
|
|
|
class LLM:
|
|
def chat(self, **_kwargs):
|
|
counters = {"input_tokens": 1000, "output_tokens": 10}
|
|
if reported is not None:
|
|
counters["cached_input_tokens"] = reported
|
|
usage, cost, final = _usage({"usage": counters})
|
|
usage.update(provider="claudexor", resolved_model="claudexor/probe",
|
|
cost=cost, cost_final=final)
|
|
return {"content": "Completed synthetic round."}, usage
|
|
|
|
for index, reported in enumerate(reports, 1):
|
|
message, _cost = call_llm_with_retry(
|
|
LLM(), [{"role": "user", "content": "Check the report."}],
|
|
"claudexor/probe", None, "medium", 1, tmp_path / "logs",
|
|
"nullable-cache", index, events, accumulated,
|
|
)
|
|
assert message["content"] == "Completed synthetic round."
|
|
rows = [json.loads(line) for line in (tmp_path / "logs/events.jsonl").read_text().splitlines()
|
|
if line.strip()]
|
|
durable = [row["cached_tokens"] for row in rows if row.get("type") == "llm_round"]
|
|
live = []
|
|
while not events.empty():
|
|
event = events.get_nowait()
|
|
if event.get("type") == "log_event" and event["data"].get("type") == "llm_round_finished":
|
|
live.append(event["data"]["cached_tokens"])
|
|
assert durable == reports
|
|
assert live == reports
|
|
assert _compute_cache_hit_rate(env) == expected_rate
|
|
|
|
|
|
def test_health_invariants_reports_remote_context_overflow(tmp_path):
|
|
env = _make_health_env(
|
|
tmp_path,
|
|
[json.dumps({"type": "remote_context_overflow", "model": "provider/model"})],
|
|
)
|
|
|
|
result = build_health_invariants(env)
|
|
|
|
assert "REMOTE CONTEXT OVERFLOW" in result
|
|
assert "provider/model x1" in result
|
|
|
|
|
|
class TestAdditionalHealthInvariantCoverage:
|
|
def test_version_desync_warning(self, tmp_path):
|
|
env = _make_health_env(tmp_path)
|
|
(tmp_path / "repo" / "pyproject.toml").write_text('version = "1.2.4"', encoding="utf-8")
|
|
|
|
result = build_health_invariants(env)
|
|
assert "VERSION DESYNC" in result
|
|
assert "pyproject.toml=1.2.4" in result
|
|
|
|
def test_web_package_version_desync_warning(self, tmp_path):
|
|
env = _make_health_env(tmp_path)
|
|
(tmp_path / "repo" / "web" / "package.json").write_text('{"version": "1.2.4"}', encoding="utf-8")
|
|
|
|
result = build_health_invariants(env)
|
|
assert "VERSION DESYNC" in result
|
|
assert "web/package.json=1.2.4" in result
|
|
|
|
def test_rc_pep440_pyproject_does_not_warn(self, tmp_path):
|
|
env = _make_health_env(tmp_path)
|
|
(tmp_path / "repo" / "VERSION").write_text("4.50.0-rc.2", encoding="utf-8")
|
|
(tmp_path / "repo" / "pyproject.toml").write_text('version = "4.50.0rc2"', encoding="utf-8")
|
|
(tmp_path / "repo" / "web" / "package.json").write_text('{"version": "4.50.0-rc.2"}', encoding="utf-8")
|
|
(tmp_path / "repo" / "README.md").write_text(
|
|
"[](VERSION)",
|
|
encoding="utf-8",
|
|
)
|
|
(tmp_path / "repo" / "docs" / "ARCHITECTURE.md").write_text(
|
|
"# Ouroboros v4.50.0-rc.2",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "VERSION DESYNC" not in result
|
|
|
|
def test_rc_badge_url_mismatch_warns(self, tmp_path):
|
|
env = _make_health_env(tmp_path)
|
|
(tmp_path / "repo" / "VERSION").write_text("4.50.0-rc.2", encoding="utf-8")
|
|
(tmp_path / "repo" / "pyproject.toml").write_text('version = "4.50.0rc2"', encoding="utf-8")
|
|
(tmp_path / "repo" / "web" / "package.json").write_text('{"version": "4.50.0-rc.2"}', encoding="utf-8")
|
|
(tmp_path / "repo" / "README.md").write_text(
|
|
"[](VERSION)",
|
|
encoding="utf-8",
|
|
)
|
|
(tmp_path / "repo" / "docs" / "ARCHITECTURE.md").write_text(
|
|
"# Ouroboros v4.50.0-rc.2",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "VERSION DESYNC" in result
|
|
assert "README badge URL token" in result
|
|
|
|
def test_duplicate_processing_warning(self, tmp_path):
|
|
env = _make_health_env(tmp_path)
|
|
(tmp_path / "logs" / "events.jsonl").write_text(
|
|
json.dumps({
|
|
"type": "owner_message_injected",
|
|
"text": "same message",
|
|
"task_id": "task-a",
|
|
}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
(tmp_path / "logs" / "supervisor.jsonl").write_text(
|
|
json.dumps({
|
|
"event_type": "owner_message_injected",
|
|
"text": "same message",
|
|
"task_id": "task-b",
|
|
}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "DUPLICATE PROCESSING" in result
|
|
assert "task-a" in result
|
|
assert "task-b" in result
|
|
|
|
def test_provider_and_overflow_warnings(self, tmp_path):
|
|
env = _make_health_env(
|
|
tmp_path,
|
|
events_lines=[
|
|
json.dumps({"type": "llm_api_error", "model": "openai/gpt-5.5"}),
|
|
json.dumps({"type": "local_context_overflow", "model": "local/qwen"}),
|
|
],
|
|
)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "PROVIDER/ROUTING ERRORS" in result
|
|
assert "openai/gpt-5.5 x1" in result
|
|
assert "LOCAL CONTEXT OVERFLOW" in result
|
|
assert "local/qwen x1" in result
|
|
|
|
def test_rescue_snapshot_warning(self, tmp_path):
|
|
env = _make_health_env(tmp_path)
|
|
rescue_dir = tmp_path / "archive" / "rescue" / "2026-04-14-test"
|
|
rescue_dir.mkdir(parents=True, exist_ok=True)
|
|
(rescue_dir / "rescue_meta.json").write_text("{}", encoding="utf-8")
|
|
(rescue_dir / "changes.diff").write_text("diff", encoding="utf-8")
|
|
|
|
result = build_health_invariants(env)
|
|
assert "RESCUE SNAPSHOT AVAILABLE" in result
|
|
assert "2026-04-14-test" in result
|
|
|
|
|
|
def _grow_file(path, size: int) -> None:
|
|
"""Create a file whose st_size is exactly `size` without writing `size` bytes."""
|
|
import os
|
|
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.touch()
|
|
os.truncate(path, size)
|
|
|
|
|
|
def _grow_ledger(path, size: int) -> None:
|
|
"""Grow a synthetic usage ledger WITHOUT triggering tail quarantine.
|
|
|
|
build_health_invariants reads the ledger (budget-drift check) BEFORE the
|
|
hot-store stat. A single torn tail row would be QUARANTINED there — the
|
|
substrate ftruncates the file — shrinking st_size before the check under
|
|
test runs. Corruption BEFORE the tail instead raises UsageLedgerCorrupt
|
|
without mutating the file, degrading the budget check to its established
|
|
"COST ACCOUNTING UNAVAILABLE" path while st_size stays exactly `size`.
|
|
"""
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
row = b"not json\n"
|
|
path.write_bytes(row * (size // len(row)) + b"x" * (size % len(row)))
|
|
|
|
|
|
class TestHotStoreGrowthInvariant:
|
|
def test_progress_growth_warns_above_threshold(self, tmp_path):
|
|
from ouroboros.context_budget import PROGRESS_LOG_WARN_BYTES
|
|
|
|
env = _make_health_env(tmp_path)
|
|
_grow_file(tmp_path / "logs" / "progress.jsonl", PROGRESS_LOG_WARN_BYTES + 1)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "HOT STORE GROWTH" in result
|
|
assert "logs/progress.jsonl" in result
|
|
assert "rotation" in result # remediation pointer
|
|
|
|
def test_exactly_at_threshold_stays_silent(self, tmp_path):
|
|
from ouroboros.context_budget import PROGRESS_LOG_WARN_BYTES
|
|
|
|
env = _make_health_env(tmp_path)
|
|
_grow_file(tmp_path / "logs" / "progress.jsonl", PROGRESS_LOG_WARN_BYTES)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "HOT STORE GROWTH" not in result
|
|
|
|
def test_ledger_growth_warns_with_lock_remediation(self, tmp_path):
|
|
from ouroboros.context_budget import USAGE_LEDGER_WARN_BYTES
|
|
|
|
env = _make_health_env(tmp_path)
|
|
_grow_ledger(tmp_path / "state" / "usage_attempts.jsonl", USAGE_LEDGER_WARN_BYTES + 1)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "HOT STORE GROWTH" in result
|
|
assert "state/usage_attempts.jsonl" in result
|
|
assert "monetary lock" in result
|
|
|
|
def test_rotated_log_thresholds_are_regression_tripwires(self, tmp_path):
|
|
"""events/tools/supervisor/task_reflections rotate on the supervisor
|
|
tick (CPL4-C1..C4); their thresholds fire only when rotation is broken."""
|
|
from ouroboros.context_budget import (
|
|
EVENTS_LOG_WARN_BYTES,
|
|
SUPERVISOR_LOG_WARN_BYTES,
|
|
TASK_REFLECTIONS_LOG_WARN_BYTES,
|
|
TOOLS_LOG_WARN_BYTES,
|
|
)
|
|
|
|
env = _make_health_env(tmp_path)
|
|
_grow_file(tmp_path / "logs" / "events.jsonl", EVENTS_LOG_WARN_BYTES + 1)
|
|
_grow_file(tmp_path / "logs" / "tools.jsonl", TOOLS_LOG_WARN_BYTES + 1)
|
|
_grow_file(tmp_path / "logs" / "supervisor.jsonl", SUPERVISOR_LOG_WARN_BYTES + 1)
|
|
_grow_file(
|
|
tmp_path / "logs" / "task_reflections.jsonl",
|
|
TASK_REFLECTIONS_LOG_WARN_BYTES + 1,
|
|
)
|
|
|
|
result = build_health_invariants(env)
|
|
assert result.count("HOT STORE GROWTH") == 4
|
|
assert "logs/events.jsonl" in result
|
|
assert "logs/tools.jsonl" in result
|
|
assert "logs/supervisor.jsonl" in result
|
|
assert "logs/task_reflections.jsonl" in result
|
|
assert "rotation is broken or missing" in result
|
|
|
|
def test_events_archive_chain_growth_warns(self, tmp_path):
|
|
"""Custody replay walks the whole events chain; the pre-rotation 100MB
|
|
replay-degradation signal now watches live + archive segments."""
|
|
from ouroboros.context_budget import EVENTS_ARCHIVE_SCAN_WARN_BYTES
|
|
|
|
env = _make_health_env(tmp_path)
|
|
segment = tmp_path / "archive" / "events_20260101T000000.jsonl"
|
|
segment.parent.mkdir(parents=True, exist_ok=True)
|
|
with segment.open("wb") as fh: # sparse: size matters, bytes do not
|
|
fh.seek(EVENTS_ARCHIVE_SCAN_WARN_BYTES)
|
|
fh.write(b"x")
|
|
|
|
result = build_health_invariants(env)
|
|
assert "HOT STORE GROWTH" in result
|
|
assert "events chain" in result
|
|
assert "never deleted" in result
|
|
|
|
def test_isolated_benchmark_sentinel_suppresses_warnings(self, tmp_path):
|
|
from supervisor.state import ISOLATED_BENCHMARK_SENTINEL
|
|
from ouroboros.context_budget import PROGRESS_LOG_WARN_BYTES, USAGE_LEDGER_WARN_BYTES
|
|
|
|
env = _make_health_env(tmp_path)
|
|
_grow_file(tmp_path / "logs" / "progress.jsonl", PROGRESS_LOG_WARN_BYTES + 1)
|
|
_grow_ledger(tmp_path / "state" / "usage_attempts.jsonl", USAGE_LEDGER_WARN_BYTES + 1)
|
|
(tmp_path / ISOLATED_BENCHMARK_SENTINEL).write_text("isolated\n", encoding="utf-8")
|
|
|
|
result = build_health_invariants(env)
|
|
assert "HOT STORE GROWTH" not in result
|
|
|
|
def test_scheduled_tasks_store_growth_warns_with_receipt_remediation(self, tmp_path):
|
|
"""The one-shot follow-up receipts (B2b W=A) made this whole-document
|
|
store grow with every fired follow-up; the scheduler re-parses and
|
|
rewrites it on every tick under the queue lock."""
|
|
from ouroboros.context_budget import SCHEDULED_TASKS_WARN_BYTES
|
|
|
|
env = _make_health_env(tmp_path)
|
|
_grow_file(tmp_path / "state" / "scheduled_tasks.json", SCHEDULED_TASKS_WARN_BYTES + 1)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "HOT STORE GROWTH" in result
|
|
assert "state/scheduled_tasks.json" in result
|
|
assert "receipts" in result # remediation pointer
|
|
|
|
def test_absent_stores_stay_silent(self, tmp_path):
|
|
env = _make_health_env(tmp_path)
|
|
|
|
result = build_health_invariants(env)
|
|
assert "HOT STORE GROWTH" not in result
|
|
|
|
|
|
def test_health_invariants_come_first_in_dynamic_context(tmp_path):
|
|
from ouroboros.context import build_llm_messages
|
|
from ouroboros.memory import Memory
|
|
|
|
class FakeEnv:
|
|
def drive_path(self, p):
|
|
return tmp_path / p
|
|
|
|
def repo_path(self, p):
|
|
return tmp_path / "repo" / p
|
|
|
|
@property
|
|
def repo_dir(self):
|
|
return tmp_path / "repo"
|
|
|
|
@property
|
|
def drive_root(self):
|
|
return tmp_path
|
|
|
|
(tmp_path / "repo" / "prompts").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "repo" / "docs").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "memory").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "logs").mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / "state").mkdir(parents=True, exist_ok=True)
|
|
|
|
(tmp_path / "repo" / "prompts" / "SYSTEM.md").write_text("System prompt", encoding="utf-8")
|
|
(tmp_path / "repo" / "BIBLE.md").write_text("Bible", encoding="utf-8")
|
|
(tmp_path / "repo" / "README.md").write_text("README", encoding="utf-8")
|
|
(tmp_path / "repo" / "docs" / "ARCHITECTURE.md").write_text("# Ouroboros v1.2.3", encoding="utf-8")
|
|
(tmp_path / "repo" / "docs" / "DEVELOPMENT.md").write_text(
|
|
"### File Size Budgets\n| Path | Budget chars |\n|------|--------------|\n| memory/identity.md | 1000 |\n",
|
|
encoding="utf-8",
|
|
)
|
|
(tmp_path / "repo" / "docs" / "CHECKLISTS.md").write_text("Checklist", encoding="utf-8")
|
|
(tmp_path / "repo" / "VERSION").write_text("1.2.3", encoding="utf-8")
|
|
(tmp_path / "repo" / "pyproject.toml").write_text('version = "1.2.3"', encoding="utf-8")
|
|
(tmp_path / "state" / "state.json").write_text('{"spent_usd": 0, "budget_drift_alert": false}', encoding="utf-8")
|
|
(tmp_path / "memory" / "identity.md").write_text("x" * 950, encoding="utf-8")
|
|
(tmp_path / "memory" / "scratchpad.md").write_text("scratchpad", encoding="utf-8")
|
|
|
|
messages, _cap_info = build_llm_messages(
|
|
env=FakeEnv(),
|
|
memory=Memory(drive_root=tmp_path),
|
|
task={"id": "task-a", "type": "task", "text": "hello"},
|
|
)
|
|
|
|
dynamic_text = messages[0]["content"][2]["text"]
|
|
assert dynamic_text.startswith("## Health Invariants")
|
|
assert dynamic_text.index("## Health Invariants") < dynamic_text.index("## Drive state")
|
|
|
|
|
|
def test_health_invariants_come_first_in_a_consciousness_wake_context(tmp_path):
|
|
"""A wake-up is an ordinary Main turn: the same builder, the same section order."""
|
|
from ouroboros.context import build_llm_messages
|
|
from ouroboros.memory import Memory
|
|
|
|
repo_dir = tmp_path / "repo"
|
|
drive_root = tmp_path / "drive"
|
|
(repo_dir / "prompts").mkdir(parents=True, exist_ok=True)
|
|
(repo_dir / "docs").mkdir(parents=True, exist_ok=True)
|
|
(drive_root / "memory" / "knowledge").mkdir(parents=True, exist_ok=True)
|
|
(drive_root / "logs").mkdir(parents=True, exist_ok=True)
|
|
(drive_root / "state").mkdir(parents=True, exist_ok=True)
|
|
|
|
(repo_dir / "prompts" / "SYSTEM.md").write_text("System prompt", encoding="utf-8")
|
|
(repo_dir / "BIBLE.md").write_text("Bible", encoding="utf-8")
|
|
(repo_dir / "VERSION").write_text("1.2.3", encoding="utf-8")
|
|
(repo_dir / "pyproject.toml").write_text('version = "1.2.3"', encoding="utf-8")
|
|
(repo_dir / "README.md").write_text("README", encoding="utf-8")
|
|
(repo_dir / "docs" / "ARCHITECTURE.md").write_text("# Ouroboros v1.2.3", encoding="utf-8")
|
|
(repo_dir / "docs" / "DEVELOPMENT.md").write_text(
|
|
"### File Size Budgets\n| Path | Budget chars |\n|------|--------------|\n| memory/identity.md | 1000 |\n",
|
|
encoding="utf-8",
|
|
)
|
|
(drive_root / "state" / "state.json").write_text('{"spent_usd": 0, "budget_drift_alert": false}', encoding="utf-8")
|
|
(drive_root / "memory" / "identity.md").write_text("x" * 950, encoding="utf-8")
|
|
(drive_root / "memory" / "scratchpad.md").write_text("scratchpad", encoding="utf-8")
|
|
(drive_root / "logs" / "chat.jsonl").write_text("", encoding="utf-8")
|
|
(drive_root / "logs" / "progress.jsonl").write_text("", encoding="utf-8")
|
|
(drive_root / "logs" / "tools.jsonl").write_text("", encoding="utf-8")
|
|
(drive_root / "logs" / "events.jsonl").write_text("", encoding="utf-8")
|
|
(drive_root / "logs" / "supervisor.jsonl").write_text("", encoding="utf-8")
|
|
(drive_root / "logs" / "task_reflections.jsonl").write_text("", encoding="utf-8")
|
|
|
|
class FakeEnv:
|
|
def drive_path(self, p):
|
|
return drive_root / p
|
|
|
|
def repo_path(self, p):
|
|
return repo_dir / p
|
|
|
|
@property
|
|
def repo_dir(self):
|
|
return repo_dir
|
|
|
|
@property
|
|
def drive_root(self):
|
|
return drive_root
|
|
|
|
messages, _cap_info = build_llm_messages(
|
|
env=FakeEnv(),
|
|
memory=Memory(drive_root=drive_root, repo_dir=repo_dir),
|
|
task={"id": "wake1", "type": "task", "text": "[Wake-up · heartbeat]", "_is_direct_chat": True,
|
|
"metadata": {"initiator": "consciousness", "usage_category": "consciousness"}},
|
|
)
|
|
|
|
dynamic_text = messages[0]["content"][2]["text"]
|
|
assert dynamic_text.startswith("## Health Invariants")
|
|
assert dynamic_text.index("## Health Invariants") < dynamic_text.index("## Drive state")
|
|
|
|
|
|
def test_project_recent_chat_filters_archives_before_recent_bound(tmp_path, monkeypatch):
|
|
"""Sibling traffic cannot hide an older own-project directive before filtering."""
|
|
from ouroboros.context import build_recent_sections
|
|
from ouroboros.memory import Memory
|
|
from ouroboros.projects_registry import create_project
|
|
|
|
monkeypatch.setenv("OUROBOROS_CONTEXT_MODE", "max")
|
|
logs = tmp_path / "logs"
|
|
archive = tmp_path / "archive"
|
|
logs.mkdir(parents=True)
|
|
archive.mkdir(parents=True)
|
|
own = create_project(tmp_path, "own")
|
|
sibling = create_project(tmp_path, "sibling")
|
|
own_chat = int(own["chat_id"])
|
|
sibling_chat = int(sibling["chat_id"])
|
|
decisive = "CLAUDEXOR_ONLY_AND_THREE_LEVEL_NESTING"
|
|
(archive / "chat_20260820T010000.jsonl").write_text(
|
|
json.dumps({"chat_id": own_chat, "direction": "in", "text": decisive}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
(logs / "chat.jsonl").write_text(
|
|
"\n".join(
|
|
json.dumps({"chat_id": sibling_chat, "direction": "in", "text": f"noise-{i}"})
|
|
for i in range(4500)
|
|
) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
combined = "\n\n".join(build_recent_sections(
|
|
Memory(drive_root=tmp_path), env=None, thread_chat_id=own_chat,
|
|
))
|
|
|
|
assert decisive in combined
|
|
assert "noise-4499" not in combined
|
|
assert '\"omitted_matching_rows\": 0' in combined
|
|
assert "chat_history(count, offset, search)" in combined
|
|
assert str(tmp_path) not in combined
|
|
|
|
|
|
def test_project_context_keeps_retention_proof_cross_thread_cat_directive_once(tmp_path):
|
|
from ouroboros.context import build_recent_sections
|
|
from ouroboros.memory import Memory
|
|
from ouroboros.project_dialogue import build_owner_message_ref
|
|
from ouroboros.projects_registry import bind_task_to_project, create_project
|
|
|
|
logs = tmp_path / "logs"
|
|
logs.mkdir(parents=True)
|
|
project = create_project(tmp_path, "cat-tower")
|
|
project_chat = int(project["chat_id"])
|
|
directive = "d" * 500 + " CLAUDEXOR_ONLY; L1 MUST ASK L2 TO SPAWN L3"
|
|
ref = build_owner_message_ref(
|
|
chat_id=1, client_message_id="cat-origin", ts="2026-08-21T00:00:00Z", text=directive,
|
|
)
|
|
bind_task_to_project(
|
|
tmp_path, "cat-root", "cat-tower", project_chat,
|
|
origin={"ref": ref, "text": directive},
|
|
)
|
|
source_row = {**ref, "direction": "in", "text": directive}
|
|
(logs / "chat.jsonl").write_text(
|
|
json.dumps(source_row) + "\n"
|
|
+ json.dumps({"chat_id": project_chat, "direction": "in", "text": "continue"}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
present = "\n\n".join(build_recent_sections(
|
|
Memory(drive_root=tmp_path), env=None, thread_chat_id=project_chat,
|
|
))
|
|
assert present.count("CLAUDEXOR_ONLY; L1 MUST ASK L2 TO SPAWN L3") == 1
|
|
|
|
(logs / "chat.jsonl").write_text(
|
|
json.dumps({"chat_id": project_chat, "direction": "in", "text": "continue"}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
combined = "\n\n".join(build_recent_sections(
|
|
Memory(drive_root=tmp_path), env=None, thread_chat_id=project_chat,
|
|
))
|
|
|
|
assert directive in combined
|
|
assert combined.count("CLAUDEXOR_ONLY; L1 MUST ASK L2 TO SPAWN L3") == 1
|
|
assert "Project owner origins (retention-proof bindings)" in combined
|
|
|
|
|
|
def test_automatic_recent_context_reads_only_bounded_generation_suffix(tmp_path, monkeypatch):
|
|
import pathlib
|
|
|
|
from ouroboros.memory import Memory, _AUTOMATIC_CHAT_GENERATIONS
|
|
|
|
logs, archive = tmp_path / "logs", tmp_path / "archive"
|
|
logs.mkdir(parents=True)
|
|
archive.mkdir()
|
|
for index in range(10):
|
|
(archive / f"chat_20260820T{index:02d}0000.jsonl").write_text(
|
|
json.dumps({"direction": "in", "text": f"archive-{index}"}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
(logs / "chat.jsonl").write_text(
|
|
json.dumps({"direction": "in", "text": "live-latest"}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
memory = Memory(tmp_path)
|
|
reads = []
|
|
original = memory._read_chat_generation
|
|
|
|
def _counted(path, **kwargs):
|
|
reads.append(path)
|
|
return original(path, **kwargs)
|
|
|
|
monkeypatch.setattr(memory, "_read_chat_generation", _counted)
|
|
entries, coverage = memory.read_unconsolidated_chat({}, 20)
|
|
|
|
assert len(reads) <= _AUTOMATIC_CHAT_GENERATIONS
|
|
assert entries[-1]["text"] == "live-latest"
|
|
assert coverage["omitted_matching_rows_unknown"] is True
|
|
assert any(gap["kind"] == "unscanned_unconsolidated_generations" for gap in coverage["gaps"])
|
|
assert [pathlib.Path(row["path"]).name for row in coverage["generations"]] == [
|
|
"chat_20260820T080000.jsonl", "chat_20260820T090000.jsonl", "chat.jsonl",
|
|
]
|
|
|
|
|
|
def test_automatic_recent_context_materializes_a_bounded_row_suffix(tmp_path):
|
|
from ouroboros.memory import Memory
|
|
|
|
logs = tmp_path / "logs"
|
|
logs.mkdir(parents=True)
|
|
(logs / "chat.jsonl").write_text(
|
|
"".join(
|
|
json.dumps({"direction": "in", "text": f"row-{index}"}) + "\n"
|
|
for index in range(20_000)
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
entries, coverage = Memory(tmp_path).read_unconsolidated_chat({}, 1)
|
|
|
|
assert [entry["text"] for entry in entries] == ["row-19999"]
|
|
assert coverage["generations"][0]["rows"] <= 100
|
|
assert coverage["omitted_matching_rows_unknown"] is True
|
|
assert any(
|
|
gap["kind"] in {"generation_prefix_unscanned", "generation_tail_rows_unscanned"}
|
|
for gap in coverage["gaps"]
|
|
)
|
|
|
|
|
|
def test_chat_history_surfaces_malformed_gap_even_when_search_matches_nothing(tmp_path):
|
|
from ouroboros.memory import Memory
|
|
|
|
logs = tmp_path / "logs"
|
|
logs.mkdir(parents=True)
|
|
(logs / "chat.jsonl").write_bytes(
|
|
b'{"direction":"in","text":"valid"}\n{"direction":"in","text":"decisive-tail"\n'
|
|
)
|
|
|
|
result = Memory(tmp_path).chat_history(count=20, search="absent-query")
|
|
|
|
assert "no observed messages matching query" in result
|
|
assert "completeness unknown" in result
|
|
assert "jsonl_malformed" in result
|
|
|
|
|
|
def test_main_recent_chat_resumes_unconsolidated_archived_generation(tmp_path):
|
|
from ouroboros.context import build_recent_sections
|
|
from ouroboros.memory import Memory
|
|
from ouroboros.utils import jsonl_generation_signature
|
|
|
|
logs = tmp_path / "logs"
|
|
archive = tmp_path / "archive"
|
|
memory_dir = tmp_path / "memory"
|
|
logs.mkdir(parents=True)
|
|
archive.mkdir(parents=True)
|
|
memory_dir.mkdir(parents=True)
|
|
old = archive / "chat_20260820T010000.jsonl"
|
|
old.write_text(
|
|
"\n".join(json.dumps({"direction": "in", "text": f"old-{i}"}) for i in range(5)) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
(logs / "chat.jsonl").write_text(
|
|
json.dumps({"direction": "in", "text": "new-live"}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
(memory_dir / "dialogue_meta.json").write_text(json.dumps({
|
|
"last_consolidated_offset": 3,
|
|
"chat_log_signature": jsonl_generation_signature(old),
|
|
}), encoding="utf-8")
|
|
|
|
combined = "\n\n".join(build_recent_sections(Memory(drive_root=tmp_path), env=None))
|
|
|
|
assert "old-0" not in combined
|
|
assert "old-2" not in combined
|
|
assert "old-3" in combined
|
|
assert "old-4" in combined
|
|
assert "new-live" in combined
|
|
assert '\"gaps\": []' in combined
|
|
|
|
|
|
def test_archive_only_chat_chain_is_complete_while_live_file_is_absent(tmp_path):
|
|
from ouroboros.memory import Memory
|
|
|
|
archive = tmp_path / "archive"
|
|
archive.mkdir(parents=True)
|
|
(archive / "chat_20260820T010000.jsonl").write_text(
|
|
json.dumps({"direction": "in", "text": "archive-only"}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
entries, coverage = Memory(drive_root=tmp_path).read_chat_generations()
|
|
|
|
assert [entry["text"] for entry in entries] == ["archive-only"]
|
|
assert coverage["gaps"] == []
|
|
|
|
|
|
def test_missing_cursor_generation_hot_path_never_replays_full_archive(tmp_path, monkeypatch):
|
|
from ouroboros.memory import Memory
|
|
|
|
logs = tmp_path / "logs"
|
|
logs.mkdir(parents=True)
|
|
(logs / "chat.jsonl").write_text(
|
|
json.dumps({"direction": "in", "text": "bounded-live"}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
memory = Memory(drive_root=tmp_path)
|
|
|
|
def _forbidden_full_replay(**_kwargs):
|
|
raise AssertionError("automatic gap recovery must not scan every archive generation")
|
|
|
|
monkeypatch.setattr(memory, "read_chat_generations", _forbidden_full_replay)
|
|
entries, coverage = memory.read_unconsolidated_chat({
|
|
"last_consolidated_offset": 50,
|
|
"chat_log_signature": {"first_line_sha256": "f" * 64, "size": 999},
|
|
}, 20)
|
|
|
|
assert [entry["text"] for entry in entries] == ["bounded-live"]
|
|
assert coverage["omitted_matching_rows_unknown"] is True
|
|
assert coverage["gaps"][0]["kind"] == "consolidation_cursor_generation_missing"
|
|
assert coverage["reader"] == "chat_history(count, offset, search)"
|
|
|
|
|
|
def test_runtime_section_carries_official_update_fact(tmp_path, monkeypatch):
|
|
env = _make_health_env(tmp_path)
|
|
# Patched WHERE IT IS USED: context.py binds the name at import, so patching the
|
|
# defining module would leave this test asserting the real projection's own answer
|
|
# and proving nothing about the injection.
|
|
monkeypatch.setattr(
|
|
"ouroboros.context.official_update_projection",
|
|
lambda head: {"status": "update_available", "running": {"sha": head}, "letter": {"state": "ready"}},
|
|
)
|
|
section = build_runtime_section(env, {"id": "task-1", "type": "task"})
|
|
payload = json.loads(section.split("\n\n", 1)[1])
|
|
|
|
assert payload["official_update"]["status"] == "update_available"
|
|
assert payload["official_update"]["letter"] == {"state": "ready"}
|
|
assert payload["official_update"]["running"]["sha"] == payload["git_head"], "the fact reads THIS repo's HEAD"
|