mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 12:18:39 +00:00
NOT_REVIEWED private integration checkpoint. Explicit startup pressure reuses dialogue, chronicle, scratchpad and knowledge maintenance with exact current task and retained sources. Large reflection inputs are read completely across authored working views. Max and pure context previews never start maintenance. Same task usage scope and recorded source progress remain authoritative. Source-domain classification reflects the pure book reader and shared native file read capability.
161 lines
7.1 KiB
Python
161 lines
7.1 KiB
Python
"""Post-task reflection and backlog promotion in ``ouroboros.agent_task_pipeline``.
|
|
|
|
Split out of ``tests/test_agent_task_pipeline.py`` when that module was divided
|
|
by theme; every moved block is verbatim. Covers `_run_reflection` entry
|
|
generation, `_update_improvement_backlog`, and the project-scoped channel
|
|
split: project memory stays project-local while backlog promotion goes to the
|
|
global drive through `_run_global_backlog_promotion_only`.
|
|
"""
|
|
|
|
import json
|
|
from types import SimpleNamespace
|
|
|
|
import ouroboros.agent_task_pipeline as pipeline
|
|
|
|
|
|
def test_project_scoped_post_task_processing_feeds_global_backlog_but_project_memory(tmp_path, monkeypatch):
|
|
import ouroboros.post_task_evolution as post_task_evolution
|
|
|
|
calls = []
|
|
reflection = {"backlog_candidates": [{"summary": "tool friction"}], "memory_actions": [{"kind": "note"}]}
|
|
monkeypatch.setattr(pipeline, "_run_task_summary", lambda *args, **kwargs: calls.append(("summary",)))
|
|
monkeypatch.setattr(pipeline, "_run_reflection", lambda *args, **kwargs: reflection)
|
|
monkeypatch.setattr(pipeline, "_update_improvement_backlog", lambda _env, entry: calls.append(("backlog", entry)) or 1)
|
|
monkeypatch.setattr(
|
|
pipeline,
|
|
"_apply_reflection_memory_actions",
|
|
lambda _env, entry, project_id="": calls.append(("memory", project_id, entry)) or 1,
|
|
)
|
|
monkeypatch.setattr(post_task_evolution, "maybe_promote", lambda _env, task, entry, _llm: calls.append(("promote", task.get("project_id"), entry)))
|
|
env = SimpleNamespace(repo_dir=tmp_path, drive_root=tmp_path, drive_path=lambda rel: tmp_path / rel)
|
|
|
|
pipeline._run_post_task_processing_async(
|
|
env,
|
|
{"id": "task-1", "type": "task", "project_id": "proj-1", "text": "fix workspace"},
|
|
{"rounds": 3, "cost": 0.1},
|
|
{"tool_calls": [], "reasoning_notes": []},
|
|
{},
|
|
tmp_path / "logs",
|
|
blocking=True,
|
|
)
|
|
|
|
assert ("backlog", reflection) in calls
|
|
assert ("memory", "proj-1", reflection) in calls
|
|
assert ("promote", "proj-1", reflection) in calls
|
|
|
|
|
|
def test_project_global_promotion_uses_real_maybe_promote_without_project_scope(tmp_path, monkeypatch):
|
|
import ouroboros.post_task_evolution as post_task_evolution
|
|
|
|
monkeypatch.setattr("ouroboros.config.get_post_task_evolution_enabled", lambda: True)
|
|
monkeypatch.setattr("ouroboros.config.get_runtime_mode", lambda: "pro")
|
|
monkeypatch.setattr("ouroboros.config.get_post_task_evolution_cadence", lambda: "every_n:1")
|
|
monkeypatch.setattr(
|
|
post_task_evolution,
|
|
"_decide_promotion",
|
|
lambda *_args, **_kwargs: {
|
|
"promote": True,
|
|
"objective": "Improve Ouroboros workspace tool feedback",
|
|
"requires_plan_review": True,
|
|
"backlog_id": "",
|
|
},
|
|
)
|
|
env = SimpleNamespace(drive_root=tmp_path, drive_path=lambda rel: tmp_path / rel)
|
|
reflection = {
|
|
"reflection": "Project-specific detail should not be forwarded.",
|
|
"memory_actions": [{"kind": "note"}],
|
|
"backlog_candidates": [{"summary": "Improve Ouroboros workspace tool feedback"}],
|
|
}
|
|
|
|
pipeline._run_global_backlog_promotion_only(
|
|
env,
|
|
{
|
|
"id": "task-project",
|
|
"project_id": "proj-1",
|
|
"workspace_root": "/tmp/project",
|
|
"workspace_mode": "external",
|
|
"metadata": {"workspace_preflight": {"git": {"head": "abc"}}},
|
|
},
|
|
reflection,
|
|
object(),
|
|
)
|
|
|
|
req = json.loads((tmp_path / "state" / "post_task_evolution_request.json").read_text(encoding="utf-8"))
|
|
assert req["objective"] == "Improve Ouroboros workspace tool feedback"
|
|
backlog = (tmp_path / "memory" / "knowledge" / "improvement-backlog.md").read_text(encoding="utf-8")
|
|
assert "Project-specific detail" not in backlog
|
|
|
|
|
|
def test_update_improvement_backlog_appends_candidates(tmp_path):
|
|
env = SimpleNamespace(drive_root=tmp_path)
|
|
|
|
added = pipeline._update_improvement_backlog(
|
|
env,
|
|
{
|
|
"backlog_candidates": [{
|
|
"summary": "Reduce recurring task friction around REVIEW_BLOCKED",
|
|
"category": "process",
|
|
"source": "execution_reflection",
|
|
"task_id": "task-backlog",
|
|
"evidence": "REVIEW_BLOCKED",
|
|
"context": "The task retried blocked review loops without narrowing scope.",
|
|
"proposed_next_step": "Run plan_task before touching review prompts again.",
|
|
}],
|
|
},
|
|
)
|
|
|
|
assert added == 1
|
|
backlog_path = tmp_path / "memory" / "knowledge" / "improvement-backlog.md"
|
|
assert backlog_path.exists()
|
|
text = backlog_path.read_text(encoding="utf-8")
|
|
assert "Reduce recurring task friction around REVIEW_BLOCKED" in text
|
|
|
|
|
|
def test_run_reflection_returns_entry_when_generated(tmp_path, monkeypatch):
|
|
captured = {}
|
|
# Entry generation/persistence is the subject; the Pattern Register has a
|
|
# separate model call and must not consume a real provider in this test.
|
|
monkeypatch.setattr(
|
|
"ouroboros.reflection._update_patterns",
|
|
lambda root, entry: captured.update(pattern_root=root, pattern_entry=entry),
|
|
)
|
|
|
|
class FakeLlm:
|
|
def chat(self, *, messages, model, reasoning_effort, max_tokens, model_role, **kwargs):
|
|
captured["prompt"] = messages[0]["content"]
|
|
captured["model_role"] = model_role
|
|
assert {tool["function"]["name"] for tool in kwargs["tools"]} == {"knowledge_read", "knowledge_list", "compact_context", "read_file"}
|
|
return {
|
|
"content": (
|
|
"Reflection text.\n"
|
|
"BACKLOG_CANDIDATES_JSON: "
|
|
"[{\"summary\":\"Reduce recurring task friction around REVIEW_BLOCKED\","
|
|
"\"category\":\"process\","
|
|
"\"source\":\"execution_reflection\","
|
|
"\"evidence\":\"REVIEW_BLOCKED\"}]"
|
|
)
|
|
}, {"cost": 0}
|
|
|
|
env = SimpleNamespace(drive_root=tmp_path)
|
|
(tmp_path / "logs").mkdir(parents=True)
|
|
|
|
entry = pipeline._run_reflection(
|
|
env,
|
|
FakeLlm(),
|
|
{"id": "task-reflect", "type": "task", "text": "Fix it", "drive_root": str(tmp_path)},
|
|
{"rounds": 2, "cost": 0.01},
|
|
{"tool_calls": [{"tool": "commit_reviewed", "is_error": True, "status": "error",
|
|
"tool_result_code": "REVIEW_BLOCKED", "result": "⚠️ REVIEW_BLOCKED"}]},
|
|
{"recent_attempts": [], "open_obligations": [{"item": "tests_affected", "reason": "Fix the failing test before commit"}]},
|
|
)
|
|
|
|
assert entry is not None
|
|
assert captured["model_role"] == "light"
|
|
assert entry["task_id"] == "task-reflect"
|
|
assert entry["reflection"] == "Reflection text."
|
|
assert len(entry["backlog_candidates"]) == 1
|
|
assert entry["backlog_candidates"][0]["summary"] == "Reduce recurring task friction around REVIEW_BLOCKED"
|
|
stored = [json.loads(line) for line in
|
|
(tmp_path / "logs" / "task_reflections.jsonl").read_text(encoding="utf-8").splitlines()]
|
|
assert stored == [entry]
|
|
assert captured["pattern_root"] == tmp_path and captured["pattern_entry"] == entry
|