mirror of
https://github.com/razzant/ouroboros.git
synced 2026-08-04 08:09:48 +00:00
Multi-task chat steering (WS1): a busy-chat decision turn STEERS the right running task instead of spawning a duplicate. It sees the chat's running tasks as a structural runtime fact (current_chat.running_tasks) and the new LLM-first steer_task(task_id, message) delivers to that task's owner-mailbox — idempotent (client_message_id-derived msg_id, no double-deliver), fail-visible on a stale target, generalizing to N concurrent tasks (the agent's judgment picks the target — no keyword gate, BIBLE P5). A busy project room routes to the same decision turn (project-scoped) rather than mechanically auto-spawning; the 1:1 project auto-delivery is idempotent too. Skill-dispatch resilience (WS2): the ctx calling-convention is decided on the RAW handler (no spurious-ctx TypeError for keyword-only handlers). (The no-deps _execution_lock skip fast path was withdrawn — it reopened a cross-skill dep leak; a reader/writer fast path is deferred to a live-verifiable release. Head-of-line is mitigated by the pre-existing out-of-process dispatch + the nested-finally lock release.) Chat-lane wedge resilience (WS3): bridge intake is hoisted EARLY in the supervisor loop; a dedicated per-generation liveness watchdog (off the serial loop) alerts the owner on a supervisor-loop stall OR a heartbeat-silent in-process chat turn (read lock-free; + /restart hint). In-process admission cannot be safely freed (the wedged turn holds _chat_agent_lock), so out-of-process kill stays deferred; WS10 ephemeral turns keep the chat responsive meanwhile. WebSocket hardening (WS4): broadcasts fan out concurrently (asyncio.gather — one slow client can't head-of-line); chat-history jsonl parses off the event loop (asyncio.to_thread). v6.33.0 review carryover (WS5): the P3 scope-review floor is owner-only + audited (POST /api/owner/scope-review-floor, merge-skipped from generic settings, guarded on shell/browser/ SAFETY channels). Max context mode is enforced at point-of-USE and point-of-BUILD (fail-closed to Low when the active route — remote OR local n_ctx — no longer confirms >=1M, read-only; USE_LOCAL_MAIN routes the gate to local n_ctx; switch_model refuses a sub-1M route while the transcript is max-sized, fail-closed). A short ephemeral decision turn runs a DEFAULT-DENY read/decision ALLOWLIST (no durable/control/review/skill/shell or extension·MCP tools) and leaves no durable task record. The external-shell secret guard catches relative interpreter- string paths. Ratified extras: op=structural code intelligence is polyglot via tree-sitter (Go/Rust/Java/... + a visible structural_unavailable marker); an OpenAI-compatible /models capability probe; Settings Max-save shares the capability-ack flow. SYSTEM.md slim (WS6): the named-project how-to moves from the prompt into the promote_chat_to_task tool description. New surface: POST /api/owner/scope-review-floor, steer_task tool, OUROBOROS_SUPERVISOR_LIVENESS_DEADLINE_SEC, OUROBOROS_PACING_INTERVAL_SEC. Reviewed by the full triad (gpt-5.5 / gemini-3.5-flash / opus-4.8) + scope (gpt-5.5, 1M) + claudexor (gpt-5.5) across 8 gauntlet rounds; converged with the sole remaining finding — the WS2 no-deps reader/writer fast path — an owner-deferred, evidence-based decline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
177 lines
7.4 KiB
Python
177 lines
7.4 KiB
Python
import pathlib
|
|
|
|
from ouroboros.tools.query_code import _query_code
|
|
from ouroboros.tools.registry import ToolContext, ToolRegistry
|
|
|
|
|
|
def _repo(tmp_path: pathlib.Path) -> pathlib.Path:
|
|
repo = tmp_path / "repo"
|
|
(repo / "pkg").mkdir(parents=True)
|
|
(repo / "pkg" / "__init__.py").write_text("", encoding="utf-8")
|
|
(repo / "pkg" / "helper.py").write_text(
|
|
"def helper():\n"
|
|
" return 1\n",
|
|
encoding="utf-8",
|
|
)
|
|
(repo / "pkg" / "main.py").write_text(
|
|
"from .helper import helper\n\n"
|
|
"class Worker:\n"
|
|
" def run(self):\n"
|
|
" return helper()\n",
|
|
encoding="utf-8",
|
|
)
|
|
(repo / "web.js").write_text("import {x} from './x.js';\nfunction start(){ return x(); }\n", encoding="utf-8")
|
|
return repo
|
|
|
|
|
|
def test_query_code_symbols_references_callers_and_impact(tmp_path):
|
|
repo = _repo(tmp_path)
|
|
data = tmp_path / "data"
|
|
ctx = ToolContext(repo_dir=repo, drive_root=data)
|
|
|
|
symbols = _query_code(ctx, op="symbols", query="Worker")
|
|
assert "pkg/main.py" in symbols
|
|
assert "class Worker" in symbols
|
|
|
|
refs = _query_code(ctx, op="references", query="helper", path="pkg/helper.py")
|
|
assert "pkg/main.py" in refs
|
|
|
|
callers = _query_code(ctx, op="callers", query="helper", path="pkg/helper.py")
|
|
assert "pkg/main.py" in callers
|
|
assert "helper" in callers
|
|
callees = _query_code(ctx, op="callees", query="run", path="pkg/main.py")
|
|
assert "helper" in callees
|
|
|
|
impact = _query_code(ctx, op="impact", query="pkg/helper.py")
|
|
assert "pkg/main.py" in impact
|
|
|
|
|
|
def test_query_code_relevant_and_structural(tmp_path):
|
|
repo = _repo(tmp_path)
|
|
ctx = ToolContext(repo_dir=repo, drive_root=tmp_path / "data")
|
|
|
|
relevant = _query_code(ctx, op="relevant_files", query="worker run helper")
|
|
assert "pkg/main.py" in relevant
|
|
|
|
structural = _query_code(ctx, op="structural", query="FunctionDef", lang="python", path="pkg")
|
|
assert "pkg/helper.py" in structural or "pkg/main.py" in structural
|
|
|
|
js_symbols = _query_code(ctx, op="definition", query="start", path="web.js")
|
|
assert "web.js" in js_symbols
|
|
|
|
|
|
def test_query_code_structural_polyglot_go_rust_and_filter(tmp_path):
|
|
"""CW11 (v6.34.0): op=structural is polyglot via tree-sitter, lang-filtered, and
|
|
never literal-matches — a node-type query must not echo a matching comment."""
|
|
repo = _repo(tmp_path)
|
|
(repo / "svc.go").write_text(
|
|
"package main\n\n// function_declaration appears in this comment\n"
|
|
"func Serve() int { return 1 }\n",
|
|
encoding="utf-8",
|
|
)
|
|
(repo / "lib.rs").write_text("pub struct Widget { n: i32 }\n", encoding="utf-8")
|
|
ctx = ToolContext(repo_dir=repo, drive_root=tmp_path / "data")
|
|
|
|
go = _query_code(ctx, op="structural", query="function_declaration", lang="go")
|
|
assert "svc.go" in go # the Go function found via tree-sitter (node), not the comment
|
|
assert "appears in this comment" not in go # no literal/text fallback
|
|
assert "pkg/helper.py" not in go and "lib.rs" not in go # lang=go scopes to Go only
|
|
|
|
rs = _query_code(ctx, op="structural", query="struct_item", lang="rust")
|
|
assert "lib.rs" in rs # Rust struct_item via tree-sitter
|
|
|
|
py = _query_code(ctx, op="structural", query="FunctionDef", lang="python", path="pkg")
|
|
assert "pkg/helper.py" in py or "pkg/main.py" in py # Python ast path unchanged
|
|
|
|
|
|
def test_query_code_structural_unavailable_marker(tmp_path, monkeypatch):
|
|
"""A missing tree-sitter grammar surfaces a visible structural_unavailable marker,
|
|
never a silent text guess."""
|
|
repo = _repo(tmp_path)
|
|
(repo / "svc.go").write_text("func Serve() int { return 1 }\n", encoding="utf-8")
|
|
ctx = ToolContext(repo_dir=repo, drive_root=tmp_path / "data")
|
|
# Force the Go grammar to look unavailable (_structural imports _ts_parser from
|
|
# code_intelligence at call time, so patch it there).
|
|
monkeypatch.setattr("ouroboros.code_intelligence._ts_parser", lambda grammar: None)
|
|
out = _query_code(ctx, op="structural", query="function_declaration", lang="go")
|
|
assert "structural_unavailable:go" in out
|
|
|
|
|
|
def test_query_code_structural_schema_enum_is_polyglot():
|
|
from ouroboros.tools.query_code import get_tools
|
|
|
|
enum = get_tools()[0].schema["parameters"]["properties"]["lang"]["enum"]
|
|
for lang in ("go", "rust", "java", "ruby", "c", "cpp"):
|
|
assert lang in enum
|
|
|
|
|
|
def test_query_code_registered_and_policy_wired(tmp_path):
|
|
from ouroboros.safety import POLICY_SKIP, TOOL_POLICY
|
|
from ouroboros.tool_capabilities import (
|
|
ACTING_SUBAGENT_TOOL_NAMES,
|
|
CORE_TOOL_NAMES,
|
|
LOCAL_READONLY_SUBAGENT_TOOL_NAMES,
|
|
READ_ONLY_PARALLEL_TOOLS,
|
|
TOOL_RESULT_LIMITS,
|
|
)
|
|
|
|
registry = ToolRegistry(repo_dir=_repo(tmp_path), drive_root=tmp_path / "data")
|
|
names = {schema["function"]["name"] for schema in registry.schemas()}
|
|
assert "query_code" in names
|
|
assert TOOL_POLICY["query_code"] == POLICY_SKIP
|
|
assert "query_code" in CORE_TOOL_NAMES
|
|
assert "query_code" in LOCAL_READONLY_SUBAGENT_TOOL_NAMES
|
|
assert "query_code" in ACTING_SUBAGENT_TOOL_NAMES
|
|
assert "query_code" in READ_ONLY_PARALLEL_TOOLS
|
|
assert TOOL_RESULT_LIMITS["query_code"] == 80_000
|
|
|
|
|
|
def test_query_code_workspace_and_subagent_schema_roots(tmp_path):
|
|
from ouroboros.contracts.task_constraint import TaskConstraint
|
|
from ouroboros.tool_capabilities import ACTING_SUBAGENT_MODE, LOCAL_READONLY_SUBAGENT_MODE
|
|
|
|
repo = _repo(tmp_path)
|
|
registry = ToolRegistry(repo_dir=repo, drive_root=tmp_path / "data")
|
|
ctx = ToolContext(repo_dir=repo, drive_root=tmp_path / "data", workspace_root=repo, workspace_mode="external")
|
|
registry.set_context(ctx)
|
|
assert "query_code" in {schema["function"]["name"] for schema in registry.schemas()}
|
|
|
|
readonly = ToolContext(
|
|
repo_dir=repo,
|
|
drive_root=tmp_path / "data",
|
|
task_constraint=TaskConstraint(mode=LOCAL_READONLY_SUBAGENT_MODE),
|
|
)
|
|
registry.set_context(readonly)
|
|
schema = registry.get_schema_by_name("query_code")["function"]
|
|
assert schema["parameters"]["properties"]["root"]["enum"] == ["active_workspace", "system_repo"]
|
|
|
|
acting = ToolContext(
|
|
repo_dir=repo,
|
|
drive_root=tmp_path / "data",
|
|
task_constraint=TaskConstraint(mode=ACTING_SUBAGENT_MODE, surface="self_worktree"),
|
|
)
|
|
registry.set_context(acting)
|
|
schema = registry.get_schema_by_name("query_code")["function"]
|
|
assert schema["parameters"]["properties"]["root"]["enum"] == ["active_workspace"]
|
|
blocked = _query_code(acting, op="symbols", query="Worker", root="system_repo")
|
|
assert "TOOL_ACCESS_BLOCKED" in blocked
|
|
|
|
|
|
def test_query_code_hides_local_readonly_subagent_secret_paths(tmp_path):
|
|
from ouroboros.contracts.task_constraint import TaskConstraint
|
|
from ouroboros.tool_capabilities import LOCAL_READONLY_SUBAGENT_MODE
|
|
|
|
repo = _repo(tmp_path)
|
|
(repo / "auth").mkdir()
|
|
(repo / "auth" / "secret.py").write_text("def leak():\n return 'TOKEN_LEAK'\n", encoding="utf-8")
|
|
ctx = ToolContext(
|
|
repo_dir=repo,
|
|
drive_root=tmp_path / "data",
|
|
task_constraint=TaskConstraint(mode=LOCAL_READONLY_SUBAGENT_MODE),
|
|
)
|
|
|
|
result = _query_code(ctx, op="structural", query="leak", path="auth")
|
|
assert "TOKEN_LEAK" not in result
|
|
assert "auth/secret.py" not in result
|
|
cache_files = list((tmp_path / "data" / "state" / "code_intel").glob("*/inventory.json"))
|
|
assert cache_files == []
|