Enforce DB-composed Leo responses before delivery (#116)
Some checks are pending
CI / lint-and-test (push) Waiting to run
Some checks are pending
CI / lint-and-test (push) Waiting to run
This commit is contained in:
parent
54b0829b98
commit
2264ad1f2b
19 changed files with 997 additions and 28 deletions
|
|
@ -19,6 +19,8 @@ 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"
|
||||||
|
HERMES_AGENT_DIR="/home/teleo/.hermes/hermes-agent"
|
||||||
|
HERMES_PATCH_DIR="/home/teleo/.hermes/teleo-runtime-patches"
|
||||||
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"
|
||||||
|
|
@ -53,6 +55,26 @@ 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
|
||||||
|
# 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.
|
||||||
|
if [ -f "$HERMES_PATCH_DIR/apply_response_transform_hook.py" ] && [ -f "$HERMES_AGENT_DIR/run_agent.py" ]; then
|
||||||
|
if ! PATCH_RESULT=$(python3 "$HERMES_PATCH_DIR/apply_response_transform_hook.py" "$HERMES_AGENT_DIR/run_agent.py"); then
|
||||||
|
log "ERROR: Hermes response-transform drift could not be repaired"
|
||||||
|
exit 1
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -81,7 +103,7 @@ fi
|
||||||
|
|
||||||
# Syntax check all Python files before copying
|
# Syntax check all Python files before copying
|
||||||
ERRORS=0
|
ERRORS=0
|
||||||
for f in lib/*.py *.py diagnostics/*.py telegram/*.py tests/*.py hermes-agent/leoclean-plugins/vps/*/*.py; do
|
for f in lib/*.py *.py diagnostics/*.py telegram/*.py tests/*.py hermes-agent/leoclean-plugins/vps/*/*.py hermes-agent/patches/*.py; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>&1; then
|
if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>&1; then
|
||||||
log "SYNTAX ERROR: $f"
|
log "SYNTAX ERROR: $f"
|
||||||
|
|
@ -120,12 +142,21 @@ if [ -d hermes-agent/leoclean-plugins/vps ]; then
|
||||||
mkdir -p "$LEOCLEAN_PLUGINS_DIR"
|
mkdir -p "$LEOCLEAN_PLUGINS_DIR"
|
||||||
rsync "${RSYNC_OPTS[@]}" hermes-agent/leoclean-plugins/vps/ "$LEOCLEAN_PLUGINS_DIR/"
|
rsync "${RSYNC_OPTS[@]}" hermes-agent/leoclean-plugins/vps/ "$LEOCLEAN_PLUGINS_DIR/"
|
||||||
fi
|
fi
|
||||||
|
if [ -d hermes-agent/patches ]; then
|
||||||
|
mkdir -p "$HERMES_PATCH_DIR"
|
||||||
|
rsync "${RSYNC_OPTS[@]}" hermes-agent/patches/ "$HERMES_PATCH_DIR/"
|
||||||
|
fi
|
||||||
[ -f research/research-session.sh ] && rsync "${RSYNC_OPTS[@]}" research/research-session.sh /opt/teleo-eval/research-session.sh
|
[ -f research/research-session.sh ] && rsync "${RSYNC_OPTS[@]}" research/research-session.sh /opt/teleo-eval/research-session.sh
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
# 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
|
||||||
# backups and generated state that may be unreadable by the deploy user.
|
# backups and generated state that may be unreadable by the deploy user.
|
||||||
for dir in "$PIPELINE_DIR" "$TELEGRAM_DIR" "$DIAGNOSTICS_DIR" "$AGENT_STATE_DIR" "$LEOCLEAN_BIN_DIR" "$LEOCLEAN_SKILLS_DIR" "$LEOCLEAN_PLUGINS_DIR"; do
|
for dir in "$PIPELINE_DIR" "$TELEGRAM_DIR" "$DIAGNOSTICS_DIR" "$AGENT_STATE_DIR" "$LEOCLEAN_BIN_DIR" "$LEOCLEAN_SKILLS_DIR" "$LEOCLEAN_PLUGINS_DIR" "$HERMES_PATCH_DIR"; do
|
||||||
[ -d "$dir" ] || continue
|
[ -d "$dir" ] || continue
|
||||||
find "$dir" -maxdepth 3 -name '*.sh' -not -perm -u+x -exec chmod +x {} +
|
find "$dir" -maxdepth 3 -name '*.sh' -not -perm -u+x -exec chmod +x {} +
|
||||||
done
|
done
|
||||||
|
|
@ -181,7 +212,7 @@ if [ "$OLD_SHA" != "none" ]; then
|
||||||
if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- diagnostics/ 2>/dev/null | grep -q '\.py$'; then
|
if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- diagnostics/ 2>/dev/null | grep -q '\.py$'; then
|
||||||
add_restart teleo-diagnostics
|
add_restart teleo-diagnostics
|
||||||
fi
|
fi
|
||||||
if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- hermes-agent/leoclean-bin/ hermes-agent/leoclean-skills/vps/ hermes-agent/leoclean-plugins/vps/ 2>/dev/null \
|
if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- hermes-agent/leoclean-bin/ hermes-agent/leoclean-skills/vps/ hermes-agent/leoclean-plugins/vps/ hermes-agent/patches/ 2>/dev/null \
|
||||||
| 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
|
||||||
|
|
@ -208,6 +239,14 @@ if [ -n "$RESTART" ]; then
|
||||||
journalctl -u "$svc" -n 5 --no-pager 2>/dev/null || true
|
journalctl -u "$svc" -n 5 --no-pager 2>/dev/null || true
|
||||||
FAIL=1
|
FAIL=1
|
||||||
fi
|
fi
|
||||||
|
if [ "$svc" = "leoclean-gateway" ] && [ -f "$HERMES_PATCH_DIR/apply_response_transform_hook.py" ]; then
|
||||||
|
if PATCH_CHECK=$(python3 "$HERMES_PATCH_DIR/apply_response_transform_hook.py" --check "$HERMES_AGENT_DIR/run_agent.py"); then
|
||||||
|
log "leoclean-gateway response transform: $PATCH_CHECK"
|
||||||
|
else
|
||||||
|
log "ERROR: leoclean-gateway response transform check failed: $PATCH_CHECK"
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if echo "$RESTART" | grep -q "teleo-pipeline"; then
|
if echo "$RESTART" | grep -q "teleo-pipeline"; then
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ 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_HERMES_AGENT="/home/teleo/.hermes/hermes-agent"
|
||||||
|
VPS_HERMES_PATCHES="/home/teleo/.hermes/teleo-runtime-patches"
|
||||||
VPS_SYSTEMD="/etc/systemd/system"
|
VPS_SYSTEMD="/etc/systemd/system"
|
||||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
|
@ -46,7 +48,7 @@ echo ""
|
||||||
# Syntax check all Python files before deploying
|
# Syntax check all Python files before deploying
|
||||||
echo "=== Pre-deploy syntax check ==="
|
echo "=== Pre-deploy syntax check ==="
|
||||||
ERRORS=0
|
ERRORS=0
|
||||||
for f in "$REPO_ROOT/lib/"*.py "$REPO_ROOT/"*.py "$REPO_ROOT/diagnostics/"*.py "$REPO_ROOT/telegram/"*.py "$REPO_ROOT/hermes-agent/leoclean-plugins/vps/"*/*.py; do
|
for f in "$REPO_ROOT/lib/"*.py "$REPO_ROOT/"*.py "$REPO_ROOT/diagnostics/"*.py "$REPO_ROOT/telegram/"*.py "$REPO_ROOT/hermes-agent/leoclean-plugins/vps/"*/*.py "$REPO_ROOT/hermes-agent/patches/"*.py; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>/dev/null; then
|
if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>/dev/null; then
|
||||||
echo "SYNTAX ERROR: $f"
|
echo "SYNTAX ERROR: $f"
|
||||||
|
|
@ -106,6 +108,16 @@ echo "=== Leoclean plugins ==="
|
||||||
rsync "${RSYNC_OPTS[@]}" "$REPO_ROOT/hermes-agent/leoclean-plugins/vps/" "$VPS_HOST:$VPS_LEOCLEAN_PLUGINS/"
|
rsync "${RSYNC_OPTS[@]}" "$REPO_ROOT/hermes-agent/leoclean-plugins/vps/" "$VPS_HOST:$VPS_LEOCLEAN_PLUGINS/"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Hermes runtime patches ==="
|
||||||
|
if $DRY_RUN; then
|
||||||
|
echo "Would sync hermes-agent/patches/ and verify the installed response-transform hook."
|
||||||
|
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'"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
echo "=== Research session ==="
|
echo "=== Research session ==="
|
||||||
rsync "${RSYNC_OPTS[@]}" "$REPO_ROOT/research/research-session.sh" "$VPS_HOST:/opt/teleo-eval/research-session.sh"
|
rsync "${RSYNC_OPTS[@]}" "$REPO_ROOT/research/research-session.sh" "$VPS_HOST:/opt/teleo-eval/research-session.sh"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ while IFS= read -r path || [ -n "$path" ]; do
|
||||||
hermes-agent/leoclean-plugins/vps/*)
|
hermes-agent/leoclean-plugins/vps/*)
|
||||||
restart_required=0
|
restart_required=0
|
||||||
;;
|
;;
|
||||||
|
hermes-agent/patches/*)
|
||||||
|
restart_required=0
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"database_context_trace_ok": true,
|
||||||
|
"db_counts_changed": false,
|
||||||
|
"generated_at_utc": "2026-07-13T11:01:33.666438+00:00",
|
||||||
|
"mode": "working_leo_oos06_post_budget_score",
|
||||||
|
"posted_to_telegram": false,
|
||||||
|
"production_db_apply_ran": false,
|
||||||
|
"safe_pass": false,
|
||||||
|
"score": {
|
||||||
|
"behavioral_rule_schema_issues": [],
|
||||||
|
"broad_semantic_issues": [],
|
||||||
|
"concepts": {
|
||||||
|
"behavioral_rule_storage": true,
|
||||||
|
"heterogeneous_types": true,
|
||||||
|
"receipt": true,
|
||||||
|
"reviewed_policy_apply": false,
|
||||||
|
"staged_review_apply": true
|
||||||
|
},
|
||||||
|
"current_schema_overclaims": [
|
||||||
|
"reasoning_tools_unshipped_fields"
|
||||||
|
],
|
||||||
|
"custom_signals": {},
|
||||||
|
"dimension": "database_composition_judgment",
|
||||||
|
"invalid_count_invariant_detected": false,
|
||||||
|
"legacy_pass": false,
|
||||||
|
"legacy_signals": {
|
||||||
|
"canonical_db": true,
|
||||||
|
"caveat_retention": false,
|
||||||
|
"next_action": false,
|
||||||
|
"no_overclaim": true,
|
||||||
|
"staging_or_review": false
|
||||||
|
},
|
||||||
|
"max_response_words": 220,
|
||||||
|
"overclaim_detected": false,
|
||||||
|
"pass": false,
|
||||||
|
"prompt_id": "OOS-06",
|
||||||
|
"proposal_readiness_issues": [],
|
||||||
|
"response_issue_detected": false,
|
||||||
|
"response_too_long": false,
|
||||||
|
"source_evidence_semantic_issues": [],
|
||||||
|
"source_intake_issues": [],
|
||||||
|
"word_count": 210
|
||||||
|
},
|
||||||
|
"service_unchanged": true,
|
||||||
|
"source_results_json": "docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-oos06-post-budget-20260713.json",
|
||||||
|
"temp_profile_removed": true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
{
|
||||||
|
"before_service": {
|
||||||
|
"ActiveState": "active",
|
||||||
|
"ExecMainStartTimestamp": "Mon 2026-07-13 11:00:09 UTC",
|
||||||
|
"MainPID": "3643400",
|
||||||
|
"NRestarts": "0",
|
||||||
|
"SubState": "running"
|
||||||
|
},
|
||||||
|
"changed_live_profile": false,
|
||||||
|
"database_context_all_ok": true,
|
||||||
|
"database_context_injection_count": 1,
|
||||||
|
"database_context_trace": [
|
||||||
|
{
|
||||||
|
"contract_ids": [
|
||||||
|
"reply_budget",
|
||||||
|
"mixed_packet_composition"
|
||||||
|
],
|
||||||
|
"contract_sha256": "e93a0c035e7d4084a197d71f11bf293c695024b05944cae01d58a57eb0d595bf",
|
||||||
|
"generated_at_utc": "2026-07-13T11:01:17.101126+00:00",
|
||||||
|
"injected": true,
|
||||||
|
"query_sha256": "13c255fda3a35cd913ba92960bcda1b4dbcf55a38f3301bdc2a74a8eb040df23",
|
||||||
|
"source": "kb_tool.py --local context",
|
||||||
|
"status": "ok"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"db_counts_after": {
|
||||||
|
"kb_stage.kb_proposals": 26,
|
||||||
|
"public.claim_edges": 4916,
|
||||||
|
"public.claim_evidence": 4670,
|
||||||
|
"public.claims": 1837,
|
||||||
|
"public.sources": 4145
|
||||||
|
},
|
||||||
|
"db_counts_before": {
|
||||||
|
"kb_stage.kb_proposals": 26,
|
||||||
|
"public.claim_edges": 4916,
|
||||||
|
"public.claim_evidence": 4670,
|
||||||
|
"public.claims": 1837,
|
||||||
|
"public.sources": 4145
|
||||||
|
},
|
||||||
|
"db_counts_changed": false,
|
||||||
|
"generated_at_utc": "2026-07-13T11:01:14.011205+00:00",
|
||||||
|
"handler": {
|
||||||
|
"authorized": true,
|
||||||
|
"session_key": "agent:main:telegram:group:-5146042086:9070919",
|
||||||
|
"temp_profile": "/tmp/leo-direct-claim-handler-nqtplnkt/profile"
|
||||||
|
},
|
||||||
|
"harness_notes": [
|
||||||
|
"Unchanged OOS-06 prompt after live table semantics and compact reply contract.",
|
||||||
|
"The harness uses a temporary copy of the leoclean profile and does not post to Telegram.",
|
||||||
|
"This proves handler-level live VPS GatewayRunner behavior, not human-visible Telegram delivery."
|
||||||
|
],
|
||||||
|
"mode": "live_vps_gatewayrunner_temp_profile_oos06_post_budget",
|
||||||
|
"mutates_kb_by_harness": false,
|
||||||
|
"pass_runtime": true,
|
||||||
|
"posted_to_telegram": false,
|
||||||
|
"remote_report_path": "/tmp/leo-oos06-post-budget-294b5bc74e32.json",
|
||||||
|
"remote_returncode": 0,
|
||||||
|
"remote_run_id": "294b5bc74e32",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"claim_ceiling": "Live VPS GatewayRunner reply from temp leoclean profile; no Telegram post; no production DB apply authorized.",
|
||||||
|
"database_context_trace": [
|
||||||
|
{
|
||||||
|
"contract_ids": [
|
||||||
|
"reply_budget",
|
||||||
|
"mixed_packet_composition"
|
||||||
|
],
|
||||||
|
"contract_sha256": "e93a0c035e7d4084a197d71f11bf293c695024b05944cae01d58a57eb0d595bf",
|
||||||
|
"generated_at_utc": "2026-07-13T11:01:17.101126+00:00",
|
||||||
|
"injected": true,
|
||||||
|
"query_sha256": "13c255fda3a35cd913ba92960bcda1b4dbcf55a38f3301bdc2a74a8eb040df23",
|
||||||
|
"source": "kb_tool.py --local context",
|
||||||
|
"status": "ok"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dimension": "database_composition_judgment",
|
||||||
|
"ended_at_utc": "2026-07-13T11:01:30.146238+00:00",
|
||||||
|
"evidence_tier": "live_vps_gatewayrunner_temp_profile",
|
||||||
|
"mutates_kb": false,
|
||||||
|
"ok": true,
|
||||||
|
"prompt": "Suppose a new research packet contains factual observations, a strategic framework, a disputed interpretation, a governance rule, and a correction to an old belief. How should Leo compose the database so it can reason over the packet later without flattening it all into claims? Give the review and apply sequence too, but do not write anything now.",
|
||||||
|
"prompt_id": "OOS-06",
|
||||||
|
"reply": "Mapping across the five elements:\n\n- Factual observations \u2192 claims (type: empirical, sources + evidence rows attached). Strategic framework \u2192 reasoning_tools (name, description, category; no scope field; no claim-edge pointing at it). Disputed interpretation \u2192 claims (type: empirical or structural, lower confidence, dispute surfaced via claim_edges between the competing claim rows). Governance rule \u2192 behavioral_rules if it is an operating rule (rule, rationale, category, rank, agent_id); only promote to governance_gates if it carries explicit evaluation criteria, an evidence bar, and a pass_condition. Correction to an old belief \u2192 beliefs row update (status: revised, new statement + confidence + falsifier; the old row's status moves to revised, not deleted).\n\n- Review/apply sequence: stage the full packet as a typed pending_review proposal without touching canonical tables; reviewer checks type assignments, evidence attachments, edge endpoints (claims only, never reasoning_tool or belief IDs), and exact row values; apply only the supported collections (claims, sources, evidence, edges, reasoning_tools, belief updates) after explicit authorization; keep behavioral_rules and governance_gates staged \u2014 approve_claim does not cover them, separate capability required; record applied_at on the proposal receipt plus created/updated row IDs and run a postflight readback.\n\n- Hard boundary: behavioral_rules and governance_gates cannot be applied in the same pass as claims even if the authorization is broad \u2014 they remain staged until a reviewed apply path for those tables exists.",
|
||||||
|
"started_at_utc": "2026-07-13T11:01:14.476172+00:00",
|
||||||
|
"turn": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"service_before_after": {
|
||||||
|
"after": {
|
||||||
|
"ActiveState": "active",
|
||||||
|
"ExecMainStartTimestamp": "Mon 2026-07-13 11:00:09 UTC",
|
||||||
|
"MainPID": "3643400",
|
||||||
|
"NRestarts": "0",
|
||||||
|
"SubState": "running"
|
||||||
|
},
|
||||||
|
"before": {
|
||||||
|
"ActiveState": "active",
|
||||||
|
"ExecMainStartTimestamp": "Mon 2026-07-13 11:00:09 UTC",
|
||||||
|
"MainPID": "3643400",
|
||||||
|
"NRestarts": "0",
|
||||||
|
"SubState": "running"
|
||||||
|
},
|
||||||
|
"unchanged_from_preexisting_live_readback": true
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"chat_id": "-5146042086",
|
||||||
|
"chat_name": "Leo",
|
||||||
|
"chat_type": "group",
|
||||||
|
"platform": "telegram",
|
||||||
|
"user_id": "9070919",
|
||||||
|
"user_name": "codex handler direct claim"
|
||||||
|
},
|
||||||
|
"source_report_path": "docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-oos06-post-budget-20260713.json",
|
||||||
|
"temp_profile_removed": true
|
||||||
|
}
|
||||||
|
|
@ -493,6 +493,62 @@ def operational_contracts(
|
||||||
return contracts
|
return contracts
|
||||||
|
|
||||||
|
|
||||||
|
def human_join(values: list[str]) -> str:
|
||||||
|
if not values:
|
||||||
|
return ""
|
||||||
|
if len(values) == 1:
|
||||||
|
return values[0]
|
||||||
|
if len(values) == 2:
|
||||||
|
return f"{values[0]} and {values[1]}"
|
||||||
|
return f"{', '.join(values[:-1])}, and {values[-1]}"
|
||||||
|
|
||||||
|
|
||||||
|
def compile_operational_response(contracts: list[dict[str, Any]]) -> str | None:
|
||||||
|
"""Compile a safe response when a model draft violates live DB contracts."""
|
||||||
|
|
||||||
|
by_id = {str(contract.get("id") or ""): contract for contract in contracts}
|
||||||
|
mixed = by_id.get("mixed_packet_composition")
|
||||||
|
if mixed:
|
||||||
|
mapping = mixed.get("map") or {}
|
||||||
|
supported = human_join([str(item) for item in mixed.get("approve_claim_supported") or []])
|
||||||
|
staged = human_join([str(item) for item in mixed.get("keep_staged_until_separate_capability") or []])
|
||||||
|
return (
|
||||||
|
f"- Map factual observations and disputed interpretations to {mapping.get('facts_and_disputes')}; keep "
|
||||||
|
"competing claims queryable through claim-to-claim edges. Store the strategic framework in "
|
||||||
|
f"{mapping.get('reusable_framework')}. Put the governance rule in {mapping.get('operating_rule')} using "
|
||||||
|
"rule, rationale, category, rank, "
|
||||||
|
"and agent_id unless it defines criteria, an evidence_bar, and a pass_condition, in which case use "
|
||||||
|
f"{mapping.get('evaluative_gate')}. Stage the correction to the old belief as a "
|
||||||
|
f"{mapping.get('agent_position')} update; claim_edges never point to "
|
||||||
|
"reasoning_tools or beliefs.\n\n"
|
||||||
|
"- Stage one typed pending_review proposal with exact rows and no canonical writes. Review mappings, "
|
||||||
|
"evidence, claim-only edge endpoints, and unsupported changes. After explicit authorization, "
|
||||||
|
f"approve_claim applies only {supported}. approve_claim "
|
||||||
|
"supports neither behavioral_rules nor governance_gates; those tables, belief updates, and existing-row "
|
||||||
|
f"updates remain staged until a separate reviewed apply capability exists. The contract keeps {staged} "
|
||||||
|
"outside this apply.\n\n"
|
||||||
|
"- Receipt: proposal-level applied_at, created or updated row IDs, and a postflight readback. Until those "
|
||||||
|
"exist, staged or approved content is not live canonical knowledge."
|
||||||
|
)
|
||||||
|
|
||||||
|
source = by_id.get("source_intake")
|
||||||
|
if source:
|
||||||
|
safe_steps = [str(item) for item in source.get("safe_without_apply_authorization") or []]
|
||||||
|
retain_step = safe_steps[0] if safe_steps else "retain the artifact plus its locator and content hash"
|
||||||
|
stage_step = safe_steps[1] if len(safe_steps) > 1 else "stage candidates in a pending_review proposal"
|
||||||
|
return (
|
||||||
|
f"- {source.get('required_lead')} Immediately {retain_step}. A filename, chat label, attachment reference, or "
|
||||||
|
"source_ref is not source identity.\n\n"
|
||||||
|
f"- {stage_step}. Deduplicate candidate claims and retain evidence excerpts and contradictions. Do not "
|
||||||
|
"create public.sources or other canonical rows during capture or staging.\n\n"
|
||||||
|
f"- Explicit approval begins {source.get('approval_begins')}. The receipt should include the retained "
|
||||||
|
"locator and hash, proposal ID and status, reviewed payload, proposal-level applied_at, created row IDs, "
|
||||||
|
"and postflight readback. Until apply succeeds, the material is staged rather than live canonical knowledge."
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_current_public_schema(args: argparse.Namespace, table_names: set[str]) -> dict[str, list[str]]:
|
def load_current_public_schema(args: argparse.Namespace, table_names: set[str]) -> dict[str, list[str]]:
|
||||||
if not table_names:
|
if not table_names:
|
||||||
return {}
|
return {}
|
||||||
|
|
@ -1217,6 +1273,7 @@ def load_edges(args: argparse.Namespace, claim_ids: list[str], limit: int) -> di
|
||||||
|
|
||||||
|
|
||||||
def bundle(args: argparse.Namespace, query: str, claim_limit: int, context_limit: int) -> dict[str, Any]:
|
def bundle(args: argparse.Namespace, query: str, claim_limit: int, context_limit: int) -> dict[str, Any]:
|
||||||
|
contracts = context_operational_contracts(args, query)
|
||||||
claims = find_claims(args, query, claim_limit)
|
claims = find_claims(args, query, claim_limit)
|
||||||
claim_ids = [claim["id"] for claim in claims]
|
claim_ids = [claim["id"] for claim in claims]
|
||||||
evidence = load_evidence(args, claim_ids, 4)
|
evidence = load_evidence(args, claim_ids, 4)
|
||||||
|
|
@ -1224,7 +1281,8 @@ def bundle(args: argparse.Namespace, query: str, claim_limit: int, context_limit
|
||||||
return {
|
return {
|
||||||
"query": query,
|
"query": query,
|
||||||
"terms": query_terms(query),
|
"terms": query_terms(query),
|
||||||
"operational_contracts": context_operational_contracts(args, query),
|
"operational_contracts": contracts,
|
||||||
|
"compiled_response": compile_operational_response(contracts),
|
||||||
"claims": [
|
"claims": [
|
||||||
{
|
{
|
||||||
**claim,
|
**claim,
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,22 @@ from __future__ import annotations
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
from collections import OrderedDict
|
||||||
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable
|
from typing import Any
|
||||||
|
|
||||||
DEFAULT_TIMEOUT_SECONDS = 10
|
DEFAULT_TIMEOUT_SECONDS = 10
|
||||||
MAX_QUERY_CHARS = 16_000
|
MAX_QUERY_CHARS = 16_000
|
||||||
|
MAX_PENDING_SNAPSHOTS = 128
|
||||||
|
|
||||||
|
_SNAPSHOT_LOCK = threading.Lock()
|
||||||
|
_PENDING_SNAPSHOTS: OrderedDict[tuple[str, str], dict[str, Any]] = OrderedDict()
|
||||||
|
|
||||||
|
|
||||||
def _trace(record: dict[str, Any]) -> None:
|
def _trace(record: dict[str, Any]) -> None:
|
||||||
|
|
@ -54,13 +62,31 @@ def _format_context(contracts: list[dict[str, Any]]) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_database_context(
|
def _snapshot_key(session_id: str, query_sha256: str) -> tuple[str, str]:
|
||||||
|
return (str(session_id or ""), query_sha256)
|
||||||
|
|
||||||
|
|
||||||
|
def _store_snapshot(session_id: str, snapshot: dict[str, Any]) -> None:
|
||||||
|
key = _snapshot_key(session_id, str(snapshot["query_sha256"]))
|
||||||
|
with _SNAPSHOT_LOCK:
|
||||||
|
_PENDING_SNAPSHOTS[key] = snapshot
|
||||||
|
_PENDING_SNAPSHOTS.move_to_end(key)
|
||||||
|
while len(_PENDING_SNAPSHOTS) > MAX_PENDING_SNAPSHOTS:
|
||||||
|
_PENDING_SNAPSHOTS.popitem(last=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _take_snapshot(session_id: str, query_sha256: str) -> dict[str, Any] | None:
|
||||||
|
with _SNAPSHOT_LOCK:
|
||||||
|
return _PENDING_SNAPSHOTS.pop(_snapshot_key(session_id, query_sha256), None)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_database_snapshot(
|
||||||
user_message: str,
|
user_message: str,
|
||||||
*,
|
*,
|
||||||
hermes_home: Path | None = None,
|
hermes_home: Path | None = None,
|
||||||
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
|
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
|
||||||
) -> str:
|
) -> dict[str, Any]:
|
||||||
"""Return current operational contracts for one exact operator question."""
|
"""Load one immutable DB-contract snapshot for generation and validation."""
|
||||||
|
|
||||||
query = str(user_message or "")[:MAX_QUERY_CHARS]
|
query = str(user_message or "")[:MAX_QUERY_CHARS]
|
||||||
home = hermes_home or Path(os.getenv("HERMES_HOME", str(Path.home() / ".hermes")))
|
home = hermes_home or Path(os.getenv("HERMES_HOME", str(Path.home() / ".hermes")))
|
||||||
|
|
@ -68,6 +94,7 @@ def build_database_context(
|
||||||
query_sha256 = hashlib.sha256(query.encode("utf-8")).hexdigest()
|
query_sha256 = hashlib.sha256(query.encode("utf-8")).hexdigest()
|
||||||
base_record: dict[str, Any] = {
|
base_record: dict[str, Any] = {
|
||||||
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
|
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"event": "pre_llm_call",
|
||||||
"query_sha256": query_sha256,
|
"query_sha256": query_sha256,
|
||||||
"source": "kb_tool.py --local context",
|
"source": "kb_tool.py --local context",
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +102,13 @@ def build_database_context(
|
||||||
if not kb_tool.is_file():
|
if not kb_tool.is_file():
|
||||||
record = base_record | {"status": "error", "error": "kb_tool_missing", "injected": True}
|
record = base_record | {"status": "error", "error": "kb_tool_missing", "injected": True}
|
||||||
_trace(record)
|
_trace(record)
|
||||||
return _failure_context("kb_tool_missing")
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"query_sha256": query_sha256,
|
||||||
|
"contracts": [],
|
||||||
|
"compiled_response": None,
|
||||||
|
"context": _failure_context("kb_tool_missing"),
|
||||||
|
}
|
||||||
|
|
||||||
timeout = int(os.getenv("LEO_DB_CONTEXT_TIMEOUT_SECONDS", str(DEFAULT_TIMEOUT_SECONDS)))
|
timeout = int(os.getenv("LEO_DB_CONTEXT_TIMEOUT_SECONDS", str(DEFAULT_TIMEOUT_SECONDS)))
|
||||||
command = [
|
command = [
|
||||||
|
|
@ -96,11 +129,23 @@ def build_database_context(
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
record = base_record | {"status": "error", "error": "timeout", "injected": True}
|
record = base_record | {"status": "error", "error": "timeout", "injected": True}
|
||||||
_trace(record)
|
_trace(record)
|
||||||
return _failure_context("timeout")
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"query_sha256": query_sha256,
|
||||||
|
"contracts": [],
|
||||||
|
"compiled_response": None,
|
||||||
|
"context": _failure_context("timeout"),
|
||||||
|
}
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
record = base_record | {"status": "error", "error": type(exc).__name__, "injected": True}
|
record = base_record | {"status": "error", "error": type(exc).__name__, "injected": True}
|
||||||
_trace(record)
|
_trace(record)
|
||||||
return _failure_context(type(exc).__name__)
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"query_sha256": query_sha256,
|
||||||
|
"contracts": [],
|
||||||
|
"compiled_response": None,
|
||||||
|
"context": _failure_context(type(exc).__name__),
|
||||||
|
}
|
||||||
|
|
||||||
if completed.returncode != 0:
|
if completed.returncode != 0:
|
||||||
record = base_record | {
|
record = base_record | {
|
||||||
|
|
@ -109,17 +154,32 @@ def build_database_context(
|
||||||
"injected": True,
|
"injected": True,
|
||||||
}
|
}
|
||||||
_trace(record)
|
_trace(record)
|
||||||
return _failure_context(f"kb_tool_exit_{completed.returncode}")
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"query_sha256": query_sha256,
|
||||||
|
"contracts": [],
|
||||||
|
"compiled_response": None,
|
||||||
|
"context": _failure_context(f"kb_tool_exit_{completed.returncode}"),
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payload = json.loads(completed.stdout)
|
payload = json.loads(completed.stdout)
|
||||||
contracts = payload.get("operational_contracts")
|
contracts = payload.get("operational_contracts")
|
||||||
if not isinstance(contracts, list) or not all(isinstance(item, dict) for item in contracts):
|
if not isinstance(contracts, list) or not all(isinstance(item, dict) for item in contracts):
|
||||||
raise ValueError("operational_contracts_missing")
|
raise ValueError("operational_contracts_missing")
|
||||||
|
compiled_response = payload.get("compiled_response")
|
||||||
|
if compiled_response is not None and not isinstance(compiled_response, str):
|
||||||
|
raise ValueError("compiled_response_invalid")
|
||||||
except (json.JSONDecodeError, TypeError, ValueError) as exc:
|
except (json.JSONDecodeError, TypeError, ValueError) as exc:
|
||||||
record = base_record | {"status": "error", "error": str(exc), "injected": True}
|
record = base_record | {"status": "error", "error": str(exc), "injected": True}
|
||||||
_trace(record)
|
_trace(record)
|
||||||
return _failure_context(str(exc))
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"query_sha256": query_sha256,
|
||||||
|
"contracts": [],
|
||||||
|
"compiled_response": None,
|
||||||
|
"context": _failure_context(str(exc)),
|
||||||
|
}
|
||||||
|
|
||||||
contract_json = json.dumps(contracts, sort_keys=True, separators=(",", ":"))
|
contract_json = json.dumps(contracts, sort_keys=True, separators=(",", ":"))
|
||||||
record = base_record | {
|
record = base_record | {
|
||||||
|
|
@ -127,14 +187,192 @@ def build_database_context(
|
||||||
"injected": True,
|
"injected": True,
|
||||||
"contract_ids": [str(item.get("id") or "") for item in contracts],
|
"contract_ids": [str(item.get("id") or "") for item in contracts],
|
||||||
"contract_sha256": hashlib.sha256(contract_json.encode("utf-8")).hexdigest(),
|
"contract_sha256": hashlib.sha256(contract_json.encode("utf-8")).hexdigest(),
|
||||||
|
"compiled_response_available": bool(compiled_response),
|
||||||
}
|
}
|
||||||
_trace(record)
|
_trace(record)
|
||||||
return _format_context(contracts)
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"query_sha256": query_sha256,
|
||||||
|
"contracts": contracts,
|
||||||
|
"compiled_response": compiled_response,
|
||||||
|
"context": _format_context(contracts),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_database_context(
|
||||||
|
user_message: str,
|
||||||
|
*,
|
||||||
|
hermes_home: Path | None = None,
|
||||||
|
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
|
||||||
|
) -> str:
|
||||||
|
"""Return current operational contracts for one exact operator question."""
|
||||||
|
|
||||||
|
return str(
|
||||||
|
_load_database_snapshot(user_message, hermes_home=hermes_home, runner=runner)["context"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _word_count(value: str) -> int:
|
||||||
|
return len(re.findall(r"\b\w+(?:[-']\w+)*\b", value))
|
||||||
|
|
||||||
|
|
||||||
|
def _reply_hard_max(contracts: list[dict[str, Any]]) -> int | None:
|
||||||
|
for contract in contracts:
|
||||||
|
if contract.get("id") == "reply_budget" and isinstance(contract.get("hard_max_words"), int):
|
||||||
|
return int(contract["hard_max_words"])
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _mixed_packet_issues(response: str) -> list[str]:
|
||||||
|
lowered = response.lower()
|
||||||
|
issues: list[str] = []
|
||||||
|
required_terms = {
|
||||||
|
"claims": "claim",
|
||||||
|
"sources": "source",
|
||||||
|
"evidence": "evidence",
|
||||||
|
"reasoning_tools": "reasoning_tools",
|
||||||
|
"behavioral_rules": "behavioral_rules",
|
||||||
|
"governance_gates": "governance_gates",
|
||||||
|
"beliefs": "belief",
|
||||||
|
"staging": "stage",
|
||||||
|
"approve_claim": "approve_claim",
|
||||||
|
}
|
||||||
|
for label, term in required_terms.items():
|
||||||
|
if term not in lowered:
|
||||||
|
issues.append(f"missing_{label}")
|
||||||
|
|
||||||
|
if not re.search(r"reviewed apply|review and authoriz|review.{0,50}apply", response, re.I | re.S):
|
||||||
|
issues.append("missing_reviewed_apply_boundary")
|
||||||
|
if not ("applied_at" in lowered and re.search(r"readback|postflight", response, re.I)):
|
||||||
|
issues.append("missing_apply_receipt")
|
||||||
|
if not re.search(
|
||||||
|
r"approve_claim.{0,220}(?:supports neither|does not (?:support|cover|apply)|cannot apply|applies only)",
|
||||||
|
response,
|
||||||
|
re.I | re.S,
|
||||||
|
):
|
||||||
|
issues.append("missing_approve_claim_boundary")
|
||||||
|
|
||||||
|
if re.search(
|
||||||
|
r"supported collections?\s*\([^)]*(?:belief|behavioral_rules|governance_gates|existing[- ]row)",
|
||||||
|
response,
|
||||||
|
re.I | re.S,
|
||||||
|
):
|
||||||
|
issues.append("unsupported_collection_listed_as_supported")
|
||||||
|
if re.search(
|
||||||
|
r"(?:approve_claim\s+)?(?:applies?|apply) only[^.;\n]{0,220}"
|
||||||
|
r"(?:belief updates?|behavioral_rules|governance_gates|existing[- ]row updates?)",
|
||||||
|
response,
|
||||||
|
re.I | re.S,
|
||||||
|
):
|
||||||
|
issues.append("unsupported_collection_listed_in_apply")
|
||||||
|
|
||||||
|
for segment in re.split(r"(?<=[.!?])\s+|\n+", response):
|
||||||
|
negative = re.search(r"\b(?:no|not|never|without|must not|do not|does not|cannot)\b", segment, re.I)
|
||||||
|
if re.search(r"reasoning_tools?.{0,100}\bscope\b", segment, re.I) and not negative:
|
||||||
|
issues.append("reasoning_tools_scope_overclaim")
|
||||||
|
if re.search(r"claims?.{0,100}\bfalsifier\b", segment, re.I) and not negative:
|
||||||
|
issues.append("claims_falsifier_overclaim")
|
||||||
|
if re.search(r"behavioral_rules?.{0,100}\bstatus\b", segment, re.I) and not negative:
|
||||||
|
issues.append("behavioral_rules_status_overclaim")
|
||||||
|
if (
|
||||||
|
re.search(r"claim_edges?.{0,140}(?:reasoning_tools?|beliefs?)", segment, re.I | re.S)
|
||||||
|
and re.search(r"point|connect|link|endpoint", segment, re.I)
|
||||||
|
and not negative
|
||||||
|
):
|
||||||
|
issues.append("non_claim_edge_endpoint")
|
||||||
|
return sorted(set(issues))
|
||||||
|
|
||||||
|
|
||||||
|
def _source_intake_issues(response: str) -> list[str]:
|
||||||
|
lowered = response.lower()
|
||||||
|
issues: list[str] = []
|
||||||
|
if not re.search(r"url|storage_path|storage path|content hash|file hash", response, re.I):
|
||||||
|
issues.append("missing_real_source_identity")
|
||||||
|
if not re.search(r"pending_review|pending review|proposal", response, re.I):
|
||||||
|
issues.append("missing_staging")
|
||||||
|
if not re.search(r"approval|authoriz", response, re.I) or "apply" not in lowered:
|
||||||
|
issues.append("missing_apply_boundary")
|
||||||
|
if not re.search(r"live.{0,80}(?:not shipped|not live|unavailable)", response, re.I | re.S):
|
||||||
|
issues.append("missing_live_intake_ceiling")
|
||||||
|
if re.search(
|
||||||
|
r"public\.sources.{0,120}(?:author|title|publisher|publication date|published_at)",
|
||||||
|
response,
|
||||||
|
re.I | re.S,
|
||||||
|
):
|
||||||
|
issues.append("unshipped_source_fields")
|
||||||
|
if re.search(
|
||||||
|
r"(?:create|write|insert).{0,80}public\.sources.{0,100}(?:before|during).{0,80}(?:review|staging)",
|
||||||
|
response,
|
||||||
|
re.I | re.S,
|
||||||
|
):
|
||||||
|
issues.append("canonical_source_before_review")
|
||||||
|
return sorted(set(issues))
|
||||||
|
|
||||||
|
|
||||||
|
def response_contract_issues(response: str, contracts: list[dict[str, Any]]) -> list[str]:
|
||||||
|
contract_ids = {str(contract.get("id") or "") for contract in contracts}
|
||||||
|
issues: list[str] = []
|
||||||
|
hard_max = _reply_hard_max(contracts)
|
||||||
|
if hard_max is not None and _word_count(response) > hard_max:
|
||||||
|
issues.append("reply_budget_exceeded")
|
||||||
|
if "mixed_packet_composition" in contract_ids:
|
||||||
|
issues.extend(_mixed_packet_issues(response))
|
||||||
|
elif "source_intake" in contract_ids:
|
||||||
|
issues.extend(_source_intake_issues(response))
|
||||||
|
return sorted(set(issues))
|
||||||
|
|
||||||
|
|
||||||
def _pre_llm_call(**kwargs: Any) -> dict[str, str]:
|
def _pre_llm_call(**kwargs: Any) -> dict[str, str]:
|
||||||
return {"context": build_database_context(str(kwargs.get("user_message") or ""))}
|
user_message = str(kwargs.get("user_message") or "")
|
||||||
|
snapshot = _load_database_snapshot(user_message)
|
||||||
|
_store_snapshot(str(kwargs.get("session_id") or ""), snapshot)
|
||||||
|
return {"context": str(snapshot["context"])}
|
||||||
|
|
||||||
|
|
||||||
|
def _post_llm_call(**kwargs: Any) -> dict[str, str] | None:
|
||||||
|
user_message = str(kwargs.get("user_message") or "")[:MAX_QUERY_CHARS]
|
||||||
|
query_sha256 = hashlib.sha256(user_message.encode("utf-8")).hexdigest()
|
||||||
|
snapshot = _take_snapshot(str(kwargs.get("session_id") or ""), query_sha256)
|
||||||
|
response = str(kwargs.get("assistant_response") or "")
|
||||||
|
base_record = {
|
||||||
|
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"event": "post_llm_call",
|
||||||
|
"query_sha256": query_sha256,
|
||||||
|
"response_sha256": hashlib.sha256(response.encode("utf-8")).hexdigest(),
|
||||||
|
"validated": True,
|
||||||
|
}
|
||||||
|
if snapshot is None:
|
||||||
|
_trace(base_record | {"status": "error", "error": "contract_snapshot_missing", "transformed": False})
|
||||||
|
return None
|
||||||
|
if snapshot.get("status") != "ok":
|
||||||
|
_trace(base_record | {"status": "skipped", "reason": "contract_lookup_failed", "transformed": False})
|
||||||
|
return None
|
||||||
|
|
||||||
|
contracts = list(snapshot.get("contracts") or [])
|
||||||
|
issues = response_contract_issues(response, contracts)
|
||||||
|
compiled_response = snapshot.get("compiled_response")
|
||||||
|
compiled_issues = (
|
||||||
|
response_contract_issues(str(compiled_response), contracts) if isinstance(compiled_response, str) else []
|
||||||
|
)
|
||||||
|
transformed = bool(issues and isinstance(compiled_response, str) and not compiled_issues)
|
||||||
|
_trace(
|
||||||
|
base_record
|
||||||
|
| {
|
||||||
|
"status": "ok" if not issues or transformed else "error",
|
||||||
|
"contract_ids": [str(item.get("id") or "") for item in contracts],
|
||||||
|
"issues": issues,
|
||||||
|
"compiled_response_issues": compiled_issues,
|
||||||
|
"transformed": transformed,
|
||||||
|
"delivered_response_sha256": hashlib.sha256(
|
||||||
|
(str(compiled_response) if transformed else response).encode("utf-8")
|
||||||
|
).hexdigest(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if transformed:
|
||||||
|
return {"assistant_response": str(compiled_response)}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def register(ctx: Any) -> None:
|
def register(ctx: Any) -> None:
|
||||||
ctx.register_hook("pre_llm_call", _pre_llm_call)
|
ctx.register_hook("pre_llm_call", _pre_llm_call)
|
||||||
|
ctx.register_hook("post_llm_call", _post_llm_call)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
name: leo-db-context
|
name: leo-db-context
|
||||||
version: 1.0.0
|
version: 1.1.0
|
||||||
description: Inject query-specific read-only Teleo database contracts before each Leo model turn.
|
description: Inject and enforce query-specific read-only Teleo database contracts for each Leo model turn.
|
||||||
author: living-ip
|
author: living-ip
|
||||||
provides_hooks:
|
provides_hooks:
|
||||||
- pre_llm_call
|
- pre_llm_call
|
||||||
|
- post_llm_call
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,14 @@ Treat the emitted `Current Runtime Contracts` / `operational_contracts` block as
|
||||||
binding deployed-code readback. Follow its `required_lead`, schema guards,
|
binding deployed-code readback. Follow its `required_lead`, schema guards,
|
||||||
capability tier, and reply budget. It overrides recalled or generic architecture.
|
capability tier, and reply budget. It overrides recalled or generic architecture.
|
||||||
|
|
||||||
|
The profile's `leo-db-context` plugin automatically uses one immutable contract
|
||||||
|
snapshot for generation and pre-delivery validation. When a covered draft
|
||||||
|
violates the snapshot, `kb_tool.py` compiles the delivered response from the
|
||||||
|
snapshot's mappings, apply capability, staged boundary, and receipt fields, and
|
||||||
|
Hermes persists that replacement before sending it. Repair the contract,
|
||||||
|
compiler, or validator when this path fails; do not add answer examples as a
|
||||||
|
substitute.
|
||||||
|
|
||||||
Use narrower bridge commands when needed:
|
Use narrower bridge commands when needed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,16 @@ Treat the emitted `Current Runtime Contracts` / `operational_contracts` block as
|
||||||
binding deployed-code readback. Follow its `required_lead`, schema guards,
|
binding deployed-code readback. Follow its `required_lead`, schema guards,
|
||||||
capability tier, and reply budget. It overrides recalled or generic architecture.
|
capability tier, and reply budget. It overrides recalled or generic architecture.
|
||||||
|
|
||||||
|
The live leoclean profile injects that exact read-only contract automatically
|
||||||
|
before the model turn. Before delivery, the same snapshot validates the draft.
|
||||||
|
For contract-covered composition and source-intake questions, an invalid draft
|
||||||
|
is replaced by a response compiled from that snapshot's mappings, supported
|
||||||
|
collections, staged collections, and receipt requirements. The replacement is
|
||||||
|
written into session memory before Telegram delivery, so remembered and visible
|
||||||
|
answers stay identical. This is database/runtime composition, not repeated
|
||||||
|
prompt training; do not work around a failed validator by adding examples to
|
||||||
|
the skill.
|
||||||
|
|
||||||
Use only the bounded follow-ups relevant to the question:
|
Use only the bounded follow-ups relevant to the question:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
151
hermes-agent/patches/apply_response_transform_hook.py
Normal file
151
hermes-agent/patches/apply_response_transform_hook.py
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Install the Hermes post-LLM response transform before persistence."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import ast
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PATCH_VERSION = "teleo-response-transform-hook-v1"
|
||||||
|
PATCH_MARKER = " # TELEO_RESPONSE_TRANSFORM_HOOK_V1\n"
|
||||||
|
START_ANCHOR = " # Save trajectory if enabled\n"
|
||||||
|
END_ANCHOR = " # Extract reasoning from the last assistant message (if any)\n"
|
||||||
|
|
||||||
|
REPLACEMENT = ''' # TELEO_RESPONSE_TRANSFORM_HOOK_V1
|
||||||
|
# Run response validators before trajectory/session persistence. A plugin
|
||||||
|
# may return {"assistant_response": "..."}; the last non-empty value wins.
|
||||||
|
# This keeps delivered text and durable conversation memory identical.
|
||||||
|
if final_response and not interrupted:
|
||||||
|
try:
|
||||||
|
from hermes_cli.plugins import invoke_hook as _invoke_hook
|
||||||
|
_post_results = _invoke_hook(
|
||||||
|
"post_llm_call",
|
||||||
|
session_id=self.session_id,
|
||||||
|
user_message=original_user_message,
|
||||||
|
assistant_response=final_response,
|
||||||
|
conversation_history=list(messages),
|
||||||
|
model=self.model,
|
||||||
|
platform=getattr(self, "platform", None) or "",
|
||||||
|
)
|
||||||
|
_original_final_response = final_response
|
||||||
|
for _post_result in _post_results:
|
||||||
|
if not isinstance(_post_result, dict):
|
||||||
|
continue
|
||||||
|
_candidate = _post_result.get("assistant_response")
|
||||||
|
if isinstance(_candidate, str) and _candidate.strip():
|
||||||
|
final_response = _candidate.strip()
|
||||||
|
if final_response != _original_final_response:
|
||||||
|
_updated_assistant_message = False
|
||||||
|
for _message in reversed(messages):
|
||||||
|
if _message.get("role") == "assistant" and not _message.get("tool_calls"):
|
||||||
|
_message["content"] = final_response
|
||||||
|
_updated_assistant_message = True
|
||||||
|
break
|
||||||
|
if not _updated_assistant_message:
|
||||||
|
messages.append({"role": "assistant", "content": final_response})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("post_llm_call hook failed: %s", exc)
|
||||||
|
|
||||||
|
# Save trajectory if enabled
|
||||||
|
self._save_trajectory(messages, user_message, completed)
|
||||||
|
|
||||||
|
# Clean up VM and browser for this task after conversation completes
|
||||||
|
self._cleanup_task_resources(effective_task_id)
|
||||||
|
|
||||||
|
# Persist session to both JSON log and SQLite
|
||||||
|
self._persist_session(messages, conversation_history)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def sha256_text(value: str) -> str:
|
||||||
|
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def patch_source(source: str) -> tuple[str, bool]:
|
||||||
|
if PATCH_MARKER in source:
|
||||||
|
required = (
|
||||||
|
'_candidate = _post_result.get("assistant_response")',
|
||||||
|
"self._save_trajectory(messages, user_message, completed)",
|
||||||
|
"self._persist_session(messages, conversation_history)",
|
||||||
|
)
|
||||||
|
missing = [item for item in required if item not in source]
|
||||||
|
if missing:
|
||||||
|
raise ValueError(f"installed patch marker is incomplete: {missing}")
|
||||||
|
return source, False
|
||||||
|
|
||||||
|
if source.count(START_ANCHOR) != 1 or source.count(END_ANCHOR) != 1:
|
||||||
|
raise ValueError("Hermes response lifecycle anchors changed; refusing an unreviewed patch")
|
||||||
|
start = source.index(START_ANCHOR)
|
||||||
|
end = source.index(END_ANCHOR, start)
|
||||||
|
original_block = source[start:end]
|
||||||
|
required_original = (
|
||||||
|
'"post_llm_call"',
|
||||||
|
"self._save_trajectory(messages, user_message, completed)",
|
||||||
|
"self._cleanup_task_resources(effective_task_id)",
|
||||||
|
"self._persist_session(messages, conversation_history)",
|
||||||
|
)
|
||||||
|
missing = [item for item in required_original if item not in original_block]
|
||||||
|
if missing:
|
||||||
|
raise ValueError(f"Hermes response lifecycle changed; missing expected statements: {missing}")
|
||||||
|
|
||||||
|
patched = source[:start] + REPLACEMENT + source[end:]
|
||||||
|
ast.parse(patched)
|
||||||
|
return patched, True
|
||||||
|
|
||||||
|
|
||||||
|
def install(path: Path, *, check: bool) -> tuple[dict[str, str | bool], int]:
|
||||||
|
source = path.read_text(encoding="utf-8")
|
||||||
|
patched, changed = patch_source(source)
|
||||||
|
before_sha = sha256_text(source)
|
||||||
|
after_sha = sha256_text(patched)
|
||||||
|
if check:
|
||||||
|
status = "patch_required" if changed else "installed"
|
||||||
|
return {
|
||||||
|
"patch_version": PATCH_VERSION,
|
||||||
|
"status": status,
|
||||||
|
"target": str(path),
|
||||||
|
"sha256": before_sha,
|
||||||
|
}, 1 if changed else 0
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
mode = path.stat().st_mode & 0o777
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
"w", encoding="utf-8", dir=path.parent, prefix=f".{path.name}.", delete=False
|
||||||
|
) as handle:
|
||||||
|
handle.write(patched)
|
||||||
|
temp_path = Path(handle.name)
|
||||||
|
try:
|
||||||
|
temp_path.chmod(mode)
|
||||||
|
os.replace(temp_path, path)
|
||||||
|
finally:
|
||||||
|
temp_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
ast.parse(path.read_text(encoding="utf-8"))
|
||||||
|
return {
|
||||||
|
"patch_version": PATCH_VERSION,
|
||||||
|
"status": "installed_now" if changed else "already_installed",
|
||||||
|
"target": str(path),
|
||||||
|
"before_sha256": before_sha,
|
||||||
|
"after_sha256": after_sha,
|
||||||
|
}, 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()
|
||||||
|
result, code = install(args.target, check=args.check)
|
||||||
|
print(json.dumps(result, sort_keys=True))
|
||||||
|
return code
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -315,10 +315,24 @@ async def run_suite():
|
||||||
temp_removed = remove_tree(tmp_root)
|
temp_removed = remove_tree(tmp_root)
|
||||||
report["temp_profile_removed"] = temp_removed
|
report["temp_profile_removed"] = temp_removed
|
||||||
report["database_context_trace"] = database_context_trace
|
report["database_context_trace"] = database_context_trace
|
||||||
report["database_context_injection_count"] = len(report["database_context_trace"])
|
context_injections = [
|
||||||
report["database_context_all_ok"] = bool(report["database_context_trace"]) and all(
|
item for item in database_context_trace if item.get("event", "pre_llm_call") == "pre_llm_call"
|
||||||
|
]
|
||||||
|
response_validations = [
|
||||||
|
item for item in database_context_trace if item.get("event") == "post_llm_call"
|
||||||
|
]
|
||||||
|
report["database_context_injection_count"] = len(context_injections)
|
||||||
|
report["database_context_all_ok"] = bool(context_injections) and all(
|
||||||
item.get("status") == "ok" and item.get("injected") is True
|
item.get("status") == "ok" and item.get("injected") is True
|
||||||
for item in report["database_context_trace"]
|
for item in context_injections
|
||||||
|
)
|
||||||
|
report["database_response_validation_count"] = len(response_validations)
|
||||||
|
report["database_response_validation_all_ok"] = bool(response_validations) and all(
|
||||||
|
item.get("status") == "ok" and item.get("validated") is True
|
||||||
|
for item in response_validations
|
||||||
|
)
|
||||||
|
report["database_response_transform_count"] = sum(
|
||||||
|
item.get("transformed") is True for item in response_validations
|
||||||
)
|
)
|
||||||
write_report_snapshot(report)
|
write_report_snapshot(report)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ def write_score_markdown(path: Path, report: dict[str, Any]) -> None:
|
||||||
f"Temporary profile removed: `{report['temp_profile_removed']}`",
|
f"Temporary profile removed: `{report['temp_profile_removed']}`",
|
||||||
f"Database context injections: `{report['database_context_injection_count']}`",
|
f"Database context injections: `{report['database_context_injection_count']}`",
|
||||||
f"Database context all OK: `{report['database_context_all_ok']}`",
|
f"Database context all OK: `{report['database_context_all_ok']}`",
|
||||||
|
f"Database response validations: `{report['database_response_validation_count']}`",
|
||||||
|
f"Database response validation all OK: `{report['database_response_validation_all_ok']}`",
|
||||||
|
f"Database-composed replacements: `{report['database_response_transform_count']}`",
|
||||||
f"Posted to Telegram: `{report['posted_to_telegram']}`",
|
f"Posted to Telegram: `{report['posted_to_telegram']}`",
|
||||||
"",
|
"",
|
||||||
"## Prompt Scores",
|
"## Prompt Scores",
|
||||||
|
|
@ -68,6 +71,9 @@ def build_score_report(report: dict[str, Any], *, memory_token: str) -> dict[str
|
||||||
"temp_profile_removed": report.get("temp_profile_removed"),
|
"temp_profile_removed": report.get("temp_profile_removed"),
|
||||||
"database_context_injection_count": report.get("database_context_injection_count"),
|
"database_context_injection_count": report.get("database_context_injection_count"),
|
||||||
"database_context_all_ok": report.get("database_context_all_ok"),
|
"database_context_all_ok": report.get("database_context_all_ok"),
|
||||||
|
"database_response_validation_count": report.get("database_response_validation_count"),
|
||||||
|
"database_response_validation_all_ok": report.get("database_response_validation_all_ok"),
|
||||||
|
"database_response_transform_count": report.get("database_response_transform_count"),
|
||||||
"posted_to_telegram": report.get("posted_to_telegram"),
|
"posted_to_telegram": report.get("posted_to_telegram"),
|
||||||
"production_db_apply_ran": False,
|
"production_db_apply_ran": False,
|
||||||
}
|
}
|
||||||
|
|
@ -82,6 +88,8 @@ def score_report_passes(report: dict[str, Any], score_report: dict[str, Any]) ->
|
||||||
and report.get("temp_profile_removed") is True
|
and report.get("temp_profile_removed") is True
|
||||||
and report.get("database_context_all_ok") is True
|
and report.get("database_context_all_ok") is True
|
||||||
and int(report.get("database_context_injection_count") or 0) >= len(report.get("results") or [])
|
and int(report.get("database_context_injection_count") or 0) >= len(report.get("results") or [])
|
||||||
|
and report.get("database_response_validation_all_ok") is True
|
||||||
|
and int(report.get("database_response_validation_count") or 0) >= len(report.get("results") or [])
|
||||||
and report.get("posted_to_telegram") is False
|
and report.get("posted_to_telegram") is False
|
||||||
and score_report["score"]["pass"]
|
and score_report["score"]["pass"]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -327,12 +327,12 @@ CONCEPT_PATTERNS: dict[str, tuple[re.Pattern[str], ...]] = {
|
||||||
re.compile(r"behavioral_rules", re.I),
|
re.compile(r"behavioral_rules", re.I),
|
||||||
re.compile(r"governance_gates", re.I),
|
re.compile(r"governance_gates", re.I),
|
||||||
re.compile(
|
re.compile(
|
||||||
r"does not (?:accept|insert|support)|supports neither|neither.{0,80}nor|"
|
r"does not (?:accept|insert|support|cover)|supports neither|neither.{0,80}nor|"
|
||||||
r"(?:behavioral_rules|governance_gates).{0,120}cannot be applied by approve_claim|"
|
r"(?:behavioral_rules|governance_gates).{0,120}cannot be applied by approve_claim|"
|
||||||
r"approve_claim.{0,120}cannot apply",
|
r"approve_claim.{0,120}cannot apply",
|
||||||
re.I | re.S,
|
re.I | re.S,
|
||||||
),
|
),
|
||||||
re.compile(r"separate.{0,50}reviewed apply|reviewed.{0,50}apply capability", re.I | re.S),
|
re.compile(r"separate.{0,50}reviewed apply|reviewed.{0,50}apply (?:capability|path)", re.I | re.S),
|
||||||
),
|
),
|
||||||
"runtime_inputs": (
|
"runtime_inputs": (
|
||||||
re.compile(r"Postgres|canonical (?:DB|database|counts?)|(?:DB|database) totals?", re.I),
|
re.compile(r"Postgres|canonical (?:DB|database|counts?)|(?:DB|database) totals?", re.I),
|
||||||
|
|
@ -471,7 +471,8 @@ COUNT_INVARIANT_REJECTION_RE = re.compile(
|
||||||
SCHEMA_GAP_QUALIFIER_RE = re.compile(
|
SCHEMA_GAP_QUALIFIER_RE = re.compile(
|
||||||
r"\b(?:proposed|future|not current|not shipped|does not exist|doesn't exist|absent|"
|
r"\b(?:proposed|future|not current|not shipped|does not exist|doesn't exist|absent|"
|
||||||
r"has no|have no|no column|not an edge|would require|schema gap|must be added|"
|
r"has no|have no|no column|not an edge|would require|schema gap|must be added|"
|
||||||
r"does not support|doesn't support|supports neither|not supported|must not|do not invent)\b",
|
r"does not support|doesn't support|supports neither|not supported|must not|do not invent)\b|"
|
||||||
|
r"\bno\s+(?:[a-z_]+\s+){0,3}(?:column|field|table)\b",
|
||||||
re.I,
|
re.I,
|
||||||
)
|
)
|
||||||
CURRENT_SCHEMA_ASSERTION_PATTERNS: dict[str, re.Pattern[str]] = {
|
CURRENT_SCHEMA_ASSERTION_PATTERNS: dict[str, re.Pattern[str]] = {
|
||||||
|
|
@ -576,6 +577,12 @@ ROW_LEVEL_APPLIED_AT_RE = re.compile(
|
||||||
r"each (?:step|row|insert).{0,60}(?:gets?|has|requires?).{0,30}(?:non-null )?applied_at",
|
r"each (?:step|row|insert).{0,60}(?:gets?|has|requires?).{0,30}(?:non-null )?applied_at",
|
||||||
re.I | re.S,
|
re.I | re.S,
|
||||||
)
|
)
|
||||||
|
UNSUPPORTED_COMPOSITION_APPLY_RE = re.compile(
|
||||||
|
r"supported collections?\s*\([^)]*(?:belief|behavioral_rules|governance_gates|existing[- ]row)|"
|
||||||
|
r"(?:approve_claim\s+)?(?:applies?|apply) only[^.;\n]{0,220}"
|
||||||
|
r"(?:belief updates?|behavioral_rules|governance_gates|existing[- ]row updates?)",
|
||||||
|
re.I | re.S,
|
||||||
|
)
|
||||||
APPROVED_APPLY_ACTION_RE = re.compile(
|
APPROVED_APPLY_ACTION_RE = re.compile(
|
||||||
r"approved.{0,240}(?:guarded )?apply|(?:guarded )?apply.{0,240}approved",
|
r"approved.{0,240}(?:guarded )?apply|(?:guarded )?apply.{0,240}approved",
|
||||||
re.I | re.S,
|
re.I | re.S,
|
||||||
|
|
@ -700,6 +707,14 @@ def source_intake_issues(prompt_id: str, reply: str) -> list[str]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def composition_capability_issues(prompt_id: str, reply: str) -> list[str]:
|
||||||
|
if prompt_id != "OOS-06":
|
||||||
|
return []
|
||||||
|
if UNSUPPORTED_COMPOSITION_APPLY_RE.search(reply):
|
||||||
|
return ["unsupported_collection_listed_as_applyable"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def score_reply(prompt: dict[str, Any], reply: str, *, memory_token: str) -> dict[str, Any]:
|
def score_reply(prompt: dict[str, Any], reply: str, *, memory_token: str) -> dict[str, Any]:
|
||||||
legacy_score = base.score_reply(prompt, reply)
|
legacy_score = base.score_reply(prompt, reply)
|
||||||
concepts = {concept: matched_concept(reply, concept) for concept in prompt["required_concepts"]}
|
concepts = {concept: matched_concept(reply, concept) for concept in prompt["required_concepts"]}
|
||||||
|
|
@ -733,6 +748,7 @@ def score_reply(prompt: dict[str, Any], reply: str, *, memory_token: str) -> dic
|
||||||
semantic_issues = broad_semantic_issues(reply)
|
semantic_issues = broad_semantic_issues(reply)
|
||||||
readiness_issues = proposal_readiness_issues(prompt["id"], reply)
|
readiness_issues = proposal_readiness_issues(prompt["id"], reply)
|
||||||
intake_issues = source_intake_issues(prompt["id"], reply)
|
intake_issues = source_intake_issues(prompt["id"], reply)
|
||||||
|
composition_issues = composition_capability_issues(prompt["id"], reply)
|
||||||
word_count = len(re.findall(r"\b\w+(?:[-']\w+)*\b", reply))
|
word_count = len(re.findall(r"\b\w+(?:[-']\w+)*\b", reply))
|
||||||
max_response_words = MAX_RESPONSE_WORDS.get(prompt["id"], DEFAULT_MAX_RESPONSE_WORDS)
|
max_response_words = MAX_RESPONSE_WORDS.get(prompt["id"], DEFAULT_MAX_RESPONSE_WORDS)
|
||||||
response_too_long = word_count > max_response_words
|
response_too_long = word_count > max_response_words
|
||||||
|
|
@ -752,6 +768,7 @@ def score_reply(prompt: dict[str, Any], reply: str, *, memory_token: str) -> dic
|
||||||
"broad_semantic_issues": semantic_issues,
|
"broad_semantic_issues": semantic_issues,
|
||||||
"proposal_readiness_issues": readiness_issues,
|
"proposal_readiness_issues": readiness_issues,
|
||||||
"source_intake_issues": intake_issues,
|
"source_intake_issues": intake_issues,
|
||||||
|
"composition_capability_issues": composition_issues,
|
||||||
"word_count": word_count,
|
"word_count": word_count,
|
||||||
"max_response_words": max_response_words,
|
"max_response_words": max_response_words,
|
||||||
"response_too_long": response_too_long,
|
"response_too_long": response_too_long,
|
||||||
|
|
@ -766,6 +783,7 @@ def score_reply(prompt: dict[str, Any], reply: str, *, memory_token: str) -> dic
|
||||||
and not semantic_issues
|
and not semantic_issues
|
||||||
and not readiness_issues
|
and not readiness_issues
|
||||||
and not intake_issues
|
and not intake_issues
|
||||||
|
and not composition_issues
|
||||||
and not response_too_long
|
and not response_too_long
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,11 @@ def load_plugin():
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
def test_plugin_registers_pre_llm_call_hook() -> None:
|
def test_plugin_registers_generation_and_delivery_hooks() -> None:
|
||||||
module = load_plugin()
|
module = load_plugin()
|
||||||
registered = []
|
registered = []
|
||||||
module.register(SimpleNamespace(register_hook=lambda name, callback: registered.append((name, callback))))
|
module.register(SimpleNamespace(register_hook=lambda name, callback: registered.append((name, callback))))
|
||||||
assert len(registered) == 1
|
assert [name for name, _callback in registered] == ["pre_llm_call", "post_llm_call"]
|
||||||
assert registered[0][0] == "pre_llm_call"
|
|
||||||
|
|
||||||
|
|
||||||
def test_plugin_injects_only_operational_contracts_and_writes_redacted_trace(
|
def test_plugin_injects_only_operational_contracts_and_writes_redacted_trace(
|
||||||
|
|
@ -45,6 +44,7 @@ def test_plugin_injects_only_operational_contracts_and_writes_redacted_trace(
|
||||||
{"id": "reply_budget", "hard_max_words": 220},
|
{"id": "reply_budget", "hard_max_words": 220},
|
||||||
{"id": "source_intake", "capability_tier": "build-local compiler only"},
|
{"id": "source_intake", "capability_tier": "build-local compiler only"},
|
||||||
],
|
],
|
||||||
|
"compiled_response": "must not be injected either",
|
||||||
}
|
}
|
||||||
calls = []
|
calls = []
|
||||||
|
|
||||||
|
|
@ -71,6 +71,92 @@ def test_plugin_injects_only_operational_contracts_and_writes_redacted_trace(
|
||||||
assert kwargs["timeout"] == module.DEFAULT_TIMEOUT_SECONDS
|
assert kwargs["timeout"] == module.DEFAULT_TIMEOUT_SECONDS
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_replaces_contract_violating_draft_with_compiled_db_response(
|
||||||
|
tmp_path: Path, monkeypatch
|
||||||
|
) -> None:
|
||||||
|
module = load_plugin()
|
||||||
|
home = tmp_path / "profile"
|
||||||
|
kb_tool = home / "bin" / "kb_tool.py"
|
||||||
|
kb_tool.parent.mkdir(parents=True)
|
||||||
|
kb_tool.write_text("# fixture\n", encoding="utf-8")
|
||||||
|
trace = tmp_path / "trace.jsonl"
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||||
|
monkeypatch.setenv("LEO_DB_CONTEXT_TRACE_PATH", str(trace))
|
||||||
|
compiled = (
|
||||||
|
"Map claims with sources and evidence; put the framework in reasoning_tools, the rule in "
|
||||||
|
"behavioral_rules, an evaluative gate in governance_gates, and stage the belief correction. "
|
||||||
|
"Stage one pending_review proposal. approve_claim applies only claims, sources, evidence, edges, and "
|
||||||
|
"reasoning_tools; approve_claim supports neither behavioral_rules nor governance_gates. Belief updates "
|
||||||
|
"remain staged until a separate reviewed apply capability exists. Receipt: proposal applied_at and "
|
||||||
|
"postflight row readback."
|
||||||
|
)
|
||||||
|
payload = {
|
||||||
|
"operational_contracts": [
|
||||||
|
{"id": "reply_budget", "hard_max_words": 200},
|
||||||
|
{"id": "mixed_packet_composition"},
|
||||||
|
],
|
||||||
|
"compiled_response": compiled,
|
||||||
|
}
|
||||||
|
|
||||||
|
def fake_runner(command, **_kwargs):
|
||||||
|
return subprocess.CompletedProcess(command, 0, stdout=json.dumps(payload), stderr="")
|
||||||
|
|
||||||
|
snapshot = module._load_database_snapshot("compose this packet", hermes_home=home, runner=fake_runner)
|
||||||
|
module._store_snapshot("session-1", snapshot)
|
||||||
|
result = module._post_llm_call(
|
||||||
|
session_id="session-1",
|
||||||
|
user_message="compose this packet",
|
||||||
|
assistant_response=(
|
||||||
|
"Claims, sources, evidence, reasoning_tools, behavioral_rules, governance_gates, and beliefs. "
|
||||||
|
"Stage and review a proposal, then apply only the supported collections (claims, sources, evidence, "
|
||||||
|
"edges, reasoning_tools, belief updates). approve_claim does not cover behavioral_rules or "
|
||||||
|
"governance_gates; use a separate reviewed apply path. Receipt: applied_at and postflight readback."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {"assistant_response": compiled}
|
||||||
|
records = [json.loads(line) for line in trace.read_text(encoding="utf-8").splitlines()]
|
||||||
|
assert records[-1]["event"] == "post_llm_call"
|
||||||
|
assert records[-1]["transformed"] is True
|
||||||
|
assert records[-1]["issues"] == [
|
||||||
|
"unsupported_collection_listed_as_supported",
|
||||||
|
"unsupported_collection_listed_in_apply",
|
||||||
|
]
|
||||||
|
assert "compose this packet" not in trace.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_keeps_contract_valid_draft(tmp_path: Path) -> None:
|
||||||
|
module = load_plugin()
|
||||||
|
home = tmp_path / "profile"
|
||||||
|
kb_tool = home / "bin" / "kb_tool.py"
|
||||||
|
kb_tool.parent.mkdir(parents=True)
|
||||||
|
kb_tool.write_text("# fixture\n", encoding="utf-8")
|
||||||
|
valid = (
|
||||||
|
"Map claims with sources and evidence; put the framework in reasoning_tools, the rule in "
|
||||||
|
"behavioral_rules, an evaluative gate in governance_gates, and stage the belief correction. "
|
||||||
|
"Stage one pending_review proposal. approve_claim applies only claims, sources, evidence, edges, and "
|
||||||
|
"reasoning_tools; approve_claim supports neither behavioral_rules nor governance_gates. Belief updates "
|
||||||
|
"remain staged until a separate reviewed apply capability exists. Receipt: proposal applied_at and "
|
||||||
|
"postflight row readback."
|
||||||
|
)
|
||||||
|
payload = {
|
||||||
|
"operational_contracts": [
|
||||||
|
{"id": "reply_budget", "hard_max_words": 200},
|
||||||
|
{"id": "mixed_packet_composition"},
|
||||||
|
],
|
||||||
|
"compiled_response": valid,
|
||||||
|
}
|
||||||
|
|
||||||
|
def fake_runner(command, **_kwargs):
|
||||||
|
return subprocess.CompletedProcess(command, 0, stdout=json.dumps(payload), stderr="")
|
||||||
|
|
||||||
|
snapshot = module._load_database_snapshot("compose this packet", hermes_home=home, runner=fake_runner)
|
||||||
|
module._store_snapshot("session-2", snapshot)
|
||||||
|
assert module._post_llm_call(
|
||||||
|
session_id="session-2", user_message="compose this packet", assistant_response=valid
|
||||||
|
) is None
|
||||||
|
|
||||||
|
|
||||||
def test_plugin_fails_closed_when_database_contract_lookup_fails(tmp_path: Path) -> None:
|
def test_plugin_fails_closed_when_database_contract_lookup_fails(tmp_path: Path) -> None:
|
||||||
module = load_plugin()
|
module = load_plugin()
|
||||||
home = tmp_path / "profile"
|
home = tmp_path / "profile"
|
||||||
|
|
@ -93,3 +179,6 @@ def test_handler_harness_retains_database_context_proof_fields() -> None:
|
||||||
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
|
||||||
|
assert '"database_response_validation_count"' in source
|
||||||
|
assert '"database_response_validation_all_ok"' in source
|
||||||
|
assert '"database_response_transform_count"' in source
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,23 @@ def test_vps_bridge_operational_contracts_are_query_specific_and_schema_exact()
|
||||||
assert {"reply_budget", "runtime_persistence"} <= runtime_ids
|
assert {"reply_budget", "runtime_persistence"} <= runtime_ids
|
||||||
|
|
||||||
|
|
||||||
|
def test_vps_bridge_compiles_safe_mixed_packet_response_from_contracts() -> None:
|
||||||
|
module = _load_module(BRIDGE_DIR / "kb_tool.py")
|
||||||
|
contracts = module.operational_contracts(
|
||||||
|
"Compose a packet with a factual observation, strategic framework, disputed interpretation, governance "
|
||||||
|
"rule, and old belief."
|
||||||
|
)
|
||||||
|
|
||||||
|
response = module.compile_operational_response(contracts)
|
||||||
|
|
||||||
|
assert response is not None
|
||||||
|
assert len(re.findall(r"\b\w+(?:[-']\w+)*\b", response)) <= 200
|
||||||
|
assert "approve_claim applies only claims, sources, evidence, edges, and reasoning_tools" in response
|
||||||
|
assert "approve_claim supports neither behavioral_rules nor governance_gates" in response
|
||||||
|
assert "belief updates, and existing-row updates remain staged" in response
|
||||||
|
assert "proposal-level applied_at" in response
|
||||||
|
|
||||||
|
|
||||||
def test_vps_bridge_context_markdown_surfaces_runtime_contracts(capsys) -> None:
|
def test_vps_bridge_context_markdown_surfaces_runtime_contracts(capsys) -> None:
|
||||||
module = _load_module(BRIDGE_DIR / "kb_tool.py")
|
module = _load_module(BRIDGE_DIR / "kb_tool.py")
|
||||||
data = {
|
data = {
|
||||||
|
|
|
||||||
88
tests/test_hermes_response_transform_patch.py
Normal file
88
tests/test_hermes_response_transform_patch.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
"""Tests for the managed Hermes response-transform lifecycle patch."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
PATCHER = ROOT / "hermes-agent" / "patches" / "apply_response_transform_hook.py"
|
||||||
|
|
||||||
|
|
||||||
|
def load_patcher():
|
||||||
|
spec = importlib.util.spec_from_file_location("hermes_response_transform_patcher", PATCHER)
|
||||||
|
assert spec and spec.loader
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def upstream_fixture() -> str:
|
||||||
|
return '''class Agent:
|
||||||
|
def run(self, messages, user_message, completed, effective_task_id, conversation_history,
|
||||||
|
final_response, interrupted, original_user_message):
|
||||||
|
# Save trajectory if enabled
|
||||||
|
self._save_trajectory(messages, user_message, completed)
|
||||||
|
|
||||||
|
# Clean up VM and browser for this task after conversation completes
|
||||||
|
self._cleanup_task_resources(effective_task_id)
|
||||||
|
|
||||||
|
# Persist session to both JSON log and SQLite
|
||||||
|
self._persist_session(messages, conversation_history)
|
||||||
|
|
||||||
|
|
||||||
|
# Plugin hook: post_llm_call
|
||||||
|
if final_response and not interrupted:
|
||||||
|
from hermes_cli.plugins import invoke_hook as _invoke_hook
|
||||||
|
_invoke_hook(
|
||||||
|
"post_llm_call",
|
||||||
|
session_id=self.session_id,
|
||||||
|
user_message=original_user_message,
|
||||||
|
assistant_response=final_response,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract reasoning from the last assistant message (if any)
|
||||||
|
last_reasoning = None
|
||||||
|
return final_response, last_reasoning
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_patch_moves_response_transform_before_persistence_and_is_idempotent() -> None:
|
||||||
|
module = load_patcher()
|
||||||
|
patched, changed = module.patch_source(upstream_fixture())
|
||||||
|
|
||||||
|
assert changed is True
|
||||||
|
assert module.PATCH_MARKER in patched
|
||||||
|
assert patched.index('_candidate = _post_result.get("assistant_response")') < patched.index(
|
||||||
|
"self._save_trajectory(messages, user_message, completed)"
|
||||||
|
)
|
||||||
|
assert patched.index("self._save_trajectory(messages, user_message, completed)") < patched.index(
|
||||||
|
"self._persist_session(messages, conversation_history)"
|
||||||
|
)
|
||||||
|
assert module.patch_source(patched) == (patched, False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_patch_refuses_unknown_hermes_lifecycle() -> None:
|
||||||
|
module = load_patcher()
|
||||||
|
with pytest.raises(ValueError, match="anchors changed"):
|
||||||
|
module.patch_source("class Agent:\n pass\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_install_check_reports_patch_required_then_installed(tmp_path: Path) -> None:
|
||||||
|
module = load_patcher()
|
||||||
|
target = tmp_path / "run_agent.py"
|
||||||
|
target.write_text(upstream_fixture(), encoding="utf-8")
|
||||||
|
|
||||||
|
before, before_code = module.install(target, check=True)
|
||||||
|
assert before_code == 1
|
||||||
|
assert before["status"] == "patch_required"
|
||||||
|
|
||||||
|
applied, applied_code = module.install(target, check=False)
|
||||||
|
assert applied_code == 0
|
||||||
|
assert applied["status"] == "installed_now"
|
||||||
|
|
||||||
|
after, after_code = module.install(target, check=True)
|
||||||
|
assert after_code == 0
|
||||||
|
assert after["status"] == "installed"
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -104,6 +103,9 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_for_runtime_cha
|
||||||
assert 'hermes-agent/leoclean-bin/' in auto_deploy
|
assert 'hermes-agent/leoclean-bin/' in auto_deploy
|
||||||
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 '"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
|
assert 'deploy/leoclean-gateway-restart-required.sh' in auto_deploy
|
||||||
assert 'TELEO_AUTO_DEPLOY_REEXECED' in auto_deploy
|
assert 'TELEO_AUTO_DEPLOY_REEXECED' in auto_deploy
|
||||||
assert 'exec bash "$DEPLOY_CHECKOUT/deploy/auto-deploy.sh"' in auto_deploy
|
assert 'exec bash "$DEPLOY_CHECKOUT/deploy/auto-deploy.sh"' in auto_deploy
|
||||||
|
|
@ -118,6 +120,7 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_for_runtime_cha
|
||||||
assert 'hermes-agent/leoclean-bin/' in manual_deploy
|
assert 'hermes-agent/leoclean-bin/' in manual_deploy
|
||||||
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 'systemctl is-active --quiet leoclean-gateway.service' in manual_deploy
|
assert 'systemctl is-active --quiet leoclean-gateway.service' in manual_deploy
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -138,6 +141,9 @@ def test_auto_deploy_restarts_gateway_for_leoclean_runtime_changes():
|
||||||
assert _leoclean_gateway_restart_required(
|
assert _leoclean_gateway_restart_required(
|
||||||
"hermes-agent/leoclean-plugins/vps/leo-db-context/__init__.py"
|
"hermes-agent/leoclean-plugins/vps/leo-db-context/__init__.py"
|
||||||
)
|
)
|
||||||
|
assert _leoclean_gateway_restart_required(
|
||||||
|
"hermes-agent/patches/apply_response_transform_hook.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_auto_deploy_sudoers_installer_is_root_only_and_visudo_checked():
|
def test_auto_deploy_sudoers_installer_is_root_only_and_visudo_checked():
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
@ -13,6 +14,15 @@ import run_leo_direct_claim_handler_suite as handler # noqa: E402
|
||||||
import working_leo_m3taversal_oos_benchmark as benchmark # noqa: E402
|
import working_leo_m3taversal_oos_benchmark as benchmark # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def load_kb_tool():
|
||||||
|
path = REPO_ROOT / "hermes-agent" / "leoclean-bin" / "kb_tool.py"
|
||||||
|
spec = importlib.util.spec_from_file_location("kb_tool_for_oos_test", path)
|
||||||
|
assert spec and spec.loader
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
def good_reply(prompt_id: str, token: str) -> str:
|
def good_reply(prompt_id: str, token: str) -> str:
|
||||||
common = (
|
common = (
|
||||||
"DB readback: claims: 1837; sources: 4145; claim_edges: 4916; claim_evidence: 4670; "
|
"DB readback: claims: 1837; sources: 4145; claim_edges: 4916; claim_evidence: 4670; "
|
||||||
|
|
@ -209,6 +219,11 @@ def test_oos_schema_guard_allows_explicit_future_schema_gap() -> None:
|
||||||
assert benchmark.current_schema_overclaims(reply) == []
|
assert benchmark.current_schema_overclaims(reply) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_oos_schema_guard_allows_compact_no_field_wording() -> None:
|
||||||
|
reply = "public.reasoning_tools stores name and description; no scope field exists in the current table."
|
||||||
|
assert benchmark.current_schema_overclaims(reply) == []
|
||||||
|
|
||||||
|
|
||||||
def test_oos_schema_guard_distinguishes_superseded_by_column_from_edge_type() -> None:
|
def test_oos_schema_guard_distinguishes_superseded_by_column_from_edge_type() -> None:
|
||||||
valid = (
|
valid = (
|
||||||
"Create a supersedes edge from the new claim to the old claim, then set the old claim's "
|
"Create a supersedes edge from the new claim to the old claim, then set the old claim's "
|
||||||
|
|
@ -461,6 +476,38 @@ def test_oos_reviewed_policy_apply_accepts_cannot_be_applied_wording() -> None:
|
||||||
assert all(pattern.search(reply) for pattern in patterns)
|
assert all(pattern.search(reply) for pattern in patterns)
|
||||||
|
|
||||||
|
|
||||||
|
def test_oos_reviewed_policy_apply_accepts_does_not_cover_and_path_wording() -> None:
|
||||||
|
patterns = benchmark.CONCEPT_PATTERNS["reviewed_policy_apply"]
|
||||||
|
reply = (
|
||||||
|
"behavioral_rules and governance_gates remain staged: approve_claim does not cover them, and both require "
|
||||||
|
"a separate reviewed apply path."
|
||||||
|
)
|
||||||
|
assert all(pattern.search(reply) for pattern in patterns)
|
||||||
|
|
||||||
|
|
||||||
|
def test_oos_composition_rejects_belief_update_in_supported_apply_list() -> None:
|
||||||
|
reply = (
|
||||||
|
"Stage a proposal, review it, then apply only the supported collections "
|
||||||
|
"(claims, sources, evidence, edges, reasoning_tools, belief updates). "
|
||||||
|
"behavioral_rules and governance_gates remain staged for a separate reviewed apply capability."
|
||||||
|
)
|
||||||
|
assert benchmark.composition_capability_issues("OOS-06", reply) == [
|
||||||
|
"unsupported_collection_listed_as_applyable"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_db_compiled_responses_pass_composition_and_source_intake_benchmarks() -> None:
|
||||||
|
kb_tool = load_kb_tool()
|
||||||
|
token = "demo-ledger-deadbeef"
|
||||||
|
prompts = {prompt["id"]: prompt for prompt in benchmark.prompt_catalog(token)}
|
||||||
|
|
||||||
|
for prompt_id in ("OOS-06", "OOS-14"):
|
||||||
|
prompt = prompts[prompt_id]
|
||||||
|
response = kb_tool.compile_operational_response(kb_tool.operational_contracts(prompt["message"]))
|
||||||
|
assert response is not None
|
||||||
|
assert benchmark.score_reply(prompt, response, memory_token=token)["pass"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_oos_source_intake_accepts_live_not_shipped_wording() -> None:
|
def test_oos_source_intake_accepts_live_not_shipped_wording() -> None:
|
||||||
patterns = benchmark.CONCEPT_PATTERNS["bounded_intake_tier"]
|
patterns = benchmark.CONCEPT_PATTERNS["bounded_intake_tier"]
|
||||||
reply = "The local compiler is proven; live VPS/chat intake is not shipped."
|
reply = "The local compiler is proven; live VPS/chat intake is not shipped."
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue