mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
212 lines
8.8 KiB
Python
212 lines
8.8 KiB
Python
"""
|
|
Tests for BackgroundConsciousness helpers.
|
|
|
|
Verifies progress events have the correct shape, reach the queue,
|
|
and respect pause / chat_id=None semantics. Also covers backlog digest
|
|
inclusion in background context.
|
|
|
|
Run: pytest tests/test_consciousness.py -v
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import queue
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
|
|
|
|
class TestEmitProgress(unittest.TestCase):
|
|
"""Tests for BackgroundConsciousness._emit_progress."""
|
|
|
|
def _make_consciousness(self, chat_id=42, event_queue=None):
|
|
"""Create a BackgroundConsciousness with mocked dependencies."""
|
|
from ouroboros.consciousness import BackgroundConsciousness
|
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
drive_root = pathlib.Path(tmpdir)
|
|
(drive_root / "logs").mkdir(parents=True, exist_ok=True)
|
|
repo_dir = pathlib.Path(tmpdir) / "repo"
|
|
repo_dir.mkdir()
|
|
|
|
eq = event_queue if event_queue is not None else queue.Queue()
|
|
|
|
with patch.object(BackgroundConsciousness, '_build_registry', return_value=MagicMock()):
|
|
bc = BackgroundConsciousness(
|
|
drive_root=drive_root,
|
|
repo_dir=repo_dir,
|
|
event_queue=eq,
|
|
owner_chat_id_fn=lambda: chat_id,
|
|
)
|
|
return bc, eq, drive_root
|
|
|
|
def test_event_shape(self):
|
|
"""Event has type, chat_id, text, is_progress, ts."""
|
|
bc, eq, _ = self._make_consciousness(chat_id=99)
|
|
bc._emit_progress("thinking about things")
|
|
evt = eq.get_nowait()
|
|
|
|
self.assertEqual(evt["type"], "send_message")
|
|
self.assertEqual(evt["chat_id"], 99)
|
|
self.assertEqual(evt["text"], "💬 thinking about things")
|
|
self.assertEqual(evt["format"], "markdown")
|
|
self.assertTrue(evt["is_progress"])
|
|
self.assertIn("ts", evt)
|
|
|
|
def test_empty_content_skipped(self):
|
|
"""Empty or whitespace-only content produces no event."""
|
|
bc, eq, drive_root = self._make_consciousness()
|
|
progress_path = drive_root / "logs" / "progress.jsonl"
|
|
|
|
bc._emit_progress("")
|
|
bc._emit_progress(" ")
|
|
bc._emit_progress(None)
|
|
|
|
self.assertTrue(eq.empty())
|
|
# Also should not persist to file
|
|
self.assertFalse(progress_path.exists())
|
|
|
|
def test_chat_id_none_skips_queue_but_persists(self):
|
|
"""When chat_id is None, event is NOT queued but IS persisted."""
|
|
bc, eq, drive_root = self._make_consciousness(chat_id=None)
|
|
bc._emit_progress("background thought")
|
|
|
|
# Queue should be empty
|
|
self.assertTrue(eq.empty())
|
|
|
|
# File should have the entry
|
|
progress_path = drive_root / "logs" / "progress.jsonl"
|
|
self.assertTrue(progress_path.exists())
|
|
entry = json.loads(progress_path.read_text().strip())
|
|
self.assertEqual(entry["type"], "send_message")
|
|
self.assertEqual(entry["content"], "background thought")
|
|
self.assertTrue(entry["is_progress"])
|
|
|
|
def test_paused_events_go_to_deferred(self):
|
|
"""When paused, events go to _deferred_events, not the queue."""
|
|
bc, eq, _ = self._make_consciousness()
|
|
bc._paused = True
|
|
bc._emit_progress("deferred thought")
|
|
|
|
self.assertTrue(eq.empty())
|
|
self.assertEqual(len(bc._deferred_events), 1)
|
|
self.assertEqual(bc._deferred_events[0]["type"], "send_message")
|
|
self.assertEqual(bc._deferred_events[0]["text"], "💬 deferred thought")
|
|
|
|
|
|
class TestBackgroundContext(unittest.TestCase):
|
|
def test_build_context_includes_improvement_backlog_digest(self):
|
|
from ouroboros.consciousness import BackgroundConsciousness
|
|
|
|
tmpdir = pathlib.Path(tempfile.mkdtemp())
|
|
drive_root = tmpdir / "drive"
|
|
repo_dir = tmpdir / "repo"
|
|
(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" / "CONSCIOUSNESS.md").write_text("Consciousness 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('# Dev', encoding="utf-8")
|
|
(drive_root / "state" / "state.json").write_text('{"spent_usd": 0}', encoding="utf-8")
|
|
(drive_root / "memory" / "identity.md").write_text("I am Ouroboros", encoding="utf-8")
|
|
(drive_root / "memory" / "scratchpad.md").write_text("scratchpad", encoding="utf-8")
|
|
(drive_root / "memory" / "knowledge" / "improvement-backlog.md").write_text(
|
|
"# Improvement Backlog\n\n### ibl-1\n- status: open\n- created_at: 2026-04-14T09:00:00+00:00\n- source: execution_reflection\n- category: process\n- task_id: task-1\n- requires_plan_review: yes\n- fingerprint: fp-1\n- summary: Reduce recurring task friction around REVIEW_BLOCKED\n",
|
|
encoding="utf-8",
|
|
)
|
|
for name in ("chat.jsonl", "progress.jsonl", "tools.jsonl", "events.jsonl", "supervisor.jsonl", "task_reflections.jsonl"):
|
|
(drive_root / "logs" / name).write_text("", encoding="utf-8")
|
|
|
|
with patch.object(BackgroundConsciousness, '_build_registry', return_value=MagicMock()):
|
|
bc = BackgroundConsciousness(
|
|
drive_root=drive_root,
|
|
repo_dir=repo_dir,
|
|
event_queue=None,
|
|
owner_chat_id_fn=lambda: None,
|
|
)
|
|
text = bc._build_context()
|
|
self.assertIn("## Improvement Backlog", text)
|
|
self.assertIn("Reduce recurring task friction around REVIEW_BLOCKED", text)
|
|
|
|
|
|
class TestBackgroundConsciousnessToolScope(unittest.TestCase):
|
|
def test_background_consciousness_cannot_execute_or_delegate(self):
|
|
from ouroboros.consciousness import BackgroundConsciousness
|
|
|
|
tmpdir = pathlib.Path(tempfile.mkdtemp())
|
|
drive_root = tmpdir / "drive"
|
|
repo_dir = tmpdir / "repo"
|
|
(drive_root / "logs").mkdir(parents=True, exist_ok=True)
|
|
repo_dir.mkdir(parents=True, exist_ok=True)
|
|
eq = queue.Queue()
|
|
|
|
bc = BackgroundConsciousness(
|
|
drive_root=drive_root,
|
|
repo_dir=repo_dir,
|
|
event_queue=eq,
|
|
owner_chat_id_fn=lambda: 42,
|
|
)
|
|
|
|
schema_names = {s.get("function", {}).get("name") for s in bc._tool_schemas()}
|
|
self.assertIn("send_user_message", schema_names)
|
|
self.assertIn("update_identity", schema_names)
|
|
self.assertIn("recent_tasks", schema_names)
|
|
self.assertNotIn("schedule_subagent", schema_names)
|
|
self.assertNotIn("get_task_result", schema_names)
|
|
self.assertNotIn("wait_task", schema_names)
|
|
self.assertNotIn("wait_tasks", schema_names)
|
|
self.assertNotIn("run_command", schema_names)
|
|
self.assertNotIn("commit_reviewed", schema_names)
|
|
|
|
|
|
class TestBackgroundConsciousnessCost(unittest.TestCase):
|
|
def test_unknown_round_cost_stays_nullable_in_durable_thought(self):
|
|
from ouroboros.consciousness import BackgroundConsciousness
|
|
|
|
tmpdir = pathlib.Path(tempfile.mkdtemp())
|
|
drive_root = tmpdir / "drive"
|
|
repo_dir = tmpdir / "repo"
|
|
(drive_root / "logs").mkdir(parents=True)
|
|
repo_dir.mkdir()
|
|
|
|
with patch.object(BackgroundConsciousness, "_build_registry", return_value=MagicMock()):
|
|
bc = BackgroundConsciousness(
|
|
drive_root=drive_root,
|
|
repo_dir=repo_dir,
|
|
event_queue=None,
|
|
owner_chat_id_fn=lambda: None,
|
|
)
|
|
|
|
with (
|
|
patch.object(bc, "_build_context", return_value="context"),
|
|
patch.object(bc, "_tool_schemas", return_value=[]),
|
|
patch.object(bc, "_check_budget", return_value=True),
|
|
patch(
|
|
"ouroboros.llm_observability.chat_observed",
|
|
return_value=({"content": "thought"}, {"cost": None}),
|
|
),
|
|
):
|
|
self.assertTrue(bc._think_scoped())
|
|
|
|
events = [
|
|
json.loads(line)
|
|
for line in (drive_root / "logs" / "events.jsonl").read_text().splitlines()
|
|
]
|
|
thought = next(event for event in events if event.get("type") == "consciousness_thought")
|
|
self.assertIsNone(thought["cost_usd"])
|
|
self.assertFalse(thought["cost_final"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|