Preserve natural claim follow-up reasoning
This commit is contained in:
parent
1a6b7d51a4
commit
0cc05aa2f4
4 changed files with 43 additions and 3 deletions
|
|
@ -581,10 +581,25 @@ def operational_contracts(
|
||||||
term in lowered
|
term in lowered
|
||||||
for term in ("proposal", "proposed", "pending", "approved", "applied", "staged", "reviewer approval")
|
for term in ("proposal", "proposed", "pending", "approved", "applied", "staged", "reviewer approval")
|
||||||
)
|
)
|
||||||
|
claim_reasoning_question = bool(
|
||||||
|
any(term in lowered for term in ("claim", "belief", "exact body"))
|
||||||
|
and any(
|
||||||
|
term in lowered
|
||||||
|
for term in (
|
||||||
|
"assumption",
|
||||||
|
"evidence",
|
||||||
|
"falsif",
|
||||||
|
"observed support",
|
||||||
|
"replacement",
|
||||||
|
"narrower",
|
||||||
|
"what supports",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
broad_kb_change_question = _has_unnegated_action(
|
broad_kb_change_question = _has_unnegated_action(
|
||||||
query, ("change", "changed", "update", "updated", "landed")
|
query, ("change", "changed", "update", "updated", "landed")
|
||||||
)
|
)
|
||||||
proposal_state_question = kb_scope and not forecast_resolution_question and (
|
proposal_state_question = kb_scope and not forecast_resolution_question and not claim_reasoning_question and (
|
||||||
proposal_lifecycle_question
|
proposal_lifecycle_question
|
||||||
or ("demo" not in lowered and broad_kb_change_question and not kb_implementation_question)
|
or ("demo" not in lowered and broad_kb_change_question and not kb_implementation_question)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,13 @@ ASSIGNMENT_SECRET_PATTERN = re.compile(
|
||||||
DB_CONTEXT_PLUGIN = (
|
DB_CONTEXT_PLUGIN = (
|
||||||
ROOT / "hermes-agent" / "leoclean-plugins" / "vps" / "leo-db-context" / "__init__.py"
|
ROOT / "hermes-agent" / "leoclean-plugins" / "vps" / "leo-db-context" / "__init__.py"
|
||||||
)
|
)
|
||||||
|
KB_TOOL = ROOT / "hermes-agent" / "leoclean-bin" / "kb_tool.py"
|
||||||
BEHAVIOR_MANIFEST = ROOT / "scripts" / "leo_behavior_manifest.py"
|
BEHAVIOR_MANIFEST = ROOT / "scripts" / "leo_behavior_manifest.py"
|
||||||
|
|
||||||
|
|
||||||
REMOTE_SCRIPT = r'''
|
REMOTE_SCRIPT = r'''
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
@ -86,6 +88,7 @@ PROMPTS = __PROMPTS_JSON__
|
||||||
REPORT_PREFIX = __REPORT_PREFIX_JSON__
|
REPORT_PREFIX = __REPORT_PREFIX_JSON__
|
||||||
REPORT_PATH = Path(f"/tmp/{REPORT_PREFIX}-{RUN_ID}.json")
|
REPORT_PATH = Path(f"/tmp/{REPORT_PREFIX}-{RUN_ID}.json")
|
||||||
DB_CONTEXT_PLUGIN_SOURCE = __DB_CONTEXT_PLUGIN_SOURCE_JSON__
|
DB_CONTEXT_PLUGIN_SOURCE = __DB_CONTEXT_PLUGIN_SOURCE_JSON__
|
||||||
|
KB_TOOL_SOURCE = __KB_TOOL_SOURCE_JSON__
|
||||||
BEHAVIOR_MANIFEST_SOURCE = __BEHAVIOR_MANIFEST_SOURCE_JSON__
|
BEHAVIOR_MANIFEST_SOURCE = __BEHAVIOR_MANIFEST_SOURCE_JSON__
|
||||||
TURN_TRACE_PLUGIN_SOURCE = """\
|
TURN_TRACE_PLUGIN_SOURCE = """\
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
@ -410,6 +413,11 @@ def install_harness_plugins(profile):
|
||||||
db_context.parent.mkdir(parents=True, exist_ok=True)
|
db_context.parent.mkdir(parents=True, exist_ok=True)
|
||||||
db_context.write_text(DB_CONTEXT_PLUGIN_SOURCE, encoding="utf-8")
|
db_context.write_text(DB_CONTEXT_PLUGIN_SOURCE, encoding="utf-8")
|
||||||
|
|
||||||
|
kb_tool = profile / "bin" / "kb_tool.py"
|
||||||
|
kb_tool.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
kb_tool.write_text(KB_TOOL_SOURCE, encoding="utf-8")
|
||||||
|
kb_tool.chmod(0o700)
|
||||||
|
|
||||||
turn_trace = profile / "plugins" / "leo-turn-execution-trace"
|
turn_trace = profile / "plugins" / "leo-turn-execution-trace"
|
||||||
turn_trace.mkdir(parents=True, exist_ok=True)
|
turn_trace.mkdir(parents=True, exist_ok=True)
|
||||||
(turn_trace / "__init__.py").write_text(TURN_TRACE_PLUGIN_SOURCE, encoding="utf-8")
|
(turn_trace / "__init__.py").write_text(TURN_TRACE_PLUGIN_SOURCE, encoding="utf-8")
|
||||||
|
|
@ -483,7 +491,7 @@ def current_agent_messages(runner, session_key):
|
||||||
return []
|
return []
|
||||||
agent = cached[0]
|
agent = cached[0]
|
||||||
messages = getattr(agent, "_session_messages", None) or []
|
messages = getattr(agent, "_session_messages", None) or []
|
||||||
return [dict(message) for message in messages if isinstance(message, dict)]
|
return [copy.deepcopy(message) for message in messages if isinstance(message, dict)]
|
||||||
|
|
||||||
|
|
||||||
def conversation_trace(messages):
|
def conversation_trace(messages):
|
||||||
|
|
@ -531,6 +539,7 @@ def conversation_trace_rows(messages):
|
||||||
content_text = content if isinstance(content, str) else json.dumps(content, sort_keys=True, default=str)
|
content_text = content if isinstance(content, str) else json.dumps(content, sort_keys=True, default=str)
|
||||||
tool_calls = message.get("tool_calls") or []
|
tool_calls = message.get("tool_calls") or []
|
||||||
rows.append({
|
rows.append({
|
||||||
|
"role": message.get("role"),
|
||||||
"content_sha256": hashlib.sha256(content_text.encode("utf-8")).hexdigest(),
|
"content_sha256": hashlib.sha256(content_text.encode("utf-8")).hexdigest(),
|
||||||
"content_bytes": len(content_text.encode("utf-8")),
|
"content_bytes": len(content_text.encode("utf-8")),
|
||||||
"tool_calls_sha256": hashlib.sha256(
|
"tool_calls_sha256": hashlib.sha256(
|
||||||
|
|
@ -654,7 +663,9 @@ async def run_suite():
|
||||||
reply = await asyncio.wait_for(runner._handle_message(event), timeout=300)
|
reply = await asyncio.wait_for(runner._handle_message(event), timeout=300)
|
||||||
ended = datetime.now(timezone.utc)
|
ended = datetime.now(timezone.utc)
|
||||||
messages_after = current_agent_messages(runner, session_key)
|
messages_after = current_agent_messages(runner, session_key)
|
||||||
history_prefix_preserved = messages_after[: len(messages_before)] == messages_before
|
before_rows = conversation_trace_rows(messages_before)
|
||||||
|
after_rows = conversation_trace_rows(messages_after)
|
||||||
|
history_prefix_preserved = after_rows[: len(before_rows)] == before_rows
|
||||||
new_messages = messages_after[len(messages_before) :] if history_prefix_preserved else messages_after
|
new_messages = messages_after[len(messages_before) :] if history_prefix_preserved else messages_after
|
||||||
database_tool_trace = extract_kb_tool_trace(new_messages)
|
database_tool_trace = extract_kb_tool_trace(new_messages)
|
||||||
results.append(
|
results.append(
|
||||||
|
|
@ -806,6 +817,7 @@ def build_remote_script(
|
||||||
.replace("__REPORT_PREFIX_JSON__", json.dumps(report_prefix))
|
.replace("__REPORT_PREFIX_JSON__", json.dumps(report_prefix))
|
||||||
.replace("__PROMPT_NOTE_JSON__", json.dumps(prompt_note))
|
.replace("__PROMPT_NOTE_JSON__", json.dumps(prompt_note))
|
||||||
.replace("__DB_CONTEXT_PLUGIN_SOURCE_JSON__", json.dumps(DB_CONTEXT_PLUGIN.read_text(encoding="utf-8")))
|
.replace("__DB_CONTEXT_PLUGIN_SOURCE_JSON__", json.dumps(DB_CONTEXT_PLUGIN.read_text(encoding="utf-8")))
|
||||||
|
.replace("__KB_TOOL_SOURCE_JSON__", json.dumps(KB_TOOL.read_text(encoding="utf-8")))
|
||||||
.replace("__BEHAVIOR_MANIFEST_SOURCE_JSON__", json.dumps(BEHAVIOR_MANIFEST.read_text(encoding="utf-8")))
|
.replace("__BEHAVIOR_MANIFEST_SOURCE_JSON__", json.dumps(BEHAVIOR_MANIFEST.read_text(encoding="utf-8")))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -486,6 +486,7 @@ def test_plugin_fails_closed_on_missing_snapshot_or_invalid_compiler() -> None:
|
||||||
def test_handler_harness_retains_database_context_proof_fields() -> None:
|
def test_handler_harness_retains_database_context_proof_fields() -> None:
|
||||||
source = (ROOT / "scripts" / "run_leo_direct_claim_handler_suite.py").read_text(encoding="utf-8")
|
source = (ROOT / "scripts" / "run_leo_direct_claim_handler_suite.py").read_text(encoding="utf-8")
|
||||||
assert "LEO_DB_CONTEXT_TRACE_PATH" in source
|
assert "LEO_DB_CONTEXT_TRACE_PATH" in source
|
||||||
|
assert "KB_TOOL_SOURCE" in source
|
||||||
assert '"database_context_trace"' in source
|
assert '"database_context_trace"' in source
|
||||||
assert '"database_context_injection_count"' in source
|
assert '"database_context_injection_count"' in source
|
||||||
assert '"database_context_all_ok"' in source
|
assert '"database_context_all_ok"' in source
|
||||||
|
|
@ -503,6 +504,8 @@ def test_handler_harness_retains_database_context_proof_fields() -> None:
|
||||||
assert '"model_response_trace"' in source
|
assert '"model_response_trace"' in source
|
||||||
assert '"conversation_before"' in source
|
assert '"conversation_before"' in source
|
||||||
assert '"conversation_after"' in source
|
assert '"conversation_after"' in source
|
||||||
|
assert "copy.deepcopy(message)" in source
|
||||||
|
assert "after_rows[: len(before_rows)] == before_rows" in source
|
||||||
assert "attach_execution_manifests" in source
|
assert "attach_execution_manifests" in source
|
||||||
assert "report_passes_safety" in source
|
assert "report_passes_safety" in source
|
||||||
assert "json.loads(redact(candidate))" in source
|
assert "json.loads(redact(candidate))" in source
|
||||||
|
|
|
||||||
|
|
@ -846,6 +846,16 @@ def test_vps_bridge_direct_intents_reject_unrelated_and_resolve_overlaps() -> No
|
||||||
assert v3_ids & direct_ids == {"proposal_state_readback"}
|
assert v3_ids & direct_ids == {"proposal_state_readback"}
|
||||||
assert "source_link_audit" not in v3_ids
|
assert "source_link_audit" not in v3_ids
|
||||||
|
|
||||||
|
reasoning_followup_ids = {
|
||||||
|
item["id"]
|
||||||
|
for item in module.operational_contracts(
|
||||||
|
"Inspect the exact claim body and evidence: what is assumption versus observed support, what would "
|
||||||
|
"falsify it, and which replacement claims would you put into review? Keep them as proposals, not live "
|
||||||
|
"knowledge."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assert reasoning_followup_ids & direct_ids == set()
|
||||||
|
|
||||||
|
|
||||||
def test_vps_bridge_named_document_question_matches_exact_proposal_and_compiles_receipt(monkeypatch) -> None:
|
def test_vps_bridge_named_document_question_matches_exact_proposal_and_compiles_receipt(monkeypatch) -> None:
|
||||||
module = _load_module(BRIDGE_DIR / "kb_tool.py")
|
module = _load_module(BRIDGE_DIR / "kb_tool.py")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue