Keep Leo candidate reasoning grounded and stop automatic chat reinforcement (#180)
Some checks are pending
CI / lint-and-test (push) Waiting to run
Some checks are pending
CI / lint-and-test (push) Waiting to run
* Preserve candidate reasoning and disable automatic chat reinforcement * Retain exact-row guards for combined claim audits
This commit is contained in:
parent
77e1336c0f
commit
415669c2e4
10 changed files with 326 additions and 14 deletions
|
|
@ -19,8 +19,10 @@ AGENT_STATE_DIR="/opt/teleo-eval/ops/agent-state"
|
|||
LEOCLEAN_BIN_DIR="/home/teleo/.hermes/profiles/leoclean/bin"
|
||||
LEOCLEAN_SKILLS_DIR="/home/teleo/.hermes/profiles/leoclean/skills"
|
||||
LEOCLEAN_PLUGINS_DIR="/home/teleo/.hermes/profiles/leoclean/plugins"
|
||||
LEOCLEAN_CONFIG="/home/teleo/.hermes/profiles/leoclean/config.yaml"
|
||||
HERMES_AGENT_DIR="/home/teleo/.hermes/hermes-agent"
|
||||
HERMES_PATCH_DIR="/home/teleo/.hermes/teleo-runtime-patches"
|
||||
LEOCLEAN_POLICY_PATCH="$HERMES_PATCH_DIR/enforce_leoclean_learning_policy.py"
|
||||
SYSTEMD_DIR="/etc/systemd/system"
|
||||
STAMP_FILE="/opt/teleo-eval/.last-deploy-sha"
|
||||
LOG_TAG="auto-deploy"
|
||||
|
|
@ -55,6 +57,7 @@ NEW_SHA=$(git rev-parse "$DEPLOY_REMOTE/main")
|
|||
OLD_SHA=$(cat "$STAMP_FILE" 2>/dev/null || echo "none")
|
||||
|
||||
if [ "$NEW_SHA" = "$OLD_SHA" ]; then
|
||||
LEOCLEAN_DRIFT_REPAIRED=false
|
||||
# Hermes can be upgraded independently of this repository. Repair an
|
||||
# overwritten response-transform hook even when no infrastructure commit
|
||||
# changed, and restart only when the patch was actually reapplied.
|
||||
|
|
@ -65,14 +68,25 @@ if [ "$NEW_SHA" = "$OLD_SHA" ]; then
|
|||
fi
|
||||
if echo "$PATCH_RESULT" | grep -q '"status": "installed_now"'; then
|
||||
log "Hermes response-transform drift repaired: $PATCH_RESULT"
|
||||
if systemctl is-active --quiet leoclean-gateway.service; then
|
||||
sudo systemctl restart leoclean-gateway
|
||||
sleep 10
|
||||
if ! systemctl is-active --quiet leoclean-gateway.service; then
|
||||
log "ERROR: leoclean-gateway failed after response-transform drift repair"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
LEOCLEAN_DRIFT_REPAIRED=true
|
||||
fi
|
||||
fi
|
||||
if [ -f "$LEOCLEAN_POLICY_PATCH" ] && [ -f "$LEOCLEAN_CONFIG" ]; then
|
||||
if ! POLICY_RESULT=$(python3 "$LEOCLEAN_POLICY_PATCH" "$LEOCLEAN_CONFIG"); then
|
||||
log "ERROR: leoclean learning-policy drift could not be repaired"
|
||||
exit 1
|
||||
fi
|
||||
if echo "$POLICY_RESULT" | grep -q '"status": "updated"'; then
|
||||
log "Leoclean learning-policy drift repaired: $POLICY_RESULT"
|
||||
LEOCLEAN_DRIFT_REPAIRED=true
|
||||
fi
|
||||
fi
|
||||
if $LEOCLEAN_DRIFT_REPAIRED && systemctl is-active --quiet leoclean-gateway.service; then
|
||||
sudo systemctl restart leoclean-gateway
|
||||
sleep 10
|
||||
if ! systemctl is-active --quiet leoclean-gateway.service; then
|
||||
log "ERROR: leoclean-gateway failed after runtime-policy drift repair"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
|
|
@ -152,6 +166,14 @@ if [ -f "$HERMES_PATCH_DIR/apply_response_transform_hook.py" ]; then
|
|||
PATCH_RESULT=$(python3 "$HERMES_PATCH_DIR/apply_response_transform_hook.py" "$HERMES_AGENT_DIR/run_agent.py")
|
||||
log "Hermes response transform: $PATCH_RESULT"
|
||||
fi
|
||||
LEOCLEAN_POLICY_CHANGED=false
|
||||
if [ -f "$LEOCLEAN_POLICY_PATCH" ] && [ -f "$LEOCLEAN_CONFIG" ]; then
|
||||
POLICY_RESULT=$(python3 "$LEOCLEAN_POLICY_PATCH" "$LEOCLEAN_CONFIG")
|
||||
log "Leoclean learning policy: $POLICY_RESULT"
|
||||
if echo "$POLICY_RESULT" | grep -q '"status": "updated"'; then
|
||||
LEOCLEAN_POLICY_CHANGED=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Safety net: ensure synced .sh files are executable after rsync.
|
||||
# Keep this bounded to deploy-owned paths: /opt/teleo-eval also contains
|
||||
|
|
@ -216,6 +238,9 @@ if [ "$OLD_SHA" != "none" ]; then
|
|||
| bash deploy/leoclean-gateway-restart-required.sh; then
|
||||
add_restart_if_unit_active leoclean-gateway
|
||||
fi
|
||||
if $LEOCLEAN_POLICY_CHANGED; then
|
||||
add_restart_if_unit_active leoclean-gateway
|
||||
fi
|
||||
else
|
||||
RESTART="teleo-pipeline teleo-diagnostics"
|
||||
add_restart_if_unit_active teleo-agent@leo
|
||||
|
|
@ -247,6 +272,14 @@ if [ -n "$RESTART" ]; then
|
|||
FAIL=1
|
||||
fi
|
||||
fi
|
||||
if [ "$svc" = "leoclean-gateway" ] && [ -f "$LEOCLEAN_POLICY_PATCH" ]; then
|
||||
if POLICY_CHECK=$(python3 "$LEOCLEAN_POLICY_PATCH" --check "$LEOCLEAN_CONFIG"); then
|
||||
log "leoclean-gateway learning policy: $POLICY_CHECK"
|
||||
else
|
||||
log "ERROR: leoclean-gateway learning policy check failed: $POLICY_CHECK"
|
||||
FAIL=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if echo "$RESTART" | grep -q "teleo-pipeline"; then
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ VPS_AGENT_STATE="/opt/teleo-eval/ops/agent-state"
|
|||
VPS_LEOCLEAN_BIN="/home/teleo/.hermes/profiles/leoclean/bin"
|
||||
VPS_LEOCLEAN_SKILLS="/home/teleo/.hermes/profiles/leoclean/skills"
|
||||
VPS_LEOCLEAN_PLUGINS="/home/teleo/.hermes/profiles/leoclean/plugins"
|
||||
VPS_LEOCLEAN_CONFIG="/home/teleo/.hermes/profiles/leoclean/config.yaml"
|
||||
VPS_HERMES_AGENT="/home/teleo/.hermes/hermes-agent"
|
||||
VPS_HERMES_PATCHES="/home/teleo/.hermes/teleo-runtime-patches"
|
||||
VPS_LEOCLEAN_POLICY_PATCH="$VPS_HERMES_PATCHES/enforce_leoclean_learning_policy.py"
|
||||
VPS_SYSTEMD="/etc/systemd/system"
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
|
|
@ -109,12 +111,18 @@ rsync "${RSYNC_OPTS[@]}" "$REPO_ROOT/hermes-agent/leoclean-plugins/vps/" "$VPS_H
|
|||
echo ""
|
||||
|
||||
echo "=== Hermes runtime patches ==="
|
||||
LEOCLEAN_POLICY_CHANGED=false
|
||||
if $DRY_RUN; then
|
||||
echo "Would sync hermes-agent/patches/ and verify the installed response-transform hook."
|
||||
echo "Would sync hermes-agent/patches/, verify the response-transform hook, and enforce the leoclean learning policy."
|
||||
else
|
||||
ssh "$VPS_HOST" "mkdir -p '$VPS_HERMES_PATCHES'"
|
||||
rsync "${RSYNC_OPTS[@]}" "$REPO_ROOT/hermes-agent/patches/" "$VPS_HOST:$VPS_HERMES_PATCHES/"
|
||||
ssh "$VPS_HOST" "python3 '$VPS_HERMES_PATCHES/apply_response_transform_hook.py' '$VPS_HERMES_AGENT/run_agent.py'"
|
||||
POLICY_RESULT=$(ssh "$VPS_HOST" "python3 '$VPS_LEOCLEAN_POLICY_PATCH' '$VPS_LEOCLEAN_CONFIG'")
|
||||
echo "$POLICY_RESULT"
|
||||
if echo "$POLICY_RESULT" | grep -q '"status": "updated"'; then
|
||||
LEOCLEAN_POLICY_CHANGED=true
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
|
|
@ -142,4 +150,9 @@ if $RESTART; then
|
|||
echo "=== Restarting services ==="
|
||||
ssh "$VPS_HOST" "sudo systemctl restart teleo-pipeline teleo-diagnostics; if systemctl is-active --quiet teleo-agent@leo.service; then sudo systemctl restart teleo-agent@leo; fi; if systemctl list-units --all --full teleo-agent@leo-wallet-test.service --no-legend | grep -q .; then sudo systemctl restart teleo-agent@leo-wallet-test; fi; if systemctl is-active --quiet leoclean-gateway.service; then sudo systemctl restart leoclean-gateway; fi"
|
||||
echo "Services restarted."
|
||||
elif $LEOCLEAN_POLICY_CHANGED; then
|
||||
echo ""
|
||||
echo "=== Restarting leoclean for learning-policy change ==="
|
||||
ssh "$VPS_HOST" "if systemctl is-active --quiet leoclean-gateway.service; then sudo systemctl restart leoclean-gateway; fi"
|
||||
echo "Leoclean gateway restarted."
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ terminal:
|
|||
memory:
|
||||
enabled: true
|
||||
search: sqlite_fts5
|
||||
nudge_interval: 0
|
||||
|
||||
skills:
|
||||
creation_nudge_interval: 0
|
||||
|
||||
tools:
|
||||
web_search: true
|
||||
|
|
|
|||
|
|
@ -587,9 +587,7 @@ def operational_contracts(
|
|||
|
||||
apply_receipt_question = any(
|
||||
term in lowered for term in ("canonical receipt", "postflight", "closure proof", "before/after")
|
||||
) or bool(
|
||||
re.search(r"\bapplied_at\b\s+(?:readback|proof|receipt|postflight)\b", lowered)
|
||||
)
|
||||
) or bool(re.search(r"\bapplied_at\b\s+(?:readback|proof|receipt|postflight)\b", lowered))
|
||||
if apply_receipt_question:
|
||||
contracts.append(
|
||||
{
|
||||
|
|
@ -685,8 +683,26 @@ def operational_contracts(
|
|||
)
|
||||
)
|
||||
)
|
||||
existing_claim_audit_question = any(
|
||||
term in lowered
|
||||
for term in (
|
||||
"inspect one exact claim",
|
||||
"exact claim body and its evidence",
|
||||
"one retrieved canonical claim",
|
||||
"linked source or evidence id",
|
||||
)
|
||||
)
|
||||
candidate_claim_composition_question = bool(
|
||||
any(
|
||||
term in lowered
|
||||
for term in ("candidate claim", "reviewable candidate", "candidate only", "revised exact claim body")
|
||||
)
|
||||
and any(term in lowered for term in ("draft", "propose", "revise", "rewrite", "narrow", "return"))
|
||||
and not existing_claim_audit_question
|
||||
)
|
||||
claim_evidence_challenge_question = bool(
|
||||
any(term in lowered for term in ("claim", "exact body", "claim body"))
|
||||
not candidate_claim_composition_question
|
||||
and any(term in lowered for term in ("claim", "exact body", "claim body"))
|
||||
and any(term in lowered for term in ("evidence", "source"))
|
||||
and any(
|
||||
term in lowered
|
||||
|
|
|
|||
114
hermes-agent/patches/enforce_leoclean_learning_policy.py
Normal file
114
hermes-agent/patches/enforce_leoclean_learning_policy.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Disable automatic chat-to-memory and chat-to-skill reinforcement for leoclean."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
POLICY_VERSION = "teleo-leoclean-learning-policy-v1"
|
||||
BACKUP_SUFFIX = ".before-teleo-learning-policy"
|
||||
MEMORY_NUDGE_INTERVAL = 0
|
||||
SKILL_CREATION_NUDGE_INTERVAL = 0
|
||||
|
||||
|
||||
def _load_config(path: Path) -> dict[str, Any]:
|
||||
if path.is_symlink():
|
||||
raise ValueError("refusing symlinked profile config")
|
||||
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(raw, dict):
|
||||
raise ValueError("profile config must be a YAML mapping")
|
||||
return raw
|
||||
|
||||
|
||||
def policy_projection(config: dict[str, Any]) -> dict[str, int | None]:
|
||||
memory = config.get("memory") if isinstance(config.get("memory"), dict) else {}
|
||||
skills = config.get("skills") if isinstance(config.get("skills"), dict) else {}
|
||||
return {
|
||||
"memory_nudge_interval": memory.get("nudge_interval"),
|
||||
"skill_creation_nudge_interval": skills.get("creation_nudge_interval"),
|
||||
}
|
||||
|
||||
|
||||
def enforce_policy(path: Path, *, check: bool = False) -> tuple[dict[str, Any], int]:
|
||||
config = _load_config(path)
|
||||
before = policy_projection(config)
|
||||
expected = {
|
||||
"memory_nudge_interval": MEMORY_NUDGE_INTERVAL,
|
||||
"skill_creation_nudge_interval": SKILL_CREATION_NUDGE_INTERVAL,
|
||||
}
|
||||
changed = before != expected
|
||||
|
||||
if check:
|
||||
return {
|
||||
"policy_version": POLICY_VERSION,
|
||||
"status": "change_required" if changed else "compliant",
|
||||
"target": str(path),
|
||||
"policy": before,
|
||||
}, 1 if changed else 0
|
||||
|
||||
backup_path = path.with_name(path.name + BACKUP_SUFFIX)
|
||||
if changed:
|
||||
memory = config.setdefault("memory", {})
|
||||
skills = config.setdefault("skills", {})
|
||||
if not isinstance(memory, dict) or not isinstance(skills, dict):
|
||||
raise ValueError("memory and skills config sections must be YAML mappings")
|
||||
memory["nudge_interval"] = MEMORY_NUDGE_INTERVAL
|
||||
skills["creation_nudge_interval"] = SKILL_CREATION_NUDGE_INTERVAL
|
||||
|
||||
if not backup_path.exists():
|
||||
shutil.copy2(path, backup_path)
|
||||
|
||||
mode = path.stat().st_mode & 0o777
|
||||
rendered = yaml.safe_dump(config, sort_keys=False, allow_unicode=False)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w", encoding="utf-8", dir=path.parent, prefix=f".{path.name}.", delete=False
|
||||
) as handle:
|
||||
handle.write(rendered)
|
||||
temp_path = Path(handle.name)
|
||||
try:
|
||||
temp_path.chmod(mode)
|
||||
os.replace(temp_path, path)
|
||||
finally:
|
||||
temp_path.unlink(missing_ok=True)
|
||||
|
||||
after = policy_projection(_load_config(path))
|
||||
if after != expected:
|
||||
raise ValueError("profile config did not retain the required learning policy")
|
||||
return {
|
||||
"policy_version": POLICY_VERSION,
|
||||
"status": "updated" if changed else "already_compliant",
|
||||
"target": str(path),
|
||||
"backup": str(backup_path) if changed else None,
|
||||
"policy": after,
|
||||
}, 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("target", type=Path)
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
result, code = enforce_policy(args.target, check=args.check)
|
||||
except (OSError, ValueError, yaml.YAMLError) as exc:
|
||||
result = {
|
||||
"policy_version": POLICY_VERSION,
|
||||
"status": "error",
|
||||
"target": str(args.target),
|
||||
"error": type(exc).__name__,
|
||||
}
|
||||
code = 2
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
return code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -235,7 +235,15 @@ def identity_config_projection(raw: dict[str, Any]) -> dict[str, Any]:
|
|||
section_fields = {
|
||||
"model": ("provider", "default", "smart_routing", "smart_routing_model", "max_tokens"),
|
||||
"agent": ("reasoning_effort",),
|
||||
"memory": ("enabled", "search"),
|
||||
"memory": (
|
||||
"enabled",
|
||||
"memory_enabled",
|
||||
"user_profile_enabled",
|
||||
"search",
|
||||
"nudge_interval",
|
||||
"flush_min_turns",
|
||||
),
|
||||
"skills": ("creation_nudge_interval",),
|
||||
"tools": ("allowed", "write_enabled"),
|
||||
"identity_runtime": ("network_send_enabled", "database_write_enabled", "allowed_tools"),
|
||||
}
|
||||
|
|
@ -328,6 +336,8 @@ def safe_model_config(config_path: Path) -> dict[str, Any]:
|
|||
"reasoning_effort": _nested(identity_config, "agent", "reasoning_effort"),
|
||||
"memory_enabled": _nested(identity_config, "memory", "enabled"),
|
||||
"memory_search": _nested(identity_config, "memory", "search"),
|
||||
"automatic_memory_review_interval": _nested(identity_config, "memory", "nudge_interval"),
|
||||
"automatic_skill_review_interval": _nested(identity_config, "skills", "creation_nudge_interval"),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -495,6 +505,8 @@ def identity_runtime_payload(manifest: dict[str, Any]) -> dict[str, Any]:
|
|||
"reasoning_effort",
|
||||
"memory_enabled",
|
||||
"memory_search",
|
||||
"automatic_memory_review_interval",
|
||||
"automatic_skill_review_interval",
|
||||
"base_model_weights",
|
||||
"actual_per_turn_model_must_be_recorded_by_the_call_receipt",
|
||||
)
|
||||
|
|
|
|||
76
tests/test_enforce_leoclean_learning_policy.py
Normal file
76
tests/test_enforce_leoclean_learning_policy.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PATCH_PATH = ROOT / "hermes-agent" / "patches" / "enforce_leoclean_learning_policy.py"
|
||||
|
||||
|
||||
def _load_module():
|
||||
spec = importlib.util.spec_from_file_location("enforce_leoclean_learning_policy", PATCH_PATH)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def test_learning_policy_disables_automatic_reviews_without_removing_explicit_tools(tmp_path: Path) -> None:
|
||||
module = _load_module()
|
||||
config_path = tmp_path / "config.yaml"
|
||||
original = """model:
|
||||
provider: openrouter
|
||||
gateway:
|
||||
telegram:
|
||||
bot_token: private-test-token
|
||||
memory:
|
||||
memory_enabled: true
|
||||
user_profile_enabled: true
|
||||
nudge_interval: 10
|
||||
skills:
|
||||
external_dirs: []
|
||||
creation_nudge_interval: 10
|
||||
"""
|
||||
config_path.write_text(original, encoding="utf-8")
|
||||
config_path.chmod(0o600)
|
||||
|
||||
result, code = module.enforce_policy(config_path)
|
||||
|
||||
assert code == 0
|
||||
assert result["status"] == "updated"
|
||||
assert result["policy"] == {
|
||||
"memory_nudge_interval": 0,
|
||||
"skill_creation_nudge_interval": 0,
|
||||
}
|
||||
assert "private-test-token" not in json.dumps(result)
|
||||
updated = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||
assert updated["memory"] == {
|
||||
"memory_enabled": True,
|
||||
"user_profile_enabled": True,
|
||||
"nudge_interval": 0,
|
||||
}
|
||||
assert updated["skills"] == {"external_dirs": [], "creation_nudge_interval": 0}
|
||||
assert updated["gateway"]["telegram"]["bot_token"] == "private-test-token"
|
||||
assert config_path.stat().st_mode & 0o777 == 0o600
|
||||
assert config_path.with_name(config_path.name + module.BACKUP_SUFFIX).read_text(encoding="utf-8") == original
|
||||
|
||||
check_result, check_code = module.enforce_policy(config_path, check=True)
|
||||
assert check_code == 0
|
||||
assert check_result["status"] == "compliant"
|
||||
|
||||
|
||||
def test_learning_policy_check_reports_drift_without_writing(tmp_path: Path) -> None:
|
||||
module = _load_module()
|
||||
config_path = tmp_path / "config.yaml"
|
||||
original = "memory:\n memory_enabled: true\n"
|
||||
config_path.write_text(original, encoding="utf-8")
|
||||
|
||||
result, code = module.enforce_policy(config_path, check=True)
|
||||
|
||||
assert code == 1
|
||||
assert result["status"] == "change_required"
|
||||
assert config_path.read_text(encoding="utf-8") == original
|
||||
assert not config_path.with_name(config_path.name + module.BACKUP_SUFFIX).exists()
|
||||
|
|
@ -1669,6 +1669,39 @@ def test_vps_bridge_binds_claim_challenge_to_exact_retrieved_rows() -> None:
|
|||
assert "no database write was made" in unbound_response
|
||||
|
||||
|
||||
def test_vps_bridge_does_not_replace_candidate_composition_with_retrieved_claim() -> None:
|
||||
module = _load_module(BRIDGE_DIR / "kb_tool.py")
|
||||
candidate_queries = [
|
||||
(
|
||||
"Draft the strongest reviewable candidate claim this discussion supports. Give the exact claim body, "
|
||||
"initial confidence, falsifier, minimum source and evidence requirements, and relations to existing "
|
||||
"canonical rows. Do not write or stage it yet."
|
||||
),
|
||||
(
|
||||
"That candidate still smuggles in unsupported assumptions. Narrow the claim. Return the revised exact "
|
||||
"claim body, revised initial confidence, a concrete falsifier, minimum source and evidence requirements, "
|
||||
"and relations to existing rows. Do not write or stage anything."
|
||||
),
|
||||
]
|
||||
|
||||
for query in candidate_queries:
|
||||
contracts = module.operational_contracts(query)
|
||||
contract_ids = {contract["id"] for contract in contracts}
|
||||
|
||||
assert "claim_evidence_challenge" not in contract_ids
|
||||
assert module.compile_operational_response(contracts) is None
|
||||
|
||||
|
||||
def test_vps_bridge_keeps_exact_row_guard_for_combined_audit_and_candidate_request() -> None:
|
||||
module = _load_module(BRIDGE_DIR / "kb_tool.py")
|
||||
contracts = module.operational_contracts(
|
||||
"Inspect one exact claim body and its evidence, challenge its unsupported assumption, and then draft a "
|
||||
"narrower candidate claim."
|
||||
)
|
||||
|
||||
assert "claim_evidence_challenge" in {contract["id"] for contract in contracts}
|
||||
|
||||
|
||||
def test_vps_prepare_source_retrieves_canonical_candidates_before_model_extraction(
|
||||
tmp_path: Path, monkeypatch, capsys
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ def test_behavior_manifest_is_deterministic_and_never_emits_config_secrets(tmp_p
|
|||
memory:
|
||||
enabled: true
|
||||
search: sqlite_fts5
|
||||
nudge_interval: 0
|
||||
skills:
|
||||
creation_nudge_interval: 0
|
||||
gateway:
|
||||
telegram:
|
||||
bot_token: super-secret-token
|
||||
|
|
@ -52,6 +55,8 @@ gateway:
|
|||
|
||||
assert first["behavior_sha256"] == second["behavior_sha256"]
|
||||
assert first["model_runtime"]["default_model"] == "anthropic/claude-sonnet-4-6"
|
||||
assert first["model_runtime"]["automatic_memory_review_interval"] == 0
|
||||
assert first["model_runtime"]["automatic_skill_review_interval"] == 0
|
||||
assert first["teleo_infrastructure_runtime"]["source_tree"]["file_count"] == 2
|
||||
assert first["components"]["persistent_memory"]["content"]["file_count"] == 1
|
||||
encoded = json.dumps(first)
|
||||
|
|
|
|||
|
|
@ -128,6 +128,10 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_for_runtime_cha
|
|||
assert "hermes-agent/leoclean-skills/vps/" in auto_deploy
|
||||
assert "hermes-agent/leoclean-plugins/vps/" in auto_deploy
|
||||
assert 'HERMES_PATCH_DIR="/home/teleo/.hermes/teleo-runtime-patches"' in auto_deploy
|
||||
assert 'LEOCLEAN_CONFIG="/home/teleo/.hermes/profiles/leoclean/config.yaml"' in auto_deploy
|
||||
assert 'LEOCLEAN_POLICY_PATCH="$HERMES_PATCH_DIR/enforce_leoclean_learning_policy.py"' in auto_deploy
|
||||
assert 'python3 "$LEOCLEAN_POLICY_PATCH" "$LEOCLEAN_CONFIG"' in auto_deploy
|
||||
assert "Leoclean learning-policy drift repaired" in auto_deploy
|
||||
assert '"status": "installed_now"' in auto_deploy
|
||||
assert "Hermes response-transform drift repaired" in auto_deploy
|
||||
assert "deploy/leoclean-gateway-restart-required.sh" in auto_deploy
|
||||
|
|
@ -145,6 +149,8 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_for_runtime_cha
|
|||
assert "hermes-agent/leoclean-skills/vps/" in manual_deploy
|
||||
assert "hermes-agent/leoclean-plugins/vps/" in manual_deploy
|
||||
assert 'VPS_HERMES_PATCHES="/home/teleo/.hermes/teleo-runtime-patches"' in manual_deploy
|
||||
assert 'VPS_LEOCLEAN_POLICY_PATCH="$VPS_HERMES_PATCHES/enforce_leoclean_learning_policy.py"' in manual_deploy
|
||||
assert "Leoclean gateway restarted." in manual_deploy
|
||||
assert "systemctl is-active --quiet leoclean-gateway.service" in manual_deploy
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue