Keep Leo candidate reasoning grounded and stop automatic chat reinforcement (#180)
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:
twentyOne2x 2026-07-17 23:07:57 +02:00 committed by GitHub
parent 77e1336c0f
commit 415669c2e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 326 additions and 14 deletions

View file

@ -19,8 +19,10 @@ AGENT_STATE_DIR="/opt/teleo-eval/ops/agent-state"
LEOCLEAN_BIN_DIR="/home/teleo/.hermes/profiles/leoclean/bin" LEOCLEAN_BIN_DIR="/home/teleo/.hermes/profiles/leoclean/bin"
LEOCLEAN_SKILLS_DIR="/home/teleo/.hermes/profiles/leoclean/skills" LEOCLEAN_SKILLS_DIR="/home/teleo/.hermes/profiles/leoclean/skills"
LEOCLEAN_PLUGINS_DIR="/home/teleo/.hermes/profiles/leoclean/plugins" 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_AGENT_DIR="/home/teleo/.hermes/hermes-agent"
HERMES_PATCH_DIR="/home/teleo/.hermes/teleo-runtime-patches" 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" SYSTEMD_DIR="/etc/systemd/system"
STAMP_FILE="/opt/teleo-eval/.last-deploy-sha" STAMP_FILE="/opt/teleo-eval/.last-deploy-sha"
LOG_TAG="auto-deploy" 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") OLD_SHA=$(cat "$STAMP_FILE" 2>/dev/null || echo "none")
if [ "$NEW_SHA" = "$OLD_SHA" ]; then if [ "$NEW_SHA" = "$OLD_SHA" ]; then
LEOCLEAN_DRIFT_REPAIRED=false
# Hermes can be upgraded independently of this repository. Repair an # Hermes can be upgraded independently of this repository. Repair an
# overwritten response-transform hook even when no infrastructure commit # overwritten response-transform hook even when no infrastructure commit
# changed, and restart only when the patch was actually reapplied. # changed, and restart only when the patch was actually reapplied.
@ -65,16 +68,27 @@ if [ "$NEW_SHA" = "$OLD_SHA" ]; then
fi fi
if echo "$PATCH_RESULT" | grep -q '"status": "installed_now"'; then if echo "$PATCH_RESULT" | grep -q '"status": "installed_now"'; then
log "Hermes response-transform drift repaired: $PATCH_RESULT" log "Hermes response-transform drift repaired: $PATCH_RESULT"
if systemctl is-active --quiet leoclean-gateway.service; then 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 sudo systemctl restart leoclean-gateway
sleep 10 sleep 10
if ! systemctl is-active --quiet leoclean-gateway.service; then if ! systemctl is-active --quiet leoclean-gateway.service; then
log "ERROR: leoclean-gateway failed after response-transform drift repair" log "ERROR: leoclean-gateway failed after runtime-policy drift repair"
exit 1 exit 1
fi fi
fi fi
fi
fi
exit 0 exit 0
fi fi
@ -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") PATCH_RESULT=$(python3 "$HERMES_PATCH_DIR/apply_response_transform_hook.py" "$HERMES_AGENT_DIR/run_agent.py")
log "Hermes response transform: $PATCH_RESULT" log "Hermes response transform: $PATCH_RESULT"
fi 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. # Safety net: ensure synced .sh files are executable after rsync.
# Keep this bounded to deploy-owned paths: /opt/teleo-eval also contains # 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 | bash deploy/leoclean-gateway-restart-required.sh; then
add_restart_if_unit_active leoclean-gateway add_restart_if_unit_active leoclean-gateway
fi fi
if $LEOCLEAN_POLICY_CHANGED; then
add_restart_if_unit_active leoclean-gateway
fi
else else
RESTART="teleo-pipeline teleo-diagnostics" RESTART="teleo-pipeline teleo-diagnostics"
add_restart_if_unit_active teleo-agent@leo add_restart_if_unit_active teleo-agent@leo
@ -247,6 +272,14 @@ if [ -n "$RESTART" ]; then
FAIL=1 FAIL=1
fi fi
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 done
if echo "$RESTART" | grep -q "teleo-pipeline"; then if echo "$RESTART" | grep -q "teleo-pipeline"; then

View file

@ -13,8 +13,10 @@ VPS_AGENT_STATE="/opt/teleo-eval/ops/agent-state"
VPS_LEOCLEAN_BIN="/home/teleo/.hermes/profiles/leoclean/bin" VPS_LEOCLEAN_BIN="/home/teleo/.hermes/profiles/leoclean/bin"
VPS_LEOCLEAN_SKILLS="/home/teleo/.hermes/profiles/leoclean/skills" VPS_LEOCLEAN_SKILLS="/home/teleo/.hermes/profiles/leoclean/skills"
VPS_LEOCLEAN_PLUGINS="/home/teleo/.hermes/profiles/leoclean/plugins" 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_AGENT="/home/teleo/.hermes/hermes-agent"
VPS_HERMES_PATCHES="/home/teleo/.hermes/teleo-runtime-patches" 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" VPS_SYSTEMD="/etc/systemd/system"
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
@ -109,12 +111,18 @@ rsync "${RSYNC_OPTS[@]}" "$REPO_ROOT/hermes-agent/leoclean-plugins/vps/" "$VPS_H
echo "" echo ""
echo "=== Hermes runtime patches ===" echo "=== Hermes runtime patches ==="
LEOCLEAN_POLICY_CHANGED=false
if $DRY_RUN; then 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 else
ssh "$VPS_HOST" "mkdir -p '$VPS_HERMES_PATCHES'" ssh "$VPS_HOST" "mkdir -p '$VPS_HERMES_PATCHES'"
rsync "${RSYNC_OPTS[@]}" "$REPO_ROOT/hermes-agent/patches/" "$VPS_HOST:$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'" 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 fi
echo "" echo ""
@ -142,4 +150,9 @@ if $RESTART; then
echo "=== Restarting services ===" 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" 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." 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 fi

View file

@ -59,6 +59,10 @@ terminal:
memory: memory:
enabled: true enabled: true
search: sqlite_fts5 search: sqlite_fts5
nudge_interval: 0
skills:
creation_nudge_interval: 0
tools: tools:
web_search: true web_search: true

View file

@ -587,9 +587,7 @@ def operational_contracts(
apply_receipt_question = any( apply_receipt_question = any(
term in lowered for term in ("canonical receipt", "postflight", "closure proof", "before/after") term in lowered for term in ("canonical receipt", "postflight", "closure proof", "before/after")
) or bool( ) or bool(re.search(r"\bapplied_at\b\s+(?:readback|proof|receipt|postflight)\b", lowered))
re.search(r"\bapplied_at\b\s+(?:readback|proof|receipt|postflight)\b", lowered)
)
if apply_receipt_question: if apply_receipt_question:
contracts.append( 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( 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 for term in ("evidence", "source"))
and any( and any(
term in lowered term in lowered

View 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())

View file

@ -235,7 +235,15 @@ def identity_config_projection(raw: dict[str, Any]) -> dict[str, Any]:
section_fields = { section_fields = {
"model": ("provider", "default", "smart_routing", "smart_routing_model", "max_tokens"), "model": ("provider", "default", "smart_routing", "smart_routing_model", "max_tokens"),
"agent": ("reasoning_effort",), "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"), "tools": ("allowed", "write_enabled"),
"identity_runtime": ("network_send_enabled", "database_write_enabled", "allowed_tools"), "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"), "reasoning_effort": _nested(identity_config, "agent", "reasoning_effort"),
"memory_enabled": _nested(identity_config, "memory", "enabled"), "memory_enabled": _nested(identity_config, "memory", "enabled"),
"memory_search": _nested(identity_config, "memory", "search"), "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", "reasoning_effort",
"memory_enabled", "memory_enabled",
"memory_search", "memory_search",
"automatic_memory_review_interval",
"automatic_skill_review_interval",
"base_model_weights", "base_model_weights",
"actual_per_turn_model_must_be_recorded_by_the_call_receipt", "actual_per_turn_model_must_be_recorded_by_the_call_receipt",
) )

View 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()

View file

@ -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 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( def test_vps_prepare_source_retrieves_canonical_candidates_before_model_extraction(
tmp_path: Path, monkeypatch, capsys tmp_path: Path, monkeypatch, capsys
) -> None: ) -> None:

View file

@ -30,6 +30,9 @@ def test_behavior_manifest_is_deterministic_and_never_emits_config_secrets(tmp_p
memory: memory:
enabled: true enabled: true
search: sqlite_fts5 search: sqlite_fts5
nudge_interval: 0
skills:
creation_nudge_interval: 0
gateway: gateway:
telegram: telegram:
bot_token: super-secret-token bot_token: super-secret-token
@ -52,6 +55,8 @@ gateway:
assert first["behavior_sha256"] == second["behavior_sha256"] assert first["behavior_sha256"] == second["behavior_sha256"]
assert first["model_runtime"]["default_model"] == "anthropic/claude-sonnet-4-6" 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["teleo_infrastructure_runtime"]["source_tree"]["file_count"] == 2
assert first["components"]["persistent_memory"]["content"]["file_count"] == 1 assert first["components"]["persistent_memory"]["content"]["file_count"] == 1
encoded = json.dumps(first) encoded = json.dumps(first)

View file

@ -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-skills/vps/" in auto_deploy
assert "hermes-agent/leoclean-plugins/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 '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 '"status": "installed_now"' in auto_deploy
assert "Hermes response-transform drift repaired" in auto_deploy assert "Hermes response-transform drift repaired" in auto_deploy
assert "deploy/leoclean-gateway-restart-required.sh" 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-skills/vps/" in manual_deploy
assert "hermes-agent/leoclean-plugins/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_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 assert "systemctl is-active --quiet leoclean-gateway.service" in manual_deploy