Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
292 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "bali-zero/teman2", "files": {"agent-library/scar_replay/world-scan-run.sh": {"9e8bb6055c42e72817fc7b01bc5035a2a7b7bf41": {"t": 1785745959.425854, "n": 8335, "h": "1d26bad6606b7ab9", "c": "#!/bin/bash\n# world-scan-run.sh — weekly external failure-pattern scan for the evolver.\n#\n# Reuse-first (2026-06-04): forked from ~/scripts/wr2-external-bench-run.sh.\n# ~80% reused (multi-LLM ingest+extract via a Claude agent, DeepSeek translate\n# via world_scan_translate.py, devils-advocate-style gate built into the\n# translator's deterministic grade, _proposed/ staging, Telegram). The only NEW\n# code is world_scan_translate.py (pattern -> draft executable probe).\n#\n# Pipeline:\n# 1. INGEST+EXTRACT (Claude Opus agent w/ web search): scan SRE/security/\n# agent-failure sources -> JSON list of failure patterns.\n# 2. TRANSLATE (world_scan_translate.py + DeepSeek): each pattern -> draft probe\n# with a DETERMINISTIC executability gate (ADOPT/OBSERVE/REJECT).\n# 3. STAGE: write drafts to research/operations/_proposed/<date>-world-scan.md.\n# 4. NOTIFY: Telegram summary to Antonello. NEVER auto-merge; human promotes\n# an ADOPT draft into scar_probes.py by hand.\n#\n# Cron: weekly (1st-of-week guard inside). Schedule via LaunchAgent.\n# Cost: ~$0.05/week (Gemini/Claude OAuth + DeepSeek translate calls).\n\nset -euo pipefail\n\nLOGDIR=\"${HOME}/logs\"; mkdir -p \"$LOGDIR\"\nLOG=\"$LOGDIR/world-scan.log\"\nERR=\"$LOGDIR/world-scan.err.log\"\n\nHARNESS_DIR=\"${SCAR_REPLAY_DIR:-${HOME}/nuzantara-deploy/agent-library/scar_replay}\"\nREPO_ROOT=\"${WORLD_SCAN_REPO_ROOT:-${HOME}/nuzantara-deploy}\"\nWEEK_TAG=\"$(date +%Y-W%V)\"\nPROPOSED_DIR=\"${REPO_ROOT}/research/operations/_proposed\"\nOUTPUT_FILE=\"${PROPOSED_DIR}/${WEEK_TAG}-world-scan-probes.md\"\nPATTERNS_JSON=\"/tmp/world-scan-patterns-${WEEK_TAG}.json\"\n\nlog() { echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*\" >> \"$LOG\"; }\n\n# telegram_alert: reused verbatim from scar-replay-run.sh (#1090). Resolves token\n# + chat id from sanctioned vaults; tolerates OWNER vs APPROVAL chat-id naming\n# drift; suppresses silently if creds unresolved (Law 4 — never block on outage).\ntelegram_alert() {\n local msg=\"$1\"\n local tok=\"${TELEGRAM_BOT_TOKEN:-}\"\n local chat=\"${TELEGRAM_OWNER_CHAT_ID:-${TELEGRAM_APPROVAL_CHAT_ID:-}}\"\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [[ -f \"$f\" ]] || continue\n [[ -z \"${tok}\" ]] && tok=\"$(grep -m1 -E '^TELEGRAM_BOT_TOKEN=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n [[ -z \"${chat}\" ]] && chat=\"$(grep -m1 -E '^TELEGRAM_(OWNER|APPROVAL)_CHAT_ID=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n done\n fi\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n log \"WARN: telegram creds unresolved — human-alert suppressed: ${msg}\"\n return 0\n fi\n curl -sS --max-time 20 -X POST \"https://api.telegram.org/bot${tok}/sendMessage\" \\\n -d \"chat_id=${chat}\" -d \"text=${msg}\" >/dev/null 2>&1 || true\n}\n\nlog \"world-scan starting (week=${WEEK_TAG}, out=${OUTPUT_FILE})\"\n\n# --- idempotence: one scan per week ---\nif [ -s \"$OUTPUT_FILE\" ]; then\n log \"skip: ${OUTPUT_FILE} already exists ($(wc -l < \"$OUTPUT_FILE\") lines). Delete to re-run.\"\n exit 0\nfi\n\n# --- worktree-isolation guard (same discipline as scar-replay-run.sh): never\n# run from inside the shared deploy worktree ---\ncase \"$(pwd -P)/\" in\n \"${REPO_ROOT}/\"*) log \"REFUSE: cwd inside shared deploy worktree; aborting.\"; exit 0 ;;\nesac\n\n# --- secrets: DEEPSEEK_API_KEY (with vault drift fallback) + telegram ---\nDS_KEY=\"${DEEPSEEK_API_KEY:-}\"\nif [ -z \"$DS_KEY\" ]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [ -f \"$f\" ] || continue\n line=$(grep -m1 -E '^DEEPSEEK_API_KEY=' \"$f\" 2>/dev/null || true)\n [ -n \"$line\" ] && { DS_KEY=\"${line#DEEPSEEK_API_KEY=}\"; DS_KEY=\"${DS_KEY%\\\"}\"; DS_KEY=\"${DS_KEY#\\\"}\"; break; }\n done\nfi\nif [ -z \"$DS_KEY\" ]; then\n log \"FATAL: DEEPSEEK_API_KEY not found in any vault — cannot translate. Aborting.\"\n exit 2\nfi\nexport DEEPSEEK_API_KEY=\"$DS_KEY\"\n\nmkdir -p \"$PROPOSED_DIR\"\n\n# --- Step 1: INGEST + EXTRACT via Claude Opus agent (OAuth-only, web search) ---\n# Reuse of the wr2-external-bench invocation shape. The agent does the web\n# research and emits a strict JSON array of failure patterns to PATTERNS_JSON.\nINGEST_PROMPT=\"You are world-scan, the evolver's external research scout.\n\nResearch the LAST ~30 days of public best-practices and post-mortems on:\n - SRE / reliability engineering (toil, retries, idempotency, locking, queues)\n - chaos engineering & fault injection\n - autonomous-agent / LLM-agent failure modes (loops, drift, tool misuse, state)\n - CI/CD & git-worktree / deploy hazards\nUse web search. Prefer concrete engineering write-ups (incident reports, SRE\nbooks/blogs, postmortems), NOT listicles.\n\nExtract 6-12 DISTINCT failure patterns relevant to a self-improving ops agent\nthat runs local git worktrees, LaunchAgent cron, shell scripts, Postgres.\n\nWrite ONLY a strict JSON array to ${PATTERNS_JSON}, each item:\n {\\\"title\\\": \\\"...\\\", \\\"text\\\": \\\"2-4 sentence description of the failure mode and why it bites\\\", \\\"source\\\": \\\"url or outlet\\\"}\nNo prose around the JSON. Reject vague/cultural advice — only concrete, testable failure mechanics.\"\n\nCLAUDE_BIN=\"${HOME}/.local/bin/claude\"; [ -x \"$CLAUDE_BIN\" ] || CLAUDE_BIN=\"/opt/homebrew/bin/claude\"\n\nlog \"step1: ingest+extract via claude opus (web search)\"\ntimeout 1800 \"$CLAUDE_BIN\" -p \\\n --model claude-opus-4-8 \\\n --permission-mode bypassPermissions \\\n --max-budget-usd \"${WORLD_SCAN_MAX_BUDGET_USD:-5}\" \\\n \"$INGEST_PROMPT\" >> \"$LOG\" 2>> \"$ERR\" || log \"WARN: ingest agent exit=$?\"\n\nif [ ! -s \"$PATTERNS_JSON\" ]; then\n log \"FATAL: no patterns JSON produced at ${PATTERNS_JSON}. Aborting.\"\n # Telegram: this needs a human (ingest produced nothing).\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: ingest produced no patterns. Check ${ERR}.\"\n exit 3\nfi\nlog \"step1 done: $(wc -c < \"$PATTERNS_JSON\") bytes of patterns\"\n\n# --- Step 2+3: TRANSLATE each pattern + STAGE drafts ---\nlog \"step2: translate patterns -> draft probes (DeepSeek + deterministic gate)\"\nPYTHONPATH=\"${HARNESS_DIR}\" python3 - \"$PATTERNS_JSON\" \"$OUTPUT_FILE\" \"$WEEK_TAG\" <<'PYEOF' >> \"$LOG\" 2>> \"$ERR\"\nimport json, sys, os, datetime\nsys.path.insert(0, os.environ[\"PYTHONPATH\"])\nimport world_scan_translate as wt\n\npatterns_path, out_path, week = sys.argv[1], sys.argv[2], sys.argv[3]\nkey = os.environ[\"DEEPSEEK_API_KEY\"]\npatterns = json.load(open(patterns_path))\nif not isinstance(patterns, list):\n print(\"patterns JSON is not a list — abort\"); sys.exit(4)\n\ndrafts = []\nfor p in patterns:\n title = (p.get(\"title\") or \"untitled\").strip()\n text = (p.get(\"text\") or \"\") + \"\\nSource: \" + (p.get(\"source\") or \"\")\n d = wt.translate_pattern(key, title, text)\n d.source_refs = p.get(\"source\") or title\n drafts.append(d)\n\nadopt = [d for d in drafts if d.category == \"ADOPT\"]\nobserve = [d for d in drafts if d.category == \"OBSERVE\"]\nreject = [d for d in drafts if d.category == \"REJECT\"]\n\nheader = (\n f\"# World-Scan draft probes — {week}\\n\\n\"\n f\"> Auto-generated by world-scan (reuse-first fork of wr2-external-bench). \"\n f\"These are DRAFT replay-probes proposed from external failure patterns. \"\n f\"**Nothing here is live.** A human promotes an ADOPT draft into \"\n f\"`agent-library/scar_replay/scar_probes.py` by hand (fixture + assertion as \"\n f\"code) and commits it. Never auto-merged (Law 5).\\n\\n\"\n f\"**Summary:** {len(adopt)} ADOPT · {len(observe)} OBSERVE · {len(reject)} REJECT \"\n f\"(of {len(drafts)} patterns scanned).\\n\\n---\\n\\n\"\n)\nbody = \"\".join(wt.render_draft_md(d) for d in (adopt + observe + reject))\nopen(out_path, \"w\").write(header + body)\nprint(f\"WORLD_SCAN_SUMMARY adopt={len(adopt)} observe={len(observe)} reject={len(reject)} total={len(drafts)}\")\nPYEOF\n\nif [ ! -s \"$OUTPUT_FILE\" ]; then\n log \"FATAL: translation produced no output file. Check ${ERR}.\"\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: translation failed. Check ${ERR}.\"\n exit 5\nfi\n\nSUMMARY=$(grep -h \"WORLD_SCAN_SUMMARY\" \"$LOG\" | tail -1 || echo \"summary unavailable\")\nlog \"step3 done: ${OUTPUT_FILE} ($(wc -l < \"$OUTPUT_FILE\") lines) — ${SUMMARY}\"\n\n# --- Step 4: notify (human reviews; never auto-merge) ---\ntelegram_alert \"🌐 world-scan ${WEEK_TAG} done. ${SUMMARY}. Draft probes staged at research/operations/_proposed/${WEEK_TAG}-world-scan-probes.md — review & promote ADOPT drafts by hand.\"\nlog \"DONE world-scan ${WEEK_TAG}\"\nexit 0\n"}, "c43b22330652a365d9fbe32ca1e5e8cdb03c8e23": {"t": 1785758294.8416717, "n": 8335, "h": "1d26bad6606b7ab9", "c": "#!/bin/bash\n# world-scan-run.sh — weekly external failure-pattern scan for the evolver.\n#\n# Reuse-first (2026-06-04): forked from ~/scripts/wr2-external-bench-run.sh.\n# ~80% reused (multi-LLM ingest+extract via a Claude agent, DeepSeek translate\n# via world_scan_translate.py, devils-advocate-style gate built into the\n# translator's deterministic grade, _proposed/ staging, Telegram). The only NEW\n# code is world_scan_translate.py (pattern -> draft executable probe).\n#\n# Pipeline:\n# 1. INGEST+EXTRACT (Claude Opus agent w/ web search): scan SRE/security/\n# agent-failure sources -> JSON list of failure patterns.\n# 2. TRANSLATE (world_scan_translate.py + DeepSeek): each pattern -> draft probe\n# with a DETERMINISTIC executability gate (ADOPT/OBSERVE/REJECT).\n# 3. STAGE: write drafts to research/operations/_proposed/<date>-world-scan.md.\n# 4. NOTIFY: Telegram summary to Antonello. NEVER auto-merge; human promotes\n# an ADOPT draft into scar_probes.py by hand.\n#\n# Cron: weekly (1st-of-week guard inside). Schedule via LaunchAgent.\n# Cost: ~$0.05/week (Gemini/Claude OAuth + DeepSeek translate calls).\n\nset -euo pipefail\n\nLOGDIR=\"${HOME}/logs\"; mkdir -p \"$LOGDIR\"\nLOG=\"$LOGDIR/world-scan.log\"\nERR=\"$LOGDIR/world-scan.err.log\"\n\nHARNESS_DIR=\"${SCAR_REPLAY_DIR:-${HOME}/nuzantara-deploy/agent-library/scar_replay}\"\nREPO_ROOT=\"${WORLD_SCAN_REPO_ROOT:-${HOME}/nuzantara-deploy}\"\nWEEK_TAG=\"$(date +%Y-W%V)\"\nPROPOSED_DIR=\"${REPO_ROOT}/research/operations/_proposed\"\nOUTPUT_FILE=\"${PROPOSED_DIR}/${WEEK_TAG}-world-scan-probes.md\"\nPATTERNS_JSON=\"/tmp/world-scan-patterns-${WEEK_TAG}.json\"\n\nlog() { echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*\" >> \"$LOG\"; }\n\n# telegram_alert: reused verbatim from scar-replay-run.sh (#1090). Resolves token\n# + chat id from sanctioned vaults; tolerates OWNER vs APPROVAL chat-id naming\n# drift; suppresses silently if creds unresolved (Law 4 — never block on outage).\ntelegram_alert() {\n local msg=\"$1\"\n local tok=\"${TELEGRAM_BOT_TOKEN:-}\"\n local chat=\"${TELEGRAM_OWNER_CHAT_ID:-${TELEGRAM_APPROVAL_CHAT_ID:-}}\"\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [[ -f \"$f\" ]] || continue\n [[ -z \"${tok}\" ]] && tok=\"$(grep -m1 -E '^TELEGRAM_BOT_TOKEN=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n [[ -z \"${chat}\" ]] && chat=\"$(grep -m1 -E '^TELEGRAM_(OWNER|APPROVAL)_CHAT_ID=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n done\n fi\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n log \"WARN: telegram creds unresolved — human-alert suppressed: ${msg}\"\n return 0\n fi\n curl -sS --max-time 20 -X POST \"https://api.telegram.org/bot${tok}/sendMessage\" \\\n -d \"chat_id=${chat}\" -d \"text=${msg}\" >/dev/null 2>&1 || true\n}\n\nlog \"world-scan starting (week=${WEEK_TAG}, out=${OUTPUT_FILE})\"\n\n# --- idempotence: one scan per week ---\nif [ -s \"$OUTPUT_FILE\" ]; then\n log \"skip: ${OUTPUT_FILE} already exists ($(wc -l < \"$OUTPUT_FILE\") lines). Delete to re-run.\"\n exit 0\nfi\n\n# --- worktree-isolation guard (same discipline as scar-replay-run.sh): never\n# run from inside the shared deploy worktree ---\ncase \"$(pwd -P)/\" in\n \"${REPO_ROOT}/\"*) log \"REFUSE: cwd inside shared deploy worktree; aborting.\"; exit 0 ;;\nesac\n\n# --- secrets: DEEPSEEK_API_KEY (with vault drift fallback) + telegram ---\nDS_KEY=\"${DEEPSEEK_API_KEY:-}\"\nif [ -z \"$DS_KEY\" ]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [ -f \"$f\" ] || continue\n line=$(grep -m1 -E '^DEEPSEEK_API_KEY=' \"$f\" 2>/dev/null || true)\n [ -n \"$line\" ] && { DS_KEY=\"${line#DEEPSEEK_API_KEY=}\"; DS_KEY=\"${DS_KEY%\\\"}\"; DS_KEY=\"${DS_KEY#\\\"}\"; break; }\n done\nfi\nif [ -z \"$DS_KEY\" ]; then\n log \"FATAL: DEEPSEEK_API_KEY not found in any vault — cannot translate. Aborting.\"\n exit 2\nfi\nexport DEEPSEEK_API_KEY=\"$DS_KEY\"\n\nmkdir -p \"$PROPOSED_DIR\"\n\n# --- Step 1: INGEST + EXTRACT via Claude Opus agent (OAuth-only, web search) ---\n# Reuse of the wr2-external-bench invocation shape. The agent does the web\n# research and emits a strict JSON array of failure patterns to PATTERNS_JSON.\nINGEST_PROMPT=\"You are world-scan, the evolver's external research scout.\n\nResearch the LAST ~30 days of public best-practices and post-mortems on:\n - SRE / reliability engineering (toil, retries, idempotency, locking, queues)\n - chaos engineering & fault injection\n - autonomous-agent / LLM-agent failure modes (loops, drift, tool misuse, state)\n - CI/CD & git-worktree / deploy hazards\nUse web search. Prefer concrete engineering write-ups (incident reports, SRE\nbooks/blogs, postmortems), NOT listicles.\n\nExtract 6-12 DISTINCT failure patterns relevant to a self-improving ops agent\nthat runs local git worktrees, LaunchAgent cron, shell scripts, Postgres.\n\nWrite ONLY a strict JSON array to ${PATTERNS_JSON}, each item:\n {\\\"title\\\": \\\"...\\\", \\\"text\\\": \\\"2-4 sentence description of the failure mode and why it bites\\\", \\\"source\\\": \\\"url or outlet\\\"}\nNo prose around the JSON. Reject vague/cultural advice — only concrete, testable failure mechanics.\"\n\nCLAUDE_BIN=\"${HOME}/.local/bin/claude\"; [ -x \"$CLAUDE_BIN\" ] || CLAUDE_BIN=\"/opt/homebrew/bin/claude\"\n\nlog \"step1: ingest+extract via claude opus (web search)\"\ntimeout 1800 \"$CLAUDE_BIN\" -p \\\n --model claude-opus-4-8 \\\n --permission-mode bypassPermissions \\\n --max-budget-usd \"${WORLD_SCAN_MAX_BUDGET_USD:-5}\" \\\n \"$INGEST_PROMPT\" >> \"$LOG\" 2>> \"$ERR\" || log \"WARN: ingest agent exit=$?\"\n\nif [ ! -s \"$PATTERNS_JSON\" ]; then\n log \"FATAL: no patterns JSON produced at ${PATTERNS_JSON}. Aborting.\"\n # Telegram: this needs a human (ingest produced nothing).\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: ingest produced no patterns. Check ${ERR}.\"\n exit 3\nfi\nlog \"step1 done: $(wc -c < \"$PATTERNS_JSON\") bytes of patterns\"\n\n# --- Step 2+3: TRANSLATE each pattern + STAGE drafts ---\nlog \"step2: translate patterns -> draft probes (DeepSeek + deterministic gate)\"\nPYTHONPATH=\"${HARNESS_DIR}\" python3 - \"$PATTERNS_JSON\" \"$OUTPUT_FILE\" \"$WEEK_TAG\" <<'PYEOF' >> \"$LOG\" 2>> \"$ERR\"\nimport json, sys, os, datetime\nsys.path.insert(0, os.environ[\"PYTHONPATH\"])\nimport world_scan_translate as wt\n\npatterns_path, out_path, week = sys.argv[1], sys.argv[2], sys.argv[3]\nkey = os.environ[\"DEEPSEEK_API_KEY\"]\npatterns = json.load(open(patterns_path))\nif not isinstance(patterns, list):\n print(\"patterns JSON is not a list — abort\"); sys.exit(4)\n\ndrafts = []\nfor p in patterns:\n title = (p.get(\"title\") or \"untitled\").strip()\n text = (p.get(\"text\") or \"\") + \"\\nSource: \" + (p.get(\"source\") or \"\")\n d = wt.translate_pattern(key, title, text)\n d.source_refs = p.get(\"source\") or title\n drafts.append(d)\n\nadopt = [d for d in drafts if d.category == \"ADOPT\"]\nobserve = [d for d in drafts if d.category == \"OBSERVE\"]\nreject = [d for d in drafts if d.category == \"REJECT\"]\n\nheader = (\n f\"# World-Scan draft probes — {week}\\n\\n\"\n f\"> Auto-generated by world-scan (reuse-first fork of wr2-external-bench). \"\n f\"These are DRAFT replay-probes proposed from external failure patterns. \"\n f\"**Nothing here is live.** A human promotes an ADOPT draft into \"\n f\"`agent-library/scar_replay/scar_probes.py` by hand (fixture + assertion as \"\n f\"code) and commits it. Never auto-merged (Law 5).\\n\\n\"\n f\"**Summary:** {len(adopt)} ADOPT · {len(observe)} OBSERVE · {len(reject)} REJECT \"\n f\"(of {len(drafts)} patterns scanned).\\n\\n---\\n\\n\"\n)\nbody = \"\".join(wt.render_draft_md(d) for d in (adopt + observe + reject))\nopen(out_path, \"w\").write(header + body)\nprint(f\"WORLD_SCAN_SUMMARY adopt={len(adopt)} observe={len(observe)} reject={len(reject)} total={len(drafts)}\")\nPYEOF\n\nif [ ! -s \"$OUTPUT_FILE\" ]; then\n log \"FATAL: translation produced no output file. Check ${ERR}.\"\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: translation failed. Check ${ERR}.\"\n exit 5\nfi\n\nSUMMARY=$(grep -h \"WORLD_SCAN_SUMMARY\" \"$LOG\" | tail -1 || echo \"summary unavailable\")\nlog \"step3 done: ${OUTPUT_FILE} ($(wc -l < \"$OUTPUT_FILE\") lines) — ${SUMMARY}\"\n\n# --- Step 4: notify (human reviews; never auto-merge) ---\ntelegram_alert \"🌐 world-scan ${WEEK_TAG} done. ${SUMMARY}. Draft probes staged at research/operations/_proposed/${WEEK_TAG}-world-scan-probes.md — review & promote ADOPT drafts by hand.\"\nlog \"DONE world-scan ${WEEK_TAG}\"\nexit 0\n"}, "b005f859986dd37a17383d49a5bf3ac12f09c7e3": {"t": 1785758296.270696, "n": 8280, "h": "45bbfea04af649db", "c": "#!/bin/bash\n# world-scan-run.sh — weekly external failure-pattern scan for the evolver.\n#\n# Reuse-first (2026-06-04): forked from ~/scripts/wr2-external-bench-run.sh.\n# ~80% reused (multi-LLM ingest+extract via a Claude agent, DeepSeek translate\n# via world_scan_translate.py, devils-advocate-style gate built into the\n# translator's deterministic grade, _proposed/ staging, Telegram). The only NEW\n# code is world_scan_translate.py (pattern -> draft executable probe).\n#\n# Pipeline:\n# 1. INGEST+EXTRACT (Claude Opus agent w/ web search): scan SRE/security/\n# agent-failure sources -> JSON list of failure patterns.\n# 2. TRANSLATE (world_scan_translate.py + DeepSeek): each pattern -> draft probe\n# with a DETERMINISTIC executability gate (ADOPT/OBSERVE/REJECT).\n# 3. STAGE: write drafts to research/operations/_proposed/<date>-world-scan.md.\n# 4. NOTIFY: Telegram summary to Antonello. NEVER auto-merge; human promotes\n# an ADOPT draft into scar_probes.py by hand.\n#\n# Cron: weekly (1st-of-week guard inside). Schedule via LaunchAgent.\n# Cost: ~$0.05/week (Gemini/Claude OAuth + DeepSeek translate calls).\n\nset -euo pipefail\n\nLOGDIR=\"${HOME}/logs\"; mkdir -p \"$LOGDIR\"\nLOG=\"$LOGDIR/world-scan.log\"\nERR=\"$LOGDIR/world-scan.err.log\"\n\nHARNESS_DIR=\"${SCAR_REPLAY_DIR:-${HOME}/nuzantara-deploy/agent-library/scar_replay}\"\nREPO_ROOT=\"${WORLD_SCAN_REPO_ROOT:-${HOME}/nuzantara-deploy}\"\nWEEK_TAG=\"$(date +%Y-W%V)\"\nPROPOSED_DIR=\"${REPO_ROOT}/research/operations/_proposed\"\nOUTPUT_FILE=\"${PROPOSED_DIR}/${WEEK_TAG}-world-scan-probes.md\"\nPATTERNS_JSON=\"/tmp/world-scan-patterns-${WEEK_TAG}.json\"\n\nlog() { echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*\" >> \"$LOG\"; }\n\n# telegram_alert: reused verbatim from scar-replay-run.sh (#1090). Resolves token\n# + chat id from sanctioned vaults; tolerates OWNER vs APPROVAL chat-id naming\n# drift; suppresses silently if creds unresolved (Law 4 — never block on outage).\ntelegram_alert() {\n local msg=\"$1\"\n local tok=\"${TELEGRAM_BOT_TOKEN:-}\"\n local chat=\"${TELEGRAM_OWNER_CHAT_ID:-${TELEGRAM_APPROVAL_CHAT_ID:-}}\"\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [[ -f \"$f\" ]] || continue\n [[ -z \"${tok}\" ]] && tok=\"$(grep -m1 -E '^TELEGRAM_BOT_TOKEN=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n [[ -z \"${chat}\" ]] && chat=\"$(grep -m1 -E '^TELEGRAM_(OWNER|APPROVAL)_CHAT_ID=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n done\n fi\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n log \"WARN: telegram creds unresolved — human-alert suppressed: ${msg}\"\n return 0\n fi\n curl -sS --max-time 20 -X POST \"https://api.telegram.org/bot${tok}/sendMessage\" \\\n -d \"chat_id=${chat}\" -d \"text=${msg}\" >/dev/null 2>&1 || true\n}\n\nlog \"world-scan starting (week=${WEEK_TAG}, out=${OUTPUT_FILE})\"\n\n# --- idempotence: one scan per week ---\nif [ -s \"$OUTPUT_FILE\" ]; then\n log \"skip: ${OUTPUT_FILE} already exists ($(wc -l < \"$OUTPUT_FILE\") lines). Delete to re-run.\"\n exit 0\nfi\n\n# --- worktree-isolation guard (same discipline as scar-replay-run.sh): never\n# run from inside the shared deploy worktree ---\ncase \"$(pwd -P)/\" in\n \"${REPO_ROOT}/\"*) log \"REFUSE: cwd inside shared deploy worktree; aborting.\"; exit 0 ;;\nesac\n\n# --- secrets: DEEPSEEK_API_KEY (with vault drift fallback) + telegram ---\nDS_KEY=\"${DEEPSEEK_API_KEY:-}\"\nif [ -z \"$DS_KEY\" ]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [ -f \"$f\" ] || continue\n line=$(grep -m1 -E '^DEEPSEEK_API_KEY=' \"$f\" 2>/dev/null || true)\n [ -n \"$line\" ] && { DS_KEY=\"${line#DEEPSEEK_API_KEY=}\"; DS_KEY=\"${DS_KEY%\\\"}\"; DS_KEY=\"${DS_KEY#\\\"}\"; break; }\n done\nfi\nif [ -z \"$DS_KEY\" ]; then\n log \"FATAL: DEEPSEEK_API_KEY not found in any vault — cannot translate. Aborting.\"\n exit 2\nfi\nexport DEEPSEEK_API_KEY=\"$DS_KEY\"\n\nmkdir -p \"$PROPOSED_DIR\"\n\n# --- Step 1: INGEST + EXTRACT via Claude Opus agent (OAuth-only, web search) ---\n# Reuse of the wr2-external-bench invocation shape. The agent does the web\n# research and emits a strict JSON array of failure patterns to PATTERNS_JSON.\nINGEST_PROMPT=\"You are world-scan, the evolver's external research scout.\n\nResearch the LAST ~30 days of public best-practices and post-mortems on:\n - SRE / reliability engineering (toil, retries, idempotency, locking, queues)\n - chaos engineering & fault injection\n - autonomous-agent / LLM-agent failure modes (loops, drift, tool misuse, state)\n - CI/CD & git-worktree / deploy hazards\nUse web search. Prefer concrete engineering write-ups (incident reports, SRE\nbooks/blogs, postmortems), NOT listicles.\n\nExtract 6-12 DISTINCT failure patterns relevant to a self-improving ops agent\nthat runs local git worktrees, LaunchAgent cron, shell scripts, Postgres.\n\nWrite ONLY a strict JSON array to ${PATTERNS_JSON}, each item:\n {\\\"title\\\": \\\"...\\\", \\\"text\\\": \\\"2-4 sentence description of the failure mode and why it bites\\\", \\\"source\\\": \\\"url or outlet\\\"}\nNo prose around the JSON. Reject vague/cultural advice — only concrete, testable failure mechanics.\"\n\nCLAUDE_BIN=\"${HOME}/.local/bin/claude\"; [ -x \"$CLAUDE_BIN\" ] || CLAUDE_BIN=\"/opt/homebrew/bin/claude\"\n\nlog \"step1: ingest+extract via claude opus (web search)\"\ntimeout 1800 \"$CLAUDE_BIN\" -p \\\n --model claude-opus-4-8 \\\n --permission-mode bypassPermissions \\\n \"$INGEST_PROMPT\" >> \"$LOG\" 2>> \"$ERR\" || log \"WARN: ingest agent exit=$?\"\n\nif [ ! -s \"$PATTERNS_JSON\" ]; then\n log \"FATAL: no patterns JSON produced at ${PATTERNS_JSON}. Aborting.\"\n # Telegram: this needs a human (ingest produced nothing).\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: ingest produced no patterns. Check ${ERR}.\"\n exit 3\nfi\nlog \"step1 done: $(wc -c < \"$PATTERNS_JSON\") bytes of patterns\"\n\n# --- Step 2+3: TRANSLATE each pattern + STAGE drafts ---\nlog \"step2: translate patterns -> draft probes (DeepSeek + deterministic gate)\"\nPYTHONPATH=\"${HARNESS_DIR}\" python3 - \"$PATTERNS_JSON\" \"$OUTPUT_FILE\" \"$WEEK_TAG\" <<'PYEOF' >> \"$LOG\" 2>> \"$ERR\"\nimport json, sys, os, datetime\nsys.path.insert(0, os.environ[\"PYTHONPATH\"])\nimport world_scan_translate as wt\n\npatterns_path, out_path, week = sys.argv[1], sys.argv[2], sys.argv[3]\nkey = os.environ[\"DEEPSEEK_API_KEY\"]\npatterns = json.load(open(patterns_path))\nif not isinstance(patterns, list):\n print(\"patterns JSON is not a list — abort\"); sys.exit(4)\n\ndrafts = []\nfor p in patterns:\n title = (p.get(\"title\") or \"untitled\").strip()\n text = (p.get(\"text\") or \"\") + \"\\nSource: \" + (p.get(\"source\") or \"\")\n d = wt.translate_pattern(key, title, text)\n d.source_refs = p.get(\"source\") or title\n drafts.append(d)\n\nadopt = [d for d in drafts if d.category == \"ADOPT\"]\nobserve = [d for d in drafts if d.category == \"OBSERVE\"]\nreject = [d for d in drafts if d.category == \"REJECT\"]\n\nheader = (\n f\"# World-Scan draft probes — {week}\\n\\n\"\n f\"> Auto-generated by world-scan (reuse-first fork of wr2-external-bench). \"\n f\"These are DRAFT replay-probes proposed from external failure patterns. \"\n f\"**Nothing here is live.** A human promotes an ADOPT draft into \"\n f\"`agent-library/scar_replay/scar_probes.py` by hand (fixture + assertion as \"\n f\"code) and commits it. Never auto-merged (Law 5).\\n\\n\"\n f\"**Summary:** {len(adopt)} ADOPT · {len(observe)} OBSERVE · {len(reject)} REJECT \"\n f\"(of {len(drafts)} patterns scanned).\\n\\n---\\n\\n\"\n)\nbody = \"\".join(wt.render_draft_md(d) for d in (adopt + observe + reject))\nopen(out_path, \"w\").write(header + body)\nprint(f\"WORLD_SCAN_SUMMARY adopt={len(adopt)} observe={len(observe)} reject={len(reject)} total={len(drafts)}\")\nPYEOF\n\nif [ ! -s \"$OUTPUT_FILE\" ]; then\n log \"FATAL: translation produced no output file. Check ${ERR}.\"\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: translation failed. Check ${ERR}.\"\n exit 5\nfi\n\nSUMMARY=$(grep -h \"WORLD_SCAN_SUMMARY\" \"$LOG\" | tail -1 || echo \"summary unavailable\")\nlog \"step3 done: ${OUTPUT_FILE} ($(wc -l < \"$OUTPUT_FILE\") lines) — ${SUMMARY}\"\n\n# --- Step 4: notify (human reviews; never auto-merge) ---\ntelegram_alert \"🌐 world-scan ${WEEK_TAG} done. ${SUMMARY}. Draft probes staged at research/operations/_proposed/${WEEK_TAG}-world-scan-probes.md — review & promote ADOPT drafts by hand.\"\nlog \"DONE world-scan ${WEEK_TAG}\"\nexit 0\n"}, "c6c3142a1f0bb3b195089705d9b3a88a5bce1896": {"t": 1785758297.456125, "n": 8296, "h": "066da63e4a87cabb", "c": "#!/bin/bash\n# world-scan-run.sh — weekly external failure-pattern scan for the evolver.\n#\n# Reuse-first (2026-06-04): forked from ~/scripts/wr2-external-bench-run.sh.\n# ~80% reused (multi-LLM ingest+extract via a Claude agent, DeepSeek translate\n# via world_scan_translate.py, devils-advocate-style gate built into the\n# translator's deterministic grade, _proposed/ staging, Telegram). The only NEW\n# code is world_scan_translate.py (pattern -> draft executable probe).\n#\n# Pipeline:\n# 1. INGEST+EXTRACT (Claude Opus agent w/ web search): scan SRE/security/\n# agent-failure sources -> JSON list of failure patterns.\n# 2. TRANSLATE (world_scan_translate.py + DeepSeek): each pattern -> draft probe\n# with a DETERMINISTIC executability gate (ADOPT/OBSERVE/REJECT).\n# 3. STAGE: write drafts to research/operations/_proposed/<date>-world-scan.md.\n# 4. NOTIFY: Telegram summary to Antonello. NEVER auto-merge; human promotes\n# an ADOPT draft into scar_probes.py by hand.\n#\n# Cron: weekly (1st-of-week guard inside). Schedule via LaunchAgent.\n# Cost: ~$0.05/week (Gemini/Claude OAuth + DeepSeek translate calls).\n\nset -euo pipefail\n\nLOGDIR=\"${HOME}/logs\"; mkdir -p \"$LOGDIR\"\nLOG=\"$LOGDIR/world-scan.log\"\nERR=\"$LOGDIR/world-scan.err.log\"\n\nHARNESS_DIR=\"${SCAR_REPLAY_DIR:-${HOME}/Desktop/nuzantara-deploy/agent-library/scar_replay}\"\nREPO_ROOT=\"${WORLD_SCAN_REPO_ROOT:-${HOME}/Desktop/nuzantara-deploy}\"\nWEEK_TAG=\"$(date +%Y-W%V)\"\nPROPOSED_DIR=\"${REPO_ROOT}/research/operations/_proposed\"\nOUTPUT_FILE=\"${PROPOSED_DIR}/${WEEK_TAG}-world-scan-probes.md\"\nPATTERNS_JSON=\"/tmp/world-scan-patterns-${WEEK_TAG}.json\"\n\nlog() { echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*\" >> \"$LOG\"; }\n\n# telegram_alert: reused verbatim from scar-replay-run.sh (#1090). Resolves token\n# + chat id from sanctioned vaults; tolerates OWNER vs APPROVAL chat-id naming\n# drift; suppresses silently if creds unresolved (Law 4 — never block on outage).\ntelegram_alert() {\n local msg=\"$1\"\n local tok=\"${TELEGRAM_BOT_TOKEN:-}\"\n local chat=\"${TELEGRAM_OWNER_CHAT_ID:-${TELEGRAM_APPROVAL_CHAT_ID:-}}\"\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [[ -f \"$f\" ]] || continue\n [[ -z \"${tok}\" ]] && tok=\"$(grep -m1 -E '^TELEGRAM_BOT_TOKEN=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n [[ -z \"${chat}\" ]] && chat=\"$(grep -m1 -E '^TELEGRAM_(OWNER|APPROVAL)_CHAT_ID=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n done\n fi\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n log \"WARN: telegram creds unresolved — human-alert suppressed: ${msg}\"\n return 0\n fi\n curl -sS --max-time 20 -X POST \"https://api.telegram.org/bot${tok}/sendMessage\" \\\n -d \"chat_id=${chat}\" -d \"text=${msg}\" >/dev/null 2>&1 || true\n}\n\nlog \"world-scan starting (week=${WEEK_TAG}, out=${OUTPUT_FILE})\"\n\n# --- idempotence: one scan per week ---\nif [ -s \"$OUTPUT_FILE\" ]; then\n log \"skip: ${OUTPUT_FILE} already exists ($(wc -l < \"$OUTPUT_FILE\") lines). Delete to re-run.\"\n exit 0\nfi\n\n# --- worktree-isolation guard (same discipline as scar-replay-run.sh): never\n# run from inside the shared deploy worktree ---\ncase \"$(pwd -P)/\" in\n \"${REPO_ROOT}/\"*) log \"REFUSE: cwd inside shared deploy worktree; aborting.\"; exit 0 ;;\nesac\n\n# --- secrets: DEEPSEEK_API_KEY (with vault drift fallback) + telegram ---\nDS_KEY=\"${DEEPSEEK_API_KEY:-}\"\nif [ -z \"$DS_KEY\" ]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [ -f \"$f\" ] || continue\n line=$(grep -m1 -E '^DEEPSEEK_API_KEY=' \"$f\" 2>/dev/null || true)\n [ -n \"$line\" ] && { DS_KEY=\"${line#DEEPSEEK_API_KEY=}\"; DS_KEY=\"${DS_KEY%\\\"}\"; DS_KEY=\"${DS_KEY#\\\"}\"; break; }\n done\nfi\nif [ -z \"$DS_KEY\" ]; then\n log \"FATAL: DEEPSEEK_API_KEY not found in any vault — cannot translate. Aborting.\"\n exit 2\nfi\nexport DEEPSEEK_API_KEY=\"$DS_KEY\"\n\nmkdir -p \"$PROPOSED_DIR\"\n\n# --- Step 1: INGEST + EXTRACT via Claude Opus agent (OAuth-only, web search) ---\n# Reuse of the wr2-external-bench invocation shape. The agent does the web\n# research and emits a strict JSON array of failure patterns to PATTERNS_JSON.\nINGEST_PROMPT=\"You are world-scan, the evolver's external research scout.\n\nResearch the LAST ~30 days of public best-practices and post-mortems on:\n - SRE / reliability engineering (toil, retries, idempotency, locking, queues)\n - chaos engineering & fault injection\n - autonomous-agent / LLM-agent failure modes (loops, drift, tool misuse, state)\n - CI/CD & git-worktree / deploy hazards\nUse web search. Prefer concrete engineering write-ups (incident reports, SRE\nbooks/blogs, postmortems), NOT listicles.\n\nExtract 6-12 DISTINCT failure patterns relevant to a self-improving ops agent\nthat runs local git worktrees, LaunchAgent cron, shell scripts, Postgres.\n\nWrite ONLY a strict JSON array to ${PATTERNS_JSON}, each item:\n {\\\"title\\\": \\\"...\\\", \\\"text\\\": \\\"2-4 sentence description of the failure mode and why it bites\\\", \\\"source\\\": \\\"url or outlet\\\"}\nNo prose around the JSON. Reject vague/cultural advice — only concrete, testable failure mechanics.\"\n\nCLAUDE_BIN=\"${HOME}/.local/bin/claude\"; [ -x \"$CLAUDE_BIN\" ] || CLAUDE_BIN=\"/opt/homebrew/bin/claude\"\n\nlog \"step1: ingest+extract via claude opus (web search)\"\ntimeout 1800 \"$CLAUDE_BIN\" -p \\\n --model claude-opus-4-8 \\\n --permission-mode bypassPermissions \\\n \"$INGEST_PROMPT\" >> \"$LOG\" 2>> \"$ERR\" || log \"WARN: ingest agent exit=$?\"\n\nif [ ! -s \"$PATTERNS_JSON\" ]; then\n log \"FATAL: no patterns JSON produced at ${PATTERNS_JSON}. Aborting.\"\n # Telegram: this needs a human (ingest produced nothing).\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: ingest produced no patterns. Check ${ERR}.\"\n exit 3\nfi\nlog \"step1 done: $(wc -c < \"$PATTERNS_JSON\") bytes of patterns\"\n\n# --- Step 2+3: TRANSLATE each pattern + STAGE drafts ---\nlog \"step2: translate patterns -> draft probes (DeepSeek + deterministic gate)\"\nPYTHONPATH=\"${HARNESS_DIR}\" python3 - \"$PATTERNS_JSON\" \"$OUTPUT_FILE\" \"$WEEK_TAG\" <<'PYEOF' >> \"$LOG\" 2>> \"$ERR\"\nimport json, sys, os, datetime\nsys.path.insert(0, os.environ[\"PYTHONPATH\"])\nimport world_scan_translate as wt\n\npatterns_path, out_path, week = sys.argv[1], sys.argv[2], sys.argv[3]\nkey = os.environ[\"DEEPSEEK_API_KEY\"]\npatterns = json.load(open(patterns_path))\nif not isinstance(patterns, list):\n print(\"patterns JSON is not a list — abort\"); sys.exit(4)\n\ndrafts = []\nfor p in patterns:\n title = (p.get(\"title\") or \"untitled\").strip()\n text = (p.get(\"text\") or \"\") + \"\\nSource: \" + (p.get(\"source\") or \"\")\n d = wt.translate_pattern(key, title, text)\n d.source_refs = p.get(\"source\") or title\n drafts.append(d)\n\nadopt = [d for d in drafts if d.category == \"ADOPT\"]\nobserve = [d for d in drafts if d.category == \"OBSERVE\"]\nreject = [d for d in drafts if d.category == \"REJECT\"]\n\nheader = (\n f\"# World-Scan draft probes — {week}\\n\\n\"\n f\"> Auto-generated by world-scan (reuse-first fork of wr2-external-bench). \"\n f\"These are DRAFT replay-probes proposed from external failure patterns. \"\n f\"**Nothing here is live.** A human promotes an ADOPT draft into \"\n f\"`agent-library/scar_replay/scar_probes.py` by hand (fixture + assertion as \"\n f\"code) and commits it. Never auto-merged (Law 5).\\n\\n\"\n f\"**Summary:** {len(adopt)} ADOPT · {len(observe)} OBSERVE · {len(reject)} REJECT \"\n f\"(of {len(drafts)} patterns scanned).\\n\\n---\\n\\n\"\n)\nbody = \"\".join(wt.render_draft_md(d) for d in (adopt + observe + reject))\nopen(out_path, \"w\").write(header + body)\nprint(f\"WORLD_SCAN_SUMMARY adopt={len(adopt)} observe={len(observe)} reject={len(reject)} total={len(drafts)}\")\nPYEOF\n\nif [ ! -s \"$OUTPUT_FILE\" ]; then\n log \"FATAL: translation produced no output file. Check ${ERR}.\"\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: translation failed. Check ${ERR}.\"\n exit 5\nfi\n\nSUMMARY=$(grep -h \"WORLD_SCAN_SUMMARY\" \"$LOG\" | tail -1 || echo \"summary unavailable\")\nlog \"step3 done: ${OUTPUT_FILE} ($(wc -l < \"$OUTPUT_FILE\") lines) — ${SUMMARY}\"\n\n# --- Step 4: notify (human reviews; never auto-merge) ---\ntelegram_alert \"🌐 world-scan ${WEEK_TAG} done. ${SUMMARY}. Draft probes staged at research/operations/_proposed/${WEEK_TAG}-world-scan-probes.md — review & promote ADOPT drafts by hand.\"\nlog \"DONE world-scan ${WEEK_TAG}\"\nexit 0\n"}, "acb1a23683b839e44dd6c465c58a94179f10bf6f": {"t": 1786067516.7546473, "n": 8335, "h": "1d26bad6606b7ab9", "c": "#!/bin/bash\n# world-scan-run.sh — weekly external failure-pattern scan for the evolver.\n#\n# Reuse-first (2026-06-04): forked from ~/scripts/wr2-external-bench-run.sh.\n# ~80% reused (multi-LLM ingest+extract via a Claude agent, DeepSeek translate\n# via world_scan_translate.py, devils-advocate-style gate built into the\n# translator's deterministic grade, _proposed/ staging, Telegram). The only NEW\n# code is world_scan_translate.py (pattern -> draft executable probe).\n#\n# Pipeline:\n# 1. INGEST+EXTRACT (Claude Opus agent w/ web search): scan SRE/security/\n# agent-failure sources -> JSON list of failure patterns.\n# 2. TRANSLATE (world_scan_translate.py + DeepSeek): each pattern -> draft probe\n# with a DETERMINISTIC executability gate (ADOPT/OBSERVE/REJECT).\n# 3. STAGE: write drafts to research/operations/_proposed/<date>-world-scan.md.\n# 4. NOTIFY: Telegram summary to Antonello. NEVER auto-merge; human promotes\n# an ADOPT draft into scar_probes.py by hand.\n#\n# Cron: weekly (1st-of-week guard inside). Schedule via LaunchAgent.\n# Cost: ~$0.05/week (Gemini/Claude OAuth + DeepSeek translate calls).\n\nset -euo pipefail\n\nLOGDIR=\"${HOME}/logs\"; mkdir -p \"$LOGDIR\"\nLOG=\"$LOGDIR/world-scan.log\"\nERR=\"$LOGDIR/world-scan.err.log\"\n\nHARNESS_DIR=\"${SCAR_REPLAY_DIR:-${HOME}/nuzantara-deploy/agent-library/scar_replay}\"\nREPO_ROOT=\"${WORLD_SCAN_REPO_ROOT:-${HOME}/nuzantara-deploy}\"\nWEEK_TAG=\"$(date +%Y-W%V)\"\nPROPOSED_DIR=\"${REPO_ROOT}/research/operations/_proposed\"\nOUTPUT_FILE=\"${PROPOSED_DIR}/${WEEK_TAG}-world-scan-probes.md\"\nPATTERNS_JSON=\"/tmp/world-scan-patterns-${WEEK_TAG}.json\"\n\nlog() { echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*\" >> \"$LOG\"; }\n\n# telegram_alert: reused verbatim from scar-replay-run.sh (#1090). Resolves token\n# + chat id from sanctioned vaults; tolerates OWNER vs APPROVAL chat-id naming\n# drift; suppresses silently if creds unresolved (Law 4 — never block on outage).\ntelegram_alert() {\n local msg=\"$1\"\n local tok=\"${TELEGRAM_BOT_TOKEN:-}\"\n local chat=\"${TELEGRAM_OWNER_CHAT_ID:-${TELEGRAM_APPROVAL_CHAT_ID:-}}\"\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [[ -f \"$f\" ]] || continue\n [[ -z \"${tok}\" ]] && tok=\"$(grep -m1 -E '^TELEGRAM_BOT_TOKEN=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n [[ -z \"${chat}\" ]] && chat=\"$(grep -m1 -E '^TELEGRAM_(OWNER|APPROVAL)_CHAT_ID=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n done\n fi\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n log \"WARN: telegram creds unresolved — human-alert suppressed: ${msg}\"\n return 0\n fi\n curl -sS --max-time 20 -X POST \"https://api.telegram.org/bot${tok}/sendMessage\" \\\n -d \"chat_id=${chat}\" -d \"text=${msg}\" >/dev/null 2>&1 || true\n}\n\nlog \"world-scan starting (week=${WEEK_TAG}, out=${OUTPUT_FILE})\"\n\n# --- idempotence: one scan per week ---\nif [ -s \"$OUTPUT_FILE\" ]; then\n log \"skip: ${OUTPUT_FILE} already exists ($(wc -l < \"$OUTPUT_FILE\") lines). Delete to re-run.\"\n exit 0\nfi\n\n# --- worktree-isolation guard (same discipline as scar-replay-run.sh): never\n# run from inside the shared deploy worktree ---\ncase \"$(pwd -P)/\" in\n \"${REPO_ROOT}/\"*) log \"REFUSE: cwd inside shared deploy worktree; aborting.\"; exit 0 ;;\nesac\n\n# --- secrets: DEEPSEEK_API_KEY (with vault drift fallback) + telegram ---\nDS_KEY=\"${DEEPSEEK_API_KEY:-}\"\nif [ -z \"$DS_KEY\" ]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [ -f \"$f\" ] || continue\n line=$(grep -m1 -E '^DEEPSEEK_API_KEY=' \"$f\" 2>/dev/null || true)\n [ -n \"$line\" ] && { DS_KEY=\"${line#DEEPSEEK_API_KEY=}\"; DS_KEY=\"${DS_KEY%\\\"}\"; DS_KEY=\"${DS_KEY#\\\"}\"; break; }\n done\nfi\nif [ -z \"$DS_KEY\" ]; then\n log \"FATAL: DEEPSEEK_API_KEY not found in any vault — cannot translate. Aborting.\"\n exit 2\nfi\nexport DEEPSEEK_API_KEY=\"$DS_KEY\"\n\nmkdir -p \"$PROPOSED_DIR\"\n\n# --- Step 1: INGEST + EXTRACT via Claude Opus agent (OAuth-only, web search) ---\n# Reuse of the wr2-external-bench invocation shape. The agent does the web\n# research and emits a strict JSON array of failure patterns to PATTERNS_JSON.\nINGEST_PROMPT=\"You are world-scan, the evolver's external research scout.\n\nResearch the LAST ~30 days of public best-practices and post-mortems on:\n - SRE / reliability engineering (toil, retries, idempotency, locking, queues)\n - chaos engineering & fault injection\n - autonomous-agent / LLM-agent failure modes (loops, drift, tool misuse, state)\n - CI/CD & git-worktree / deploy hazards\nUse web search. Prefer concrete engineering write-ups (incident reports, SRE\nbooks/blogs, postmortems), NOT listicles.\n\nExtract 6-12 DISTINCT failure patterns relevant to a self-improving ops agent\nthat runs local git worktrees, LaunchAgent cron, shell scripts, Postgres.\n\nWrite ONLY a strict JSON array to ${PATTERNS_JSON}, each item:\n {\\\"title\\\": \\\"...\\\", \\\"text\\\": \\\"2-4 sentence description of the failure mode and why it bites\\\", \\\"source\\\": \\\"url or outlet\\\"}\nNo prose around the JSON. Reject vague/cultural advice — only concrete, testable failure mechanics.\"\n\nCLAUDE_BIN=\"${HOME}/.local/bin/claude\"; [ -x \"$CLAUDE_BIN\" ] || CLAUDE_BIN=\"/opt/homebrew/bin/claude\"\n\nlog \"step1: ingest+extract via claude opus (web search)\"\ntimeout 1800 \"$CLAUDE_BIN\" -p \\\n --model claude-opus-4-8 \\\n --permission-mode bypassPermissions \\\n --max-budget-usd \"${WORLD_SCAN_MAX_BUDGET_USD:-5}\" \\\n \"$INGEST_PROMPT\" >> \"$LOG\" 2>> \"$ERR\" || log \"WARN: ingest agent exit=$?\"\n\nif [ ! -s \"$PATTERNS_JSON\" ]; then\n log \"FATAL: no patterns JSON produced at ${PATTERNS_JSON}. Aborting.\"\n # Telegram: this needs a human (ingest produced nothing).\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: ingest produced no patterns. Check ${ERR}.\"\n exit 3\nfi\nlog \"step1 done: $(wc -c < \"$PATTERNS_JSON\") bytes of patterns\"\n\n# --- Step 2+3: TRANSLATE each pattern + STAGE drafts ---\nlog \"step2: translate patterns -> draft probes (DeepSeek + deterministic gate)\"\nPYTHONPATH=\"${HARNESS_DIR}\" python3 - \"$PATTERNS_JSON\" \"$OUTPUT_FILE\" \"$WEEK_TAG\" <<'PYEOF' >> \"$LOG\" 2>> \"$ERR\"\nimport json, sys, os, datetime\nsys.path.insert(0, os.environ[\"PYTHONPATH\"])\nimport world_scan_translate as wt\n\npatterns_path, out_path, week = sys.argv[1], sys.argv[2], sys.argv[3]\nkey = os.environ[\"DEEPSEEK_API_KEY\"]\npatterns = json.load(open(patterns_path))\nif not isinstance(patterns, list):\n print(\"patterns JSON is not a list — abort\"); sys.exit(4)\n\ndrafts = []\nfor p in patterns:\n title = (p.get(\"title\") or \"untitled\").strip()\n text = (p.get(\"text\") or \"\") + \"\\nSource: \" + (p.get(\"source\") or \"\")\n d = wt.translate_pattern(key, title, text)\n d.source_refs = p.get(\"source\") or title\n drafts.append(d)\n\nadopt = [d for d in drafts if d.category == \"ADOPT\"]\nobserve = [d for d in drafts if d.category == \"OBSERVE\"]\nreject = [d for d in drafts if d.category == \"REJECT\"]\n\nheader = (\n f\"# World-Scan draft probes — {week}\\n\\n\"\n f\"> Auto-generated by world-scan (reuse-first fork of wr2-external-bench). \"\n f\"These are DRAFT replay-probes proposed from external failure patterns. \"\n f\"**Nothing here is live.** A human promotes an ADOPT draft into \"\n f\"`agent-library/scar_replay/scar_probes.py` by hand (fixture + assertion as \"\n f\"code) and commits it. Never auto-merged (Law 5).\\n\\n\"\n f\"**Summary:** {len(adopt)} ADOPT · {len(observe)} OBSERVE · {len(reject)} REJECT \"\n f\"(of {len(drafts)} patterns scanned).\\n\\n---\\n\\n\"\n)\nbody = \"\".join(wt.render_draft_md(d) for d in (adopt + observe + reject))\nopen(out_path, \"w\").write(header + body)\nprint(f\"WORLD_SCAN_SUMMARY adopt={len(adopt)} observe={len(observe)} reject={len(reject)} total={len(drafts)}\")\nPYEOF\n\nif [ ! -s \"$OUTPUT_FILE\" ]; then\n log \"FATAL: translation produced no output file. Check ${ERR}.\"\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: translation failed. Check ${ERR}.\"\n exit 5\nfi\n\nSUMMARY=$(grep -h \"WORLD_SCAN_SUMMARY\" \"$LOG\" | tail -1 || echo \"summary unavailable\")\nlog \"step3 done: ${OUTPUT_FILE} ($(wc -l < \"$OUTPUT_FILE\") lines) — ${SUMMARY}\"\n\n# --- Step 4: notify (human reviews; never auto-merge) ---\ntelegram_alert \"🌐 world-scan ${WEEK_TAG} done. ${SUMMARY}. Draft probes staged at research/operations/_proposed/${WEEK_TAG}-world-scan-probes.md — review & promote ADOPT drafts by hand.\"\nlog \"DONE world-scan ${WEEK_TAG}\"\nexit 0\n"}, "435945f89392b92ca558834d695b57058c697670": {"t": 1786068346.440976, "n": 8335, "h": "1d26bad6606b7ab9", "c": "#!/bin/bash\n# world-scan-run.sh — weekly external failure-pattern scan for the evolver.\n#\n# Reuse-first (2026-06-04): forked from ~/scripts/wr2-external-bench-run.sh.\n# ~80% reused (multi-LLM ingest+extract via a Claude agent, DeepSeek translate\n# via world_scan_translate.py, devils-advocate-style gate built into the\n# translator's deterministic grade, _proposed/ staging, Telegram). The only NEW\n# code is world_scan_translate.py (pattern -> draft executable probe).\n#\n# Pipeline:\n# 1. INGEST+EXTRACT (Claude Opus agent w/ web search): scan SRE/security/\n# agent-failure sources -> JSON list of failure patterns.\n# 2. TRANSLATE (world_scan_translate.py + DeepSeek): each pattern -> draft probe\n# with a DETERMINISTIC executability gate (ADOPT/OBSERVE/REJECT).\n# 3. STAGE: write drafts to research/operations/_proposed/<date>-world-scan.md.\n# 4. NOTIFY: Telegram summary to Antonello. NEVER auto-merge; human promotes\n# an ADOPT draft into scar_probes.py by hand.\n#\n# Cron: weekly (1st-of-week guard inside). Schedule via LaunchAgent.\n# Cost: ~$0.05/week (Gemini/Claude OAuth + DeepSeek translate calls).\n\nset -euo pipefail\n\nLOGDIR=\"${HOME}/logs\"; mkdir -p \"$LOGDIR\"\nLOG=\"$LOGDIR/world-scan.log\"\nERR=\"$LOGDIR/world-scan.err.log\"\n\nHARNESS_DIR=\"${SCAR_REPLAY_DIR:-${HOME}/nuzantara-deploy/agent-library/scar_replay}\"\nREPO_ROOT=\"${WORLD_SCAN_REPO_ROOT:-${HOME}/nuzantara-deploy}\"\nWEEK_TAG=\"$(date +%Y-W%V)\"\nPROPOSED_DIR=\"${REPO_ROOT}/research/operations/_proposed\"\nOUTPUT_FILE=\"${PROPOSED_DIR}/${WEEK_TAG}-world-scan-probes.md\"\nPATTERNS_JSON=\"/tmp/world-scan-patterns-${WEEK_TAG}.json\"\n\nlog() { echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*\" >> \"$LOG\"; }\n\n# telegram_alert: reused verbatim from scar-replay-run.sh (#1090). Resolves token\n# + chat id from sanctioned vaults; tolerates OWNER vs APPROVAL chat-id naming\n# drift; suppresses silently if creds unresolved (Law 4 — never block on outage).\ntelegram_alert() {\n local msg=\"$1\"\n local tok=\"${TELEGRAM_BOT_TOKEN:-}\"\n local chat=\"${TELEGRAM_OWNER_CHAT_ID:-${TELEGRAM_APPROVAL_CHAT_ID:-}}\"\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [[ -f \"$f\" ]] || continue\n [[ -z \"${tok}\" ]] && tok=\"$(grep -m1 -E '^TELEGRAM_BOT_TOKEN=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n [[ -z \"${chat}\" ]] && chat=\"$(grep -m1 -E '^TELEGRAM_(OWNER|APPROVAL)_CHAT_ID=' \"$f\" 2>/dev/null | cut -d= -f2- | tr -d '\"')\"\n done\n fi\n if [[ -z \"${tok}\" || -z \"${chat}\" ]]; then\n log \"WARN: telegram creds unresolved — human-alert suppressed: ${msg}\"\n return 0\n fi\n curl -sS --max-time 20 -X POST \"https://api.telegram.org/bot${tok}/sendMessage\" \\\n -d \"chat_id=${chat}\" -d \"text=${msg}\" >/dev/null 2>&1 || true\n}\n\nlog \"world-scan starting (week=${WEEK_TAG}, out=${OUTPUT_FILE})\"\n\n# --- idempotence: one scan per week ---\nif [ -s \"$OUTPUT_FILE\" ]; then\n log \"skip: ${OUTPUT_FILE} already exists ($(wc -l < \"$OUTPUT_FILE\") lines). Delete to re-run.\"\n exit 0\nfi\n\n# --- worktree-isolation guard (same discipline as scar-replay-run.sh): never\n# run from inside the shared deploy worktree ---\ncase \"$(pwd -P)/\" in\n \"${REPO_ROOT}/\"*) log \"REFUSE: cwd inside shared deploy worktree; aborting.\"; exit 0 ;;\nesac\n\n# --- secrets: DEEPSEEK_API_KEY (with vault drift fallback) + telegram ---\nDS_KEY=\"${DEEPSEEK_API_KEY:-}\"\nif [ -z \"$DS_KEY\" ]; then\n for f in \"${HOME}/.nuzantara-secrets.env\" \"${HOME}/.openclaw/workspace/.env.master\"; do\n [ -f \"$f\" ] || continue\n line=$(grep -m1 -E '^DEEPSEEK_API_KEY=' \"$f\" 2>/dev/null || true)\n [ -n \"$line\" ] && { DS_KEY=\"${line#DEEPSEEK_API_KEY=}\"; DS_KEY=\"${DS_KEY%\\\"}\"; DS_KEY=\"${DS_KEY#\\\"}\"; break; }\n done\nfi\nif [ -z \"$DS_KEY\" ]; then\n log \"FATAL: DEEPSEEK_API_KEY not found in any vault — cannot translate. Aborting.\"\n exit 2\nfi\nexport DEEPSEEK_API_KEY=\"$DS_KEY\"\n\nmkdir -p \"$PROPOSED_DIR\"\n\n# --- Step 1: INGEST + EXTRACT via Claude Opus agent (OAuth-only, web search) ---\n# Reuse of the wr2-external-bench invocation shape. The agent does the web\n# research and emits a strict JSON array of failure patterns to PATTERNS_JSON.\nINGEST_PROMPT=\"You are world-scan, the evolver's external research scout.\n\nResearch the LAST ~30 days of public best-practices and post-mortems on:\n - SRE / reliability engineering (toil, retries, idempotency, locking, queues)\n - chaos engineering & fault injection\n - autonomous-agent / LLM-agent failure modes (loops, drift, tool misuse, state)\n - CI/CD & git-worktree / deploy hazards\nUse web search. Prefer concrete engineering write-ups (incident reports, SRE\nbooks/blogs, postmortems), NOT listicles.\n\nExtract 6-12 DISTINCT failure patterns relevant to a self-improving ops agent\nthat runs local git worktrees, LaunchAgent cron, shell scripts, Postgres.\n\nWrite ONLY a strict JSON array to ${PATTERNS_JSON}, each item:\n {\\\"title\\\": \\\"...\\\", \\\"text\\\": \\\"2-4 sentence description of the failure mode and why it bites\\\", \\\"source\\\": \\\"url or outlet\\\"}\nNo prose around the JSON. Reject vague/cultural advice — only concrete, testable failure mechanics.\"\n\nCLAUDE_BIN=\"${HOME}/.local/bin/claude\"; [ -x \"$CLAUDE_BIN\" ] || CLAUDE_BIN=\"/opt/homebrew/bin/claude\"\n\nlog \"step1: ingest+extract via claude opus (web search)\"\ntimeout 1800 \"$CLAUDE_BIN\" -p \\\n --model claude-opus-4-8 \\\n --permission-mode bypassPermissions \\\n --max-budget-usd \"${WORLD_SCAN_MAX_BUDGET_USD:-5}\" \\\n \"$INGEST_PROMPT\" >> \"$LOG\" 2>> \"$ERR\" || log \"WARN: ingest agent exit=$?\"\n\nif [ ! -s \"$PATTERNS_JSON\" ]; then\n log \"FATAL: no patterns JSON produced at ${PATTERNS_JSON}. Aborting.\"\n # Telegram: this needs a human (ingest produced nothing).\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: ingest produced no patterns. Check ${ERR}.\"\n exit 3\nfi\nlog \"step1 done: $(wc -c < \"$PATTERNS_JSON\") bytes of patterns\"\n\n# --- Step 2+3: TRANSLATE each pattern + STAGE drafts ---\nlog \"step2: translate patterns -> draft probes (DeepSeek + deterministic gate)\"\nPYTHONPATH=\"${HARNESS_DIR}\" python3 - \"$PATTERNS_JSON\" \"$OUTPUT_FILE\" \"$WEEK_TAG\" <<'PYEOF' >> \"$LOG\" 2>> \"$ERR\"\nimport json, sys, os, datetime\nsys.path.insert(0, os.environ[\"PYTHONPATH\"])\nimport world_scan_translate as wt\n\npatterns_path, out_path, week = sys.argv[1], sys.argv[2], sys.argv[3]\nkey = os.environ[\"DEEPSEEK_API_KEY\"]\npatterns = json.load(open(patterns_path))\nif not isinstance(patterns, list):\n print(\"patterns JSON is not a list — abort\"); sys.exit(4)\n\ndrafts = []\nfor p in patterns:\n title = (p.get(\"title\") or \"untitled\").strip()\n text = (p.get(\"text\") or \"\") + \"\\nSource: \" + (p.get(\"source\") or \"\")\n d = wt.translate_pattern(key, title, text)\n d.source_refs = p.get(\"source\") or title\n drafts.append(d)\n\nadopt = [d for d in drafts if d.category == \"ADOPT\"]\nobserve = [d for d in drafts if d.category == \"OBSERVE\"]\nreject = [d for d in drafts if d.category == \"REJECT\"]\n\nheader = (\n f\"# World-Scan draft probes — {week}\\n\\n\"\n f\"> Auto-generated by world-scan (reuse-first fork of wr2-external-bench). \"\n f\"These are DRAFT replay-probes proposed from external failure patterns. \"\n f\"**Nothing here is live.** A human promotes an ADOPT draft into \"\n f\"`agent-library/scar_replay/scar_probes.py` by hand (fixture + assertion as \"\n f\"code) and commits it. Never auto-merged (Law 5).\\n\\n\"\n f\"**Summary:** {len(adopt)} ADOPT · {len(observe)} OBSERVE · {len(reject)} REJECT \"\n f\"(of {len(drafts)} patterns scanned).\\n\\n---\\n\\n\"\n)\nbody = \"\".join(wt.render_draft_md(d) for d in (adopt + observe + reject))\nopen(out_path, \"w\").write(header + body)\nprint(f\"WORLD_SCAN_SUMMARY adopt={len(adopt)} observe={len(observe)} reject={len(reject)} total={len(drafts)}\")\nPYEOF\n\nif [ ! -s \"$OUTPUT_FILE\" ]; then\n log \"FATAL: translation produced no output file. Check ${ERR}.\"\n telegram_alert \"⚠️ world-scan ${WEEK_TAG}: translation failed. Check ${ERR}.\"\n exit 5\nfi\n\nSUMMARY=$(grep -h \"WORLD_SCAN_SUMMARY\" \"$LOG\" | tail -1 || echo \"summary unavailable\")\nlog \"step3 done: ${OUTPUT_FILE} ($(wc -l < \"$OUTPUT_FILE\") lines) — ${SUMMARY}\"\n\n# --- Step 4: notify (human reviews; never auto-merge) ---\ntelegram_alert \"🌐 world-scan ${WEEK_TAG} done. ${SUMMARY}. Draft probes staged at research/operations/_proposed/${WEEK_TAG}-world-scan-probes.md — review & promote ADOPT drafts by hand.\"\nlog \"DONE world-scan ${WEEK_TAG}\"\nexit 0\n"}}, "docs/specs/arsenal-probe-v1.md": {"9e8bb6055c42e72817fc7b01bc5035a2a7b7bf41": {"t": 1785746399.636789, "n": 8306, "h": "75d3df861bc8453a", "c": "# Arsenal Probe v1 — spec (DESIGN artifact, modus stage 2)\n\nDate: 2026-07-06 · Author: Fable 5 (interactive) · Implementer: Sonnet 5 (worktree ops-arsenal-probe)\nMandate: Zero 2026-07-06 \"sonda pesantemente\" — the quota-cascade can silently degrade to 2-deep\n(Codex 401 silent, agy keychain-bound, GLM 529/401, DeepSeek 402). Recommended since 2026-05-24,\nnever armed. This tool makes every AI seat's liveness EMPIRICAL, per-machine, per-context.\n\n## What it is\n\n`scripts/arsenal_probe.py` — a standalone, signal-only prober (never mutates anything outside\n`~/.organism/`). One run = live-probe every AI seat reachable from THIS machine, classify each\ninto an honest taxonomy, write a JSON report + heartbeat sidecar, detect transitions vs the\nprevious run. Consumed by: healer wrapper (Mini, 4h), proprioception (freshness + verdict reader),\nhumans (`--table`).\n\n## Seats & probe commands (each: subprocess, per-seat timeout, thread-parallel)\n\n| seat | probe | LIVE signal |\n|---|---|---|\n| claude | `$ARSENAL_CLAUDE_BIN -p \"Reply with exactly: PONG\" --model claude-sonnet-5` (default bin `/opt/homebrew/bin/claude`; strip `ANTHROPIC_API_KEY` from env, pass through `CLAUDE_CODE_OAUTH_TOKEN` if set) | `PONG` in stdout |\n| glm | token = `security find-generic-password -s glm-coding-plan-token -w` (NEVER logged); direct `curl` POST `https://api.z.ai/api/anthropic/v1/messages`, model `glm-5.2`, max_tokens 8, anthropic-version 2023-06-01 | HTTP 200 + `\"model\"` in body |\n| agy | `agy -p \"Reply with exactly: PONG\"` (which-resolve; also try `~/.local/bin/agy`) | `PONG` in stdout |\n| codex | `codex exec --sandbox read-only --skip-git-repo-check \"Reply with exactly: PONG\"` with stdin=DEVNULL (it blocks on open stdin) | `PONG` in stdout |\n| deepseek | key = parse `DEEPSEEK_API_KEY=` from `~/.openclaw/workspace/.env.master` (read in-python, value NEVER logged); `curl` POST `https://api.deepseek.com/chat/completions` model `deepseek-v4-flash`, max_tokens 1 | HTTP 200 |\n| ollama | `ollama list` → presence of `qwen3.5`; with `--live-gen` also 1-token `ollama run qwen3.5:9b` | model listed (or gen output) |\n| nlm | `nlm list notebooks` (which-resolve) | stdout parses as JSON (list or dict) |\n\nTimeouts (defaults, per-seat override via `--timeout SEC` global multiplier): claude 120s,\nglm 45s, agy 120s, codex 180s, deepseek 45s, ollama 30s (live-gen 120s), nlm 60s.\n\n## Status taxonomy (the heart — classify by OUTPUT CONTENT, never exit code alone; scar #2)\n\n- `LIVE` — the live signal observed.\n- `AUTH_DEAD` — output matches `401|Authentication Failed|token_revoked|refresh_token_reused|OAuth token|authentication failed` (codex/glm/claude/nlm auth class). For agy: only when NOT in an ssh context.\n- `CONTEXT_AUTH` — agy-style GUI-keychain failure while `SSH_CONNECTION`/`SSH_TTY` env present or no GUI session: the seat is dead IN THIS CONTEXT, possibly alive in GUI. Distinct because the cure differs (context, not credential).\n- `QUOTA_DEAD` — `out of extra usage|usage limit|quota|429|rate.limit|exhausted`.\n- `BALANCE_DEAD` — HTTP 402 / `Insufficient Balance` (deepseek class).\n- `MODEL_ERR` — `1211|Unknown Model` (glm class — config drift, e.g. the fable-5[1m] leak).\n- `SHED` — HTTP 529 / `overloaded` (provider-side transient load-shedding).\n- `TIMEOUT` — probe hit its per-seat timeout.\n- `CRED_UNAVAILABLE` — credential source absent/locked on this host (keychain locked → `security` exit 36/25300-class or empty in non-interactive; env.master missing; token file absent). NOT a seat death — a host limitation. Never strict-fails.\n- `NOT_INSTALLED` — binary not found on PATH (nor known fallback paths). Never strict-fails.\n- `UNKNOWN_ERR` — none of the above matched; evidence tail retained.\n\n`healthy` bool = status == LIVE. `context_limited` = status in {CONTEXT_AUTH, CRED_UNAVAILABLE, NOT_INSTALLED}.\nSTRICT-FAIL set (persistent + fixable): {AUTH_DEAD, BALANCE_DEAD, MODEL_ERR, UNKNOWN_ERR}.\n(QUOTA_DEAD/SHED/TIMEOUT are alarm-worthy transitions but transient — never exit-1.)\n\n## Machine awareness\n\n`machine_label()`: hostname → `m5` (Air-M5) / `pro` (Nuzantara) / `mini` (Mini-Pro2) / raw-lowercase.\nDefault seat selection = all 7; `--seats claude,glm,...` filters. REQUIRED map (used by `--strict`):\n`mini: [claude, glm, codex, ollama]` · `pro: [claude, codex, deepseek, ollama, nlm]` ·\n`m5: [claude, glm, agy, codex]` — a required seat in the STRICT-FAIL set → exit 1.\nNon-required seats always probed and reported (unless filtered), just never fail the run.\n\n## Outputs\n\n1. Report `~/.organism/arsenal/last.json` (atomic write via tempfile+rename; keep previous as\n `prev.json` before overwrite):\n```json\n{\"schema\": 1, \"machine\": \"mini\", \"ts\": \"2026-07-06T12:00:00Z\",\n \"context\": {\"ssh\": true, \"interactive\": false},\n \"seats\": [{\"seat\": \"glm\", \"status\": \"LIVE\", \"healthy\": true, \"latency_ms\": 1240,\n \"evidence\": \"HTTP 200 model glm-5.2\", \"required\": true}],\n \"transitions\": [{\"seat\": \"codex\", \"from\": \"LIVE\", \"to\": \"AUTH_DEAD\"}],\n \"summary\": {\"live\": 5, \"dead_strict\": 1, \"context_limited\": 1, \"transient\": 0}}\n```\n2. Heartbeat `~/.organism/last_seen/<machine>.arsenal_probe.json` — same shape as healer's:\n `{\"organ\": \"<machine>.arsenal_probe\", \"status\": \"ok|degraded\", \"note\": \"<summary line>\", \"ts\": ...}`\n (`degraded` iff any strict-fail among required seats).\n3. stdout: human table by default; `--json` full report; `--quiet` one summary line.\n\n## Flags\n\n`--seats CSV` · `--json` · `--table` (default) · `--quiet` · `--strict` · `--timeout MULT`\n(float multiplier on all per-seat timeouts) · `--live-gen` (ollama real generate) ·\n`--read-last` (NO probing: re-emit last.json as `{\"findings\": [seats not in ok-set]}` for the\nproprioception wrap consumer; missing file → `{\"findings\": [{\"seat\": \"(all)\", \"status\": \"NEVER_RAN\"}]}`) ·\n`--selftest` (see below).\n\n## Redaction (scar #4 — non-negotiable)\n\nEvidence tails (≤160 chars) pass through `scrub()`: replace `Bearer\\s+\\S+`, `sk-[A-Za-z0-9_-]{8,}`,\nany 24+ char alnum/`._-` token, and the literal values of any env var whose NAME matches\n`(TOKEN|KEY|PASSWORD|SECRET)` that the probe itself loaded, with `<REDACTED>`. Credential values\nlive only in locals, never in the report, never in exceptions (wrap curl calls so the\nAuthorization header never appears in error strings). `security`/env.master reads must not echo.\n\n## Selftest (W84 blind-scan guard)\n\n`--selftest`: (a) classifier table — canned output samples per status (the real strings above:\nz.ai 1211 body, 529 body, codex token_revoked line, agy auth-failed line, deepseek 402, quota\nstrings) each classify correctly; (b) scrub() removes a planted fake token; (c) blind-scan guard:\na run that probed 0 seats must exit 2, never \"clean\"; (d) `--read-last` on a fixture file. Exit 0\nonly if all pass, print `SELFTEST OK — N checks`.\n\n## Tests — `scripts/tests/test_arsenal_probe.py` (pytest, NO live network/LLM calls)\n\nMonkeypatch subprocess/HTTP layers. Guilt AND innocence per classifier (e.g. `PONG` → LIVE never\nAUTH_DEAD; `1211` → MODEL_ERR never SHED; agy auth-fail + SSH_CONNECTION set → CONTEXT_AUTH,\nwithout ssh env → AUTH_DEAD). Transitions computed vs prev report. Strict exit semantics: required\nAUTH_DEAD → 1; CRED_UNAVAILABLE → 0. Redaction: fake `Bearer abc...` and a 40-char token never\nappear in report JSON. Atomic write leaves prev.json. `--read-last` fixtures (ok-set filtering,\nNEVER_RAN). Blind-scan exit 2. Aim ~20-30 focused tests. Style: mirror existing\n`scripts/tests/test_pending_arms_report.py` conventions (tmp_path, capsys).\n\n## Explicitly OUT of scope for the implementer (Fable does these after, funnel-in)\n\n- Edits to `scripts/proprioception.py` (guardian_freshness item + wrap registry entry)\n- Edits to `infra/healer/healer-run.sh` (receptor 4: run probe + transitions→ACTIONABLE + Telegram line)\n- `.github/workflows/immune-enforcement.yml` test wiring\n- `docs/runbooks/arsenal-probe.md`\n- PENDING-ARMS arming lines\n\n## Style contract\n\nPython 3.11 stdlib-only (subprocess, json, concurrent.futures, urllib.request for HTTP — no\nrequests dep), full type hints, logger-style stderr prints OK for a CLI tool, module docstring\nwith the one-line mission + scar references (#2 Esiste≠Armato, #4 secrets, W84 blind-scan).\nMatch the voice of `scripts/proprioception.py` (concise comments explaining WHY, not what).\n"}, "4e43a923b76601fcbae6810d61f0a6cf1fbc9bf5": {"t": 1785758296.2479012, "n": 8306, "h": "75d3df861bc8453a", "c": "# Arsenal Probe v1 — spec (DESIGN artifact, modus stage 2)\n\nDate: 2026-07-06 · Author: Fable 5 (interactive) · Implementer: Sonnet 5 (worktree ops-arsenal-probe)\nMandate: Zero 2026-07-06 \"sonda pesantemente\" — the quota-cascade can silently degrade to 2-deep\n(Codex 401 silent, agy keychain-bound, GLM 529/401, DeepSeek 402). Recommended since 2026-05-24,\nnever armed. This tool makes every AI seat's liveness EMPIRICAL, per-machine, per-context.\n\n## What it is\n\n`scripts/arsenal_probe.py` — a standalone, signal-only prober (never mutates anything outside\n`~/.organism/`). One run = live-probe every AI seat reachable from THIS machine, classify each\ninto an honest taxonomy, write a JSON report + heartbeat sidecar, detect transitions vs the\nprevious run. Consumed by: healer wrapper (Mini, 4h), proprioception (freshness + verdict reader),\nhumans (`--table`).\n\n## Seats & probe commands (each: subprocess, per-seat timeout, thread-parallel)\n\n| seat | probe | LIVE signal |\n|---|---|---|\n| claude | `$ARSENAL_CLAUDE_BIN -p \"Reply with exactly: PONG\" --model claude-sonnet-5` (default bin `/opt/homebrew/bin/claude`; strip `ANTHROPIC_API_KEY` from env, pass through `CLAUDE_CODE_OAUTH_TOKEN` if set) | `PONG` in stdout |\n| glm | token = `security find-generic-password -s glm-coding-plan-token -w` (NEVER logged); direct `curl` POST `https://api.z.ai/api/anthropic/v1/messages`, model `glm-5.2`, max_tokens 8, anthropic-version 2023-06-01 | HTTP 200 + `\"model\"` in body |\n| agy | `agy -p \"Reply with exactly: PONG\"` (which-resolve; also try `~/.local/bin/agy`) | `PONG` in stdout |\n| codex | `codex exec --sandbox read-only --skip-git-repo-check \"Reply with exactly: PONG\"` with stdin=DEVNULL (it blocks on open stdin) | `PONG` in stdout |\n| deepseek | key = parse `DEEPSEEK_API_KEY=` from `~/.openclaw/workspace/.env.master` (read in-python, value NEVER logged); `curl` POST `https://api.deepseek.com/chat/completions` model `deepseek-v4-flash`, max_tokens 1 | HTTP 200 |\n| ollama | `ollama list` → presence of `qwen3.5`; with `--live-gen` also 1-token `ollama run qwen3.5:9b` | model listed (or gen output) |\n| nlm | `nlm list notebooks` (which-resolve) | stdout parses as JSON (list or dict) |\n\nTimeouts (defaults, per-seat override via `--timeout SEC` global multiplier): claude 120s,\nglm 45s, agy 120s, codex 180s, deepseek 45s, ollama 30s (live-gen 120s), nlm 60s.\n\n## Status taxonomy (the heart — classify by OUTPUT CONTENT, never exit code alone; scar #2)\n\n- `LIVE` — the live signal observed.\n- `AUTH_DEAD` — output matches `401|Authentication Failed|token_revoked|refresh_token_reused|OAuth token|authentication failed` (codex/glm/claude/nlm auth class). For agy: only when NOT in an ssh context.\n- `CONTEXT_AUTH` — agy-style GUI-keychain failure while `SSH_CONNECTION`/`SSH_TTY` env present or no GUI session: the seat is dead IN THIS CONTEXT, possibly alive in GUI. Distinct because the cure differs (context, not credential).\n- `QUOTA_DEAD` — `out of extra usage|usage limit|quota|429|rate.limit|exhausted`.\n- `BALANCE_DEAD` — HTTP 402 / `Insufficient Balance` (deepseek class).\n- `MODEL_ERR` — `1211|Unknown Model` (glm class — config drift, e.g. the fable-5[1m] leak).\n- `SHED` — HTTP 529 / `overloaded` (provider-side transient load-shedding).\n- `TIMEOUT` — probe hit its per-seat timeout.\n- `CRED_UNAVAILABLE` — credential source absent/locked on this host (keychain locked → `security` exit 36/25300-class or empty in non-interactive; env.master missing; token file absent). NOT a seat death — a host limitation. Never strict-fails.\n- `NOT_INSTALLED` — binary not found on PATH (nor known fallback paths). Never strict-fails.\n- `UNKNOWN_ERR` — none of the above matched; evidence tail retained.\n\n`healthy` bool = status == LIVE. `context_limited` = status in {CONTEXT_AUTH, CRED_UNAVAILABLE, NOT_INSTALLED}.\nSTRICT-FAIL set (persistent + fixable): {AUTH_DEAD, BALANCE_DEAD, MODEL_ERR, UNKNOWN_ERR}.\n(QUOTA_DEAD/SHED/TIMEOUT are alarm-worthy transitions but transient — never exit-1.)\n\n## Machine awareness\n\n`machine_label()`: hostname → `m5` (Air-M5) / `pro` (Nuzantara) / `mini` (Mini-Pro2) / raw-lowercase.\nDefault seat selection = all 7; `--seats claude,glm,...` filters. REQUIRED map (used by `--strict`):\n`mini: [claude, glm, codex, ollama]` · `pro: [claude, codex, deepseek, ollama, nlm]` ·\n`m5: [claude, glm, agy, codex]` — a required seat in the STRICT-FAIL set → exit 1.\nNon-required seats always probed and reported (unless filtered), just never fail the run.\n\n## Outputs\n\n1. Report `~/.organism/arsenal/last.json` (atomic write via tempfile+rename; keep previous as\n `prev.json` before overwrite):\n```json\n{\"schema\": 1, \"machine\": \"mini\", \"ts\": \"2026-07-06T12:00:00Z\",\n \"context\": {\"ssh\": true, \"interactive\": false},\n \"seats\": [{\"seat\": \"glm\", \"status\": \"LIVE\", \"healthy\": true, \"latency_ms\": 1240,\n \"evidence\": \"HTTP 200 model glm-5.2\", \"required\": true}],\n \"transitions\": [{\"seat\": \"codex\", \"from\": \"LIVE\", \"to\": \"AUTH_DEAD\"}],\n \"summary\": {\"live\": 5, \"dead_strict\": 1, \"context_limited\": 1, \"transient\": 0}}\n```\n2. Heartbeat `~/.organism/last_seen/<machine>.arsenal_probe.json` — same shape as healer's:\n `{\"organ\": \"<machine>.arsenal_probe\", \"status\": \"ok|degraded\", \"note\": \"<summary line>\", \"ts\": ...}`\n (`degraded` iff any strict-fail among required seats).\n3. stdout: human table by default; `--json` full report; `--quiet` one summary line.\n\n## Flags\n\n`--seats CSV` · `--json` · `--table` (default) · `--quiet` · `--strict` · `--timeout MULT`\n(float multiplier on all per-seat timeouts) · `--live-gen` (ollama real generate) ·\n`--read-last` (NO probing: re-emit last.json as `{\"findings\": [seats not in ok-set]}` for the\nproprioception wrap consumer; missing file → `{\"findings\": [{\"seat\": \"(all)\", \"status\": \"NEVER_RAN\"}]}`) ·\n`--selftest` (see below).\n\n## Redaction (scar #4 — non-negotiable)\n\nEvidence tails (≤160 chars) pass through `scrub()`: replace `Bearer\\s+\\S+`, `sk-[A-Za-z0-9_-]{8,}`,\nany 24+ char alnum/`._-` token, and the literal values of any env var whose NAME matches\n`(TOKEN|KEY|PASSWORD|SECRET)` that the probe itself loaded, with `<REDACTED>`. Credential values\nlive only in locals, never in the report, never in exceptions (wrap curl calls so the\nAuthorization header never appears in error strings). `security`/env.master reads must not echo.\n\n## Selftest (W84 blind-scan guard)\n\n`--selftest`: (a) classifier table — canned output samples per status (the real strings above:\nz.ai 1211 body, 529 body, codex token_revoked line, agy auth-failed line, deepseek 402, quota\nstrings) each classify correctly; (b) scrub() removes a planted fake token; (c) blind-scan guard:\na run that probed 0 seats must exit 2, never \"clean\"; (d) `--read-last` on a fixture file. Exit 0\nonly if all pass, print `SELFTEST OK — N checks`.\n\n## Tests — `scripts/tests/test_arsenal_probe.py` (pytest, NO live network/LLM calls)\n\nMonkeypatch subprocess/HTTP layers. Guilt AND innocence per classifier (e.g. `PONG` → LIVE never\nAUTH_DEAD; `1211` → MODEL_ERR never SHED; agy auth-fail + SSH_CONNECTION set → CONTEXT_AUTH,\nwithout ssh env → AUTH_DEAD). Transitions computed vs prev report. Strict exit semantics: required\nAUTH_DEAD → 1; CRED_UNAVAILABLE → 0. Redaction: fake `Bearer abc...` and a 40-char token never\nappear in report JSON. Atomic write leaves prev.json. `--read-last` fixtures (ok-set filtering,\nNEVER_RAN). Blind-scan exit 2. Aim ~20-30 focused tests. Style: mirror existing\n`scripts/tests/test_pending_arms_report.py` conventions (tmp_path, capsys).\n\n## Explicitly OUT of scope for the implementer (Fable does these after, funnel-in)\n\n- Edits to `scripts/proprioception.py` (guardian_freshness item + wrap registry entry)\n- Edits to `infra/healer/healer-run.sh` (receptor 4: run probe + transitions→ACTIONABLE + Telegram line)\n- `.github/workflows/immune-enforcement.yml` test wiring\n- `docs/runbooks/arsenal-probe.md`\n- PENDING-ARMS arming lines\n\n## Style contract\n\nPython 3.11 stdlib-only (subprocess, json, concurrent.futures, urllib.request for HTTP — no\nrequests dep), full type hints, logger-style stderr prints OK for a CLI tool, module docstring\nwith the one-line mission + scar references (#2 Esiste≠Armato, #4 secrets, W84 blind-scan).\nMatch the voice of `scripts/proprioception.py` (concise comments explaining WHY, not what).\n"}, "acb1a23683b839e44dd6c465c58a94179f10bf6f": {"t": 1786067740.9658964, "n": 8306, "h": "75d3df861bc8453a", "c": "# Arsenal Probe v1 — spec (DESIGN artifact, modus stage 2)\n\nDate: 2026-07-06 · Author: Fable 5 (interactive) · Implementer: Sonnet 5 (worktree ops-arsenal-probe)\nMandate: Zero 2026-07-06 \"sonda pesantemente\" — the quota-cascade can silently degrade to 2-deep\n(Codex 401 silent, agy keychain-bound, GLM 529/401, DeepSeek 402). Recommended since 2026-05-24,\nnever armed. This tool makes every AI seat's liveness EMPIRICAL, per-machine, per-context.\n\n## What it is\n\n`scripts/arsenal_probe.py` — a standalone, signal-only prober (never mutates anything outside\n`~/.organism/`). One run = live-probe every AI seat reachable from THIS machine, classify each\ninto an honest taxonomy, write a JSON report + heartbeat sidecar, detect transitions vs the\nprevious run. Consumed by: healer wrapper (Mini, 4h), proprioception (freshness + verdict reader),\nhumans (`--table`).\n\n## Seats & probe commands (each: subprocess, per-seat timeout, thread-parallel)\n\n| seat | probe | LIVE signal |\n|---|---|---|\n| claude | `$ARSENAL_CLAUDE_BIN -p \"Reply with exactly: PONG\" --model claude-sonnet-5` (default bin `/opt/homebrew/bin/claude`; strip `ANTHROPIC_API_KEY` from env, pass through `CLAUDE_CODE_OAUTH_TOKEN` if set) | `PONG` in stdout |\n| glm | token = `security find-generic-password -s glm-coding-plan-token -w` (NEVER logged); direct `curl` POST `https://api.z.ai/api/anthropic/v1/messages`, model `glm-5.2`, max_tokens 8, anthropic-version 2023-06-01 | HTTP 200 + `\"model\"` in body |\n| agy | `agy -p \"Reply with exactly: PONG\"` (which-resolve; also try `~/.local/bin/agy`) | `PONG` in stdout |\n| codex | `codex exec --sandbox read-only --skip-git-repo-check \"Reply with exactly: PONG\"` with stdin=DEVNULL (it blocks on open stdin) | `PONG` in stdout |\n| deepseek | key = parse `DEEPSEEK_API_KEY=` from `~/.openclaw/workspace/.env.master` (read in-python, value NEVER logged); `curl` POST `https://api.deepseek.com/chat/completions` model `deepseek-v4-flash`, max_tokens 1 | HTTP 200 |\n| ollama | `ollama list` → presence of `qwen3.5`; with `--live-gen` also 1-token `ollama run qwen3.5:9b` | model listed (or gen output) |\n| nlm | `nlm list notebooks` (which-resolve) | stdout parses as JSON (list or dict) |\n\nTimeouts (defaults, per-seat override via `--timeout SEC` global multiplier): claude 120s,\nglm 45s, agy 120s, codex 180s, deepseek 45s, ollama 30s (live-gen 120s), nlm 60s.\n\n## Status taxonomy (the heart — classify by OUTPUT CONTENT, never exit code alone; scar #2)\n\n- `LIVE` — the live signal observed.\n- `AUTH_DEAD` — output matches `401|Authentication Failed|token_revoked|refresh_token_reused|OAuth token|authentication failed` (codex/glm/claude/nlm auth class). For agy: only when NOT in an ssh context.\n- `CONTEXT_AUTH` — agy-style GUI-keychain failure while `SSH_CONNECTION`/`SSH_TTY` env present or no GUI session: the seat is dead IN THIS CONTEXT, possibly alive in GUI. Distinct because the cure differs (context, not credential).\n- `QUOTA_DEAD` — `out of extra usage|usage limit|quota|429|rate.limit|exhausted`.\n- `BALANCE_DEAD` — HTTP 402 / `Insufficient Balance` (deepseek class).\n- `MODEL_ERR` — `1211|Unknown Model` (glm class — config drift, e.g. the fable-5[1m] leak).\n- `SHED` — HTTP 529 / `overloaded` (provider-side transient load-shedding).\n- `TIMEOUT` — probe hit its per-seat timeout.\n- `CRED_UNAVAILABLE` — credential source absent/locked on this host (keychain locked → `security` exit 36/25300-class or empty in non-interactive; env.master missing; token file absent). NOT a seat death — a host limitation. Never strict-fails.\n- `NOT_INSTALLED` — binary not found on PATH (nor known fallback paths). Never strict-fails.\n- `UNKNOWN_ERR` — none of the above matched; evidence tail retained.\n\n`healthy` bool = status == LIVE. `context_limited` = status in {CONTEXT_AUTH, CRED_UNAVAILABLE, NOT_INSTALLED}.\nSTRICT-FAIL set (persistent + fixable): {AUTH_DEAD, BALANCE_DEAD, MODEL_ERR, UNKNOWN_ERR}.\n(QUOTA_DEAD/SHED/TIMEOUT are alarm-worthy transitions but transient — never exit-1.)\n\n## Machine awareness\n\n`machine_label()`: hostname → `m5` (Air-M5) / `pro` (Nuzantara) / `mini` (Mini-Pro2) / raw-lowercase.\nDefault seat selection = all 7; `--seats claude,glm,...` filters. REQUIRED map (used by `--strict`):\n`mini: [claude, glm, codex, ollama]` · `pro: [claude, codex, deepseek, ollama, nlm]` ·\n`m5: [claude, glm, agy, codex]` — a required seat in the STRICT-FAIL set → exit 1.\nNon-required seats always probed and reported (unless filtered), just never fail the run.\n\n## Outputs\n\n1. Report `~/.organism/arsenal/last.json` (atomic write via tempfile+rename; keep previous as\n `prev.json` before overwrite):\n```json\n{\"schema\": 1, \"machine\": \"mini\", \"ts\": \"2026-07-06T12:00:00Z\",\n \"context\": {\"ssh\": true, \"interactive\": false},\n \"seats\": [{\"seat\": \"glm\", \"status\": \"LIVE\", \"healthy\": true, \"latency_ms\": 1240,\n \"evidence\": \"HTTP 200 model glm-5.2\", \"required\": true}],\n \"transitions\": [{\"seat\": \"codex\", \"from\": \"LIVE\", \"to\": \"AUTH_DEAD\"}],\n \"summary\": {\"live\": 5, \"dead_strict\": 1, \"context_limited\": 1, \"transient\": 0}}\n```\n2. Heartbeat `~/.organism/last_seen/<machine>.arsenal_probe.json` — same shape as healer's:\n `{\"organ\": \"<machine>.arsenal_probe\", \"status\": \"ok|degraded\", \"note\": \"<summary line>\", \"ts\": ...}`\n (`degraded` iff any strict-fail among required seats).\n3. stdout: human table by default; `--json` full report; `--quiet` one summary line.\n\n## Flags\n\n`--seats CSV` · `--json` · `--table` (default) · `--quiet` · `--strict` · `--timeout MULT`\n(float multiplier on all per-seat timeouts) · `--live-gen` (ollama real generate) ·\n`--read-last` (NO probing: re-emit last.json as `{\"findings\": [seats not in ok-set]}` for the\nproprioception wrap consumer; missing file → `{\"findings\": [{\"seat\": \"(all)\", \"status\": \"NEVER_RAN\"}]}`) ·\n`--selftest` (see below).\n\n## Redaction (scar #4 — non-negotiable)\n\nEvidence tails (≤160 chars) pass through `scrub()`: replace `Bearer\\s+\\S+`, `sk-[A-Za-z0-9_-]{8,}`,\nany 24+ char alnum/`._-` token, and the literal values of any env var whose NAME matches\n`(TOKEN|KEY|PASSWORD|SECRET)` that the probe itself loaded, with `<REDACTED>`. Credential values\nlive only in locals, never in the report, never in exceptions (wrap curl calls so the\nAuthorization header never appears in error strings). `security`/env.master reads must not echo.\n\n## Selftest (W84 blind-scan guard)\n\n`--selftest`: (a) classifier table — canned output samples per status (the real strings above:\nz.ai 1211 body, 529 body, codex token_revoked line, agy auth-failed line, deepseek 402, quota\nstrings) each classify correctly; (b) scrub() removes a planted fake token; (c) blind-scan guard:\na run that probed 0 seats must exit 2, never \"clean\"; (d) `--read-last` on a fixture file. Exit 0\nonly if all pass, print `SELFTEST OK — N checks`.\n\n## Tests — `scripts/tests/test_arsenal_probe.py` (pytest, NO live network/LLM calls)\n\nMonkeypatch subprocess/HTTP layers. Guilt AND innocence per classifier (e.g. `PONG` → LIVE never\nAUTH_DEAD; `1211` → MODEL_ERR never SHED; agy auth-fail + SSH_CONNECTION set → CONTEXT_AUTH,\nwithout ssh env → AUTH_DEAD). Transitions computed vs prev report. Strict exit semantics: required\nAUTH_DEAD → 1; CRED_UNAVAILABLE → 0. Redaction: fake `Bearer abc...` and a 40-char token never\nappear in report JSON. Atomic write leaves prev.json. `--read-last` fixtures (ok-set filtering,\nNEVER_RAN). Blind-scan exit 2. Aim ~20-30 focused tests. Style: mirror existing\n`scripts/tests/test_pending_arms_report.py` conventions (tmp_path, capsys).\n\n## Explicitly OUT of scope for the implementer (Fable does these after, funnel-in)\n\n- Edits to `scripts/proprioception.py` (guardian_freshness item + wrap registry entry)\n- Edits to `infra/healer/healer-run.sh` (receptor 4: run probe + transitions→ACTIONABLE + Telegram line)\n- `.github/workflows/immune-enforcement.yml` test wiring\n- `docs/runbooks/arsenal-probe.md`\n- PENDING-ARMS arming lines\n\n## Style contract\n\nPython 3.11 stdlib-only (subprocess, json, concurrent.futures, urllib.request for HTTP — no\nrequests dep), full type hints, logger-style stderr prints OK for a CLI tool, module docstring\nwith the one-line mission + scar references (#2 Esiste≠Armato, #4 secrets, W84 blind-scan).\nMatch the voice of `scripts/proprioception.py` (concise comments explaining WHY, not what).\n"}, "435945f89392b92ca558834d695b57058c697670": {"t": 1786068438.595821, "n": 8306, "h": "75d3df861bc8453a", "c": "# Arsenal Probe v1 — spec (DESIGN artifact, modus stage 2)\n\nDate: 2026-07-06 · Author: Fable 5 (interactive) · Implementer: Sonnet 5 (worktree ops-arsenal-probe)\nMandate: Zero 2026-07-06 \"sonda pesantemente\" — the quota-cascade can silently degrade to 2-deep\n(Codex 401 silent, agy keychain-bound, GLM 529/401, DeepSeek 402). Recommended since 2026-05-24,\nnever armed. This tool makes every AI seat's liveness EMPIRICAL, per-machine, per-context.\n\n## What it is\n\n`scripts/arsenal_probe.py` — a standalone, signal-only prober (never mutates anything outside\n`~/.organism/`). One run = live-probe every AI seat reachable from THIS machine, classify each\ninto an honest taxonomy, write a JSON report + heartbeat sidecar, detect transitions vs the\nprevious run. Consumed by: healer wrapper (Mini, 4h), proprioception (freshness + verdict reader),\nhumans (`--table`).\n\n## Seats & probe commands (each: subprocess, per-seat timeout, thread-parallel)\n\n| seat | probe | LIVE signal |\n|---|---|---|\n| claude | `$ARSENAL_CLAUDE_BIN -p \"Reply with exactly: PONG\" --model claude-sonnet-5` (default bin `/opt/homebrew/bin/claude`; strip `ANTHROPIC_API_KEY` from env, pass through `CLAUDE_CODE_OAUTH_TOKEN` if set) | `PONG` in stdout |\n| glm | token = `security find-generic-password -s glm-coding-plan-token -w` (NEVER logged); direct `curl` POST `https://api.z.ai/api/anthropic/v1/messages`, model `glm-5.2`, max_tokens 8, anthropic-version 2023-06-01 | HTTP 200 + `\"model\"` in body |\n| agy | `agy -p \"Reply with exactly: PONG\"` (which-resolve; also try `~/.local/bin/agy`) | `PONG` in stdout |\n| codex | `codex exec --sandbox read-only --skip-git-repo-check \"Reply with exactly: PONG\"` with stdin=DEVNULL (it blocks on open stdin) | `PONG` in stdout |\n| deepseek | key = parse `DEEPSEEK_API_KEY=` from `~/.openclaw/workspace/.env.master` (read in-python, value NEVER logged); `curl` POST `https://api.deepseek.com/chat/completions` model `deepseek-v4-flash`, max_tokens 1 | HTTP 200 |\n| ollama | `ollama list` → presence of `qwen3.5`; with `--live-gen` also 1-token `ollama run qwen3.5:9b` | model listed (or gen output) |\n| nlm | `nlm list notebooks` (which-resolve) | stdout parses as JSON (list or dict) |\n\nTimeouts (defaults, per-seat override via `--timeout SEC` global multiplier): claude 120s,\nglm 45s, agy 120s, codex 180s, deepseek 45s, ollama 30s (live-gen 120s), nlm 60s.\n\n## Status taxonomy (the heart — classify by OUTPUT CONTENT, never exit code alone; scar #2)\n\n- `LIVE` — the live signal observed.\n- `AUTH_DEAD` — output matches `401|Authentication Failed|token_revoked|refresh_token_reused|OAuth token|authentication failed` (codex/glm/claude/nlm auth class). For agy: only when NOT in an ssh context.\n- `CONTEXT_AUTH` — agy-style GUI-keychain failure while `SSH_CONNECTION`/`SSH_TTY` env present or no GUI session: the seat is dead IN THIS CONTEXT, possibly alive in GUI. Distinct because the cure differs (context, not credential).\n- `QUOTA_DEAD` — `out of extra usage|usage limit|quota|429|rate.limit|exhausted`.\n- `BALANCE_DEAD` — HTTP 402 / `Insufficient Balance` (deepseek class).\n- `MODEL_ERR` — `1211|Unknown Model` (glm class — config drift, e.g. the fable-5[1m] leak).\n- `SHED` — HTTP 529 / `overloaded` (provider-side transient load-shedding).\n- `TIMEOUT` — probe hit its per-seat timeout.\n- `CRED_UNAVAILABLE` — credential source absent/locked on this host (keychain locked → `security` exit 36/25300-class or empty in non-interactive; env.master missing; token file absent). NOT a seat death — a host limitation. Never strict-fails.\n- `NOT_INSTALLED` — binary not found on PATH (nor known fallback paths). Never strict-fails.\n- `UNKNOWN_ERR` — none of the above matched; evidence tail retained.\n\n`healthy` bool = status == LIVE. `context_limited` = status in {CONTEXT_AUTH, CRED_UNAVAILABLE, NOT_INSTALLED}.\nSTRICT-FAIL set (persistent + fixable): {AUTH_DEAD, BALANCE_DEAD, MODEL_ERR, UNKNOWN_ERR}.\n(QUOTA_DEAD/SHED/TIMEOUT are alarm-worthy transitions but transient — never exit-1.)\n\n## Machine awareness\n\n`machine_label()`: hostname → `m5` (Air-M5) / `pro` (Nuzantara) / `mini` (Mini-Pro2) / raw-lowercase.\nDefault seat selection = all 7; `--seats claude,glm,...` filters. REQUIRED map (used by `--strict`):\n`mini: [claude, glm, codex, ollama]` · `pro: [claude, codex, deepseek, ollama, nlm]` ·\n`m5: [claude, glm, agy, codex]` — a required seat in the STRICT-FAIL set → exit 1.\nNon-required seats always probed and reported (unless filtered), just never fail the run.\n\n## Outputs\n\n1. Report `~/.organism/arsenal/last.json` (atomic write via tempfile+rename; keep previous as\n `prev.json` before overwrite):\n```json\n{\"schema\": 1, \"machine\": \"mini\", \"ts\": \"2026-07-06T12:00:00Z\",\n \"context\": {\"ssh\": true, \"interactive\": false},\n \"seats\": [{\"seat\": \"glm\", \"status\": \"LIVE\", \"healthy\": true, \"latency_ms\": 1240,\n \"evidence\": \"HTTP 200 model glm-5.2\", \"required\": true}],\n \"transitions\": [{\"seat\": \"codex\", \"from\": \"LIVE\", \"to\": \"AUTH_DEAD\"}],\n \"summary\": {\"live\": 5, \"dead_strict\": 1, \"context_limited\": 1, \"transient\": 0}}\n```\n2. Heartbeat `~/.organism/last_seen/<machine>.arsenal_probe.json` — same shape as healer's:\n `{\"organ\": \"<machine>.arsenal_probe\", \"status\": \"ok|degraded\", \"note\": \"<summary line>\", \"ts\": ...}`\n (`degraded` iff any strict-fail among required seats).\n3. stdout: human table by default; `--json` full report; `--quiet` one summary line.\n\n## Flags\n\n`--seats CSV` · `--json` · `--table` (default) · `--quiet` · `--strict` · `--timeout MULT`\n(float multiplier on all per-seat timeouts) · `--live-gen` (ollama real generate) ·\n`--read-last` (NO probing: re-emit last.json as `{\"findings\": [seats not in ok-set]}` for the\nproprioception wrap consumer; missing file → `{\"findings\": [{\"seat\": \"(all)\", \"status\": \"NEVER_RAN\"}]}`) ·\n`--selftest` (see below).\n\n## Redaction (scar #4 — non-negotiable)\n\nEvidence tails (≤160 chars) pass through `scrub()`: replace `Bearer\\s+\\S+`, `sk-[A-Za-z0-9_-]{8,}`,\nany 24+ char alnum/`._-` token, and the literal values of any env var whose NAME matches\n`(TOKEN|KEY|PASSWORD|SECRET)` that the probe itself loaded, with `<REDACTED>`. Credential values\nlive only in locals, never in the report, never in exceptions (wrap curl calls so the\nAuthorization header never appears in error strings). `security`/env.master reads must not echo.\n\n## Selftest (W84 blind-scan guard)\n\n`--selftest`: (a) classifier table — canned output samples per status (the real strings above:\nz.ai 1211 body, 529 body, codex token_revoked line, agy auth-failed line, deepseek 402, quota\nstrings) each classify correctly; (b) scrub() removes a planted fake token; (c) blind-scan guard:\na run that probed 0 seats must exit 2, never \"clean\"; (d) `--read-last` on a fixture file. Exit 0\nonly if all pass, print `SELFTEST OK — N checks`.\n\n## Tests — `scripts/tests/test_arsenal_probe.py` (pytest, NO live network/LLM calls)\n\nMonkeypatch subprocess/HTTP layers. Guilt AND innocence per classifier (e.g. `PONG` → LIVE never\nAUTH_DEAD; `1211` → MODEL_ERR never SHED; agy auth-fail + SSH_CONNECTION set → CONTEXT_AUTH,\nwithout ssh env → AUTH_DEAD). Transitions computed vs prev report. Strict exit semantics: required\nAUTH_DEAD → 1; CRED_UNAVAILABLE → 0. Redaction: fake `Bearer abc...` and a 40-char token never\nappear in report JSON. Atomic write leaves prev.json. `--read-last` fixtures (ok-set filtering,\nNEVER_RAN). Blind-scan exit 2. Aim ~20-30 focused tests. Style: mirror existing\n`scripts/tests/test_pending_arms_report.py` conventions (tmp_path, capsys).\n\n## Explicitly OUT of scope for the implementer (Fable does these after, funnel-in)\n\n- Edits to `scripts/proprioception.py` (guardian_freshness item + wrap registry entry)\n- Edits to `infra/healer/healer-run.sh` (receptor 4: run probe + transitions→ACTIONABLE + Telegram line)\n- `.github/workflows/immune-enforcement.yml` test wiring\n- `docs/runbooks/arsenal-probe.md`\n- PENDING-ARMS arming lines\n\n## Style contract\n\nPython 3.11 stdlib-only (subprocess, json, concurrent.futures, urllib.request for HTTP — no\nrequests dep), full type hints, logger-style stderr prints OK for a CLI tool, module docstring\nwith the one-line mission + scar references (#2 Esiste≠Armato, #4 secrets, W84 blind-scan).\nMatch the voice of `scripts/proprioception.py` (concise comments explaining WHY, not what).\n"}}, "docs/audits/2026-05-02-cell-openclaw-brainstorm/07_openclaw_deep_research.md": {"9e8bb6055c42e72817fc7b01bc5035a2a7b7bf41": {"t": 1785746582.731969, "n": 34193, "h": "c11e0bae09aa0645", "c": "# OpenClaw Deep Research: Architecture, Capabilities & Current Usage\n**Date**: 2026-05-02 | **Version**: openclaw 2026.3.31 | **Local Install**: `~/.openclaw/lib/node_modules/openclaw/` | **Config**: `~/.openclaw/openclaw.json`\n\n**Audience**: Claude (reader) + cross-LLM reviewers (Codex GPT-5.5, Gemini 3.1 Pro, DeepSeek V4 Reasoner) preparing to reason about OpenClaw's role in Nuzantara automations.\n\n---\n\n## Executive Summary\n\nOpenClaw is a **Node.js-based personal AI gateway** running a WebSocket control plane (`ws://127.0.0.1:18789`) that routes inbound messages from 23+ messaging channels (Telegram, WhatsApp, Discord, Slack, iMessage, etc.) to isolated agents, which dispatch LLM requests with multi-model fallback + tool streaming. The runtime is process-managed by LaunchAgent on macOS; the agent itself is embedded (RPC mode). \n\n**Nuzantara is using OpenClaw for**:\n- Primary messaging channel: Telegram (enabled, receiving message timeouts)\n- Multi-LLM routing: MiniMax M2.7 (primary) → Kimi K2.6 → DeepSeek Chat → Ollama Qwen3.5\n- Persistent memory: memory-core plugin (storing observations from tool use)\n- Orchestration: lobster plugin (workflow DSL, enabled)\n- Voice: voice-call skill (enabled)\n- Custom extensions: claude-mem (v12.1.0, marketplace install)\n\n**Version Gap**: Installed version is 2026.3.31 (released ~Mar 31); latest is 2026.4.29 (released Apr 30). Recent releases include Knowledge Agents system (12.1.0 in claude-mem), improved Telegram channel timeouts, and model failover hardening.\n\n---\n\n## A. Architecture\n\n### Runtime Model\n\nOpenClaw is a **Node.js daemon** (v22.22.0, bundled) running:\n- **Gateway** (port 18789, loopback-only, hybrid reload on config change)\n- **Control UI** (Web dashboard, http://127.0.0.1:18789)\n- **Pi agent** (embedded RPC, not external process)\n- **Channel adapters** (Telegram: grammY, Discord: discord.js, WhatsApp: Baileys, etc.)\n\n**Process Management**:\n- macOS: LaunchAgent (`~/Library/LaunchAgents/openclaw.plist` — managed by Nuzantara's `monitor-air.log` cron)\n- KeepAlive policy: checked via heartbeat monitor every 1h (Nuzantara config: `heartbeat.every=\"1h\"`)\n- Uptime target: 24/7 (threshold exceeded May 2, 2026 at 01:13 UTC — gap analysis in logs)\n\n### Agent Abstractions\n\n**Agent Definition** (`agents.list[]`):\n- Nuzantara has 2 agents:\n 1. **main**: primary, Telegram-bound, sandbox=off, model routing primary→fallback chain\n 2. **coder**: isolated workspace, sandbox=off, web tools denied (no web_search, web_fetch, browser)\n\n**Model Routing**:\n```\nmain:\n primary: openrouter/minimax/minimax-m2.7\n fallbacks: [kimi-k2.6, deepseek-chat, ollama/qwen3.5:9b]\n\ncoder:\n primary: openrouter/qwen/qwen3-max\n fallbacks: [qwen3.6-plus, kimi-k2.6, deepseek-reasoner, qwen3.5:9b]\n```\n\n**Fallback Chain Logic**:\n- Model request → try primary; if fails (auth, rate_limit, not_found) → try next fallback\n- Auth cooldown: 300s lockout after 10 auth failures in 60s window (gateway.auth.rateLimit)\n- Rate-limit cooldown: automatic provider cooldown (all-profiles-unavailable state) visible in logs (Mar 1-2, Google Gemini CLI exhausted)\n\n**Sandbox Modes**:\n- Default: `non-main` (separate container per agent workspace)\n- Nuzantara: `mode=off` for both agents (no isolation) + `prune.idleHours=24` (clean up idle containers)\n\n### Plugin System\n\nPlugins extend gateway capabilities via allow-list:\n\n**Loaded Plugins** (`plugins.entries`):\n- **memory-core** (enabled): observation feed + persistent storage\n- **memory-lancedb** (disabled): vector search backend (not active)\n- **lobster** (enabled): workflow orchestration DSL\n- **llm-task** (enabled): multi-step LLM orchestration\n- **voice-call** (enabled): phone/VOIP integration\n- **telegram** (implicit, channel adapter)\n\n**Plugin Trust Model**:\n- Plugins in `~/.openclaw/extensions/` are scanned; only those in `plugins.allow[]` are loaded\n- Nuzantara's installed plugin: `claude-mem` (v12.1.0) in `~/.openclaw/extensions/claude-mem/`\n- **Trust Warning**: claude-mem flagged in gateway logs as \"loaded without install/load-path provenance\" (implies manual fs install, not via package manager)\n\n### Skill System\n\nSkills add tools to agents via hooks + HTTP API.\n\n**Bundled Skills** (`~/.openclaw/lib/node_modules/openclaw/skills/`):\n- goplaces (Google Places API), xurl (URL shortener), voice-call (Twilio), notion, slack, discord, trello, spotify-player, etc.\n\n**Nuzantara Enabled Skills** (`skills.entries`):\n- **goplaces**: enabled (env: GOOGLE_PLACES_API_KEY)\n- **xurl**: enabled\n- **voice-call**: enabled\n- **notion**: enabled (env: NOTION_API_KEY)\n- **antigravity**: enabled\n\n**Custom Skills Directory**: `/Users/nuzantara/Desktop/nuzantara/apps/nuzantara-mcp/skills` (loaded via `skills.load.extraDirs[]`)\n\n### Channel Bindings\n\n**Telegram Channel** (enabled, dmPolicy=open):\n- **BotToken**: configured (redacted in config, present in plaintext)\n- **Allowed Senders**: `allowFrom=[\"*\"]` (open to all)\n- **Streaming**: disabled (responses sent as complete message blocks)\n- **Timeout**: 60s per message\n- **Retry**: 2 attempts, min 5s delay, max 60s delay, 0.5 jitter\n- **Known Issues**:\n - Network timeouts (ETIMEDOUT / EHOSTUNREACH observed Mar 1-2, 30s+ waits)\n - BOT_COMMANDS_TOO_MUCH (Telegram API limit: max 100 commands, Nuzantara registering 92-97, dropping 19-20 on each attempt)\n\n**Other Channels**:\n- WhatsApp, Discord, Slack, Signal, iMessage (BlueBubbles), IRC, Teams, Matrix: all disabled in config\n\n### Memory Model\n\n**memory-core** plugin:\n- SQLite database: `~/.openclaw/agents/main/agent/memory.db` (not visible in config, inferred from logs)\n- Observation feed: tool_use events → semantic summaries → injected into future sessions\n- Token-aware: context pruning mode=cache-ttl (1h TTL), compaction mode=safeguard (96k reserve floor, 40% history max)\n- Worker Service: runs on separate port (likely 37777 per claude-mem docs), managed by Bun\n- Vector backend: Chroma (if memory-lancedb enabled; currently off)\n\n### mcporter Integration\n\n**Tool Surface**:\n- Built-in: browser, canvas, nodes, cron, sessions\n- MCP tools: claude-mem (`search`, `timeline`, `get_observations`), custom skills\n\n**Safety Allowlist** (`tools.exec.safeBins[]`):\n```\nls, cat, tail, which, nuzantara-ops, mcporter\n```\n- Only these binaries can be executed without sandbox approval\n- `mcporter` is whitelisted (allows MCP tool invocation from agent)\n- **Known Issue**: mcporter allowlist audited Mar 22; safeBins entries not matching plugin config (audit log shows mismatch fixed via plugin reload)\n\n### Lobster Workflows\n\n**Lobster Plugin** (enabled):\n- DSL for multi-step orchestration\n- Workflow files: `~/.openclaw/workspace/.lobster/*.yml` (inferred)\n- Nuzantara logs show no active workflows, but plugin enabled for future use\n\n### Sandbox & Safety\n\n**Exec Approval Socket** (`exec-approvals.sock`):\n- Manual approval flow for unsafe commands\n- Not actively used in Nuzantara (sandbox=off, so no approvals needed)\n\n**Circuit Breaker** (`tools.loopDetection`):\n- Enabled globally (threshold=10 repeats)\n- Detectors:\n - **genericRepeat**: catch identical tool calls in sequence\n - **knownPollNoProgress**: detect polling without state change\n - **pingPong**: detect tool A → B → A cycles\n- Nuzantara: enabled, no circuit trips observed (logs clean on this metric)\n\n---\n\n## B. Current Capabilities\n\n### Agentic Tasks\n\n**Single-turn**: `openclaw agent --message \"query\"` (CLI or Gateway RPC)\n**Multi-turn**: Session state preserved in memory; model compaction + context pruning per config\n**Persistent Loops**: Supported via Lobster workflows (enabled but not in use)\n\n### Tool Use\n\n| Tool | Enabled | Notes |\n|------|---------|-------|\n| **web_search** | Yes | Integrated |\n| **web_fetch** | Yes | Integrated |\n| **browser** | No (coder agent denies) | Chrome control, snapshots, actions |\n| **exec** | Partial (safeBins only) | ls, cat, tail, nuzantara-ops, mcporter |\n| **file ops** | Via browser canvas | Not direct agent tool |\n| **image** | Yes (image model routing) | Primary: ollama/qwen2.5vl:7b, fallback: openrouter/qwen3-vl |\n| **canvas** (macOS) | Yes | A2UI protocol, push/reset/eval/snapshot |\n| **cron** | Yes | 24 jobs max (Nuzantara shows 0 active, scheduler may be frozen as of Apr 30) |\n| **custom MCP** | Yes (mcporter) | Route to local/remote MCP servers |\n\n**Multi-LLM Routing**: Each agent has primary + 3 fallback models; per-model params (temperature, max_tokens) configurable\n\n### Multi-LLM Routing\n\n**Provider Configuration**:\n- **openrouter**: native support, baseUrl=https://openrouter.ai/api/v1, 6 models configured\n- **deepseek**: native support, baseUrl=https://api.deepseek.com/v1, 2 models configured\n- **ollama**: local, baseUrl=http://127.0.0.1:11434, 0 models pre-configured (dynamic discovery)\n- **anthropic**: **NOT configured** (no ANTHROPIC_API_KEY in env; Nuzantara policy: banned)\n- **openai** (via openrouter): shim via openrouter provider\n\n**Per-Model Tuning**:\n```\ntemperature: 0.2–0.4 (reasoning), 0.3–0.4 (chat)\nmax_tokens: 4096–8192\nollama-specific: keep_alive=10m (memory cache), top_p=0.9, top_k=20\n```\n\n**Fallback Behavior**: \n- Try primary; if fails, cascade through fallbacks\n- Rate-limit cooldown (300s) applied per provider\n- Auth errors caught and retried with different auth profile (if multi-profile configured)\n\n### Persistent Memory\n\n**memory-core** (enabled):\n- **Lifecycle Hooks**: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd\n- **Observation Compression**: tool_use events → semantic summaries (via Claude API or local LLM)\n- **Injection Strategy**: progressive disclosure (layer 1: index; layer 2: timeline; layer 3: full details)\n- **Search Tools**: `mem-search`, `timeline`, `get_observations` (MCP interface)\n- **Durability**: SQLite persistent store, survives session/process restarts\n\n### Channel I/O\n\n**Telegram** (primary):\n- Inbound: message events → matched to agent via binding (main agent, Telegram channel)\n- Outbound: agent reply → formatted as Telegram message → sent via botToken\n- **State**: connected (logs show recent message processing up to May 2 01:13)\n- **Timeouts**: retry logic (2 attempts, 5–60s backoff) masks network latency\n\n### Voice-Call\n\n**voice-call Skill** (enabled):\n- Integration: Twilio (inferred from OpenClaw docs; not explicitly in Nuzantara config)\n- Capabilities: accept voice calls, stream audio, TTS output (ElevenLabs or system fallback)\n- **Status in Nuzantara**: enabled but not actively tested\n\n### Browser Automation\n\n**Browser Tool** (available, not in use by main agent):\n- Chrome/Chromium control (dedicated OpenClaw instance)\n- Actions: snapshot, click, type, scroll, upload, profile switching\n- **Nuzantara**: coder agent has browser denied via `tools.deny=[\"browser\"]`\n\n### Workflow Orchestration (Lobster)\n\n**Lobster DSL** (enabled, not in active use):\n- YAML-based multi-step orchestration\n- Syntax: task dependencies, conditional routing, parallel execution\n- **Nuzantara**: loaded but no workflows observed in logs\n\n### Subagents\n\n**Subagent Pattern**: supported via agent routing (`bindings[]` match rules)\n- **Nuzantara**: 2 agents defined (main, coder); no inter-agent delegation observed\n- **Limitation**: agents are isolated; no direct parent-child RPC (agents are standalone)\n\n### Cron / Scheduled Jobs\n\n**Cron Tool**:\n- API: `POST /gateway/cron` or `openclaw cron add <schedule> <command>`\n- Max 24 jobs per gateway\n- **Nuzantara Status**: 0 active jobs logged (scheduler may be frozen since Apr 30 per audit notes)\n\n### Hooks\n\n**7 Lifecycle Hooks** (claude-mem):\n- SessionStart, UserPromptSubmit, PostToolUse, Summary, SessionEnd, PreToolUse, Stop\n- **Nuzantara Use**: memory-core hooks active (observation capture, summary generation)\n\n### Plugin Development\n\n**Plugin Template**:\n- Entry point: `plugin/index.ts` or `dist/index.js`\n- Hooks: export `hooks: { SessionStart, UserPromptSubmit, ... }`\n- MCP Tools: export `tools: [ ... ]` (JSON-RPC 2.0 interface)\n- **Nuzantara Custom Skills**: loaded from `/Users/nuzantara/Desktop/nuzantara/apps/nuzantara-mcp/skills/` (no plugins observed)\n\n### Embedded Agent Runtime\n\n**Pi Agent** (embedded, not delegated):\n- Runs in-process with gateway (RPC loopback)\n- Session state: maintained in gateway memory (not persistent across restarts without memory plugin)\n- Tool dispatch: streaming responses back to gateway → channel output\n\n---\n\n## C. What's NEW (Recent OpenClaw Releases)\n\n| Release | Date | Highlights |\n|---------|------|-----------|\n| **v2026.4.29** | Apr 30, 2026 | Stability improvements, auth profile migration docs |\n| **v2026.4.09** | Apr 9, 2026 | Knowledge Agents system (6 new MCP tools: build_corpus, prime_corpus, query_corpus, etc.) |\n| **v2026.3.31** | Mar 31, 2026 | **[INSTALLED]** DM pairing default, Telegram net timeout handling, provider enum changes |\n| **v2026.1.5** | Jan 5, 2026 | Image model config, agent.imageModel + fallbacks, model shorthands (opus, sonnet, gpt, gemini) |\n\n**Most Notable for Nuzantara**:\n- **Knowledge Agents** (v2026.4.09): Compile filtered corpus from claude-mem observations, query conversationally. 6 new MCP tools (build_corpus, list_corpora, prime_corpus, query_corpus, rebuild_corpus, reprime_corpus). **Not yet exploited.**\n- **Auth Profile Migration** (v2026.3.31 → v2026.4.29): DM pairing now default (requires approval codes); legacy \"open to all\" requires explicit dmPolicy=open + allowFrom=[\"*\"]. **Nuzantara already configured.**\n- **Telegram Timeout Hardening** (v2026.3.31): Retry + backoff for network errors (30s+ waits masked). **Experienced but handled gracefully.**\n\n**Claude-Mem Integration** (v12.1.0, installed via marketplace):\n- Knowledge agents now supported (native corpus build, prime, query MCP tools)\n- Smart-explore extended to 24 languages\n- File-read decision gate (PreToolUse hook blocks redundant reads using observation timeline)\n\n---\n\n## D. Comparison vs Alternatives\n\n| Dimension | OpenClaw | cron-agent-python (Nuzantara) | LangChain/LangGraph | Anthropic Agent SDK (direct) |\n|-----------|----------|------|-----------|---------|\n| **Primary UX** | Gateway + multi-channel | Python cron + custom CLI | Chain/graph DSL | Messages API |\n| **Multi-LLM** | Native (fallback chains) | Manual (explicit per-agent config) | LLMChain abstraction | No native support |\n| **Persistent Memory** | Plugin-based (memory-core) | NotebookLM (external) | No built-in | No built-in |\n| **Channel Binding** | 23+ integrations (Telegram, Discord, etc.) | CLI-only / webhooks | None (user's responsibility) | None |\n| **Workflow Orchestration** | Lobster DSL | Python task queue | LangGraph (DAG-based) | Claude Agent SDK (new) |\n| **Tool Streaming** | Yes (block streaming) | Manual | Yes (via tool classes) | Yes (native) |\n| **Sandbox** | Exec approval socket | None (direct subprocess) | None | None (external runner) |\n| **State Persistence** | SQLite (via plugins) | Disk-backed (JSON/state files) | Not built-in | Not built-in |\n| **Scaling** | Single gateway (local) | Multi-machine (cron dispatch) | App-level (doesn't scale infra) | Delegated to cloud provider |\n| **Ease of Setup** | `openclaw onboard` (90s) | Python venv + config | npm install + boilerplate | anthropic SDK (Python/JS) |\n\n**OpenClaw's Unique Value Propositions**:\n1. **Multi-channel inbox**: Single control plane for Telegram, WhatsApp, Discord, Slack, iMessage, IRC, Teams, Signal, etc.\n2. **Persistence out-of-the-box**: memory-core plugin captures observations + injects into future sessions (no manual logging)\n3. **Native fallback chains**: multi-model routing with per-provider cooldowns + auth management\n4. **Embedded + local**: runs on your machine (no cloud), binds to loopback only (no public expose by default)\n\n**Where OpenClaw Loses**:\n1. **No cloud sync**: single-machine deployment (no multi-region HA out of the box)\n2. **Provider enum rigidity**: only supports explicit providers (openrouter, deepseek, ollama, anthropic); no generic OpenAI-compatible override until v2026.3.31+ (baseUrl interpolation)\n3. **Command limit (Telegram)**: max 100 commands registered; Nuzantara hitting 92-97, dropping 19-20 on each sync\n4. **Scheduler frozen** (apr 30+): cron jobs not running (24-job queue inert); cause unknown\n\n**vs cron-agent-python** (Nuzantara parallel):\n- cron-agent-python: 19 active strategies, each runs independently on schedule; state decoupled (disk-backed JSON)\n- OpenClaw: single agent with multi-LLM routing; state unified in memory-core\n- **Recommendation**: cron-agent-python excels for **independent, scheduled tasks** (classification, analysis per-document); OpenClaw excels for **interactive, multi-turn conversations** with memory (Telegram chatbot + persistent context)\n\n---\n\n## E. Scaling Characteristics\n\n### Concurrency\n\n**Max Concurrent Agents**: 1 gateway, N agents (in Nuzantara: 2)\n- **Per-Agent Sessions**: Unbounded (session state pruned on demand)\n- **Concurrent Requests**: Limited by LLM provider rate limits (OpenRouter: ~100 req/min; DeepSeek: API doc unclear; Ollama: local, unbounded)\n- **Telegram Message Queue**: Sequential (one message processed at a time, 60s timeout → retry → backoff)\n\n### Memory Footprint\n\n| Component | RSS (Idle) | RSS (Active) | Notes |\n|-----------|-----------|-------------|-------|\n| Gateway process | ~200–300 MB | ~400–600 MB | Node.js runtime + channel adapters |\n| memory-core SQLite | ~10 MB | ~50+ MB | Depends on observation count + vector index |\n| Chroma (if enabled) | N/A | ~200+ MB | Python subprocess (uv-managed), not active in Nuzantara |\n| Total (Nuzantara) | ~250 MB | ~500+ MB | Measured via LaunchAgent memory monitor |\n\n### Latency Overhead\n\n| Operation | Latency | Notes |\n|-----------|---------|-------|\n| Gateway routing (inbound message) | <10ms | WebSocket demux + binding match |\n| Agent dispatch (to LLM) | <50ms | HTTP to OpenRouter/DeepSeek |\n| LLM request | 500ms–5s | Depends on model, fallback chain depth |\n| Response streaming (Telegram) | 1–30s | Tool execution + LLM generation + retry backoff |\n| Memory injection (PreToolUse) | 100–500ms | SQLite query + compression (if observation reuse) |\n\n**Gateway overhead is negligible** (<10% of E2E latency).\n\n### State Persistence\n\n**Durable Across Crashes**: Yes (if memory-core enabled)\n- Session state saved to SQLite after each tool_use hook\n- Observation timeline reconstructed on SessionStart\n\n**Graceful Shutdown**: LaunchAgent KeepAlive policy\n- monitored by `launchctl unload/load` (manual) or cron watchdog (Nuzantara)\n- Timeout: 30s (hard kill if not responsive)\n\n### Crash Recovery\n\n**KeepAlive=true** (Nuzantara config):\n- LaunchAgent auto-restarts gateway on exit (non-zero exit code)\n- State surviving: memory-core observations (durable), in-flight messages (lost, retried by Telegram)\n- **Observed**: gateway crashes not logged; LaunchAgent restarts appear transparent\n\n### Multi-Instance Topology\n\n**Federation**: Not supported natively\n- **Workaround**: run multiple gateways on different ports (18789, 18790, ...), but agents are isolated per gateway\n- **Nuzantara**: single gateway (port 18789); no multi-instance setup\n\n---\n\n## F. Security Model\n\n### Loopback-Only Binding\n\n**gateway.bind=loopback** (Nuzantara default):\n- Gateway listens on 127.0.0.1:18789 (localhost only)\n- No public internet exposure (requires SSH tunnel or Tailscale to access from remote)\n- **Threat Model**: protects against direct internet attacks; assumes local machine is secure\n\n### Auth Token & Rate Limiting\n\n**Token** (gateway.auth.token):\n- Long random string (example: 64 char base64)\n- Required in WebSocket `?token=...` query param or HTTP `Authorization: Bearer <token>` header\n- **Storage**: plaintext in `~/.openclaw/openclaw.json` (same permissions as SQLite DB)\n- **Rotation**: manual edit + `openclaw reload` (no auto-rotation tool visible)\n\n**Rate Limiting**:\n- **maxAttempts**: 10 failures in 60s window (Nuzantara config)\n- **lockoutMs**: 300s (5 min) lockout after threshold\n- **Scope**: per-IP (loopback is single IP, so affects all local clients equally)\n\n### exec-approvals.sock\n\n**Manual Approval Flow**:\n- Unsafe exec commands → `exec-approvals.sock` (Unix domain socket, `/tmp/exec-approvals-<pid>.sock`)\n- User approval via CLI: `echo \"approve <exec-id>\" | nc -U /tmp/exec-approvals-<pid>.sock`\n- **Nuzantara Status**: sandbox=off for both agents, so approval flow never triggered\n\n### Sandbox Modes\n\n| Mode | Isolation | Use Case |\n|------|-----------|----------|\n| **off** | None | Development, trusted local use |\n| **non-main** | Per-agent container | Multi-agent isolation (default agents.defaults.sandbox.mode) |\n| **full** | Per-session container | Maximum isolation (experimental) |\n\n**Nuzantara**: mode=off (no isolation). Rationale: single user, local machine, trusted workflows. Trade-off: agents can interfere (shared filesystem, process space).\n\n### Safety Bins Allowlist\n\n**Tools.exec.safeBins**:\n```\nls, cat, tail, which, nuzantara-ops, mcporter\n```\n- Only these binaries bypass approval flow\n- Others require exec approval (if sandbox=non-main or full)\n- **Known Issue** (Mar 22): safeBins not matching plugin config; resolved via plugin reload\n\n**Attack Surface**: agent can only run safe binaries; can't execute arbitrary commands (unless sandbox=off + agent has write access to filesystem, which it does in Nuzantara)\n\n### Plugin Trust Model\n\n**Plugin Loading** (`plugins.allow[]`):\n- Whitelist-based: only plugins in allow-list are loaded\n- Nuzantara loads: memory-core, lobster, llm-task, voice-call (implicit)\n- claude-mem: loaded from `~/.openclaw/extensions/claude-mem/` (manual install, not in core plugins.allow)\n\n**Trust Signals**:\n- Official plugins: signed releases, open-source GitHub\n- User plugins (Nuzantara custom skills): loaded from `/Desktop/nuzantara/apps/nuzantara-mcp/skills/` (same machine, implicit trust)\n- **Warning**: claude-mem flagged as \"loaded without install/load-path provenance\" → suggests side-load outside normal package path\n\n### Secret Handling\n\n**API Key Interpolation**:\n```json\n{\n \"providers\": {\n \"openrouter\": {\n \"apiKey\": \"${OPENROUTER_API_KEY}\"\n }\n }\n}\n```\n\n**Resolution**:\n1. Check environment variable (shellEnv=true in Nuzantara config, shell timeout 15s)\n2. Check EnvironmentVariables from LaunchAgent plist (if running as daemon)\n3. Fall back to plaintext in config (NOT recommended)\n\n**Nuzantara Config**:\n- OPENROUTER_API_KEY, DEEPSEEK_API_KEY, GOOGLE_PLACES_API_KEY, NOTION_API_KEY → in `~/.openclaw/.env` (not shown, but inferred from skills.env)\n- **Risk**: if `.env` checked into git, keys exposed\n\n---\n\n## G. Operational Model\n\n### LaunchAgent Management\n\n**Service File**: `~/Library/LaunchAgents/openclaw.plist` (macOS)\n- **Label**: com.openclaw.daemon\n- **Program**: node (bundled in ~/.openclaw/tools/node-v22.22.0/bin/node)\n- **KeepAlive**: true (auto-restart on exit)\n- **ThrottleInterval**: 30s (min time between restarts after crash)\n- **RunAtLoad**: true (start on login)\n\n**Manual Control**:\n```bash\nlaunchctl load ~/Library/LaunchAgents/openclaw.plist\nlaunchctl unload ~/Library/LaunchAgents/openclaw.plist\nlaunchctl start com.openclaw.daemon\nlaunchctl stop com.openclaw.daemon\n```\n\n**Nuzantara Monitoring**: cron job `monitor-air.log` (implied by logs; monitors for crashes + auto-restart failures)\n\n### Logs\n\n| Log | Path | Rotation | Notes |\n|-----|------|----------|-------|\n| **gateway.log** | `~/.openclaw/logs/gateway.log` | Yes (daily) | Main diagnostic output (diagnostic, channel, agent events) |\n| **gateway.err.log** | `~/.openclaw/logs/gateway.err.log` | Yes (daily) | Errors, warnings, stack traces |\n| **commands.log** | `~/.openclaw/logs/commands.log` | N/A | Slash command executions |\n| **config-audit.jsonl** | `~/.openclaw/logs/config-audit.jsonl` | Append | Config change audit trail |\n\n**Log Level**: `logging.level=info` (Nuzantara config); controlled via `openclaw config set logging.level=debug` (or restart with env var)\n\n**Retention**: no explicit TTL in config; logs grow unbounded (gateway.log: 21.7 GB as of May 2, gateway.err.log: 288 MB). **Risk**: disk space exhaustion.\n\n### Health Endpoint\n\n**Endpoint**: `GET /healthz` (Gateway)\n- **Response**: `{ \"status\": \"ok\" }` (200 OK) or 500 on error\n- **Nuzantara Use**: implied by monitor-air; likely polled every 5–60s\n\n### Metrics Export\n\n**Prometheus**: Not built-in to OpenClaw (as of v2026.3.31)\n- **Workaround**: parse logs with regex + ship to external metrics service\n- **Nuzantara**: no custom metrics exporter observed\n\n### Debugging\n\n**Verbose Mode**: `openclaw gateway --verbose` (CLI flag)\n**Log Level Override**: `OPENCLAW_LOG_LEVEL=debug` (env var at startup)\n**RPC Tracing**: gateway logs all RPC calls (agent → channel) with stack traces on failure\n\n**Trace an Agent Decision**:\n1. Enable debug logging\n2. Send message via Telegram\n3. Check gateway.log for SessionStart → UserPromptSubmit → tool_use → agent response flow\n4. Correlate with memory-core observations (check `.openclaw/agents/main/agent/memory.db` if needed)\n\n### Update Process\n\n**Manual Update**:\n```bash\nnpm install -g openclaw@latest\nopenclaw onboard --update # or --install-daemon to refresh daemon\n```\n\n**Safe Update (Nuzantara)**:\n1. `launchctl stop com.openclaw.daemon` (halt gateway)\n2. Backup `~/.openclaw/openclaw.json` + `~/.openclaw/workspace/`\n3. `npm install -g openclaw@latest`\n4. Review CHANGELOG for breaking changes\n5. Run `openclaw doctor` (auto-migrate config if needed)\n6. `launchctl start com.openclaw.daemon` (restart)\n\n**Auto-Update**: Not configurable in v2026.3.31; manual only.\n\n---\n\n## H. Known Issues & Pitfalls\n\n### 1. Provider Enum Rigidity\n\n**Issue**: openai-completions API wrapper only recognizes explicit `api: \"openai\"` (no generic override)\n\n**Impact**: Can't use arbitrary OpenAI-compatible backends (e.g., vLLM, LocalAI) without forking\n\n**Workaround** (Nuzantara): Use `api: \"openai-completions\"` with openrouter shim (all models exposed via openrouter baseURL)\n\n**Status**: Likely fixed in v2026.4.29 (baseUrl interpolation mentioned in changelog), but not tested against Nuzantara's installed v2026.3.31\n\n### 2. Secret Interpolation (`${VAR}` Only in env)\n\n**Issue**: `apiKey: \"${VAR}\"` resolves ONLY if VAR is in EnvironmentVariables (from LaunchAgent plist or shell env at startup), NOT from `.env` file or inline plaintext\n\n**Impact**: Easy to forget to export env var → silent fallback to missing key → auth failures\n\n**Example** (Nuzantara):\n```json\n{\n \"openrouter\": {\n \"apiKey\": \"${OPENROUTER_API_KEY}\" // resolves to env var if set, else null\n }\n}\n```\n\n**Workaround**: Explicitly set in LaunchAgent plist `EnvironmentVariables` section or `source ~/.openclaw/.env` before `launchctl load`\n\n### 3. mcporter Allowlist Quirks\n\n**Issue** (Mar 22 audit log): safeBins entries `ls, cat, tail, which, nuzantara-ops, mcporter` not matching plugin config → mcporter tool calls rejected\n\n**Root Cause**: Plugin loading race condition (safeBins checked before plugin registration completes)\n\n**Status**: Resolved in Nuzantara by manual reload (launchctl stop/start)\n\n**Fix Expected**: v2026.4.29+ (not tested)\n\n### 4. Plugin Trust Warnings\n\n**Issue**: claude-mem loaded from `~/.openclaw/extensions/claude-mem/` without install/load-path provenance\n\n**Impact**: Minor (code review recommended for untrusted sources); claude-mem is open-source + widely used\n\n### 5. Telegram Network Timeouts\n\n**Issue**: `getUpdates` request times out after 30s; retry backoff (5s → 60s) masks latency but creates gaps in message delivery (Apr 30 + May 2 logs show repeated timeouts)\n\n**Impact**: Inbound messages may be delayed 30–120s during network flaps\n\n**Cause**: Likely ISP or Telegram API throttling (Nuzantara in Bali, geographically distant from Telegram servers)\n\n**Workaround**: Increase timeout (Telegram: `timeoutSeconds=120`) or use webhook polling (if supported in next version)\n\n**Status**: Retry logic + backoff now in v2026.3.31; gracefully handled but not ideal UX\n\n### 6. BOT_COMMANDS_TOO_MUCH (Telegram API Limit)\n\n**Issue**: Telegram API limits /command menu to 100 entries; Nuzantara registering 92–97 commands (skills + custom), dropping 19–20 on each sync\n\n**Impact**: Some skills unavailable in Telegram /command autocomplete menu (but still callable via message mention)\n\n**Log Evidence**:\n```\n[telegram] rejected 92 commands (BOT_COMMANDS_TOO_MUCH); retrying with 73.\n[telegram] accepted 73 commands after BOT_COMMANDS_TOO_MUCH (started with 92; omitted 19).\n```\n\n**Root Cause**: Too many skills enabled (goplaces, xurl, voice-call, notion, antigravity) + OpenClaw bundled commands\n\n**Workaround**: Disable low-use skills in `skills.entries` (set enabled=false for unused ones)\n\n**Fix**: None in OpenClaw core (Telegram API limit). Requires operator intervention.\n\n### 7. Scheduler Frozen (Apr 30+)\n\n**Issue**: Cron jobs not running; 24-job queue inert\n\n**Evidence**: No cron logs in gateway.log since Apr 30; cron tool still responds to queries but doesn't execute\n\n**Impact**: Scheduled tasks (batch processing, daily summaries) missed\n\n**Likely Cause**: \n- Scheduler process crash (separate thread)\n- Job queue lock contention\n- State corruption after config reload\n\n**Status**: Unknown (no debugging done); requires `openclaw doctor` + manual job purge/recreate\n\n**Workaround**: Migrate to external cron runner (separate Python process with its own scheduler) or upgrade to v2026.4.29+\n\n### 8. Context Compaction False Positives\n\n**Issue**: context pruning + compaction may discard relevant observations if TTL expires before session end\n\n**Config** (Nuzantara):\n```\ncontextPruning.mode=cache-ttl, ttl=1h\ncompaction.mode=safeguard, reserveTokensFloor=96k\n```\n\n**Impact**: Multi-day sessions lose early context after 1h, even if still relevant\n\n**Workaround**: Increase TTL to 24h or use `mode: \"never\"` (disables pruning, risks context explosion)\n\n---\n\n## I. Roadmap & Future Direction\n\n**Public Roadmap**: https://github.com/openclaw/openclaw/issues (GitHub Issues, no official roadmap doc)\n\n**Recent PR Signals** (inferred from changelog):\n- **Knowledge Agents** (v2026.4.09): Building queryable \"brains\" from observation history (corpus build + prime + query)\n- **Auth Profile System** (v2026.3.31+): Multi-profile support + role-based failover (not fully enabled in Nuzantara)\n- **DM Pairing Security**: Default dmPolicy=pairing (require approval code for unknown senders) — security-first direction\n- **Provider Enum Expansion**: Support for more providers + generic OpenAI-compatible override (v2026.4.29 changelog hints)\n\n**Likely Future Work** (speculative):\n1. **Cloud Sync**: Multi-region HA + state replication (not in current roadmap, but enterprise feature request)\n2. **Structured Output**: Typed tool responses (Zod schema validation) — currently working, likely formalization\n3. **Voice Wake Improvements**: More language support, custom wake words (v2026.3.31 has voice-call, room for improvement)\n4. **Scheduler Stability**: Fix cron job queue race conditions (based on Apr 30 frozen state)\n5. **Knowledge Agent UI**: Web viewer for corpus browsing + semantic search (claude-mem v12.1.0 foundation)\n\n---\n\n## Summary Table: What Nuzantara Is Using vs. Available\n\n| Feature | Available | Enabled | Status |\n|---------|-----------|---------|--------|\n| **Multi-LLM Routing** | Yes (4 providers) | Yes (fallback chain) | Working, no issues |\n| **Persistent Memory** | Yes (memory-core) | Yes | Working, observations injected |\n| **Telegram Channel** | Yes | Yes | Working, but network timeouts every 30s+ |\n| **Workflow Orchestration** | Yes (Lobster) | Enabled but unused | Ready to use, no barriers |\n| **Knowledge Agents** | Yes (new in v12.1.0) | No | **NOT EXPLOITED** (v2026.4.09 feature) |\n| **Voice-Call Integration** | Yes | Yes | Enabled, not actively tested |\n| **Browser Automation** | Yes (main agent denied) | Partial (coder agent) | Available for coder agent |\n| **Cron Scheduler** | Yes (24 job limit) | Yes but frozen | **BROKEN** since Apr 30 |\n| **Custom Skills** | Yes (extraDirs) | Partially | `/nuzantara-mcp/skills/` not fully synced |\n| **Canvas (macOS)** | Yes | Implicit | Available if using macOS |\n| **Multi-Channel** | Yes (23 channels) | Only Telegram | Others disabled; can be enabled |\n\n---\n\n## Recommendations for Nuzantara\n\n1. **Upgrade to v2026.4.29** (from v2026.3.31): Knowledge Agents system ready; cron scheduler likely fixed; auth profiles stabilized\n2. **Exploit Knowledge Agents**: Build corpus from claude-mem observations; enable conversational history replay (query_corpus tool)\n3. **Fix Cron Scheduler**: Debug Apr 30 freeze; if unresolved in v2026.4.29, fallback to external cron-agent-python runner\n4. **Reduce Telegram Commands**: Disable unused skills (elite-longterm-memory, bluebubbles, discord, slack, spotify, trello) to stay under 100-command limit\n5. **Increase Telegram Timeout**: Raise `timeoutSeconds=120` to mask network latency (or implement webhook polling in next OpenClaw version)\n6. **Monitor Memory Growth**: Track `~/.openclaw/logs/gateway.log` size (currently 21.7 GB); implement log rotation or external log shipping\n7. **Test Voice-Call Skill**: Verify Twilio integration if voice interaction is planned\n8. **Sync Custom Skills**: Ensure `/nuzantara-mcp/skills/` fully registered with OpenClaw (check skills.entries for all custom entries)\n\n---\n\n## Files Referenced\n\n- **Config**: `/Users/nuzantara/.openclaw/openclaw.json` (v2026.3.31 schema)\n- **Installation**: `/Users/nuzantara/.openclaw/lib/node_modules/openclaw/`\n- **Logs**: `~/.openclaw/logs/gateway.log`, `gateway.err.log`\n- **Memory DB**: `~/.openclaw/agents/main/agent/memory.db` (SQLite, not visible in config)\n- **Workspace**: `~/.openclaw/workspace/` (markdown docs + agent state)\n- **Extensions**: `~/.openclaw/extensions/claude-mem/` (installed plugin)\n- **GitHub**: https://github.com/openclaw/openclaw (releases, issues, docs)\n- **Documentation**: https://docs.openclaw.ai (official, updated for v2026.4.29)\n- **claude-mem Marketplace**: `/Users/nuzantara/.claude/plugins/marketplaces/thedotmack/` (v12.1.0, AGPL-3.0)\n\n---\n\n**Document Compiled**: 2026-05-02 13:20 UTC | **Last Config Change**: 2026-05-02 13:20 UTC (Nuzantara-reported version)\n"}, "72ea7583cf1584a3b46956c063f0e71e069e04bb": {"t": 1785758295.7772791, "n": 34193, "h": "c11e0bae09aa0645", "c": "# OpenClaw Deep Research: Architecture, Capabilities & Current Usage\n**Date**: 2026-05-02 | **Version**: openclaw 2026.3.31 | **Local Install**: `~/.openclaw/lib/node_modules/openclaw/` | **Config**: `~/.openclaw/openclaw.json`\n\n**Audience**: Claude (reader) + cross-LLM reviewers (Codex GPT-5.5, Gemini 3.1 Pro, DeepSeek V4 Reasoner) preparing to reason about OpenClaw's role in Nuzantara automations.\n\n---\n\n## Executive Summary\n\nOpenClaw is a **Node.js-based personal AI gateway** running a WebSocket control plane (`ws://127.0.0.1:18789`) that routes inbound messages from 23+ messaging channels (Telegram, WhatsApp, Discord, Slack, iMessage, etc.) to isolated agents, which dispatch LLM requests with multi-model fallback + tool streaming. The runtime is process-managed by LaunchAgent on macOS; the agent itself is embedded (RPC mode). \n\n**Nuzantara is using OpenClaw for**:\n- Primary messaging channel: Telegram (enabled, receiving message timeouts)\n- Multi-LLM routing: MiniMax M2.7 (primary) → Kimi K2.6 → DeepSeek Chat → Ollama Qwen3.5\n- Persistent memory: memory-core plugin (storing observations from tool use)\n- Orchestration: lobster plugin (workflow DSL, enabled)\n- Voice: voice-call skill (enabled)\n- Custom extensions: claude-mem (v12.1.0, marketplace install)\n\n**Version Gap**: Installed version is 2026.3.31 (released ~Mar 31); latest is 2026.4.29 (released Apr 30). Recent releases include Knowledge Agents system (12.1.0 in claude-mem), improved Telegram channel timeouts, and model failover hardening.\n\n---\n\n## A. Architecture\n\n### Runtime Model\n\nOpenClaw is a **Node.js daemon** (v22.22.0, bundled) running:\n- **Gateway** (port 18789, loopback-only, hybrid reload on config change)\n- **Control UI** (Web dashboard, http://127.0.0.1:18789)\n- **Pi agent** (embedded RPC, not external process)\n- **Channel adapters** (Telegram: grammY, Discord: discord.js, WhatsApp: Baileys, etc.)\n\n**Process Management**:\n- macOS: LaunchAgent (`~/Library/LaunchAgents/openclaw.plist` — managed by Nuzantara's `monitor-air.log` cron)\n- KeepAlive policy: checked via heartbeat monitor every 1h (Nuzantara config: `heartbeat.every=\"1h\"`)\n- Uptime target: 24/7 (threshold exceeded May 2, 2026 at 01:13 UTC — gap analysis in logs)\n\n### Agent Abstractions\n\n**Agent Definition** (`agents.list[]`):\n- Nuzantara has 2 agents:\n 1. **main**: primary, Telegram-bound, sandbox=off, model routing primary→fallback chain\n 2. **coder**: isolated workspace, sandbox=off, web tools denied (no web_search, web_fetch, browser)\n\n**Model Routing**:\n```\nmain:\n primary: openrouter/minimax/minimax-m2.7\n fallbacks: [kimi-k2.6, deepseek-chat, ollama/qwen3.5:9b]\n\ncoder:\n primary: openrouter/qwen/qwen3-max\n fallbacks: [qwen3.6-plus, kimi-k2.6, deepseek-reasoner, qwen3.5:9b]\n```\n\n**Fallback Chain Logic**:\n- Model request → try primary; if fails (auth, rate_limit, not_found) → try next fallback\n- Auth cooldown: 300s lockout after 10 auth failures in 60s window (gateway.auth.rateLimit)\n- Rate-limit cooldown: automatic provider cooldown (all-profiles-unavailable state) visible in logs (Mar 1-2, Google Gemini CLI exhausted)\n\n**Sandbox Modes**:\n- Default: `non-main` (separate container per agent workspace)\n- Nuzantara: `mode=off` for both agents (no isolation) + `prune.idleHours=24` (clean up idle containers)\n\n### Plugin System\n\nPlugins extend gateway capabilities via allow-list:\n\n**Loaded Plugins** (`plugins.entries`):\n- **memory-core** (enabled): observation feed + persistent storage\n- **memory-lancedb** (disabled): vector search backend (not active)\n- **lobster** (enabled): workflow orchestration DSL\n- **llm-task** (enabled): multi-step LLM orchestration\n- **voice-call** (enabled): phone/VOIP integration\n- **telegram** (implicit, channel adapter)\n\n**Plugin Trust Model**:\n- Plugins in `~/.openclaw/extensions/` are scanned; only those in `plugins.allow[]` are loaded\n- Nuzantara's installed plugin: `claude-mem` (v12.1.0) in `~/.openclaw/extensions/claude-mem/`\n- **Trust Warning**: claude-mem flagged in gateway logs as \"loaded without install/load-path provenance\" (implies manual fs install, not via package manager)\n\n### Skill System\n\nSkills add tools to agents via hooks + HTTP API.\n\n**Bundled Skills** (`~/.openclaw/lib/node_modules/openclaw/skills/`):\n- goplaces (Google Places API), xurl (URL shortener), voice-call (Twilio), notion, slack, discord, trello, spotify-player, etc.\n\n**Nuzantara Enabled Skills** (`skills.entries`):\n- **goplaces**: enabled (env: GOOGLE_PLACES_API_KEY)\n- **xurl**: enabled\n- **voice-call**: enabled\n- **notion**: enabled (env: NOTION_API_KEY)\n- **antigravity**: enabled\n\n**Custom Skills Directory**: `/Users/nuzantara/Desktop/nuzantara/apps/nuzantara-mcp/skills` (loaded via `skills.load.extraDirs[]`)\n\n### Channel Bindings\n\n**Telegram Channel** (enabled, dmPolicy=open):\n- **BotToken**: configured (redacted in config, present in plaintext)\n- **Allowed Senders**: `allowFrom=[\"*\"]` (open to all)\n- **Streaming**: disabled (responses sent as complete message blocks)\n- **Timeout**: 60s per message\n- **Retry**: 2 attempts, min 5s delay, max 60s delay, 0.5 jitter\n- **Known Issues**:\n - Network timeouts (ETIMEDOUT / EHOSTUNREACH observed Mar 1-2, 30s+ waits)\n - BOT_COMMANDS_TOO_MUCH (Telegram API limit: max 100 commands, Nuzantara registering 92-97, dropping 19-20 on each attempt)\n\n**Other Channels**:\n- WhatsApp, Discord, Slack, Signal, iMessage (BlueBubbles), IRC, Teams, Matrix: all disabled in config\n\n### Memory Model\n\n**memory-core** plugin:\n- SQLite database: `~/.openclaw/agents/main/agent/memory.db` (not visible in config, inferred from logs)\n- Observation feed: tool_use events → semantic summaries → injected into future sessions\n- Token-aware: context pruning mode=cache-ttl (1h TTL), compaction mode=safeguard (96k reserve floor, 40% history max)\n- Worker Service: runs on separate port (likely 37777 per claude-mem docs), managed by Bun\n- Vector backend: Chroma (if memory-lancedb enabled; currently off)\n\n### mcporter Integration\n\n**Tool Surface**:\n- Built-in: browser, canvas, nodes, cron, sessions\n- MCP tools: claude-mem (`search`, `timeline`, `get_observations`), custom skills\n\n**Safety Allowlist** (`tools.exec.safeBins[]`):\n```\nls, cat, tail, which, nuzantara-ops, mcporter\n```\n- Only these binaries can be executed without sandbox approval\n- `mcporter` is whitelisted (allows MCP tool invocation from agent)\n- **Known Issue**: mcporter allowlist audited Mar 22; safeBins entries not matching plugin config (audit log shows mismatch fixed via plugin reload)\n\n### Lobster Workflows\n\n**Lobster Plugin** (enabled):\n- DSL for multi-step orchestration\n- Workflow files: `~/.openclaw/workspace/.lobster/*.yml` (inferred)\n- Nuzantara logs show no active workflows, but plugin enabled for future use\n\n### Sandbox & Safety\n\n**Exec Approval Socket** (`exec-approvals.sock`):\n- Manual approval flow for unsafe commands\n- Not actively used in Nuzantara (sandbox=off, so no approvals needed)\n\n**Circuit Breaker** (`tools.loopDetection`):\n- Enabled globally (threshold=10 repeats)\n- Detectors:\n - **genericRepeat**: catch identical tool calls in sequence\n - **knownPollNoProgress**: detect polling without state change\n - **pingPong**: detect tool A → B → A cycles\n- Nuzantara: enabled, no circuit trips observed (logs clean on this metric)\n\n---\n\n## B. Current Capabilities\n\n### Agentic Tasks\n\n**Single-turn**: `openclaw agent --message \"query\"` (CLI or Gateway RPC)\n**Multi-turn**: Session state preserved in memory; model compaction + context pruning per config\n**Persistent Loops**: Supported via Lobster workflows (enabled but not in use)\n\n### Tool Use\n\n| Tool | Enabled | Notes |\n|------|---------|-------|\n| **web_search** | Yes | Integrated |\n| **web_fetch** | Yes | Integrated |\n| **browser** | No (coder agent denies) | Chrome control, snapshots, actions |\n| **exec** | Partial (safeBins only) | ls, cat, tail, nuzantara-ops, mcporter |\n| **file ops** | Via browser canvas | Not direct agent tool |\n| **image** | Yes (image model routing) | Primary: ollama/qwen2.5vl:7b, fallback: openrouter/qwen3-vl |\n| **canvas** (macOS) | Yes | A2UI protocol, push/reset/eval/snapshot |\n| **cron** | Yes | 24 jobs max (Nuzantara shows 0 active, scheduler may be frozen as of Apr 30) |\n| **custom MCP** | Yes (mcporter) | Route to local/remote MCP servers |\n\n**Multi-LLM Routing**: Each agent has primary + 3 fallback models; per-model params (temperature, max_tokens) configurable\n\n### Multi-LLM Routing\n\n**Provider Configuration**:\n- **openrouter**: native support, baseUrl=https://openrouter.ai/api/v1, 6 models configured\n- **deepseek**: native support, baseUrl=https://api.deepseek.com/v1, 2 models configured\n- **ollama**: local, baseUrl=http://127.0.0.1:11434, 0 models pre-configured (dynamic discovery)\n- **anthropic**: **NOT configured** (no ANTHROPIC_API_KEY in env; Nuzantara policy: banned)\n- **openai** (via openrouter): shim via openrouter provider\n\n**Per-Model Tuning**:\n```\ntemperature: 0.2–0.4 (reasoning), 0.3–0.4 (chat)\nmax_tokens: 4096–8192\nollama-specific: keep_alive=10m (memory cache), top_p=0.9, top_k=20\n```\n\n**Fallback Behavior**: \n- Try primary; if fails, cascade through fallbacks\n- Rate-limit cooldown (300s) applied per provider\n- Auth errors caught and retried with different auth profile (if multi-profile configured)\n\n### Persistent Memory\n\n**memory-core** (enabled):\n- **Lifecycle Hooks**: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd\n- **Observation Compression**: tool_use events → semantic summaries (via Claude API or local LLM)\n- **Injection Strategy**: progressive disclosure (layer 1: index; layer 2: timeline; layer 3: full details)\n- **Search Tools**: `mem-search`, `timeline`, `get_observations` (MCP interface)\n- **Durability**: SQLite persistent store, survives session/process restarts\n\n### Channel I/O\n\n**Telegram** (primary):\n- Inbound: message events → matched to agent via binding (main agent, Telegram channel)\n- Outbound: agent reply → formatted as Telegram message → sent via botToken\n- **State**: connected (logs show recent message processing up to May 2 01:13)\n- **Timeouts**: retry logic (2 attempts, 5–60s backoff) masks network latency\n\n### Voice-Call\n\n**voice-call Skill** (enabled):\n- Integration: Twilio (inferred from OpenClaw docs; not explicitly in Nuzantara config)\n- Capabilities: accept voice calls, stream audio, TTS output (ElevenLabs or system fallback)\n- **Status in Nuzantara**: enabled but not actively tested\n\n### Browser Automation\n\n**Browser Tool** (available, not in use by main agent):\n- Chrome/Chromium control (dedicated OpenClaw instance)\n- Actions: snapshot, click, type, scroll, upload, profile switching\n- **Nuzantara**: coder agent has browser denied via `tools.deny=[\"browser\"]`\n\n### Workflow Orchestration (Lobster)\n\n**Lobster DSL** (enabled, not in active use):\n- YAML-based multi-step orchestration\n- Syntax: task dependencies, conditional routing, parallel execution\n- **Nuzantara**: loaded but no workflows observed in logs\n\n### Subagents\n\n**Subagent Pattern**: supported via agent routing (`bindings[]` match rules)\n- **Nuzantara**: 2 agents defined (main, coder); no inter-agent delegation observed\n- **Limitation**: agents are isolated; no direct parent-child RPC (agents are standalone)\n\n### Cron / Scheduled Jobs\n\n**Cron Tool**:\n- API: `POST /gateway/cron` or `openclaw cron add <schedule> <command>`\n- Max 24 jobs per gateway\n- **Nuzantara Status**: 0 active jobs logged (scheduler may be frozen since Apr 30 per audit notes)\n\n### Hooks\n\n**7 Lifecycle Hooks** (claude-mem):\n- SessionStart, UserPromptSubmit, PostToolUse, Summary, SessionEnd, PreToolUse, Stop\n- **Nuzantara Use**: memory-core hooks active (observation capture, summary generation)\n\n### Plugin Development\n\n**Plugin Template**:\n- Entry point: `plugin/index.ts` or `dist/index.js`\n- Hooks: export `hooks: { SessionStart, UserPromptSubmit, ... }`\n- MCP Tools: export `tools: [ ... ]` (JSON-RPC 2.0 interface)\n- **Nuzantara Custom Skills**: loaded from `/Users/nuzantara/Desktop/nuzantara/apps/nuzantara-mcp/skills/` (no plugins observed)\n\n### Embedded Agent Runtime\n\n**Pi Agent** (embedded, not delegated):\n- Runs in-process with gateway (RPC loopback)\n- Session state: maintained in gateway memory (not persistent across restarts without memory plugin)\n- Tool dispatch: streaming responses back to gateway → channel output\n\n---\n\n## C. What's NEW (Recent OpenClaw Releases)\n\n| Release | Date | Highlights |\n|---------|------|-----------|\n| **v2026.4.29** | Apr 30, 2026 | Stability improvements, auth profile migration docs |\n| **v2026.4.09** | Apr 9, 2026 | Knowledge Agents system (6 new MCP tools: build_corpus, prime_corpus, query_corpus, etc.) |\n| **v2026.3.31** | Mar 31, 2026 | **[INSTALLED]** DM pairing default, Telegram net timeout handling, provider enum changes |\n| **v2026.1.5** | Jan 5, 2026 | Image model config, agent.imageModel + fallbacks, model shorthands (opus, sonnet, gpt, gemini) |\n\n**Most Notable for Nuzantara**:\n- **Knowledge Agents** (v2026.4.09): Compile filtered corpus from claude-mem observations, query conversationally. 6 new MCP tools (build_corpus, list_corpora, prime_corpus, query_corpus, rebuild_corpus, reprime_corpus). **Not yet exploited.**\n- **Auth Profile Migration** (v2026.3.31 → v2026.4.29): DM pairing now default (requires approval codes); legacy \"open to all\" requires explicit dmPolicy=open + allowFrom=[\"*\"]. **Nuzantara already configured.**\n- **Telegram Timeout Hardening** (v2026.3.31): Retry + backoff for network errors (30s+ waits masked). **Experienced but handled gracefully.**\n\n**Claude-Mem Integration** (v12.1.0, installed via marketplace):\n- Knowledge agents now supported (native corpus build, prime, query MCP tools)\n- Smart-explore extended to 24 languages\n- File-read decision gate (PreToolUse hook blocks redundant reads using observation timeline)\n\n---\n\n## D. Comparison vs Alternatives\n\n| Dimension | OpenClaw | cron-agent-python (Nuzantara) | LangChain/LangGraph | Anthropic Agent SDK (direct) |\n|-----------|----------|------|-----------|---------|\n| **Primary UX** | Gateway + multi-channel | Python cron + custom CLI | Chain/graph DSL | Messages API |\n| **Multi-LLM** | Native (fallback chains) | Manual (explicit per-agent config) | LLMChain abstraction | No native support |\n| **Persistent Memory** | Plugin-based (memory-core) | NotebookLM (external) | No built-in | No built-in |\n| **Channel Binding** | 23+ integrations (Telegram, Discord, etc.) | CLI-only / webhooks | None (user's responsibility) | None |\n| **Workflow Orchestration** | Lobster DSL | Python task queue | LangGraph (DAG-based) | Claude Agent SDK (new) |\n| **Tool Streaming** | Yes (block streaming) | Manual | Yes (via tool classes) | Yes (native) |\n| **Sandbox** | Exec approval socket | None (direct subprocess) | None | None (external runner) |\n| **State Persistence** | SQLite (via plugins) | Disk-backed (JSON/state files) | Not built-in | Not built-in |\n| **Scaling** | Single gateway (local) | Multi-machine (cron dispatch) | App-level (doesn't scale infra) | Delegated to cloud provider |\n| **Ease of Setup** | `openclaw onboard` (90s) | Python venv + config | npm install + boilerplate | anthropic SDK (Python/JS) |\n\n**OpenClaw's Unique Value Propositions**:\n1. **Multi-channel inbox**: Single control plane for Telegram, WhatsApp, Discord, Slack, iMessage, IRC, Teams, Signal, etc.\n2. **Persistence out-of-the-box**: memory-core plugin captures observations + injects into future sessions (no manual logging)\n3. **Native fallback chains**: multi-model routing with per-provider cooldowns + auth management\n4. **Embedded + local**: runs on your machine (no cloud), binds to loopback only (no public expose by default)\n\n**Where OpenClaw Loses**:\n1. **No cloud sync**: single-machine deployment (no multi-region HA out of the box)\n2. **Provider enum rigidity**: only supports explicit providers (openrouter, deepseek, ollama, anthropic); no generic OpenAI-compatible override until v2026.3.31+ (baseUrl interpolation)\n3. **Command limit (Telegram)**: max 100 commands registered; Nuzantara hitting 92-97, dropping 19-20 on each sync\n4. **Scheduler frozen** (apr 30+): cron jobs not running (24-job queue inert); cause unknown\n\n**vs cron-agent-python** (Nuzantara parallel):\n- cron-agent-python: 19 active strategies, each runs independently on schedule; state decoupled (disk-backed JSON)\n- OpenClaw: single agent with multi-LLM routing; state unified in memory-core\n- **Recommendation**: cron-agent-python excels for **independent, scheduled tasks** (classification, analysis per-document); OpenClaw excels for **interactive, multi-turn conversations** with memory (Telegram chatbot + persistent context)\n\n---\n\n## E. Scaling Characteristics\n\n### Concurrency\n\n**Max Concurrent Agents**: 1 gateway, N agents (in Nuzantara: 2)\n- **Per-Agent Sessions**: Unbounded (session state pruned on demand)\n- **Concurrent Requests**: Limited by LLM provider rate limits (OpenRouter: ~100 req/min; DeepSeek: API doc unclear; Ollama: local, unbounded)\n- **Telegram Message Queue**: Sequential (one message processed at a time, 60s timeout → retry → backoff)\n\n### Memory Footprint\n\n| Component | RSS (Idle) | RSS (Active) | Notes |\n|-----------|-----------|-------------|-------|\n| Gateway process | ~200–300 MB | ~400–600 MB | Node.js runtime + channel adapters |\n| memory-core SQLite | ~10 MB | ~50+ MB | Depends on observation count + vector index |\n| Chroma (if enabled) | N/A | ~200+ MB | Python subprocess (uv-managed), not active in Nuzantara |\n| Total (Nuzantara) | ~250 MB | ~500+ MB | Measured via LaunchAgent memory monitor |\n\n### Latency Overhead\n\n| Operation | Latency | Notes |\n|-----------|---------|-------|\n| Gateway routing (inbound message) | <10ms | WebSocket demux + binding match |\n| Agent dispatch (to LLM) | <50ms | HTTP to OpenRouter/DeepSeek |\n| LLM request | 500ms–5s | Depends on model, fallback chain depth |\n| Response streaming (Telegram) | 1–30s | Tool execution + LLM generation + retry backoff |\n| Memory injection (PreToolUse) | 100–500ms | SQLite query + compression (if observation reuse) |\n\n**Gateway overhead is negligible** (<10% of E2E latency).\n\n### State Persistence\n\n**Durable Across Crashes**: Yes (if memory-core enabled)\n- Session state saved to SQLite after each tool_use hook\n- Observation timeline reconstructed on SessionStart\n\n**Graceful Shutdown**: LaunchAgent KeepAlive policy\n- monitored by `launchctl unload/load` (manual) or cron watchdog (Nuzantara)\n- Timeout: 30s (hard kill if not responsive)\n\n### Crash Recovery\n\n**KeepAlive=true** (Nuzantara config):\n- LaunchAgent auto-restarts gateway on exit (non-zero exit code)\n- State surviving: memory-core observations (durable), in-flight messages (lost, retried by Telegram)\n- **Observed**: gateway crashes not logged; LaunchAgent restarts appear transparent\n\n### Multi-Instance Topology\n\n**Federation**: Not supported natively\n- **Workaround**: run multiple gateways on different ports (18789, 18790, ...), but agents are isolated per gateway\n- **Nuzantara**: single gateway (port 18789); no multi-instance setup\n\n---\n\n## F. Security Model\n\n### Loopback-Only Binding\n\n**gateway.bind=loopback** (Nuzantara default):\n- Gateway listens on 127.0.0.1:18789 (localhost only)\n- No public internet exposure (requires SSH tunnel or Tailscale to access from remote)\n- **Threat Model**: protects against direct internet attacks; assumes local machine is secure\n\n### Auth Token & Rate Limiting\n\n**Token** (gateway.auth.token):\n- Long random string (example: 64 char base64)\n- Required in WebSocket `?token=...` query param or HTTP `Authorization: Bearer <token>` header\n- **Storage**: plaintext in `~/.openclaw/openclaw.json` (same permissions as SQLite DB)\n- **Rotation**: manual edit + `openclaw reload` (no auto-rotation tool visible)\n\n**Rate Limiting**:\n- **maxAttempts**: 10 failures in 60s window (Nuzantara config)\n- **lockoutMs**: 300s (5 min) lockout after threshold\n- **Scope**: per-IP (loopback is single IP, so affects all local clients equally)\n\n### exec-approvals.sock\n\n**Manual Approval Flow**:\n- Unsafe exec commands → `exec-approvals.sock` (Unix domain socket, `/tmp/exec-approvals-<pid>.sock`)\n- User approval via CLI: `echo \"approve <exec-id>\" | nc -U /tmp/exec-approvals-<pid>.sock`\n- **Nuzantara Status**: sandbox=off for both agents, so approval flow never triggered\n\n### Sandbox Modes\n\n| Mode | Isolation | Use Case |\n|------|-----------|----------|\n| **off** | None | Development, trusted local use |\n| **non-main** | Per-agent container | Multi-agent isolation (default agents.defaults.sandbox.mode) |\n| **full** | Per-session container | Maximum isolation (experimental) |\n\n**Nuzantara**: mode=off (no isolation). Rationale: single user, local machine, trusted workflows. Trade-off: agents can interfere (shared filesystem, process space).\n\n### Safety Bins Allowlist\n\n**Tools.exec.safeBins**:\n```\nls, cat, tail, which, nuzantara-ops, mcporter\n```\n- Only these binaries bypass approval flow\n- Others require exec approval (if sandbox=non-main or full)\n- **Known Issue** (Mar 22): safeBins not matching plugin config; resolved via plugin reload\n\n**Attack Surface**: agent can only run safe binaries; can't execute arbitrary commands (unless sandbox=off + agent has write access to filesystem, which it does in Nuzantara)\n\n### Plugin Trust Model\n\n**Plugin Loading** (`plugins.allow[]`):\n- Whitelist-based: only plugins in allow-list are loaded\n- Nuzantara loads: memory-core, lobster, llm-task, voice-call (implicit)\n- claude-mem: loaded from `~/.openclaw/extensions/claude-mem/` (manual install, not in core plugins.allow)\n\n**Trust Signals**:\n- Official plugins: signed releases, open-source GitHub\n- User plugins (Nuzantara custom skills): loaded from `/Desktop/nuzantara/apps/nuzantara-mcp/skills/` (same machine, implicit trust)\n- **Warning**: claude-mem flagged as \"loaded without install/load-path provenance\" → suggests side-load outside normal package path\n\n### Secret Handling\n\n**API Key Interpolation**:\n```json\n{\n \"providers\": {\n \"openrouter\": {\n \"apiKey\": \"${OPENROUTER_API_KEY}\"\n }\n }\n}\n```\n\n**Resolution**:\n1. Check environment variable (shellEnv=true in Nuzantara config, shell timeout 15s)\n2. Check EnvironmentVariables from LaunchAgent plist (if running as daemon)\n3. Fall back to plaintext in config (NOT recommended)\n\n**Nuzantara Config**:\n- OPENROUTER_API_KEY, DEEPSEEK_API_KEY, GOOGLE_PLACES_API_KEY, NOTION_API_KEY → in `~/.openclaw/.env` (not shown, but inferred from skills.env)\n- **Risk**: if `.env` checked into git, keys exposed\n\n---\n\n## G. Operational Model\n\n### LaunchAgent Management\n\n**Service File**: `~/Library/LaunchAgents/openclaw.plist` (macOS)\n- **Label**: com.openclaw.daemon\n- **Program**: node (bundled in ~/.openclaw/tools/node-v22.22.0/bin/node)\n- **KeepAlive**: true (auto-restart on exit)\n- **ThrottleInterval**: 30s (min time between restarts after crash)\n- **RunAtLoad**: true (start on login)\n\n**Manual Control**:\n```bash\nlaunchctl load ~/Library/LaunchAgents/openclaw.plist\nlaunchctl unload ~/Library/LaunchAgents/openclaw.plist\nlaunchctl start com.openclaw.daemon\nlaunchctl stop com.openclaw.daemon\n```\n\n**Nuzantara Monitoring**: cron job `monitor-air.log` (implied by logs; monitors for crashes + auto-restart failures)\n\n### Logs\n\n| Log | Path | Rotation | Notes |\n|-----|------|----------|-------|\n| **gateway.log** | `~/.openclaw/logs/gateway.log` | Yes (daily) | Main diagnostic output (diagnostic, channel, agent events) |\n| **gateway.err.log** | `~/.openclaw/logs/gateway.err.log` | Yes (daily) | Errors, warnings, stack traces |\n| **commands.log** | `~/.openclaw/logs/commands.log` | N/A | Slash command executions |\n| **config-audit.jsonl** | `~/.openclaw/logs/config-audit.jsonl` | Append | Config change audit trail |\n\n**Log Level**: `logging.level=info` (Nuzantara config); controlled via `openclaw config set logging.level=debug` (or restart with env var)\n\n**Retention**: no explicit TTL in config; logs grow unbounded (gateway.log: 21.7 GB as of May 2, gateway.err.log: 288 MB). **Risk**: disk space exhaustion.\n\n### Health Endpoint\n\n**Endpoint**: `GET /healthz` (Gateway)\n- **Response**: `{ \"status\": \"ok\" }` (200 OK) or 500 on error\n- **Nuzantara Use**: implied by monitor-air; likely polled every 5–60s\n\n### Metrics Export\n\n**Prometheus**: Not built-in to OpenClaw (as of v2026.3.31)\n- **Workaround**: parse logs with regex + ship to external metrics service\n- **Nuzantara**: no custom metrics exporter observed\n\n### Debugging\n\n**Verbose Mode**: `openclaw gateway --verbose` (CLI flag)\n**Log Level Override**: `OPENCLAW_LOG_LEVEL=debug` (env var at startup)\n**RPC Tracing**: gateway logs all RPC calls (agent → channel) with stack traces on failure\n\n**Trace an Agent Decision**:\n1. Enable debug logging\n2. Send message via Telegram\n3. Check gateway.log for SessionStart → UserPromptSubmit → tool_use → agent response flow\n4. Correlate with memory-core observations (check `.openclaw/agents/main/agent/memory.db` if needed)\n\n### Update Process\n\n**Manual Update**:\n```bash\nnpm install -g openclaw@latest\nopenclaw onboard --update # or --install-daemon to refresh daemon\n```\n\n**Safe Update (Nuzantara)**:\n1. `launchctl stop com.openclaw.daemon` (halt gateway)\n2. Backup `~/.openclaw/openclaw.json` + `~/.openclaw/workspace/`\n3. `npm install -g openclaw@latest`\n4. Review CHANGELOG for breaking changes\n5. Run `openclaw doctor` (auto-migrate config if needed)\n6. `launchctl start com.openclaw.daemon` (restart)\n\n**Auto-Update**: Not configurable in v2026.3.31; manual only.\n\n---\n\n## H. Known Issues & Pitfalls\n\n### 1. Provider Enum Rigidity\n\n**Issue**: openai-completions API wrapper only recognizes explicit `api: \"openai\"` (no generic override)\n\n**Impact**: Can't use arbitrary OpenAI-compatible backends (e.g., vLLM, LocalAI) without forking\n\n**Workaround** (Nuzantara): Use `api: \"openai-completions\"` with openrouter shim (all models exposed via openrouter baseURL)\n\n**Status**: Likely fixed in v2026.4.29 (baseUrl interpolation mentioned in changelog), but not tested against Nuzantara's installed v2026.3.31\n\n### 2. Secret Interpolation (`${VAR}` Only in env)\n\n**Issue**: `apiKey: \"${VAR}\"` resolves ONLY if VAR is in EnvironmentVariables (from LaunchAgent plist or shell env at startup), NOT from `.env` file or inline plaintext\n\n**Impact**: Easy to forget to export env var → silent fallback to missing key → auth failures\n\n**Example** (Nuzantara):\n```json\n{\n \"openrouter\": {\n \"apiKey\": \"${OPENROUTER_API_KEY}\" // resolves to env var if set, else null\n }\n}\n```\n\n**Workaround**: Explicitly set in LaunchAgent plist `EnvironmentVariables` section or `source ~/.openclaw/.env` before `launchctl load`\n\n### 3. mcporter Allowlist Quirks\n\n**Issue** (Mar 22 audit log): safeBins entries `ls, cat, tail, which, nuzantara-ops, mcporter` not matching plugin config → mcporter tool calls rejected\n\n**Root Cause**: Plugin loading race condition (safeBins checked before plugin registration completes)\n\n**Status**: Resolved in Nuzantara by manual reload (launchctl stop/start)\n\n**Fix Expected**: v2026.4.29+ (not tested)\n\n### 4. Plugin Trust Warnings\n\n**Issue**: claude-mem loaded from `~/.openclaw/extensions/claude-mem/` without install/load-path provenance\n\n**Impact**: Minor (code review recommended for untrusted sources); claude-mem is open-source + widely used\n\n### 5. Telegram Network Timeouts\n\n**Issue**: `getUpdates` request times out after 30s; retry backoff (5s → 60s) masks latency but creates gaps in message delivery (Apr 30 + May 2 logs show repeated timeouts)\n\n**Impact**: Inbound messages may be delayed 30–120s during network flaps\n\n**Cause**: Likely ISP or Telegram API throttling (Nuzantara in Bali, geographically distant from Telegram servers)\n\n**Workaround**: Increase timeout (Telegram: `timeoutSeconds=120`) or use webhook polling (if supported in next version)\n\n**Status**: Retry logic + backoff now in v2026.3.31; gracefully handled but not ideal UX\n\n### 6. BOT_COMMANDS_TOO_MUCH (Telegram API Limit)\n\n**Issue**: Telegram API limits /command menu to 100 entries; Nuzantara registering 92–97 commands (skills + custom), dropping 19–20 on each sync\n\n**Impact**: Some skills unavailable in Telegram /command autocomplete menu (but still callable via message mention)\n\n**Log Evidence**:\n```\n[telegram] rejected 92 commands (BOT_COMMANDS_TOO_MUCH); retrying with 73.\n[telegram] accepted 73 commands after BOT_COMMANDS_TOO_MUCH (started with 92; omitted 19).\n```\n\n**Root Cause**: Too many skills enabled (goplaces, xurl, voice-call, notion, antigravity) + OpenClaw bundled commands\n\n**Workaround**: Disable low-use skills in `skills.entries` (set enabled=false for unused ones)\n\n**Fix**: None in OpenClaw core (Telegram API limit). Requires operator intervention.\n\n### 7. Scheduler Frozen (Apr 30+)\n\n**Issue**: Cron jobs not running; 24-job queue inert\n\n**Evidence**: No cron logs in gateway.log since Apr 30; cron tool still responds to queries but doesn't execute\n\n**Impact**: Scheduled tasks (batch processing, daily summaries) missed\n\n**Likely Cause**: \n- Scheduler process crash (separate thread)\n- Job queue lock contention\n- State corruption after config reload\n\n**Status**: Unknown (no debugging done); requires `openclaw doctor` + manual job purge/recreate\n\n**Workaround**: Migrate to external cron runner (separate Python process with its own scheduler) or upgrade to v2026.4.29+\n\n### 8. Context Compaction False Positives\n\n**Issue**: context pruning + compaction may discard relevant observations if TTL expires before session end\n\n**Config** (Nuzantara):\n```\ncontextPruning.mode=cache-ttl, ttl=1h\ncompaction.mode=safeguard, reserveTokensFloor=96k\n```\n\n**Impact**: Multi-day sessions lose early context after 1h, even if still relevant\n\n**Workaround**: Increase TTL to 24h or use `mode: \"never\"` (disables pruning, risks context explosion)\n\n---\n\n## I. Roadmap & Future Direction\n\n**Public Roadmap**: https://github.com/openclaw/openclaw/issues (GitHub Issues, no official roadmap doc)\n\n**Recent PR Signals** (inferred from changelog):\n- **Knowledge Agents** (v2026.4.09): Building queryable \"brains\" from observation history (corpus build + prime + query)\n- **Auth Profile System** (v2026.3.31+): Multi-profile support + role-based failover (not fully enabled in Nuzantara)\n- **DM Pairing Security**: Default dmPolicy=pairing (require approval code for unknown senders) — security-first direction\n- **Provider Enum Expansion**: Support for more providers + generic OpenAI-compatible override (v2026.4.29 changelog hints)\n\n**Likely Future Work** (speculative):\n1. **Cloud Sync**: Multi-region HA + state replication (not in current roadmap, but enterprise feature request)\n2. **Structured Output**: Typed tool responses (Zod schema validation) — currently working, likely formalization\n3. **Voice Wake Improvements**: More language support, custom wake words (v2026.3.31 has voice-call, room for improvement)\n4. **Scheduler Stability**: Fix cron job queue race conditions (based on Apr 30 frozen state)\n5. **Knowledge Agent UI**: Web viewer for corpus browsing + semantic search (claude-mem v12.1.0 foundation)\n\n---\n\n## Summary Table: What Nuzantara Is Using vs. Available\n\n| Feature | Available | Enabled | Status |\n|---------|-----------|---------|--------|\n| **Multi-LLM Routing** | Yes (4 providers) | Yes (fallback chain) | Working, no issues |\n| **Persistent Memory** | Yes (memory-core) | Yes | Working, observations injected |\n| **Telegram Channel** | Yes | Yes | Working, but network timeouts every 30s+ |\n| **Workflow Orchestration** | Yes (Lobster) | Enabled but unused | Ready to use, no barriers |\n| **Knowledge Agents** | Yes (new in v12.1.0) | No | **NOT EXPLOITED** (v2026.4.09 feature) |\n| **Voice-Call Integration** | Yes | Yes | Enabled, not actively tested |\n| **Browser Automation** | Yes (main agent denied) | Partial (coder agent) | Available for coder agent |\n| **Cron Scheduler** | Yes (24 job limit) | Yes but frozen | **BROKEN** since Apr 30 |\n| **Custom Skills** | Yes (extraDirs) | Partially | `/nuzantara-mcp/skills/` not fully synced |\n| **Canvas (macOS)** | Yes | Implicit | Available if using macOS |\n| **Multi-Channel** | Yes (23 channels) | Only Telegram | Others disabled; can be enabled |\n\n---\n\n## Recommendations for Nuzantara\n\n1. **Upgrade to v2026.4.29** (from v2026.3.31): Knowledge Agents system ready; cron scheduler likely fixed; auth profiles stabilized\n2. **Exploit Knowledge Agents**: Build corpus from claude-mem observations; enable conversational history replay (query_corpus tool)\n3. **Fix Cron Scheduler**: Debug Apr 30 freeze; if unresolved in v2026.4.29, fallback to external cron-agent-python runner\n4. **Reduce Telegram Commands**: Disable unused skills (elite-longterm-memory, bluebubbles, discord, slack, spotify, trello) to stay under 100-command limit\n5. **Increase Telegram Timeout**: Raise `timeoutSeconds=120` to mask network latency (or implement webhook polling in next OpenClaw version)\n6. **Monitor Memory Growth**: Track `~/.openclaw/logs/gateway.log` size (currently 21.7 GB); implement log rotation or external log shipping\n7. **Test Voice-Call Skill**: Verify Twilio integration if voice interaction is planned\n8. **Sync Custom Skills**: Ensure `/nuzantara-mcp/skills/` fully registered with OpenClaw (check skills.entries for all custom entries)\n\n---\n\n## Files Referenced\n\n- **Config**: `/Users/nuzantara/.openclaw/openclaw.json` (v2026.3.31 schema)\n- **Installation**: `/Users/nuzantara/.openclaw/lib/node_modules/openclaw/`\n- **Logs**: `~/.openclaw/logs/gateway.log`, `gateway.err.log`\n- **Memory DB**: `~/.openclaw/agents/main/agent/memory.db` (SQLite, not visible in config)\n- **Workspace**: `~/.openclaw/workspace/` (markdown docs + agent state)\n- **Extensions**: `~/.openclaw/extensions/claude-mem/` (installed plugin)\n- **GitHub**: https://github.com/openclaw/openclaw (releases, issues, docs)\n- **Documentation**: https://docs.openclaw.ai (official, updated for v2026.4.29)\n- **claude-mem Marketplace**: `/Users/nuzantara/.claude/plugins/marketplaces/thedotmack/` (v12.1.0, AGPL-3.0)\n\n---\n\n**Document Compiled**: 2026-05-02 13:20 UTC | **Last Config Change**: 2026-05-02 13:20 UTC (Nuzantara-reported version)\n"}, "acb1a23683b839e44dd6c465c58a94179f10bf6f": {"t": 1786067829.5726113, "n": 34193, "h": "c11e0bae09aa0645", "c": "# OpenClaw Deep Research: Architecture, Capabilities & Current Usage\n**Date**: 2026-05-02 | **Version**: openclaw 2026.3.31 | **Local Install**: `~/.openclaw/lib/node_modules/openclaw/` | **Config**: `~/.openclaw/openclaw.json`\n\n**Audience**: Claude (reader) + cross-LLM reviewers (Codex GPT-5.5, Gemini 3.1 Pro, DeepSeek V4 Reasoner) preparing to reason about OpenClaw's role in Nuzantara automations.\n\n---\n\n## Executive Summary\n\nOpenClaw is a **Node.js-based personal AI gateway** running a WebSocket control plane (`ws://127.0.0.1:18789`) that routes inbound messages from 23+ messaging channels (Telegram, WhatsApp, Discord, Slack, iMessage, etc.) to isolated agents, which dispatch LLM requests with multi-model fallback + tool streaming. The runtime is process-managed by LaunchAgent on macOS; the agent itself is embedded (RPC mode). \n\n**Nuzantara is using OpenClaw for**:\n- Primary messaging channel: Telegram (enabled, receiving message timeouts)\n- Multi-LLM routing: MiniMax M2.7 (primary) → Kimi K2.6 → DeepSeek Chat → Ollama Qwen3.5\n- Persistent memory: memory-core plugin (storing observations from tool use)\n- Orchestration: lobster plugin (workflow DSL, enabled)\n- Voice: voice-call skill (enabled)\n- Custom extensions: claude-mem (v12.1.0, marketplace install)\n\n**Version Gap**: Installed version is 2026.3.31 (released ~Mar 31); latest is 2026.4.29 (released Apr 30). Recent releases include Knowledge Agents system (12.1.0 in claude-mem), improved Telegram channel timeouts, and model failover hardening.\n\n---\n\n## A. Architecture\n\n### Runtime Model\n\nOpenClaw is a **Node.js daemon** (v22.22.0, bundled) running:\n- **Gateway** (port 18789, loopback-only, hybrid reload on config change)\n- **Control UI** (Web dashboard, http://127.0.0.1:18789)\n- **Pi agent** (embedded RPC, not external process)\n- **Channel adapters** (Telegram: grammY, Discord: discord.js, WhatsApp: Baileys, etc.)\n\n**Process Management**:\n- macOS: LaunchAgent (`~/Library/LaunchAgents/openclaw.plist` — managed by Nuzantara's `monitor-air.log` cron)\n- KeepAlive policy: checked via heartbeat monitor every 1h (Nuzantara config: `heartbeat.every=\"1h\"`)\n- Uptime target: 24/7 (threshold exceeded May 2, 2026 at 01:13 UTC — gap analysis in logs)\n\n### Agent Abstractions\n\n**Agent Definition** (`agents.list[]`):\n- Nuzantara has 2 agents:\n 1. **main**: primary, Telegram-bound, sandbox=off, model routing primary→fallback chain\n 2. **coder**: isolated workspace, sandbox=off, web tools denied (no web_search, web_fetch, browser)\n\n**Model Routing**:\n```\nmain:\n primary: openrouter/minimax/minimax-m2.7\n fallbacks: [kimi-k2.6, deepseek-chat, ollama/qwen3.5:9b]\n\ncoder:\n primary: openrouter/qwen/qwen3-max\n fallbacks: [qwen3.6-plus, kimi-k2.6, deepseek-reasoner, qwen3.5:9b]\n```\n\n**Fallback Chain Logic**:\n- Model request → try primary; if fails (auth, rate_limit, not_found) → try next fallback\n- Auth cooldown: 300s lockout after 10 auth failures in 60s window (gateway.auth.rateLimit)\n- Rate-limit cooldown: automatic provider cooldown (all-profiles-unavailable state) visible in logs (Mar 1-2, Google Gemini CLI exhausted)\n\n**Sandbox Modes**:\n- Default: `non-main` (separate container per agent workspace)\n- Nuzantara: `mode=off` for both agents (no isolation) + `prune.idleHours=24` (clean up idle containers)\n\n### Plugin System\n\nPlugins extend gateway capabilities via allow-list:\n\n**Loaded Plugins** (`plugins.entries`):\n- **memory-core** (enabled): observation feed + persistent storage\n- **memory-lancedb** (disabled): vector search backend (not active)\n- **lobster** (enabled): workflow orchestration DSL\n- **llm-task** (enabled): multi-step LLM orchestration\n- **voice-call** (enabled): phone/VOIP integration\n- **telegram** (implicit, channel adapter)\n\n**Plugin Trust Model**:\n- Plugins in `~/.openclaw/extensions/` are scanned; only those in `plugins.allow[]` are loaded\n- Nuzantara's installed plugin: `claude-mem` (v12.1.0) in `~/.openclaw/extensions/claude-mem/`\n- **Trust Warning**: claude-mem flagged in gateway logs as \"loaded without install/load-path provenance\" (implies manual fs install, not via package manager)\n\n### Skill System\n\nSkills add tools to agents via hooks + HTTP API.\n\n**Bundled Skills** (`~/.openclaw/lib/node_modules/openclaw/skills/`):\n- goplaces (Google Places API), xurl (URL shortener), voice-call (Twilio), notion, slack, discord, trello, spotify-player, etc.\n\n**Nuzantara Enabled Skills** (`skills.entries`):\n- **goplaces**: enabled (env: GOOGLE_PLACES_API_KEY)\n- **xurl**: enabled\n- **voice-call**: enabled\n- **notion**: enabled (env: NOTION_API_KEY)\n- **antigravity**: enabled\n\n**Custom Skills Directory**: `/Users/nuzantara/Desktop/nuzantara/apps/nuzantara-mcp/skills` (loaded via `skills.load.extraDirs[]`)\n\n### Channel Bindings\n\n**Telegram Channel** (enabled, dmPolicy=open):\n- **BotToken**: configured (redacted in config, present in plaintext)\n- **Allowed Senders**: `allowFrom=[\"*\"]` (open to all)\n- **Streaming**: disabled (responses sent as complete message blocks)\n- **Timeout**: 60s per message\n- **Retry**: 2 attempts, min 5s delay, max 60s delay, 0.5 jitter\n- **Known Issues**:\n - Network timeouts (ETIMEDOUT / EHOSTUNREACH observed Mar 1-2, 30s+ waits)\n - BOT_COMMANDS_TOO_MUCH (Telegram API limit: max 100 commands, Nuzantara registering 92-97, dropping 19-20 on each attempt)\n\n**Other Channels**:\n- WhatsApp, Discord, Slack, Signal, iMessage (BlueBubbles), IRC, Teams, Matrix: all disabled in config\n\n### Memory Model\n\n**memory-core** plugin:\n- SQLite database: `~/.openclaw/agents/main/agent/memory.db` (not visible in config, inferred from logs)\n- Observation feed: tool_use events → semantic summaries → injected into future sessions\n- Token-aware: context pruning mode=cache-ttl (1h TTL), compaction mode=safeguard (96k reserve floor, 40% history max)\n- Worker Service: runs on separate port (likely 37777 per claude-mem docs), managed by Bun\n- Vector backend: Chroma (if memory-lancedb enabled; currently off)\n\n### mcporter Integration\n\n**Tool Surface**:\n- Built-in: browser, canvas, nodes, cron, sessions\n- MCP tools: claude-mem (`search`, `timeline`, `get_observations`), custom skills\n\n**Safety Allowlist** (`tools.exec.safeBins[]`):\n```\nls, cat, tail, which, nuzantara-ops, mcporter\n```\n- Only these binaries can be executed without sandbox approval\n- `mcporter` is whitelisted (allows MCP tool invocation from agent)\n- **Known Issue**: mcporter allowlist audited Mar 22; safeBins entries not matching plugin config (audit log shows mismatch fixed via plugin reload)\n\n### Lobster Workflows\n\n**Lobster Plugin** (enabled):\n- DSL for multi-step orchestration\n- Workflow files: `~/.openclaw/workspace/.lobster/*.yml` (inferred)\n- Nuzantara logs show no active workflows, but plugin enabled for future use\n\n### Sandbox & Safety\n\n**Exec Approval Socket** (`exec-approvals.sock`):\n- Manual approval flow for unsafe commands\n- Not actively used in Nuzantara (sandbox=off, so no approvals needed)\n\n**Circuit Breaker** (`tools.loopDetection`):\n- Enabled globally (threshold=10 repeats)\n- Detectors:\n - **genericRepeat**: catch identical tool calls in sequence\n - **knownPollNoProgress**: detect polling without state change\n - **pingPong**: detect tool A → B → A cycles\n- Nuzantara: enabled, no circuit trips observed (logs clean on this metric)\n\n---\n\n## B. Current Capabilities\n\n### Agentic Tasks\n\n**Single-turn**: `openclaw agent --message \"query\"` (CLI or Gateway RPC)\n**Multi-turn**: Session state preserved in memory; model compaction + context pruning per config\n**Persistent Loops**: Supported via Lobster workflows (enabled but not in use)\n\n### Tool Use\n\n| Tool | Enabled | Notes |\n|------|---------|-------|\n| **web_search** | Yes | Integrated |\n| **web_fetch** | Yes | Integrated |\n| **browser** | No (coder agent denies) | Chrome control, snapshots, actions |\n| **exec** | Partial (safeBins only) | ls, cat, tail, nuzantara-ops, mcporter |\n| **file ops** | Via browser canvas | Not direct agent tool |\n| **image** | Yes (image model routing) | Primary: ollama/qwen2.5vl:7b, fallback: openrouter/qwen3-vl |\n| **canvas** (macOS) | Yes | A2UI protocol, push/reset/eval/snapshot |\n| **cron** | Yes | 24 jobs max (Nuzantara shows 0 active, scheduler may be frozen as of Apr 30) |\n| **custom MCP** | Yes (mcporter) | Route to local/remote MCP servers |\n\n**Multi-LLM Routing**: Each agent has primary + 3 fallback models; per-model params (temperature, max_tokens) configurable\n\n### Multi-LLM Routing\n\n**Provider Configuration**:\n- **openrouter**: native support, baseUrl=https://openrouter.ai/api/v1, 6 models configured\n- **deepseek**: native support, baseUrl=https://api.deepseek.com/v1, 2 models configured\n- **ollama**: local, baseUrl=http://127.0.0.1:11434, 0 models pre-configured (dynamic discovery)\n- **anthropic**: **NOT configured** (no ANTHROPIC_API_KEY in env; Nuzantara policy: banned)\n- **openai** (via openrouter): shim via openrouter provider\n\n**Per-Model Tuning**:\n```\ntemperature: 0.2–0.4 (reasoning), 0.3–0.4 (chat)\nmax_tokens: 4096–8192\nollama-specific: keep_alive=10m (memory cache), top_p=0.9, top_k=20\n```\n\n**Fallback Behavior**: \n- Try primary; if fails, cascade through fallbacks\n- Rate-limit cooldown (300s) applied per provider\n- Auth errors caught and retried with different auth profile (if multi-profile configured)\n\n### Persistent Memory\n\n**memory-core** (enabled):\n- **Lifecycle Hooks**: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd\n- **Observation Compression**: tool_use events → semantic summaries (via Claude API or local LLM)\n- **Injection Strategy**: progressive disclosure (layer 1: index; layer 2: timeline; layer 3: full details)\n- **Search Tools**: `mem-search`, `timeline`, `get_observations` (MCP interface)\n- **Durability**: SQLite persistent store, survives session/process restarts\n\n### Channel I/O\n\n**Telegram** (primary):\n- Inbound: message events → matched to agent via binding (main agent, Telegram channel)\n- Outbound: agent reply → formatted as Telegram message → sent via botToken\n- **State**: connected (logs show recent message processing up to May 2 01:13)\n- **Timeouts**: retry logic (2 attempts, 5–60s backoff) masks network latency\n\n### Voice-Call\n\n**voice-call Skill** (enabled):\n- Integration: Twilio (inferred from OpenClaw docs; not explicitly in Nuzantara config)\n- Capabilities: accept voice calls, stream audio, TTS output (ElevenLabs or system fallback)\n- **Status in Nuzantara**: enabled but not actively tested\n\n### Browser Automation\n\n**Browser Tool** (available, not in use by main agent):\n- Chrome/Chromium control (dedicated OpenClaw instance)\n- Actions: snapshot, click, type, scroll, upload, profile switching\n- **Nuzantara**: coder agent has browser denied via `tools.deny=[\"browser\"]`\n\n### Workflow Orchestration (Lobster)\n\n**Lobster DSL** (enabled, not in active use):\n- YAML-based multi-step orchestration\n- Syntax: task dependencies, conditional routing, parallel execution\n- **Nuzantara**: loaded but no workflows observed in logs\n\n### Subagents\n\n**Subagent Pattern**: supported via agent routing (`bindings[]` match rules)\n- **Nuzantara**: 2 agents defined (main, coder); no inter-agent delegation observed\n- **Limitation**: agents are isolated; no direct parent-child RPC (agents are standalone)\n\n### Cron / Scheduled Jobs\n\n**Cron Tool**:\n- API: `POST /gateway/cron` or `openclaw cron add <schedule> <command>`\n- Max 24 jobs per gateway\n- **Nuzantara Status**: 0 active jobs logged (scheduler may be frozen since Apr 30 per audit notes)\n\n### Hooks\n\n**7 Lifecycle Hooks** (claude-mem):\n- SessionStart, UserPromptSubmit, PostToolUse, Summary, SessionEnd, PreToolUse, Stop\n- **Nuzantara Use**: memory-core hooks active (observation capture, summary generation)\n\n### Plugin Development\n\n**Plugin Template**:\n- Entry point: `plugin/index.ts` or `dist/index.js`\n- Hooks: export `hooks: { SessionStart, UserPromptSubmit, ... }`\n- MCP Tools: export `tools: [ ... ]` (JSON-RPC 2.0 interface)\n- **Nuzantara Custom Skills**: loaded from `/Users/nuzantara/Desktop/nuzantara/apps/nuzantara-mcp/skills/` (no plugins observed)\n\n### Embedded Agent Runtime\n\n**Pi Agent** (embedded, not delegated):\n- Runs in-process with gateway (RPC loopback)\n- Session state: maintained in gateway memory (not persistent across restarts without memory plugin)\n- Tool dispatch: streaming responses back to gateway → channel output\n\n---\n\n## C. What's NEW (Recent OpenClaw Releases)\n\n| Release | Date | Highlights |\n|---------|------|-----------|\n| **v2026.4.29** | Apr 30, 2026 | Stability improvements, auth profile migration docs |\n| **v2026.4.09** | Apr 9, 2026 | Knowledge Agents system (6 new MCP tools: build_corpus, prime_corpus, query_corpus, etc.) |\n| **v2026.3.31** | Mar 31, 2026 | **[INSTALLED]** DM pairing default, Telegram net timeout handling, provider enum changes |\n| **v2026.1.5** | Jan 5, 2026 | Image model config, agent.imageModel + fallbacks, model shorthands (opus, sonnet, gpt, gemini) |\n\n**Most Notable for Nuzantara**:\n- **Knowledge Agents** (v2026.4.09): Compile filtered corpus from claude-mem observations, query conversationally. 6 new MCP tools (build_corpus, list_corpora, prime_corpus, query_corpus, rebuild_corpus, reprime_corpus). **Not yet exploited.**\n- **Auth Profile Migration** (v2026.3.31 → v2026.4.29): DM pairing now default (requires approval codes); legacy \"open to all\" requires explicit dmPolicy=open + allowFrom=[\"*\"]. **Nuzantara already configured.**\n- **Telegram Timeout Hardening** (v2026.3.31): Retry + backoff for network errors (30s+ waits masked). **Experienced but handled gracefully.**\n\n**Claude-Mem Integration** (v12.1.0, installed via marketplace):\n- Knowledge agents now supported (native corpus build, prime, query MCP tools)\n- Smart-explore extended to 24 languages\n- File-read decision gate (PreToolUse hook blocks redundant reads using observation timeline)\n\n---\n\n## D. Comparison vs Alternatives\n\n| Dimension | OpenClaw | cron-agent-python (Nuzantara) | LangChain/LangGraph | Anthropic Agent SDK (direct) |\n|-----------|----------|------|-----------|---------|\n| **Primary UX** | Gateway + multi-channel | Python cron + custom CLI | Chain/graph DSL | Messages API |\n| **Multi-LLM** | Native (fallback chains) | Manual (explicit per-agent config) | LLMChain abstraction | No native support |\n| **Persistent Memory** | Plugin-based (memory-core) | NotebookLM (external) | No built-in | No built-in |\n| **Channel Binding** | 23+ integrations (Telegram, Discord, etc.) | CLI-only / webhooks | None (user's responsibility) | None |\n| **Workflow Orchestration** | Lobster DSL | Python task queue | LangGraph (DAG-based) | Claude Agent SDK (new) |\n| **Tool Streaming** | Yes (block streaming) | Manual | Yes (via tool classes) | Yes (native) |\n| **Sandbox** | Exec approval socket | None (direct subprocess) | None | None (external runner) |\n| **State Persistence** | SQLite (via plugins) | Disk-backed (JSON/state files) | Not built-in | Not built-in |\n| **Scaling** | Single gateway (local) | Multi-machine (cron dispatch) | App-level (doesn't scale infra) | Delegated to cloud provider |\n| **Ease of Setup** | `openclaw onboard` (90s) | Python venv + config | npm install + boilerplate | anthropic SDK (Python/JS) |\n\n**OpenClaw's Unique Value Propositions**:\n1. **Multi-channel inbox**: Single control plane for Telegram, WhatsApp, Discord, Slack, iMessage, IRC, Teams, Signal, etc.\n2. **Persistence out-of-the-box**: memory-core plugin captures observations + injects into future sessions (no manual logging)\n3. **Native fallback chains**: multi-model routing with per-provider cooldowns + auth management\n4. **Embedded + local**: runs on your machine (no cloud), binds to loopback only (no public expose by default)\n\n**Where OpenClaw Loses**:\n1. **No cloud sync**: single-machine deployment (no multi-region HA out of the box)\n2. **Provider enum rigidity**: only supports explicit providers (openrouter, deepseek, ollama, anthropic); no generic OpenAI-compatible override until v2026.3.31+ (baseUrl interpolation)\n3. **Command limit (Telegram)**: max 100 commands registered; Nuzantara hitting 92-97, dropping 19-20 on each sync\n4. **Scheduler frozen** (apr 30+): cron jobs not running (24-job queue inert); cause unknown\n\n**vs cron-agent-python** (Nuzantara parallel):\n- cron-agent-python: 19 active strategies, each runs independently on schedule; state decoupled (disk-backed JSON)\n- OpenClaw: single agent with multi-LLM routing; state unified in memory-core\n- **Recommendation**: cron-agent-python excels for **independent, scheduled tasks** (classification, analysis per-document); OpenClaw excels for **interactive, multi-turn conversations** with memory (Telegram chatbot + persistent context)\n\n---\n\n## E. Scaling Characteristics\n\n### Concurrency\n\n**Max Concurrent Agents**: 1 gateway, N agents (in Nuzantara: 2)\n- **Per-Agent Sessions**: Unbounded (session state pruned on demand)\n- **Concurrent Requests**: Limited by LLM provider rate limits (OpenRouter: ~100 req/min; DeepSeek: API doc unclear; Ollama: local, unbounded)\n- **Telegram Message Queue**: Sequential (one message processed at a time, 60s timeout → retry → backoff)\n\n### Memory Footprint\n\n| Component | RSS (Idle) | RSS (Active) | Notes |\n|-----------|-----------|-------------|-------|\n| Gateway process | ~200–300 MB | ~400–600 MB | Node.js runtime + channel adapters |\n| memory-core SQLite | ~10 MB | ~50+ MB | Depends on observation count + vector index |\n| Chroma (if enabled) | N/A | ~200+ MB | Python subprocess (uv-managed), not active in Nuzantara |\n| Total (Nuzantara) | ~250 MB | ~500+ MB | Measured via LaunchAgent memory monitor |\n\n### Latency Overhead\n\n| Operation | Latency | Notes |\n|-----------|---------|-------|\n| Gateway routing (inbound message) | <10ms | WebSocket demux + binding match |\n| Agent dispatch (to LLM) | <50ms | HTTP to OpenRouter/DeepSeek |\n| LLM request | 500ms–5s | Depends on model, fallback chain depth |\n| Response streaming (Telegram) | 1–30s | Tool execution + LLM generation + retry backoff |\n| Memory injection (PreToolUse) | 100–500ms | SQLite query + compression (if observation reuse) |\n\n**Gateway overhead is negligible** (<10% of E2E latency).\n\n### State Persistence\n\n**Durable Across Crashes**: Yes (if memory-core enabled)\n- Session state saved to SQLite after each tool_use hook\n- Observation timeline reconstructed on SessionStart\n\n**Graceful Shutdown**: LaunchAgent KeepAlive policy\n- monitored by `launchctl unload/load` (manual) or cron watchdog (Nuzantara)\n- Timeout: 30s (hard kill if not responsive)\n\n### Crash Recovery\n\n**KeepAlive=true** (Nuzantara config):\n- LaunchAgent auto-restarts gateway on exit (non-zero exit code)\n- State surviving: memory-core observations (durable), in-flight messages (lost, retried by Telegram)\n- **Observed**: gateway crashes not logged; LaunchAgent restarts appear transparent\n\n### Multi-Instance Topology\n\n**Federation**: Not supported natively\n- **Workaround**: run multiple gateways on different ports (18789, 18790, ...), but agents are isolated per gateway\n- **Nuzantara**: single gateway (port 18789); no multi-instance setup\n\n---\n\n## F. Security Model\n\n### Loopback-Only Binding\n\n**gateway.bind=loopback** (Nuzantara default):\n- Gateway listens on 127.0.0.1:18789 (localhost only)\n- No public internet exposure (requires SSH tunnel or Tailscale to access from remote)\n- **Threat Model**: protects against direct internet attacks; assumes local machine is secure\n\n### Auth Token & Rate Limiting\n\n**Token** (gateway.auth.token):\n- Long random string (example: 64 char base64)\n- Required in WebSocket `?token=...` query param or HTTP `Authorization: Bearer <token>` header\n- **Storage**: plaintext in `~/.openclaw/openclaw.json` (same permissions as SQLite DB)\n- **Rotation**: manual edit + `openclaw reload` (no auto-rotation tool visible)\n\n**Rate Limiting**:\n- **maxAttempts**: 10 failures in 60s window (Nuzantara config)\n- **lockoutMs**: 300s (5 min) lockout after threshold\n- **Scope**: per-IP (loopback is single IP, so affects all local clients equally)\n\n### exec-approvals.sock\n\n**Manual Approval Flow**:\n- Unsafe exec commands → `exec-approvals.sock` (Unix domain socket, `/tmp/exec-approvals-<pid>.sock`)\n- User approval via CLI: `echo \"approve <exec-id>\" | nc -U /tmp/exec-approvals-<pid>.sock`\n- **Nuzantara Status**: sandbox=off for both agents, so approval flow never triggered\n\n### Sandbox Modes\n\n| Mode | Isolation | Use Case |\n|------|-----------|----------|\n| **off** | None | Development, trusted local use |\n| **non-main** | Per-agent container | Multi-agent isolation (default agents.defaults.sandbox.mode) |\n| **full** | Per-session container | Maximum isolation (experimental) |\n\n**Nuzantara**: mode=off (no isolation). Rationale: single user, local machine, trusted workflows. Trade-off: agents can interfere (shared filesystem, process space).\n\n### Safety Bins Allowlist\n\n**Tools.exec.safeBins**:\n```\nls, cat, tail, which, nuzantara-ops, mcporter\n```\n- Only these binaries bypass approval flow\n- Others require exec approval (if sandbox=non-main or full)\n- **Known Issue** (Mar 22): safeBins not matching plugin config; resolved via plugin reload\n\n**Attack Surface**: agent can only run safe binaries; can't execute arbitrary commands (unless sandbox=off + agent has write access to filesystem, which it does in Nuzantara)\n\n### Plugin Trust Model\n\n**Plugin Loading** (`plugins.allow[]`):\n- Whitelist-based: only plugins in allow-list are loaded\n- Nuzantara loads: memory-core, lobster, llm-task, voice-call (implicit)\n- claude-mem: loaded from `~/.openclaw/extensions/claude-mem/` (manual install, not in core plugins.allow)\n\n**Trust Signals**:\n- Official plugins: signed releases, open-source GitHub\n- User plugins (Nuzantara custom skills): loaded from `/Desktop/nuzantara/apps/nuzantara-mcp/skills/` (same machine, implicit trust)\n- **Warning**: claude-mem flagged as \"loaded without install/load-path provenance\" → suggests side-load outside normal package path\n\n### Secret Handling\n\n**API Key Interpolation**:\n```json\n{\n \"providers\": {\n \"openrouter\": {\n \"apiKey\": \"${OPENROUTER_API_KEY}\"\n }\n }\n}\n```\n\n**Resolution**:\n1. Check environment variable (shellEnv=true in Nuzantara config, shell timeout 15s)\n2. Check EnvironmentVariables from LaunchAgent plist (if running as daemon)\n3. Fall back to plaintext in config (NOT recommended)\n\n**Nuzantara Config**:\n- OPENROUTER_API_KEY, DEEPSEEK_API_KEY, GOOGLE_PLACES_API_KEY, NOTION_API_KEY → in `~/.openclaw/.env` (not shown, but inferred from skills.env)\n- **Risk**: if `.env` checked into git, keys exposed\n\n---\n\n## G. Operational Model\n\n### LaunchAgent Management\n\n**Service File**: `~/Library/LaunchAgents/openclaw.plist` (macOS)\n- **Label**: com.openclaw.daemon\n- **Program**: node (bundled in ~/.openclaw/tools/node-v22.22.0/bin/node)\n- **KeepAlive**: true (auto-restart on exit)\n- **ThrottleInterval**: 30s (min time between restarts after crash)\n- **RunAtLoad**: true (start on login)\n\n**Manual Control**:\n```bash\nlaunchctl load ~/Library/LaunchAgents/openclaw.plist\nlaunchctl unload ~/Library/LaunchAgents/openclaw.plist\nlaunchctl start com.openclaw.daemon\nlaunchctl stop com.openclaw.daemon\n```\n\n**Nuzantara Monitoring**: cron job `monitor-air.log` (implied by logs; monitors for crashes + auto-restart failures)\n\n### Logs\n\n| Log | Path | Rotation | Notes |\n|-----|------|----------|-------|\n| **gateway.log** | `~/.openclaw/logs/gateway.log` | Yes (daily) | Main diagnostic output (diagnostic, channel, agent events) |\n| **gateway.err.log** | `~/.openclaw/logs/gateway.err.log` | Yes (daily) | Errors, warnings, stack traces |\n| **commands.log** | `~/.openclaw/logs/commands.log` | N/A | Slash command executions |\n| **config-audit.jsonl** | `~/.openclaw/logs/config-audit.jsonl` | Append | Config change audit trail |\n\n**Log Level**: `logging.level=info` (Nuzantara config); controlled via `openclaw config set logging.level=debug` (or restart with env var)\n\n**Retention**: no explicit TTL in config; logs grow unbounded (gateway.log: 21.7 GB as of May 2, gateway.err.log: 288 MB). **Risk**: disk space exhaustion.\n\n### Health Endpoint\n\n**Endpoint**: `GET /healthz` (Gateway)\n- **Response**: `{ \"status\": \"ok\" }` (200 OK) or 500 on error\n- **Nuzantara Use**: implied by monitor-air; likely polled every 5–60s\n\n### Metrics Export\n\n**Prometheus**: Not built-in to OpenClaw (as of v2026.3.31)\n- **Workaround**: parse logs with regex + ship to external metrics service\n- **Nuzantara**: no custom metrics exporter observed\n\n### Debugging\n\n**Verbose Mode**: `openclaw gateway --verbose` (CLI flag)\n**Log Level Override**: `OPENCLAW_LOG_LEVEL=debug` (env var at startup)\n**RPC Tracing**: gateway logs all RPC calls (agent → channel) with stack traces on failure\n\n**Trace an Agent Decision**:\n1. Enable debug logging\n2. Send message via Telegram\n3. Check gateway.log for SessionStart → UserPromptSubmit → tool_use → agent response flow\n4. Correlate with memory-core observations (check `.openclaw/agents/main/agent/memory.db` if needed)\n\n### Update Process\n\n**Manual Update**:\n```bash\nnpm install -g openclaw@latest\nopenclaw onboard --update # or --install-daemon to refresh daemon\n```\n\n**Safe Update (Nuzantara)**:\n1. `launchctl stop com.openclaw.daemon` (halt gateway)\n2. Backup `~/.openclaw/openclaw.json` + `~/.openclaw/workspace/`\n3. `npm install -g openclaw@latest`\n4. Review CHANGELOG for breaking changes\n5. Run `openclaw doctor` (auto-migrate config if needed)\n6. `launchctl start com.openclaw.daemon` (restart)\n\n**Auto-Update**: Not configurable in v2026.3.31; manual only.\n\n---\n\n## H. Known Issues & Pitfalls\n\n### 1. Provider Enum Rigidity\n\n**Issue**: openai-completions API wrapper only recognizes explicit `api: \"openai\"` (no generic override)\n\n**Impact**: Can't use arbitrary OpenAI-compatible backends (e.g., vLLM, LocalAI) without forking\n\n**Workaround** (Nuzantara): Use `api: \"openai-completions\"` with openrouter shim (all models exposed via openrouter baseURL)\n\n**Status**: Likely fixed in v2026.4.29 (baseUrl interpolation mentioned in changelog), but not tested against Nuzantara's installed v2026.3.31\n\n### 2. Secret Interpolation (`${VAR}` Only in env)\n\n**Issue**: `apiKey: \"${VAR}\"` resolves ONLY if VAR is in EnvironmentVariables (from LaunchAgent plist or shell env at startup), NOT from `.env` file or inline plaintext\n\n**Impact**: Easy to forget to export env var → silent fallback to missing key → auth failures\n\n**Example** (Nuzantara):\n```json\n{\n \"openrouter\": {\n \"apiKey\": \"${OPENROUTER_API_KEY}\" // resolves to env var if set, else null\n }\n}\n```\n\n**Workaround**: Explicitly set in LaunchAgent plist `EnvironmentVariables` section or `source ~/.openclaw/.env` before `launchctl load`\n\n### 3. mcporter Allowlist Quirks\n\n**Issue** (Mar 22 audit log): safeBins entries `ls, cat, tail, which, nuzantara-ops, mcporter` not matching plugin config → mcporter tool calls rejected\n\n**Root Cause**: Plugin loading race condition (safeBins checked before plugin registration completes)\n\n**Status**: Resolved in Nuzantara by manual reload (launchctl stop/start)\n\n**Fix Expected**: v2026.4.29+ (not tested)\n\n### 4. Plugin Trust Warnings\n\n**Issue**: claude-mem loaded from `~/.openclaw/extensions/claude-mem/` without install/load-path provenance\n\n**Impact**: Minor (code review recommended for untrusted sources); claude-mem is open-source + widely used\n\n### 5. Telegram Network Timeouts\n\n**Issue**: `getUpdates` request times out after 30s; retry backoff (5s → 60s) masks latency but creates gaps in message delivery (Apr 30 + May 2 logs show repeated timeouts)\n\n**Impact**: Inbound messages may be delayed 30–120s during network flaps\n\n**Cause**: Likely ISP or Telegram API throttling (Nuzantara in Bali, geographically distant from Telegram servers)\n\n**Workaround**: Increase timeout (Telegram: `timeoutSeconds=120`) or use webhook polling (if supported in next version)\n\n**Status**: Retry logic + backoff now in v2026.3.31; gracefully handled but not ideal UX\n\n### 6. BOT_COMMANDS_TOO_MUCH (Telegram API Limit)\n\n**Issue**: Telegram API limits /command menu to 100 entries; Nuzantara registering 92–97 commands (skills + custom), dropping 19–20 on each sync\n\n**Impact**: Some skills unavailable in Telegram /command autocomplete menu (but still callable via message mention)\n\n**Log Evidence**:\n```\n[telegram] rejected 92 commands (BOT_COMMANDS_TOO_MUCH); retrying with 73.\n[telegram] accepted 73 commands after BOT_COMMANDS_TOO_MUCH (started with 92; omitted 19).\n```\n\n**Root Cause**: Too many skills enabled (goplaces, xurl, voice-call, notion, antigravity) + OpenClaw bundled commands\n\n**Workaround**: Disable low-use skills in `skills.entries` (set enabled=false for unused ones)\n\n**Fix**: None in OpenClaw core (Telegram API limit). Requires operator intervention.\n\n### 7. Scheduler Frozen (Apr 30+)\n\n**Issue**: Cron jobs not running; 24-job queue inert\n\n**Evidence**: No cron logs in gateway.log since Apr 30; cron tool still responds to queries but doesn't execute\n\n**Impact**: Scheduled tasks (batch processing, daily summaries) missed\n\n**Likely Cause**: \n- Scheduler process crash (separate thread)\n- Job queue lock contention\n- State corruption after config reload\n\n**Status**: Unknown (no debugging done); requires `openclaw doctor` + manual job purge/recreate\n\n**Workaround**: Migrate to external cron runner (separate Python process with its own scheduler) or upgrade to v2026.4.29+\n\n### 8. Context Compaction False Positives\n\n**Issue**: context pruning + compaction may discard relevant observations if TTL expires before session end\n\n**Config** (Nuzantara):\n```\ncontextPruning.mode=cache-ttl, ttl=1h\ncompaction.mode=safeguard, reserveTokensFloor=96k\n```\n\n**Impact**: Multi-day sessions lose early context after 1h, even if still relevant\n\n**Workaround**: Increase TTL to 24h or use `mode: \"never\"` (disables pruning, risks context explosion)\n\n---\n\n## I. Roadmap & Future Direction\n\n**Public Roadmap**: https://github.com/openclaw/openclaw/issues (GitHub Issues, no official roadmap doc)\n\n**Recent PR Signals** (inferred from changelog):\n- **Knowledge Agents** (v2026.4.09): Building queryable \"brains\" from observation history (corpus build + prime + query)\n- **Auth Profile System** (v2026.3.31+): Multi-profile support + role-based failover (not fully enabled in Nuzantara)\n- **DM Pairing Security**: Default dmPolicy=pairing (require approval code for unknown senders) — security-first direction\n- **Provider Enum Expansion**: Support for more providers + generic OpenAI-compatible override (v2026.4.29 changelog hints)\n\n**Likely Future Work** (speculative):\n1. **Cloud Sync**: Multi-region HA + state replication (not in current roadmap, but enterprise feature request)\n2. **Structured Output**: Typed tool responses (Zod schema validation) — currently working, likely formalization\n3. **Voice Wake Improvements**: More language support, custom wake words (v2026.3.31 has voice-call, room for improvement)\n4. **Scheduler Stability**: Fix cron job queue race conditions (based on Apr 30 frozen state)\n5. **Knowledge Agent UI**: Web viewer for corpus browsing + semantic search (claude-mem v12.1.0 foundation)\n\n---\n\n## Summary Table: What Nuzantara Is Using vs. Available\n\n| Feature | Available | Enabled | Status |\n|---------|-----------|---------|--------|\n| **Multi-LLM Routing** | Yes (4 providers) | Yes (fallback chain) | Working, no issues |\n| **Persistent Memory** | Yes (memory-core) | Yes | Working, observations injected |\n| **Telegram Channel** | Yes | Yes | Working, but network timeouts every 30s+ |\n| **Workflow Orchestration** | Yes (Lobster) | Enabled but unused | Ready to use, no barriers |\n| **Knowledge Agents** | Yes (new in v12.1.0) | No | **NOT EXPLOITED** (v2026.4.09 feature) |\n| **Voice-Call Integration** | Yes | Yes | Enabled, not actively tested |\n| **Browser Automation** | Yes (main agent denied) | Partial (coder agent) | Available for coder agent |\n| **Cron Scheduler** | Yes (24 job limit) | Yes but frozen | **BROKEN** since Apr 30 |\n| **Custom Skills** | Yes (extraDirs) | Partially | `/nuzantara-mcp/skills/` not fully synced |\n| **Canvas (macOS)** | Yes | Implicit | Available if using macOS |\n| **Multi-Channel** | Yes (23 channels) | Only Telegram | Others disabled; can be enabled |\n\n---\n\n## Recommendations for Nuzantara\n\n1. **Upgrade to v2026.4.29** (from v2026.3.31): Knowledge Agents system ready; cron scheduler likely fixed; auth profiles stabilized\n2. **Exploit Knowledge Agents**: Build corpus from claude-mem observations; enable conversational history replay (query_corpus tool)\n3. **Fix Cron Scheduler**: Debug Apr 30 freeze; if unresolved in v2026.4.29, fallback to external cron-agent-python runner\n4. **Reduce Telegram Commands**: Disable unused skills (elite-longterm-memory, bluebubbles, discord, slack, spotify, trello) to stay under 100-command limit\n5. **Increase Telegram Timeout**: Raise `timeoutSeconds=120` to mask network latency (or implement webhook polling in next OpenClaw version)\n6. **Monitor Memory Growth**: Track `~/.openclaw/logs/gateway.log` size (currently 21.7 GB); implement log rotation or external log shipping\n7. **Test Voice-Call Skill**: Verify Twilio integration if voice interaction is planned\n8. **Sync Custom Skills**: Ensure `/nuzantara-mcp/skills/` fully registered with OpenClaw (check skills.entries for all custom entries)\n\n---\n\n## Files Referenced\n\n- **Config**: `/Users/nuzantara/.openclaw/openclaw.json` (v2026.3.31 schema)\n- **Installation**: `/Users/nuzantara/.openclaw/lib/node_modules/openclaw/`\n- **Logs**: `~/.openclaw/logs/gateway.log`, `gateway.err.log`\n- **Memory DB**: `~/.openclaw/agents/main/agent/memory.db` (SQLite, not visible in config)\n- **Workspace**: `~/.openclaw/workspace/` (markdown docs + agent state)\n- **Extensions**: `~/.openclaw/extensions/claude-mem/` (installed plugin)\n- **GitHub**: https://github.com/openclaw/openclaw (releases, issues, docs)\n- **Documentation**: https://docs.openclaw.ai (official, updated for v2026.4.29)\n- **claude-mem Marketplace**: `/Users/nuzantara/.claude/plugins/marketplaces/thedotmack/` (v12.1.0, AGPL-3.0)\n\n---\n\n**Document Compiled**: 2026-05-02 13:20 UTC | **Last Config Change**: 2026-05-02 13:20 UTC (Nuzantara-reported version)\n"}, "435945f89392b92ca558834d695b57058c697670": {"t": 1786068476.14526, "n": 34193, "h": "c11e0bae09aa0645", "c": "# OpenClaw Deep Research: Architecture, Capabilities & Current Usage\n**Date**: 2026-05-02 | **Version**: openclaw 2026.3.31 | **Local Install**: `~/.openclaw/lib/node_modules/openclaw/` | **Config**: `~/.openclaw/openclaw.json`\n\n**Audience**: Claude (reader) + cross-LLM reviewers (Codex GPT-5.5, Gemini 3.1 Pro, DeepSeek V4 Reasoner) preparing to reason about OpenClaw's role in Nuzantara automations.\n\n---\n\n## Executive Summary\n\nOpenClaw is a **Node.js-based personal AI gateway** running a WebSocket control plane (`ws://127.0.0.1:18789`) that routes inbound messages from 23+ messaging channels (Telegram, WhatsApp, Discord, Slack, iMessage, etc.) to isolated agents, which dispatch LLM requests with multi-model fallback + tool streaming. The runtime is process-managed by LaunchAgent on macOS; the agent itself is embedded (RPC mode). \n\n**Nuzantara is using OpenClaw for**:\n- Primary messaging channel: Telegram (enabled, receiving message timeouts)\n- Multi-LLM routing: MiniMax M2.7 (primary) → Kimi K2.6 → DeepSeek Chat → Ollama Qwen3.5\n- Persistent memory: memory-core plugin (storing observations from tool use)\n- Orchestration: lobster plugin (workflow DSL, enabled)\n- Voice: voice-call skill (enabled)\n- Custom extensions: claude-mem (v12.1.0, marketplace install)\n\n**Version Gap**: Installed version is 2026.3.31 (released ~Mar 31); latest is 2026.4.29 (released Apr 30). Recent releases include Knowledge Agents system (12.1.0 in claude-mem), improved Telegram channel timeouts, and model failover hardening.\n\n---\n\n## A. Architecture\n\n### Runtime Model\n\nOpenClaw is a **Node.js daemon** (v22.22.0, bundled) running:\n- **Gateway** (port 18789, loopback-only, hybrid reload on config change)\n- **Control UI** (Web dashboard, http://127.0.0.1:18789)\n- **Pi agent** (embedded RPC, not external process)\n- **Channel adapters** (Telegram: grammY, Discord: discord.js, WhatsApp: Baileys, etc.)\n\n**Process Management**:\n- macOS: LaunchAgent (`~/Library/LaunchAgents/openclaw.plist` — managed by Nuzantara's `monitor-air.log` cron)\n- KeepAlive policy: checked via heartbeat monitor every 1h (Nuzantara config: `heartbeat.every=\"1h\"`)\n- Uptime target: 24/7 (threshold exceeded May 2, 2026 at 01:13 UTC — gap analysis in logs)\n\n### Agent Abstractions\n\n**Agent Definition** (`agents.list[]`):\n- Nuzantara has 2 agents:\n 1. **main**: primary, Telegram-bound, sandbox=off, model routing primary→fallback chain\n 2. **coder**: isolated workspace, sandbox=off, web tools denied (no web_search, web_fetch, browser)\n\n**Model Routing**:\n```\nmain:\n primary: openrouter/minimax/minimax-m2.7\n fallbacks: [kimi-k2.6, deepseek-chat, ollama/qwen3.5:9b]\n\ncoder:\n primary: openrouter/qwen/qwen3-max\n fallbacks: [qwen3.6-plus, kimi-k2.6, deepseek-reasoner, qwen3.5:9b]\n```\n\n**Fallback Chain Logic**:\n- Model request → try primary; if fails (auth, rate_limit, not_found) → try next fallback\n- Auth cooldown: 300s lockout after 10 auth failures in 60s window (gateway.auth.rateLimit)\n- Rate-limit cooldown: automatic provider cooldown (all-profiles-unavailable state) visible in logs (Mar 1-2, Google Gemini CLI exhausted)\n\n**Sandbox Modes**:\n- Default: `non-main` (separate container per agent workspace)\n- Nuzantara: `mode=off` for both agents (no isolation) + `prune.idleHours=24` (clean up idle containers)\n\n### Plugin System\n\nPlugins extend gateway capabilities via allow-list:\n\n**Loaded Plugins** (`plugins.entries`):\n- **memory-core** (enabled): observation feed + persistent storage\n- **memory-lancedb** (disabled): vector search backend (not active)\n- **lobster** (enabled): workflow orchestration DSL\n- **llm-task** (enabled): multi-step LLM orchestration\n- **voice-call** (enabled): phone/VOIP integration\n- **telegram** (implicit, channel adapter)\n\n**Plugin Trust Model**:\n- Plugins in `~/.openclaw/extensions/` are scanned; only those in `plugins.allow[]` are loaded\n- Nuzantara's installed plugin: `claude-mem` (v12.1.0) in `~/.openclaw/extensions/claude-mem/`\n- **Trust Warning**: claude-mem flagged in gateway logs as \"loaded without install/load-path provenance\" (implies manual fs install, not via package manager)\n\n### Skill System\n\nSkills add tools to agents via hooks + HTTP API.\n\n**Bundled Skills** (`~/.openclaw/lib/node_modules/openclaw/skills/`):\n- goplaces (Google Places API), xurl (URL shortener), voice-call (Twilio), notion, slack, discord, trello, spotify-player, etc.\n\n**Nuzantara Enabled Skills** (`skills.entries`):\n- **goplaces**: enabled (env: GOOGLE_PLACES_API_KEY)\n- **xurl**: enabled\n- **voice-call**: enabled\n- **notion**: enabled (env: NOTION_API_KEY)\n- **antigravity**: enabled\n\n**Custom Skills Directory**: `/Users/nuzantara/Desktop/nuzantara/apps/nuzantara-mcp/skills` (loaded via `skills.load.extraDirs[]`)\n\n### Channel Bindings\n\n**Telegram Channel** (enabled, dmPolicy=open):\n- **BotToken**: configured (redacted in config, present in plaintext)\n- **Allowed Senders**: `allowFrom=[\"*\"]` (open to all)\n- **Streaming**: disabled (responses sent as complete message blocks)\n- **Timeout**: 60s per message\n- **Retry**: 2 attempts, min 5s delay, max 60s delay, 0.5 jitter\n- **Known Issues**:\n - Network timeouts (ETIMEDOUT / EHOSTUNREACH observed Mar 1-2, 30s+ waits)\n - BOT_COMMANDS_TOO_MUCH (Telegram API limit: max 100 commands, Nuzantara registering 92-97, dropping 19-20 on each attempt)\n\n**Other Channels**:\n- WhatsApp, Discord, Slack, Signal, iMessage (BlueBubbles), IRC, Teams, Matrix: all disabled in config\n\n### Memory Model\n\n**memory-core** plugin:\n- SQLite database: `~/.openclaw/agents/main/agent/memory.db` (not visible in config, inferred from logs)\n- Observation feed: tool_use events → semantic summaries → injected into future sessions\n- Token-aware: context pruning mode=cache-ttl (1h TTL), compaction mode=safeguard (96k reserve floor, 40% history max)\n- Worker Service: runs on separate port (likely 37777 per claude-mem docs), managed by Bun\n- Vector backend: Chroma (if memory-lancedb enabled; currently off)\n\n### mcporter Integration\n\n**Tool Surface**:\n- Built-in: browser, canvas, nodes, cron, sessions\n- MCP tools: claude-mem (`search`, `timeline`, `get_observations`), custom skills\n\n**Safety Allowlist** (`tools.exec.safeBins[]`):\n```\nls, cat, tail, which, nuzantara-ops, mcporter\n```\n- Only these binaries can be executed without sandbox approval\n- `mcporter` is whitelisted (allows MCP tool invocation from agent)\n- **Known Issue**: mcporter allowlist audited Mar 22; safeBins entries not matching plugin config (audit log shows mismatch fixed via plugin reload)\n\n### Lobster Workflows\n\n**Lobster Plugin** (enabled):\n- DSL for multi-step orchestration\n- Workflow files: `~/.openclaw/workspace/.lobster/*.yml` (inferred)\n- Nuzantara logs show no active workflows, but plugin enabled for future use\n\n### Sandbox & Safety\n\n**Exec Approval Socket** (`exec-approvals.sock`):\n- Manual approval flow for unsafe commands\n- Not actively used in Nuzantara (sandbox=off, so no approvals needed)\n\n**Circuit Breaker** (`tools.loopDetection`):\n- Enabled globally (threshold=10 repeats)\n- Detectors:\n - **genericRepeat**: catch identical tool calls in sequence\n - **knownPollNoProgress**: detect polling without state change\n - **pingPong**: detect tool A → B → A cycles\n- Nuzantara: enabled, no circuit trips observed (logs clean on this metric)\n\n---\n\n## B. Current Capabilities\n\n### Agentic Tasks\n\n**Single-turn**: `openclaw agent --message \"query\"` (CLI or Gateway RPC)\n**Multi-turn**: Session state preserved in memory; model compaction + context pruning per config\n**Persistent Loops**: Supported via Lobster workflows (enabled but not in use)\n\n### Tool Use\n\n| Tool | Enabled | Notes |\n|------|---------|-------|\n| **web_search** | Yes | Integrated |\n| **web_fetch** | Yes | Integrated |\n| **browser** | No (coder agent denies) | Chrome control, snapshots, actions |\n| **exec** | Partial (safeBins only) | ls, cat, tail, nuzantara-ops, mcporter |\n| **file ops** | Via browser canvas | Not direct agent tool |\n| **image** | Yes (image model routing) | Primary: ollama/qwen2.5vl:7b, fallback: openrouter/qwen3-vl |\n| **canvas** (macOS) | Yes | A2UI protocol, push/reset/eval/snapshot |\n| **cron** | Yes | 24 jobs max (Nuzantara shows 0 active, scheduler may be frozen as of Apr 30) |\n| **custom MCP** | Yes (mcporter) | Route to local/remote MCP servers |\n\n**Multi-LLM Routing**: Each agent has primary + 3 fallback models; per-model params (temperature, max_tokens) configurable\n\n### Multi-LLM Routing\n\n**Provider Configuration**:\n- **openrouter**: native support, baseUrl=https://openrouter.ai/api/v1, 6 models configured\n- **deepseek**: native support, baseUrl=https://api.deepseek.com/v1, 2 models configured\n- **ollama**: local, baseUrl=http://127.0.0.1:11434, 0 models pre-configured (dynamic discovery)\n- **anthropic**: **NOT configured** (no ANTHROPIC_API_KEY in env; Nuzantara policy: banned)\n- **openai** (via openrouter): shim via openrouter provider\n\n**Per-Model Tuning**:\n```\ntemperature: 0.2–0.4 (reasoning), 0.3–0.4 (chat)\nmax_tokens: 4096–8192\nollama-specific: keep_alive=10m (memory cache), top_p=0.9, top_k=20\n```\n\n**Fallback Behavior**: \n- Try primary; if fails, cascade through fallbacks\n- Rate-limit cooldown (300s) applied per provider\n- Auth errors caught and retried with different auth profile (if multi-profile configured)\n\n### Persistent Memory\n\n**memory-core** (enabled):\n- **Lifecycle Hooks**: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd\n- **Observation Compression**: tool_use events → semantic summaries (via Claude API or local LLM)\n- **Injection Strategy**: progressive disclosure (layer 1: index; layer 2: timeline; layer 3: full details)\n- **Search Tools**: `mem-search`, `timeline`, `get_observations` (MCP interface)\n- **Durability**: SQLite persistent store, survives session/process restarts\n\n### Channel I/O\n\n**Telegram** (primary):\n- Inbound: message events → matched to agent via binding (main agent, Telegram channel)\n- Outbound: agent reply → formatted as Telegram message → sent via botToken\n- **State**: connected (logs show recent message processing up to May 2 01:13)\n- **Timeouts**: retry logic (2 attempts, 5–60s backoff) masks network latency\n\n### Voice-Call\n\n**voice-call Skill** (enabled):\n- Integration: Twilio (inferred from OpenClaw docs; not explicitly in Nuzantara config)\n- Capabilities: accept voice calls, stream audio, TTS output (ElevenLabs or system fallback)\n- **Status in Nuzantara**: enabled but not actively tested\n\n### Browser Automation\n\n**Browser Tool** (available, not in use by main agent):\n- Chrome/Chromium control (dedicated OpenClaw instance)\n- Actions: snapshot, click, type, scroll, upload, profile switching\n- **Nuzantara**: coder agent has browser denied via `tools.deny=[\"browser\"]`\n\n### Workflow Orchestration (Lobster)\n\n**Lobster DSL** (enabled, not in active use):\n- YAML-based multi-step orchestration\n- Syntax: task dependencies, conditional routing, parallel execution\n- **Nuzantara**: loaded but no workflows observed in logs\n\n### Subagents\n\n**Subagent Pattern**: supported via agent routing (`bindings[]` match rules)\n- **Nuzantara**: 2 agents defined (main, coder); no inter-agent delegation observed\n- **Limitation**: agents are isolated; no direct parent-child RPC (agents are standalone)\n\n### Cron / Scheduled Jobs\n\n**Cron Tool**:\n- API: `POST /gateway/cron` or `openclaw cron add <schedule> <command>`\n- Max 24 jobs per gateway\n- **Nuzantara Status**: 0 active jobs logged (scheduler may be frozen since Apr 30 per audit notes)\n\n### Hooks\n\n**7 Lifecycle Hooks** (claude-mem):\n- SessionStart, UserPromptSubmit, PostToolUse, Summary, SessionEnd, PreToolUse, Stop\n- **Nuzantara Use**: memory-core hooks active (observation capture, summary generation)\n\n### Plugin Development\n\n**Plugin Template**:\n- Entry point: `plugin/index.ts` or `dist/index.js`\n- Hooks: export `hooks: { SessionStart, UserPromptSubmit, ... }`\n- MCP Tools: export `tools: [ ... ]` (JSON-RPC 2.0 interface)\n- **Nuzantara Custom Skills**: loaded from `/Users/nuzantara/Desktop/nuzantara/apps/nuzantara-mcp/skills/` (no plugins observed)\n\n### Embedded Agent Runtime\n\n**Pi Agent** (embedded, not delegated):\n- Runs in-process with gateway (RPC loopback)\n- Session state: maintained in gateway memory (not persistent across restarts without memory plugin)\n- Tool dispatch: streaming responses back to gateway → channel output\n\n---\n\n## C. What's NEW (Recent OpenClaw Releases)\n\n| Release | Date | Highlights |\n|---------|------|-----------|\n| **v2026.4.29** | Apr 30, 2026 | Stability improvements, auth profile migration docs |\n| **v2026.4.09** | Apr 9, 2026 | Knowledge Agents system (6 new MCP tools: build_corpus, prime_corpus, query_corpus, etc.) |\n| **v2026.3.31** | Mar 31, 2026 | **[INSTALLED]** DM pairing default, Telegram net timeout handling, provider enum changes |\n| **v2026.1.5** | Jan 5, 2026 | Image model config, agent.imageModel + fallbacks, model shorthands (opus, sonnet, gpt, gemini) |\n\n**Most Notable for Nuzantara**:\n- **Knowledge Agents** (v2026.4.09): Compile filtered corpus from claude-mem observations, query conversationally. 6 new MCP tools (build_corpus, list_corpora, prime_corpus, query_corpus, rebuild_corpus, reprime_corpus). **Not yet exploited.**\n- **Auth Profile Migration** (v2026.3.31 → v2026.4.29): DM pairing now default (requires approval codes); legacy \"open to all\" requires explicit dmPolicy=open + allowFrom=[\"*\"]. **Nuzantara already configured.**\n- **Telegram Timeout Hardening** (v2026.3.31): Retry + backoff for network errors (30s+ waits masked). **Experienced but handled gracefully.**\n\n**Claude-Mem Integration** (v12.1.0, installed via marketplace):\n- Knowledge agents now supported (native corpus build, prime, query MCP tools)\n- Smart-explore extended to 24 languages\n- File-read decision gate (PreToolUse hook blocks redundant reads using observation timeline)\n\n---\n\n## D. Comparison vs Alternatives\n\n| Dimension | OpenClaw | cron-agent-python (Nuzantara) | LangChain/LangGraph | Anthropic Agent SDK (direct) |\n|-----------|----------|------|-----------|---------|\n| **Primary UX** | Gateway + multi-channel | Python cron + custom CLI | Chain/graph DSL | Messages API |\n| **Multi-LLM** | Native (fallback chains) | Manual (explicit per-agent config) | LLMChain abstraction | No native support |\n| **Persistent Memory** | Plugin-based (memory-core) | NotebookLM (external) | No built-in | No built-in |\n| **Channel Binding** | 23+ integrations (Telegram, Discord, etc.) | CLI-only / webhooks | None (user's responsibility) | None |\n| **Workflow Orchestration** | Lobster DSL | Python task queue | LangGraph (DAG-based) | Claude Agent SDK (new) |\n| **Tool Streaming** | Yes (block streaming) | Manual | Yes (via tool classes) | Yes (native) |\n| **Sandbox** | Exec approval socket | None (direct subprocess) | None | None (external runner) |\n| **State Persistence** | SQLite (via plugins) | Disk-backed (JSON/state files) | Not built-in | Not built-in |\n| **Scaling** | Single gateway (local) | Multi-machine (cron dispatch) | App-level (doesn't scale infra) | Delegated to cloud provider |\n| **Ease of Setup** | `openclaw onboard` (90s) | Python venv + config | npm install + boilerplate | anthropic SDK (Python/JS) |\n\n**OpenClaw's Unique Value Propositions**:\n1. **Multi-channel inbox**: Single control plane for Telegram, WhatsApp, Discord, Slack, iMessage, IRC, Teams, Signal, etc.\n2. **Persistence out-of-the-box**: memory-core plugin captures observations + injects into future sessions (no manual logging)\n3. **Native fallback chains**: multi-model routing with per-provider cooldowns + auth management\n4. **Embedded + local**: runs on your machine (no cloud), binds to loopback only (no public expose by default)\n\n**Where OpenClaw Loses**:\n1. **No cloud sync**: single-machine deployment (no multi-region HA out of the box)\n2. **Provider enum rigidity**: only supports explicit providers (openrouter, deepseek, ollama, anthropic); no generic OpenAI-compatible override until v2026.3.31+ (baseUrl interpolation)\n3. **Command limit (Telegram)**: max 100 commands registered; Nuzantara hitting 92-97, dropping 19-20 on each sync\n4. **Scheduler frozen** (apr 30+): cron jobs not running (24-job queue inert); cause unknown\n\n**vs cron-agent-python** (Nuzantara parallel):\n- cron-agent-python: 19 active strategies, each runs independently on schedule; state decoupled (disk-backed JSON)\n- OpenClaw: single agent with multi-LLM routing; state unified in memory-core\n- **Recommendation**: cron-agent-python excels for **independent, scheduled tasks** (classification, analysis per-document); OpenClaw excels for **interactive, multi-turn conversations** with memory (Telegram chatbot + persistent context)\n\n---\n\n## E. Scaling Characteristics\n\n### Concurrency\n\n**Max Concurrent Agents**: 1 gateway, N agents (in Nuzantara: 2)\n- **Per-Agent Sessions**: Unbounded (session state pruned on demand)\n- **Concurrent Requests**: Limited by LLM provider rate limits (OpenRouter: ~100 req/min; DeepSeek: API doc unclear; Ollama: local, unbounded)\n- **Telegram Message Queue**: Sequential (one message processed at a time, 60s timeout → retry → backoff)\n\n### Memory Footprint\n\n| Component | RSS (Idle) | RSS (Active) | Notes |\n|-----------|-----------|-------------|-------|\n| Gateway process | ~200–300 MB | ~400–600 MB | Node.js runtime + channel adapters |\n| memory-core SQLite | ~10 MB | ~50+ MB | Depends on observation count + vector index |\n| Chroma (if enabled) | N/A | ~200+ MB | Python subprocess (uv-managed), not active in Nuzantara |\n| Total (Nuzantara) | ~250 MB | ~500+ MB | Measured via LaunchAgent memory monitor |\n\n### Latency Overhead\n\n| Operation | Latency | Notes |\n|-----------|---------|-------|\n| Gateway routing (inbound message) | <10ms | WebSocket demux + binding match |\n| Agent dispatch (to LLM) | <50ms | HTTP to OpenRouter/DeepSeek |\n| LLM request | 500ms–5s | Depends on model, fallback chain depth |\n| Response streaming (Telegram) | 1–30s | Tool execution + LLM generation + retry backoff |\n| Memory injection (PreToolUse) | 100–500ms | SQLite query + compression (if observation reuse) |\n\n**Gateway overhead is negligible** (<10% of E2E latency).\n\n### State Persistence\n\n**Durable Across Crashes**: Yes (if memory-core enabled)\n- Session state saved to SQLite after each tool_use hook\n- Observation timeline reconstructed on SessionStart\n\n**Graceful Shutdown**: LaunchAgent KeepAlive policy\n- monitored by `launchctl unload/load` (manual) or cron watchdog (Nuzantara)\n- Timeout: 30s (hard kill if not responsive)\n\n### Crash Recovery\n\n**KeepAlive=true** (Nuzantara config):\n- LaunchAgent auto-restarts gateway on exit (non-zero exit code)\n- State surviving: memory-core observations (durable), in-flight messages (lost, retried by Telegram)\n- **Observed**: gateway crashes not logged; LaunchAgent restarts appear transparent\n\n### Multi-Instance Topology\n\n**Federation**: Not supported natively\n- **Workaround**: run multiple gateways on different ports (18789, 18790, ...), but agents are isolated per gateway\n- **Nuzantara**: single gateway (port 18789); no multi-instance setup\n\n---\n\n## F. Security Model\n\n### Loopback-Only Binding\n\n**gateway.bind=loopback** (Nuzantara default):\n- Gateway listens on 127.0.0.1:18789 (localhost only)\n- No public internet exposure (requires SSH tunnel or Tailscale to access from remote)\n- **Threat Model**: protects against direct internet attacks; assumes local machine is secure\n\n### Auth Token & Rate Limiting\n\n**Token** (gateway.auth.token):\n- Long random string (example: 64 char base64)\n- Required in WebSocket `?token=...` query param or HTTP `Authorization: Bearer <token>` header\n- **Storage**: plaintext in `~/.openclaw/openclaw.json` (same permissions as SQLite DB)\n- **Rotation**: manual edit + `openclaw reload` (no auto-rotation tool visible)\n\n**Rate Limiting**:\n- **maxAttempts**: 10 failures in 60s window (Nuzantara config)\n- **lockoutMs**: 300s (5 min) lockout after threshold\n- **Scope**: per-IP (loopback is single IP, so affects all local clients equally)\n\n### exec-approvals.sock\n\n**Manual Approval Flow**:\n- Unsafe exec commands → `exec-approvals.sock` (Unix domain socket, `/tmp/exec-approvals-<pid>.sock`)\n- User approval via CLI: `echo \"approve <exec-id>\" | nc -U /tmp/exec-approvals-<pid>.sock`\n- **Nuzantara Status**: sandbox=off for both agents, so approval flow never triggered\n\n### Sandbox Modes\n\n| Mode | Isolation | Use Case |\n|------|-----------|----------|\n| **off** | None | Development, trusted local use |\n| **non-main** | Per-agent container | Multi-agent isolation (default agents.defaults.sandbox.mode) |\n| **full** | Per-session container | Maximum isolation (experimental) |\n\n**Nuzantara**: mode=off (no isolation). Rationale: single user, local machine, trusted workflows. Trade-off: agents can interfere (shared filesystem, process space).\n\n### Safety Bins Allowlist\n\n**Tools.exec.safeBins**:\n```\nls, cat, tail, which, nuzantara-ops, mcporter\n```\n- Only these binaries bypass approval flow\n- Others require exec approval (if sandbox=non-main or full)\n- **Known Issue** (Mar 22): safeBins not matching plugin config; resolved via plugin reload\n\n**Attack Surface**: agent can only run safe binaries; can't execute arbitrary commands (unless sandbox=off + agent has write access to filesystem, which it does in Nuzantara)\n\n### Plugin Trust Model\n\n**Plugin Loading** (`plugins.allow[]`):\n- Whitelist-based: only plugins in allow-list are loaded\n- Nuzantara loads: memory-core, lobster, llm-task, voice-call (implicit)\n- claude-mem: loaded from `~/.openclaw/extensions/claude-mem/` (manual install, not in core plugins.allow)\n\n**Trust Signals**:\n- Official plugins: signed releases, open-source GitHub\n- User plugins (Nuzantara custom skills): loaded from `/Desktop/nuzantara/apps/nuzantara-mcp/skills/` (same machine, implicit trust)\n- **Warning**: claude-mem flagged as \"loaded without install/load-path provenance\" → suggests side-load outside normal package path\n\n### Secret Handling\n\n**API Key Interpolation**:\n```json\n{\n \"providers\": {\n \"openrouter\": {\n \"apiKey\": \"${OPENROUTER_API_KEY}\"\n }\n }\n}\n```\n\n**Resolution**:\n1. Check environment variable (shellEnv=true in Nuzantara config, shell timeout 15s)\n2. Check EnvironmentVariables from LaunchAgent plist (if running as daemon)\n3. Fall back to plaintext in config (NOT recommended)\n\n**Nuzantara Config**:\n- OPENROUTER_API_KEY, DEEPSEEK_API_KEY, GOOGLE_PLACES_API_KEY, NOTION_API_KEY → in `~/.openclaw/.env` (not shown, but inferred from skills.env)\n- **Risk**: if `.env` checked into git, keys exposed\n\n---\n\n## G. Operational Model\n\n### LaunchAgent Management\n\n**Service File**: `~/Library/LaunchAgents/openclaw.plist` (macOS)\n- **Label**: com.openclaw.daemon\n- **Program**: node (bundled in ~/.openclaw/tools/node-v22.22.0/bin/node)\n- **KeepAlive**: true (auto-restart on exit)\n- **ThrottleInterval**: 30s (min time between restarts after crash)\n- **RunAtLoad**: true (start on login)\n\n**Manual Control**:\n```bash\nlaunchctl load ~/Library/LaunchAgents/openclaw.plist\nlaunchctl unload ~/Library/LaunchAgents/openclaw.plist\nlaunchctl start com.openclaw.daemon\nlaunchctl stop com.openclaw.daemon\n```\n\n**Nuzantara Monitoring**: cron job `monitor-air.log` (implied by logs; monitors for crashes + auto-restart failures)\n\n### Logs\n\n| Log | Path | Rotation | Notes |\n|-----|------|----------|-------|\n| **gateway.log** | `~/.openclaw/logs/gateway.log` | Yes (daily) | Main diagnostic output (diagnostic, channel, agent events) |\n| **gateway.err.log** | `~/.openclaw/logs/gateway.err.log` | Yes (daily) | Errors, warnings, stack traces |\n| **commands.log** | `~/.openclaw/logs/commands.log` | N/A | Slash command executions |\n| **config-audit.jsonl** | `~/.openclaw/logs/config-audit.jsonl` | Append | Config change audit trail |\n\n**Log Level**: `logging.level=info` (Nuzantara config); controlled via `openclaw config set logging.level=debug` (or restart with env var)\n\n**Retention**: no explicit TTL in config; logs grow unbounded (gateway.log: 21.7 GB as of May 2, gateway.err.log: 288 MB). **Risk**: disk space exhaustion.\n\n### Health Endpoint\n\n**Endpoint**: `GET /healthz` (Gateway)\n- **Response**: `{ \"status\": \"ok\" }` (200 OK) or 500 on error\n- **Nuzantara Use**: implied by monitor-air; likely polled every 5–60s\n\n### Metrics Export\n\n**Prometheus**: Not built-in to OpenClaw (as of v2026.3.31)\n- **Workaround**: parse logs with regex + ship to external metrics service\n- **Nuzantara**: no custom metrics exporter observed\n\n### Debugging\n\n**Verbose Mode**: `openclaw gateway --verbose` (CLI flag)\n**Log Level Override**: `OPENCLAW_LOG_LEVEL=debug` (env var at startup)\n**RPC Tracing**: gateway logs all RPC calls (agent → channel) with stack traces on failure\n\n**Trace an Agent Decision**:\n1. Enable debug logging\n2. Send message via Telegram\n3. Check gateway.log for SessionStart → UserPromptSubmit → tool_use → agent response flow\n4. Correlate with memory-core observations (check `.openclaw/agents/main/agent/memory.db` if needed)\n\n### Update Process\n\n**Manual Update**:\n```bash\nnpm install -g openclaw@latest\nopenclaw onboard --update # or --install-daemon to refresh daemon\n```\n\n**Safe Update (Nuzantara)**:\n1. `launchctl stop com.openclaw.daemon` (halt gateway)\n2. Backup `~/.openclaw/openclaw.json` + `~/.openclaw/workspace/`\n3. `npm install -g openclaw@latest`\n4. Review CHANGELOG for breaking changes\n5. Run `openclaw doctor` (auto-migrate config if needed)\n6. `launchctl start com.openclaw.daemon` (restart)\n\n**Auto-Update**: Not configurable in v2026.3.31; manual only.\n\n---\n\n## H. Known Issues & Pitfalls\n\n### 1. Provider Enum Rigidity\n\n**Issue**: openai-completions API wrapper only recognizes explicit `api: \"openai\"` (no generic override)\n\n**Impact**: Can't use arbitrary OpenAI-compatible backends (e.g., vLLM, LocalAI) without forking\n\n**Workaround** (Nuzantara): Use `api: \"openai-completions\"` with openrouter shim (all models exposed via openrouter baseURL)\n\n**Status**: Likely fixed in v2026.4.29 (baseUrl interpolation mentioned in changelog), but not tested against Nuzantara's installed v2026.3.31\n\n### 2. Secret Interpolation (`${VAR}` Only in env)\n\n**Issue**: `apiKey: \"${VAR}\"` resolves ONLY if VAR is in EnvironmentVariables (from LaunchAgent plist or shell env at startup), NOT from `.env` file or inline plaintext\n\n**Impact**: Easy to forget to export env var → silent fallback to missing key → auth failures\n\n**Example** (Nuzantara):\n```json\n{\n \"openrouter\": {\n \"apiKey\": \"${OPENROUTER_API_KEY}\" // resolves to env var if set, else null\n }\n}\n```\n\n**Workaround**: Explicitly set in LaunchAgent plist `EnvironmentVariables` section or `source ~/.openclaw/.env` before `launchctl load`\n\n### 3. mcporter Allowlist Quirks\n\n**Issue** (Mar 22 audit log): safeBins entries `ls, cat, tail, which, nuzantara-ops, mcporter` not matching plugin config → mcporter tool calls rejected\n\n**Root Cause**: Plugin loading race condition (safeBins checked before plugin registration completes)\n\n**Status**: Resolved in Nuzantara by manual reload (launchctl stop/start)\n\n**Fix Expected**: v2026.4.29+ (not tested)\n\n### 4. Plugin Trust Warnings\n\n**Issue**: claude-mem loaded from `~/.openclaw/extensions/claude-mem/` without install/load-path provenance\n\n**Impact**: Minor (code review recommended for untrusted sources); claude-mem is open-source + widely used\n\n### 5. Telegram Network Timeouts\n\n**Issue**: `getUpdates` request times out after 30s; retry backoff (5s → 60s) masks latency but creates gaps in message delivery (Apr 30 + May 2 logs show repeated timeouts)\n\n**Impact**: Inbound messages may be delayed 30–120s during network flaps\n\n**Cause**: Likely ISP or Telegram API throttling (Nuzantara in Bali, geographically distant from Telegram servers)\n\n**Workaround**: Increase timeout (Telegram: `timeoutSeconds=120`) or use webhook polling (if supported in next version)\n\n**Status**: Retry logic + backoff now in v2026.3.31; gracefully handled but not ideal UX\n\n### 6. BOT_COMMANDS_TOO_MUCH (Telegram API Limit)\n\n**Issue**: Telegram API limits /command menu to 100 entries; Nuzantara registering 92–97 commands (skills + custom), dropping 19–20 on each sync\n\n**Impact**: Some skills unavailable in Telegram /command autocomplete menu (but still callable via message mention)\n\n**Log Evidence**:\n```\n[telegram] rejected 92 commands (BOT_COMMANDS_TOO_MUCH); retrying with 73.\n[telegram] accepted 73 commands after BOT_COMMANDS_TOO_MUCH (started with 92; omitted 19).\n```\n\n**Root Cause**: Too many skills enabled (goplaces, xurl, voice-call, notion, antigravity) + OpenClaw bundled commands\n\n**Workaround**: Disable low-use skills in `skills.entries` (set enabled=false for unused ones)\n\n**Fix**: None in OpenClaw core (Telegram API limit). Requires operator intervention.\n\n### 7. Scheduler Frozen (Apr 30+)\n\n**Issue**: Cron jobs not running; 24-job queue inert\n\n**Evidence**: No cron logs in gateway.log since Apr 30; cron tool still responds to queries but doesn't execute\n\n**Impact**: Scheduled tasks (batch processing, daily summaries) missed\n\n**Likely Cause**: \n- Scheduler process crash (separate thread)\n- Job queue lock contention\n- State corruption after config reload\n\n**Status**: Unknown (no debugging done); requires `openclaw doctor` + manual job purge/recreate\n\n**Workaround**: Migrate to external cron runner (separate Python process with its own scheduler) or upgrade to v2026.4.29+\n\n### 8. Context Compaction False Positives\n\n**Issue**: context pruning + compaction may discard relevant observations if TTL expires before session end\n\n**Config** (Nuzantara):\n```\ncontextPruning.mode=cache-ttl, ttl=1h\ncompaction.mode=safeguard, reserveTokensFloor=96k\n```\n\n**Impact**: Multi-day sessions lose early context after 1h, even if still relevant\n\n**Workaround**: Increase TTL to 24h or use `mode: \"never\"` (disables pruning, risks context explosion)\n\n---\n\n## I. Roadmap & Future Direction\n\n**Public Roadmap**: https://github.com/openclaw/openclaw/issues (GitHub Issues, no official roadmap doc)\n\n**Recent PR Signals** (inferred from changelog):\n- **Knowledge Agents** (v2026.4.09): Building queryable \"brains\" from observation history (corpus build + prime + query)\n- **Auth Profile System** (v2026.3.31+): Multi-profile support + role-based failover (not fully enabled in Nuzantara)\n- **DM Pairing Security**: Default dmPolicy=pairing (require approval code for unknown senders) — security-first direction\n- **Provider Enum Expansion**: Support for more providers + generic OpenAI-compatible override (v2026.4.29 changelog hints)\n\n**Likely Future Work** (speculative):\n1. **Cloud Sync**: Multi-region HA + state replication (not in current roadmap, but enterprise feature request)\n2. **Structured Output**: Typed tool responses (Zod schema validation) — currently working, likely formalization\n3. **Voice Wake Improvements**: More language support, custom wake words (v2026.3.31 has voice-call, room for improvement)\n4. **Scheduler Stability**: Fix cron job queue race conditions (based on Apr 30 frozen state)\n5. **Knowledge Agent UI**: Web viewer for corpus browsing + semantic search (claude-mem v12.1.0 foundation)\n\n---\n\n## Summary Table: What Nuzantara Is Using vs. Available\n\n| Feature | Available | Enabled | Status |\n|---------|-----------|---------|--------|\n| **Multi-LLM Routing** | Yes (4 providers) | Yes (fallback chain) | Working, no issues |\n| **Persistent Memory** | Yes (memory-core) | Yes | Working, observations injected |\n| **Telegram Channel** | Yes | Yes | Working, but network timeouts every 30s+ |\n| **Workflow Orchestration** | Yes (Lobster) | Enabled but unused | Ready to use, no barriers |\n| **Knowledge Agents** | Yes (new in v12.1.0) | No | **NOT EXPLOITED** (v2026.4.09 feature) |\n| **Voice-Call Integration** | Yes | Yes | Enabled, not actively tested |\n| **Browser Automation** | Yes (main agent denied) | Partial (coder agent) | Available for coder agent |\n| **Cron Scheduler** | Yes (24 job limit) | Yes but frozen | **BROKEN** since Apr 30 |\n| **Custom Skills** | Yes (extraDirs) | Partially | `/nuzantara-mcp/skills/` not fully synced |\n| **Canvas (macOS)** | Yes | Implicit | Available if using macOS |\n| **Multi-Channel** | Yes (23 channels) | Only Telegram | Others disabled; can be enabled |\n\n---\n\n## Recommendations for Nuzantara\n\n1. **Upgrade to v2026.4.29** (from v2026.3.31): Knowledge Agents system ready; cron scheduler likely fixed; auth profiles stabilized\n2. **Exploit Knowledge Agents**: Build corpus from claude-mem observations; enable conversational history replay (query_corpus tool)\n3. **Fix Cron Scheduler**: Debug Apr 30 freeze; if unresolved in v2026.4.29, fallback to external cron-agent-python runner\n4. **Reduce Telegram Commands**: Disable unused skills (elite-longterm-memory, bluebubbles, discord, slack, spotify, trello) to stay under 100-command limit\n5. **Increase Telegram Timeout**: Raise `timeoutSeconds=120` to mask network latency (or implement webhook polling in next OpenClaw version)\n6. **Monitor Memory Growth**: Track `~/.openclaw/logs/gateway.log` size (currently 21.7 GB); implement log rotation or external log shipping\n7. **Test Voice-Call Skill**: Verify Twilio integration if voice interaction is planned\n8. **Sync Custom Skills**: Ensure `/nuzantara-mcp/skills/` fully registered with OpenClaw (check skills.entries for all custom entries)\n\n---\n\n## Files Referenced\n\n- **Config**: `/Users/nuzantara/.openclaw/openclaw.json` (v2026.3.31 schema)\n- **Installation**: `/Users/nuzantara/.openclaw/lib/node_modules/openclaw/`\n- **Logs**: `~/.openclaw/logs/gateway.log`, `gateway.err.log`\n- **Memory DB**: `~/.openclaw/agents/main/agent/memory.db` (SQLite, not visible in config)\n- **Workspace**: `~/.openclaw/workspace/` (markdown docs + agent state)\n- **Extensions**: `~/.openclaw/extensions/claude-mem/` (installed plugin)\n- **GitHub**: https://github.com/openclaw/openclaw (releases, issues, docs)\n- **Documentation**: https://docs.openclaw.ai (official, updated for v2026.4.29)\n- **claude-mem Marketplace**: `/Users/nuzantara/.claude/plugins/marketplaces/thedotmack/` (v12.1.0, AGPL-3.0)\n\n---\n\n**Document Compiled**: 2026-05-02 13:20 UTC | **Last Config Change**: 2026-05-02 13:20 UTC (Nuzantara-reported version)\n"}}, "scripts/agent-library-evolver-run.sh": {"435945f89392b92ca558834d695b57058c697670": {"t": 1786068353.1132598, "n": 26763, "h": "7a8ea5e5e2b2f826", "c": "#!/usr/bin/env bash\n# Agent Library Evolver — weekly wrapper (Phase 1)\n#\n# Invoked by ~/Library/LaunchAgents/com.balizero.agent-library-evolver.weekly.plist\n# Sunday 03:00 WITA. Drives the full pipeline:\n#\n# 1. Source SECRETS_FILE (default ~/.nuzantara-secrets.env)\n# Reject /dev/null, empty file, or missing DEEPSEEK_API_KEY.\n# 2. PG advisory lock (pg_try_advisory_lock) — single-flight guarantee\n# 3. Context gathering: mem query + git log + cicatrix-scars.md slice\n# 4. MANDATORY redaction via scripts/_redact_pii.py (Symbiosis Law 2)\n# 5. uv run evoskill run --config agent-library/.evoskill/config.toml\n# 6. Parse telemetry.json — fail-closed if total_cost_usd > BUDGET_USD\n# 7. scripts/_evidence_lint.py over proposals/YYYY-MM-DD/\n# 8. scripts/_entailment_check.py over passed-existence/\n# 9. Open draft PR via scripts/agent-library-evolver-propose-pr.sh\n# (inline function below — no separate script needed yet)\n# 10. Telegram alert with run summary\n#\n# Spec: docs/superpowers/specs/2026-05-17-agent-library-evoskill-design.md\n# Phase 1 — addresses L6, L11, L23 from .known-limitations-v1.md.\n#\n# CLI flags:\n# --dry-run Skip steps 5-10. Exits after context gathering +\n# redaction validation. Useful for smoke testing.\n# --help Print usage and exit 0.\n#\n# Environment overrides:\n# BUDGET_USD Hard cap on total_cost_usd (default 1.00 prod,\n# 0.10 smoke). Wrapper aborts if telemetry exceeds.\n# SECRETS_FILE Path to env file with DEEPSEEK_API_KEY etc.\n# Default ~/.nuzantara-secrets.env. /dev/null rejected.\n# REPO_ROOT Repository root. Default = parent of this script.\n# TELEMETRY_DIR Where to write context-raw.md, context-redacted.md,\n# telemetry.json. Default\n# ~/.agent/decisions/agent-library-evolver/telemetry\n# (L11 fix — was /tmp/agent-library-evolver in spec\n# draft, weaker on reboot).\n# TELEGRAM_BOT_TOKEN, TELEGRAM_OWNER_CHAT_ID\n# Sourced from SECRETS_FILE. chat_id 1125336968\n# (Zero's @zero0101010101010 verified live 2026-04-07).\n#\n# Exit codes:\n# 0 Success — pipeline completed (with or without proposals)\n# 1 Generic failure (any step failure not specifically classified)\n# 2 Secrets validation failed (missing/empty file, missing key,\n# /dev/null shortcut detected)\n# 3 PG advisory lock held — another run in progress, skip\n# 4 Redaction fail-closed (over-redaction, missing _redact_pii.py,\n# Symbiosis Law 2 violation)\n# 5 Budget exceeded — total_cost_usd > BUDGET_USD post-run\n\nset -euo pipefail\n# Note: we deliberately do NOT set `set -x` — secrets like\n# DEEPSEEK_API_KEY would leak to launchd log files.\n\n# ─── 0. Bootstrap ────────────────────────────────────────────────────\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nREPO_ROOT=\"${REPO_ROOT:-$(cd \"${SCRIPT_DIR}/..\" && pwd)}\"\nBUDGET_USD=\"${BUDGET_USD:-1.00}\"\nSECRETS_FILE=\"${SECRETS_FILE:-${HOME}/.nuzantara-secrets.env}\"\nTELEMETRY_DIR=\"${TELEMETRY_DIR:-${HOME}/.agent/decisions/agent-library-evolver/telemetry}\"\nRUN_DATE=\"$(date +%Y-%m-%d)\"\nRUN_TS=\"$(date +%Y-%m-%dT%H:%M:%S%z)\"\nRUN_TELEMETRY_DIR=\"${TELEMETRY_DIR}/${RUN_DATE}\"\nDRY_RUN=\"0\"\n\n# ─── S13-P6b (2026-06-02): return-to-branch guard ────────────────────\n# evoskill (vendor/evoskill ProgramManager) does native git checkout of\n# branches INSIDE REPO_ROOT and can leave the worktree parked on the\n# wrong branch when it exits (incl. on FATAL). It checks out program/*\n# branches AND, between programs, pre-existing agent/* branches\n# (e.g. agent/nuzantara/backend-rag/crm-guardian-audit). In production\n# REPO_ROOT is the deploy worktree pinned to deploy/main; ANY left-over\n# non-deploy/main checkout breaks the next wr2-deploy-pull (wrong-branch\n# gate) — observed twice on 2026-06-03, manual restore both times.\n# S13-#11 (2026-06-03): broaden the restore to fire whenever HEAD is on\n# any branch other than deploy/main (not only program/*), guarded by a\n# clean-tree check so we never clobber uncommitted tracked work in a\n# feature/test worktree. Untracked files (cache/) are fine.\n_restore_deploy_branch() {\n git -C \"${REPO_ROOT}\" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0\n local cur\n cur=\"$(git -C \"${REPO_ROOT}\" rev-parse --abbrev-ref HEAD 2>/dev/null || echo )\"\n if [[ -n \"${cur}\" && \"${cur}\" != \"deploy/main\" ]] \\\n && git -C \"${REPO_ROOT}\" show-ref --verify --quiet refs/heads/deploy/main; then\n if git -C \"${REPO_ROOT}\" diff --quiet && git -C \"${REPO_ROOT}\" diff --cached --quiet; then\n git -C \"${REPO_ROOT}\" checkout deploy/main >/dev/null 2>&1 \\\n && log \"return-to-branch guard: restored deploy/main (was ${cur})\" \\\n || log \"WARN: return-to-branch guard failed to restore deploy/main from ${cur}\"\n else\n log \"WARN: return-to-branch guard: uncommitted tracked changes, NOT switching from ${cur}\"\n fi\n fi\n}\n# NOTE: do NOT register a standalone EXIT trap here — bash allows only\n# one EXIT trap and the advisory-lock trap (acquire_lock) would overwrite\n# it. _restore_deploy_branch is invoked from the combined trap below.\n\n# ─── CLI parsing ─────────────────────────────────────────────────────\n\nusage() {\n sed -n '2,/^set -euo pipefail$/p' \"${BASH_SOURCE[0]}\" | sed 's/^# \\{0,1\\}//'\n exit 0\n}\n\nfor arg in \"$@\"; do\n case \"${arg}\" in\n --dry-run) DRY_RUN=\"1\" ;;\n --help|-h) usage ;;\n *) echo \"ERROR: unknown flag ${arg}. Use --help\" >&2; exit 1 ;;\n esac\ndone\n\n# ─── 1. Bootstrap telemetry dir + log helper ─────────────────────────\n\nmkdir -p \"${RUN_TELEMETRY_DIR}\"\n# Tighten perms — telemetry holds raw context (pre-redaction) which\n# contains PII. L11 fix: out of /tmp, into ~/.agent/.\nchmod 0700 \"${RUN_TELEMETRY_DIR}\" 2>/dev/null || true\n\nLOG_FILE=\"${RUN_TELEMETRY_DIR}/evolver.log\"\n\nlog() {\n local ts\n ts=\"$(date '+%Y-%m-%dT%H:%M:%S%z')\"\n printf '[%s] %s\\n' \"${ts}\" \"$*\" | tee -a \"${LOG_FILE}\"\n}\n\nlog \"=== agent-library-evolver run ${RUN_TS} ===\"\nlog \"repo_root=${REPO_ROOT}\"\nlog \"budget_usd=${BUDGET_USD}\"\nlog \"telemetry_dir=${RUN_TELEMETRY_DIR}\"\nlog \"dry_run=${DRY_RUN}\"\n\n# ─── 2. Secrets validation (L20-style fail-closed) ───────────────────\n\nvalidate_secrets() {\n if [[ \"${SECRETS_FILE}\" == \"/dev/null\" ]]; then\n log \"FATAL: SECRETS_FILE=/dev/null is rejected (shortcut would bypass auth check)\"\n exit 2\n fi\n if [[ ! -f \"${SECRETS_FILE}\" ]]; then\n log \"FATAL: SECRETS_FILE not found: ${SECRETS_FILE}\"\n exit 2\n fi\n if [[ ! -s \"${SECRETS_FILE}\" ]]; then\n log \"FATAL: SECRETS_FILE is empty: ${SECRETS_FILE}\"\n exit 2\n fi\n # Source after validation — set +x defensively so secrets don't\n # appear in any debug trace upstream.\n set +x\n # shellcheck disable=SC1090\n source \"${SECRETS_FILE}\"\n set +x\n # S13-P6 (2026-06-02): DEEPSEEK_API_KEY drifts out of SECRETS_FILE on\n # re-sync from apps/backend-rag/.env (last lost 2026-05-29). Fall back to\n # the single-source-of-truth .env.master, extracting ONLY this one key —\n # never source the whole file (it holds ~90 unrelated secrets incl.\n # OPENAI/OAuth that must not leak into the evoskill subprocess env).\n if [[ -z \"${DEEPSEEK_API_KEY:-}\" ]]; then\n ds_master=\"${DEEPSEEK_MASTER_ENV:-${HOME}/.openclaw/workspace/.env.master}\"\n if [[ -f \"${ds_master}\" ]]; then\n ds_line=\"$(grep -m1 -E '^DEEPSEEK_API_KEY=' \"${ds_master}\" || true)\"\n if [[ -n \"${ds_line}\" ]]; then\n export DEEPSEEK_API_KEY=\"${ds_line#DEEPSEEK_API_KEY=}\"\n DEEPSEEK_API_KEY=\"${DEEPSEEK_API_KEY%\\\"}\"; DEEPSEEK_API_KEY=\"${DEEPSEEK_API_KEY#\\\"}\"\n log \"DEEPSEEK_API_KEY recovered from ${ds_master} (SECRETS_FILE drift)\"\n fi\n fi\n fi\n if [[ -z \"${DEEPSEEK_API_KEY:-}\" ]]; then\n log \"FATAL: DEEPSEEK_API_KEY not set after sourcing ${SECRETS_FILE}\"\n exit 2\n fi\n # CLAUDE.md hard rule (panel R5 BLOCKING #8): the wrapper REJECTS a\n # contaminated environment with ANTHROPIC_API_KEY set. Silently\n # unset-ing would hide the violation upstream; failing loud surfaces\n # the mistake so the operator can remove the bad config.\n if [[ -n \"${ANTHROPIC_API_KEY:-}\" ]]; then\n log \"FATAL: ANTHROPIC_API_KEY is set in the environment — this is a\"\n log \"CLAUDE.md hard rule violation (no Anthropic API anywhere). The\"\n log \"wrapper refuses to run with the key exposed; please remove it\"\n log \"from ${SECRETS_FILE} (or any parent env) and retry.\"\n exit 2\n fi\n log \"secrets OK (DEEPSEEK_API_KEY set, ANTHROPIC_API_KEY confirmed absent)\"\n}\nvalidate_secrets\n\n# ─── 2b. Telegram alert helpers (defined early — bash doesn't hoist) ─\n\ntelegram_alert() {\n local msg=\"$1\"\n if [[ -z \"${TELEGRAM_BOT_TOKEN:-}\" ]] || [[ -z \"${TELEGRAM_OWNER_CHAT_ID:-}\" ]]; then\n log \"WARN: TELEGRAM_BOT_TOKEN/CHAT_ID not set — alert skipped: ${msg}\"\n return 0\n fi\n local payload\n payload=\"$(python3 -c \"\nimport json, sys\nprint(json.dumps({\n 'chat_id': '${TELEGRAM_OWNER_CHAT_ID}',\n 'text': sys.argv[1],\n}))\n\" \"${msg}\")\"\n curl -sS -X POST \\\n \"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage\" \\\n -H \"Content-Type: application/json\" \\\n -d \"${payload}\" >> \"${LOG_FILE}\" 2>&1 || \\\n log \"WARN: Telegram alert send failed\"\n}\n\nfinal_alert() {\n local cost=\"(unknown)\"\n if [[ -f \"${TELEMETRY_JSON:-}\" ]]; then\n cost=\"$(python3 -c \"\nimport json\ntry:\n with open('${TELEMETRY_JSON}') as f:\n print(f\\\"\\$\\\" + str(json.load(f).get('total_cost_usd', 0.0)))\nexcept Exception:\n print('(parse error)')\n\")\"\n fi\n telegram_alert \"🌱 agent-library-evolver ${RUN_DATE}\nproposals_passed=${PROPOSALS_PASSED:-0}\ncost=${cost}\nbudget=\\$${BUDGET_USD}\nlog=${LOG_FILE}\"\n}\n\n# ─── 3. PG advisory lock — single-flight ─────────────────────────────\n\nPG_LOCK_KEY=\"${PG_LOCK_KEY:-0xA9E471BAA1}\" # arbitrary stable int → 728338108065 fits int64\n# Default DATABASE_URL from SECRETS_FILE (CRM Pro local PG). Phase 1\n# accepts no-PG fallback for smoke testing (sets lock to no-op).\n\nacquire_lock() {\n if [[ -z \"${DATABASE_URL:-}\" ]]; then\n log \"WARN: DATABASE_URL not set — skipping advisory lock (single-flight not enforced)\"\n return 0\n fi\n if ! command -v psql >/dev/null 2>&1; then\n log \"WARN: psql not on PATH — skipping advisory lock\"\n return 0\n fi\n # Phase 1.1 fix B (2026-05-19 smoke #4 discovery): we must\n # distinguish three psql outcomes — only one of them means\n # \"another run holds the lock\":\n #\n # 1. psql exit 0, output \"t\" → lock acquired, proceed\n # 2. psql exit 0, output \"f\" → another run holds, exit 3\n # 3. psql exit non-zero → connection failure (e.g.\n # DATABASE_URL points to Fly flycast hostname not reachable\n # from outside Fly net), degrade gracefully per Symbiosis\n # Law 4 — log warning + skip lock + continue. Treating\n # connection failure as \"lock held\" would block every\n # Sunday run forever if the operator's DATABASE_URL is\n # misconfigured. Better to lose single-flight protection\n # than to silently never run.\n local got rc\n set +e\n got=\"$(psql \"${DATABASE_URL}\" -At -c \"SELECT pg_try_advisory_lock(${PG_LOCK_KEY});\" 2>>\"${LOG_FILE}\")\"\n rc=$?\n set -e\n if [[ \"${rc}\" -ne 0 ]]; then\n log \"WARN: psql connection to DATABASE_URL failed (rc=${rc}) — skipping advisory lock (single-flight degraded, see ${LOG_FILE})\"\n return 0\n fi\n if [[ \"${got}\" == \"t\" ]]; then\n log \"advisory lock acquired (key=${PG_LOCK_KEY})\"\n # Best-effort release on EXIT (Bash trap)\n # shellcheck disable=SC2064\n # Combined EXIT trap: release the advisory lock AND restore deploy/main\n # (bash keeps only ONE EXIT trap, so both actions must live here — a\n # separate `trap _restore_deploy_branch EXIT` would silently overwrite\n # this one, S13-P6b regression discovered 2026-06-02).\n # shellcheck disable=SC2064\n trap \"psql '${DATABASE_URL}' -c 'SELECT pg_advisory_unlock(${PG_LOCK_KEY});' >/dev/null 2>&1 || true; _restore_deploy_branch\" EXIT\n return 0\n fi\n log \"another run holds the advisory lock (key=${PG_LOCK_KEY}) — exiting\"\n exit 3\n}\nacquire_lock\n\n# ─── 4. Context gathering ────────────────────────────────────────────\n\nCONTEXT_RAW=\"${RUN_TELEMETRY_DIR}/context-raw.md\"\nCONTEXT_REDACTED=\"${RUN_TELEMETRY_DIR}/context-redacted.md\"\n\ngather_context() {\n log \"gathering context → ${CONTEXT_RAW}\"\n {\n printf '# Context for agent-library evolver run %s\\n\\n' \"${RUN_TS}\"\n\n printf '## Recent git activity (--since=7days)\\n\\n```\\n'\n git -C \"${REPO_ROOT}\" log --since=7days --oneline --no-merges 2>/dev/null | head -200 || true\n printf '```\\n\\n'\n\n printf '## Cicatrix scars (.claude/rules/cicatrix-scars.md)\\n\\n'\n if [[ -f \"${REPO_ROOT}/.claude/rules/cicatrix-scars.md\" ]]; then\n head -300 \"${REPO_ROOT}/.claude/rules/cicatrix-scars.md\" || true\n else\n printf '(no cicatrix-scars.md found)\\n'\n fi\n printf '\\n'\n\n printf '## Recent mem entries (top 20 by importance)\\n\\n'\n if command -v mem >/dev/null 2>&1; then\n mem recent --limit 20 2>/dev/null || mem query \"recent\" 2>/dev/null || true\n else\n printf '(mem CLI not available)\\n'\n fi\n printf '\\n'\n\n printf '## Existing agent-library inventory\\n\\n'\n if [[ -f \"${REPO_ROOT}/agent-library/01-inventory.md\" ]]; then\n head -100 \"${REPO_ROOT}/agent-library/01-inventory.md\" || true\n fi\n } > \"${CONTEXT_RAW}\"\n\n local raw_bytes\n raw_bytes=\"$(wc -c < \"${CONTEXT_RAW}\" | tr -d ' ')\"\n log \"context-raw: ${raw_bytes} bytes\"\n if [[ \"${raw_bytes}\" -lt 200 ]]; then\n log \"FATAL: context-raw too small (${raw_bytes} bytes < 200) — gathering failed\"\n exit 1\n fi\n}\ngather_context\n\n# ─── 5. MANDATORY redaction (Symbiosis Law 2 + UU PDP) ───────────────\n\nREDACTOR=\"${REPO_ROOT}/scripts/_redact_pii.py\"\nif [[ ! -f \"${REDACTOR}\" ]]; then\n log \"FATAL: redactor not found at ${REDACTOR} — fail-closed per Symbiosis Law 2\"\n exit 4\nfi\n\nredact_context() {\n log \"redacting context → ${CONTEXT_REDACTED}\"\n if ! python3 \"${REDACTOR}\" < \"${CONTEXT_RAW}\" > \"${CONTEXT_REDACTED}\" 2>>\"${LOG_FILE}\"; then\n log \"FATAL: redactor exited non-zero — fail-closed\"\n exit 4\n fi\n local red_bytes\n red_bytes=\"$(wc -c < \"${CONTEXT_REDACTED}\" | tr -d ' ')\"\n log \"context-redacted: ${red_bytes} bytes\"\n if [[ \"${red_bytes}\" -lt 100 ]]; then\n log \"FATAL: redacted context too small (${red_bytes} bytes < 100) — over-redaction or empty input\"\n exit 4\n fi\n chmod 0600 \"${CONTEXT_RAW}\" \"${CONTEXT_REDACTED}\" 2>/dev/null || true\n}\nredact_context\n\nif [[ \"${DRY_RUN}\" == \"1\" ]]; then\n log \"dry-run mode — skipping evoskill invocation, evidence/entailment, PR, alert\"\n log \"DONE (dry-run) — exit 0\"\n exit 0\nfi\n\n# ─── 6. uv run evoskill run ──────────────────────────────────────────\n\nTELEMETRY_JSON=\"${RUN_TELEMETRY_DIR}/telemetry.json\"\nEVOSKILL_DIR=\"${REPO_ROOT}/vendor/evoskill\"\nEVOSKILL_CONFIG=\"${REPO_ROOT}/agent-library/.evoskill/config.toml\"\n\nif [[ ! -f \"${EVOSKILL_CONFIG}\" ]]; then\n log \"FATAL: evoskill config not found at ${EVOSKILL_CONFIG}\"\n exit 1\nfi\n\nrun_evoskill() {\n # S13-P6b (2026-06-02): evoskill creates program/* branches inside REPO_ROOT\n # and leaves them; a prior run's branches make this run's `git checkout -b\n # program/iter-skill-N` fail (exit 128, already-exists). Prune them first.\n # Safe: program/* are evoskill-only artifacts, never pushed, regenerated each\n # run. The base is recreated clean by the fixed writer.\n if git -C \"${REPO_ROOT}\" rev-parse --is-inside-work-tree >/dev/null 2>&1; then\n for _pb in $(git -C \"${REPO_ROOT}\" branch --list 'program/*' --format='%(refname:short)' 2>/dev/null); do\n git -C \"${REPO_ROOT}\" branch -D \"${_pb}\" >/dev/null 2>&1 \\\n && log \"pruned stale evoskill branch ${_pb}\" || true\n done\n fi\n log \"invoking uv run evoskill run --config ${EVOSKILL_CONFIG}\"\n # Codex panel R5 BLOCKING #2 fix: evoskill CLI does NOT support\n # `--output-telemetry`. Cost is reported via the post-run RunReport\n # written as markdown to .evoskill/reports/run-<timestamp>.md (see\n # vendor/evoskill/src/cli/report.py:save). The post-run budget\n # check below scans that report instead. Wrapper exit 1 on\n # evoskill failure — no fall-through that bypasses budget gate.\n #\n # Phase 1.1 fix (2026-05-19 smoke discovery): EvoSkill's\n # ProgramManager (vendor/evoskill/src/registry/manager.py:574)\n # invokes `git commit` from `_find_repo_root()` cwd to save\n # program changes. That cwd resolves to the main Nuzantara repo,\n # which has a pre-commit hook running prettier+typecheck that\n # rejects EvoSkill's internal commits with exit 1 → \"Cannot save\n # EvoSkill program changes\". The error message is real but the\n # block is wrong: EvoSkill commits are internal-state checkpoints\n # on a side branch (program/iter-N), they don't touch our code\n # files and have no business going through our prettier check.\n #\n # Fix: GIT_CONFIG_PARAMETERS bypasses ALL hooks ONLY for the\n # evoskill subprocess (and its child gits). This is NOT\n # `git commit --no-verify` (banned by global rules) — it's a\n # localized environment override scoped to a single subprocess\n # tree. The parent shell + any other git commands you run in\n # parallel still honour the hooks. The override is documented\n # in the launchd plist EnvironmentVariables for transparency.\n #\n # We ALSO set GIT_AUTHOR_* + GIT_COMMITTER_* so EvoSkill's\n # internal commits carry a clear \"agent-library-evolver\" identity\n # rather than inheriting Antonello's git config — keeps the\n # audit trail clean if anyone greps the program/* refs.\n if ! (\n cd \"${EVOSKILL_DIR}\" && \\\n BUDGET_USD=\"${BUDGET_USD}\" \\\n DEEPSEEK_API_KEY=\"${DEEPSEEK_API_KEY}\" \\\n GIT_CONFIG_PARAMETERS=\"'core.hooksPath=/dev/null'\" \\\n GIT_AUTHOR_NAME=\"agent-library-evolver\" \\\n GIT_AUTHOR_EMAIL=\"noreply@balizero.com\" \\\n GIT_COMMITTER_NAME=\"agent-library-evolver\" \\\n GIT_COMMITTER_EMAIL=\"noreply@balizero.com\" \\\n uv run evoskill run \\\n --config \"${EVOSKILL_CONFIG}\" \\\n >> \"${LOG_FILE}\" 2>&1\n ); then\n log \"FATAL: evoskill run failed — see ${LOG_FILE}\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: uv run evoskill run non-zero exit (see ${LOG_FILE})\"\n exit 1\n fi\n log \"evoskill run completed\"\n}\nrun_evoskill\n\n# ─── 7. Budget check (L6 fail-closed) ────────────────────────────────\n\n# Path to evoskill's own RunReport. After Codex panel R5 BLOCKING #2\n# we scan the most-recent run-*.md and parse `| Total cost | $X.XXXX |`\n# (see vendor/evoskill/src/cli/report.py:_render_markdown). Missing\n# report = fail-closed (we don't know the cost → can't honour budget).\nEVOSKILL_REPORTS_DIR=\"${REPO_ROOT}/agent-library/.evoskill/reports\"\n\nenforce_budget() {\n if [[ ! -d \"${EVOSKILL_REPORTS_DIR}\" ]]; then\n log \"FATAL: .evoskill/reports/ missing after evoskill run — cannot verify budget\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: missing .evoskill/reports/ → cannot enforce BUDGET_USD\"\n exit 5\n fi\n # Pick the most-recently-modified run-*.md\n local latest_report\n latest_report=\"$(ls -t \"${EVOSKILL_REPORTS_DIR}\"/run-*.md 2>/dev/null | head -1)\"\n if [[ -z \"${latest_report}\" ]]; then\n log \"FATAL: no run-*.md found in ${EVOSKILL_REPORTS_DIR} — cannot verify budget\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: no evoskill report generated → cannot enforce BUDGET_USD\"\n exit 5\n fi\n log \"parsing budget from ${latest_report}\"\n local total_cost\n total_cost=\"$(python3 -c \"\nimport re, sys\ntry:\n with open('${latest_report}') as f:\n text = f.read()\n m = re.search(r'\\| Total cost \\| \\\\\\$([0-9]+\\.[0-9]+) \\|', text)\n if not m:\n print('PARSE_FAIL')\n else:\n print(m.group(1))\nexcept Exception as e:\n print(f'PARSE_FAIL: {e}', file=sys.stderr)\n print('PARSE_FAIL')\n\" 2>>\"${LOG_FILE}\")\"\n if [[ \"${total_cost}\" == \"PARSE_FAIL\" ]] || [[ \"${total_cost}\" == PARSE_FAIL* ]]; then\n log \"FATAL: could not parse Total cost from ${latest_report} — fail-closed\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: cost parse error from report → fail-closed\"\n exit 5\n fi\n log \"report.total_cost_usd=${total_cost} budget_usd=${BUDGET_USD}\"\n # Persist a small JSON snapshot for final_alert + downstream tooling\n TELEMETRY_JSON=\"${RUN_TELEMETRY_DIR}/telemetry.json\"\n python3 -c \"\nimport json\nwith open('${TELEMETRY_JSON}', 'w') as f:\n json.dump({\n 'total_cost_usd': float('${total_cost}'),\n 'budget_usd': float('${BUDGET_USD}'),\n 'source_report': '${latest_report}',\n }, f, indent=2)\n\" 2>>\"${LOG_FILE}\" || log \"WARN: could not write telemetry.json snapshot\"\n local over\n over=\"$(python3 -c \"print(1 if float('${total_cost}') > float('${BUDGET_USD}') else 0)\")\"\n if [[ \"${over}\" == \"1\" ]]; then\n log \"FATAL: total_cost_usd=${total_cost} exceeds budget_usd=${BUDGET_USD} — fail-closed\"\n telegram_alert \"Budget exceeded: \\$${total_cost} > \\$${BUDGET_USD}. Run halted before proposals shipped.\"\n exit 5\n fi\n}\nenforce_budget\n\n# ─── 8. Evidence lint + entailment check ─────────────────────────────\n\nPROPOSALS_DIR=\"${REPO_ROOT}/agent-library/proposals/${RUN_DATE}\"\nEVIDENCE_LINT=\"${REPO_ROOT}/scripts/_evidence_lint.py\"\nENTAILMENT_CHECK=\"${REPO_ROOT}/scripts/_entailment_check.py\"\n\nPROPOSALS_PASSED=0\n\nrun_evidence_gates() {\n if [[ ! -d \"${PROPOSALS_DIR}\" ]]; then\n log \"no proposals directory at ${PROPOSALS_DIR} — evoskill produced 0 proposals\"\n return 0\n fi\n # Codex panel R5 BLOCKING #4: gate exit codes MUST be terminal.\n # Previously `|| log \"...\"` swallowed every failure including\n # _entailment_check.py exit 2 (Gemini quota exhaust) — wrapper\n # would proceed to PR creation with stale/partial passed/ output.\n # Now: linter exit 1 (fatal) + entailment exit 1 or 2 (quota or\n # fatal) abort the run with Telegram alert; only exit 0 means\n # \"partition completed cleanly, some proposals may be in rejected/\".\n if [[ ! -f \"${EVIDENCE_LINT}\" ]]; then\n log \"FATAL: ${EVIDENCE_LINT} not found — Phase 1 evidence gate missing\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: evidence linter script missing\"\n exit 1\n fi\n log \"running evidence linter on ${PROPOSALS_DIR}\"\n if ! python3 \"${EVIDENCE_LINT}\" \"${PROPOSALS_DIR}\" >> \"${LOG_FILE}\" 2>&1; then\n log \"FATAL: evidence linter exited non-zero — fail-closed\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: _evidence_lint.py crashed (see ${LOG_FILE})\"\n exit 1\n fi\n\n # Skip entailment if evidence produced 0 passed-existence proposals\n if [[ ! -d \"${PROPOSALS_DIR}/passed-existence\" ]] || \\\n [[ -z \"$(ls -A \"${PROPOSALS_DIR}/passed-existence\" 2>/dev/null)\" ]]; then\n log \"0 proposals passed evidence gate — skipping entailment check\"\n return 0\n fi\n\n if [[ ! -f \"${ENTAILMENT_CHECK}\" ]]; then\n log \"FATAL: ${ENTAILMENT_CHECK} not found — Phase 1 entailment gate missing\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: entailment checker script missing\"\n exit 1\n fi\n log \"running entailment check on ${PROPOSALS_DIR}/passed-existence/\"\n set +e\n python3 \"${ENTAILMENT_CHECK}\" \"${PROPOSALS_DIR}/passed-existence\" >> \"${LOG_FILE}\" 2>&1\n local entailment_rc=$?\n set -e\n if [[ \"${entailment_rc}\" == \"2\" ]]; then\n log \"FATAL: entailment check exited 2 (Gemini quota exhausted) — fail-closed\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: Gemini quota exhausted, retry tomorrow\"\n exit 1\n fi\n if [[ \"${entailment_rc}\" != \"0\" ]]; then\n log \"FATAL: entailment check exited ${entailment_rc} — fail-closed\"\n telegram_alert \"evolver ${RUN_DATE} FAIL: _entailment_check.py rc=${entailment_rc} (see ${LOG_FILE})\"\n exit 1\n fi\n\n if [[ -d \"${PROPOSALS_DIR}/passed\" ]]; then\n PROPOSALS_PASSED=\"$(find \"${PROPOSALS_DIR}/passed\" -name 'SKILL.md' -type f | wc -l | tr -d ' ')\"\n fi\n log \"proposals_passed=${PROPOSALS_PASSED}\"\n}\nrun_evidence_gates\n\n# ─── 9. Open draft PR (only if ≥1 proposal passed) ───────────────────\n\nopen_pr_draft() {\n if [[ \"${PROPOSALS_PASSED}\" -lt 1 ]]; then\n log \"0 proposals passed both gates — skipping PR creation\"\n return 0\n fi\n if ! command -v gh >/dev/null 2>&1; then\n log \"WARN: gh CLI not available — skipping PR creation, proposals stay on disk\"\n return 0\n fi\n local branch=\"auto/agent-library-${RUN_DATE}\"\n log \"creating draft PR on branch ${branch} with ${PROPOSALS_PASSED} proposals\"\n (\n cd \"${REPO_ROOT}\"\n git checkout -b \"${branch}\" 2>>\"${LOG_FILE}\" || git checkout \"${branch}\" 2>>\"${LOG_FILE}\"\n git add \"agent-library/proposals/${RUN_DATE}/passed/\" 2>>\"${LOG_FILE}\"\n git commit -m \"feat(agent-library): auto-evolver proposals ${RUN_DATE}\n\n${PROPOSALS_PASSED} proposals passed both evidence + entailment gates.\nGenerated by scripts/agent-library-evolver-run.sh ${RUN_TS}.\n\nHuman-merge gate required per L2 autonomous ops — do NOT auto-merge.\nEach SKILL.md cites file:line / commit / URL evidence; review the\ncited content before merging.\n\nCo-Authored-By: agent-library-evolver <noreply@balizero.com>\" 2>>\"${LOG_FILE}\" || true\n git push -u origin \"${branch}\" 2>>\"${LOG_FILE}\" || true\n gh pr create --draft \\\n --title \"auto: agent-library proposals ${RUN_DATE}\" \\\n --body \"Weekly auto-evolver run ${RUN_TS}. ${PROPOSALS_PASSED} proposals passed evidence + entailment gates. Human review required before merge.\" \\\n --base main \\\n --head \"${branch}\" \\\n >> \"${LOG_FILE}\" 2>&1 || \\\n log \"gh pr create failed — proposals committed on branch ${branch}, no PR opened\"\n )\n}\nopen_pr_draft\n\n# ─── 10. Telegram final alert ────────────────────────────────────────\n# (telegram_alert + final_alert helpers defined earlier in §2b for use\n# in enforce_budget's overage path — bash doesn't hoist function defs)\n\nfinal_alert\n\nlog \"DONE (proposals_passed=${PROPOSALS_PASSED}) — exit 0\"\nexit 0\n"}}, "docs/audits/2026-04-29-zero-crash-audit/prompts/wave1/_coordination.sh": {"435945f89392b92ca558834d695b57058c697670": {"t": 1786068354.297052, "n": 8154, "h": "348fca3dd2f03935", "c": "#!/usr/bin/env bash\n# Coordination helpers for Wave 1 parallel sessions.\n# Source this from each session's wrap-up script.\n#\n# Usage:\n# source ~/Desktop/nuzantara/docs/audits/2026-04-29-zero-crash-audit/prompts/wave1/_coordination.sh\n# coord_commit \"feat(p0-X): ...\" path1 path2\n# coord_push origin <branch>\n# coord_deploy_fly nuzantara-rag\n#\n# Locks live in ~/.claude/locks/.\n# Each function: acquire lock with timeout, run command, release.\n\nLOCK_DIR=\"$HOME/.claude/locks\"\nmkdir -p \"$LOCK_DIR\"\n\n# Wait for lock with timeout. Returns 0 if acquired, 1 if timeout.\n# Args: lock_name, timeout_seconds\n_acquire_lock() {\n local lock_name=\"$1\"\n local timeout=\"${2:-1800}\" # default 30 min\n local lockfile=\"$LOCK_DIR/$lock_name.lock\"\n local start=$(date +%s)\n\n while true; do\n # Try to acquire (non-blocking)\n if ( set -o noclobber; echo \"$$:$(date +%s):$(whoami)@$(hostname)\" > \"$lockfile\" ) 2>/dev/null; then\n return 0\n fi\n\n # Check timeout\n local now=$(date +%s)\n local elapsed=$((now - start))\n if [ \"$elapsed\" -ge \"$timeout\" ]; then\n echo \"[coord] TIMEOUT after ${elapsed}s waiting for $lock_name lock\" >&2\n local holder=$(cat \"$lockfile\" 2>/dev/null || echo \"unknown\")\n echo \"[coord] Lock held by: $holder\" >&2\n return 1\n fi\n\n # Inspect holder for staleness (>30 min PID gone)\n if [ -f \"$lockfile\" ]; then\n local holder_pid=$(cut -d: -f1 \"$lockfile\" 2>/dev/null)\n local holder_ts=$(cut -d: -f2 \"$lockfile\" 2>/dev/null)\n if [ -n \"$holder_pid\" ] && ! kill -0 \"$holder_pid\" 2>/dev/null; then\n local age=$((now - ${holder_ts:-0}))\n if [ \"$age\" -gt 1800 ]; then\n echo \"[coord] STALE lock detected (PID $holder_pid gone, age ${age}s); breaking\" >&2\n rm -f \"$lockfile\"\n continue\n fi\n fi\n fi\n\n echo \"[coord] Waiting for $lock_name lock (${elapsed}s elapsed)... holder: $(cat $lockfile 2>/dev/null)\"\n sleep 30\n done\n}\n\n_release_lock() {\n local lock_name=\"$1\"\n local lockfile=\"$LOCK_DIR/$lock_name.lock\"\n rm -f \"$lockfile\"\n}\n\n# Commit with lock. Args: message, files...\ncoord_commit() {\n local message=\"$1\"; shift\n local files=(\"$@\")\n\n echo \"[coord] Acquiring git-commit lock...\"\n _acquire_lock \"git-commit\" 1800 || return 1\n\n trap '_release_lock git-commit' RETURN\n\n if [ \"${#files[@]}\" -eq 0 ]; then\n git add -A\n else\n git add \"${files[@]}\"\n fi\n\n # Verify there's something to commit\n if git diff --staged --quiet; then\n echo \"[coord] No staged changes; skipping commit\"\n _release_lock git-commit\n trap - RETURN\n return 0\n fi\n\n git commit -m \"$(cat <<EOF\n$message\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\nEOF\n)\"\n local rc=$?\n _release_lock git-commit\n trap - RETURN\n return $rc\n}\n\n# Push with lock. Args: remote, branch\ncoord_push() {\n local remote=\"${1:-origin}\"\n local branch=\"${2:-$(git rev-parse --abbrev-ref HEAD)}\"\n\n echo \"[coord] Acquiring git-push lock...\"\n _acquire_lock \"git-push\" 1800 || return 1\n\n trap '_release_lock git-push' RETURN\n\n git push -u \"$remote\" \"$branch\"\n local rc=$?\n _release_lock git-push\n trap - RETURN\n return $rc\n}\n\n# Fly deploy with lock. Args: app_name, optional extra flyctl args\ncoord_deploy_fly() {\n local app=\"${1:?app name required}\"; shift\n\n echo \"[coord] Acquiring fly-deploy lock...\"\n _acquire_lock \"fly-deploy\" 3600 || return 1\n\n trap '_release_lock fly-deploy' RETURN\n\n cd \"$HOME/Desktop/nuzantara/apps/backend-rag\"\n fly deploy --strategy rolling --app \"$app\" \"$@\"\n local rc=$?\n _release_lock fly-deploy\n trap - RETURN\n return $rc\n}\n\n# Status: show all current locks\ncoord_status() {\n echo \"=== Current locks in $LOCK_DIR ===\"\n for lock in \"$LOCK_DIR\"/*.lock; do\n [ -f \"$lock\" ] || continue\n local name=$(basename \"$lock\" .lock)\n local content=$(cat \"$lock\" 2>/dev/null)\n echo \" $name: $content\"\n done\n}\n\n# Brainstorm cross-LLM dispatch helper\n# Args: topic, brief_file_path\n# Outputs: 4 files in ./brainstorm_output/<llm>.md\ncoord_brainstorm() {\n local topic=\"$1\"\n local brief_file=\"$2\"\n local out_dir=\"${3:-./brainstorm_output}\"\n\n mkdir -p \"$out_dir\"\n\n if [ ! -f \"$brief_file\" ]; then\n echo \"[coord] ERROR: brief file not found: $brief_file\" >&2\n return 1\n fi\n\n echo \"[coord] Dispatching cross-LLM brainstorm on '$topic'\"\n echo \"[coord] Brief: $brief_file ($(wc -c < \"$brief_file\") bytes)\"\n echo \"[coord] Output: $out_dir/\"\n\n # Codex (sandbox workspace-write, MCP disabled to avoid OAuth refresh fail)\n (\n echo \"[codex] starting $(date)\"\n codex exec --full-auto --sandbox workspace-write < \"$brief_file\" \\\n > \"$out_dir/codex.md\" 2>&1\n echo \"[codex] done $(date) ($(wc -l < \"$out_dir/codex.md\") lines)\"\n ) &\n\n # Gemini 3.1 Pro yolo (no sandbox to allow file ops)\n (\n echo \"[gemini] starting $(date)\"\n gemini -m gemini-3.1-pro-preview --yolo -p \"$(cat \"$brief_file\")\" \\\n > \"$out_dir/gemini.md\" 2>&1\n echo \"[gemini] done $(date) ($(wc -l < \"$out_dir/gemini.md\") lines)\"\n ) &\n\n # DeepSeek v4-pro via API (CLI-only law exception per CLAUDE.md)\n (\n echo \"[deepseek] starting $(date)\"\n local key=$(grep '^DEEPSEEK_API_KEY=' ~/.nuzantara-secrets.env | cut -d= -f2)\n python3 - \"$brief_file\" \"$key\" > \"$out_dir/deepseek.md\" 2>&1 <<'PYEOF'\nimport json, sys, urllib.request\nbrief_file, api_key = sys.argv[1], sys.argv[2]\nwith open(brief_file) as f: brief = f.read()\npayload = {\n \"model\": \"deepseek-v4-pro\",\n \"messages\": [\n {\"role\": \"system\", \"content\": \"You are DeepSeek doing implementation strategy brainstorm. Be specific to the codebase, propose concrete code changes, options with tradeoffs.\"},\n {\"role\": \"user\", \"content\": brief}\n ],\n \"max_tokens\": 8000, \"temperature\": 0.3\n}\nreq = urllib.request.Request(\n \"https://api.deepseek.com/v1/chat/completions\",\n data=json.dumps(payload).encode(),\n headers={\"Authorization\": f\"Bearer {api_key}\", \"Content-Type\": \"application/json\"},\n method=\"POST\"\n)\nwith urllib.request.urlopen(req, timeout=900) as resp:\n result = json.loads(resp.read().decode())\nchoice = result[\"choices\"][0][\"message\"]\nprint(\"# DeepSeek v4-pro brainstorm\\n\")\nif choice.get(\"reasoning_content\"):\n print(\"## Reasoning\\n\")\n print(choice[\"reasoning_content\"])\n print()\nprint(\"## Analysis\\n\")\nprint(choice.get(\"content\", \"\"))\nPYEOF\n echo \"[deepseek] done $(date)\"\n ) &\n\n # NotebookLM via MCP (cross_notebook_query NB-1 architecture + NB-14 sessions)\n # Note: requires nlm-mcp running. We use the wrapper script.\n (\n echo \"[nlm] starting $(date)\"\n # Use the codebase NB (f6ecd115-...) as ground truth source\n local nb_id=\"f6ecd115-dd89-4c9b-b3dd-071e0e2f1876\"\n local query=$(cat \"$brief_file\")\n python3 - \"$nb_id\" > \"$out_dir/notebooklm.md\" 2>&1 <<PYEOF\nimport sys, json, subprocess\nnb_id = sys.argv[1]\nquery = \"\"\"$query\"\"\"\n# Use mcporter wrapper if available, else fallback to direct cli\ntry:\n # Try mcporter wrapper for nlm-mcp\n result = subprocess.run(\n [\"nlm\", \"query\", nb_id, query[:5000]],\n capture_output=True, text=True, timeout=300\n )\n print(\"# NotebookLM NB-1 brainstorm\\n\")\n print(result.stdout)\n if result.returncode != 0:\n print(f\"\\nSTDERR: {result.stderr}\")\nexcept FileNotFoundError:\n print(\"# NotebookLM NB-1 brainstorm\\n\")\n print(\"ERROR: nlm CLI not found. Skipping NotebookLM. Use mcp__notebooklm-mcp__notebook_query interactively in main session if needed.\")\nexcept subprocess.TimeoutExpired:\n print(\"# NotebookLM NB-1 brainstorm\\n\")\n print(\"ERROR: NotebookLM timeout (300s). Brief might be too long.\")\nPYEOF\n echo \"[nlm] done $(date)\"\n ) &\n\n wait\n\n echo \"[coord] Brainstorm complete. Files in $out_dir/:\"\n ls -lh \"$out_dir/\"\n}\n"}}, "scripts/test_call_llm_deepseek.py": {"435945f89392b92ca558834d695b57058c697670": {"t": 1786068451.2308931, "n": 10435, "h": "d63c2f231de76689", "c": "\"\"\"Unit tests for Phase 1 Task #22 — DeepSeek branch in vendor/evoskill/src/cli/shared.py.\n\nVerifies:\n - infer_provider() routes \"deepseek-v4-pro\" to \"deepseek\" (NOT to\n fallback \"anthropic\" which would trigger the hard-rule ImportError)\n - infer_provider() routes \"deepseek/anything\" to \"deepseek\"\n - infer_provider() preserves existing routes (claude→anthropic,\n gpt-*→openai, gemini→google) — no regression\n - _normalize_provider_model strips \"deepseek/\" prefix when provider=deepseek\n - call_llm(provider=\"deepseek\") routes to OpenAI-compatible\n base_url=\"https://api.deepseek.com/v1\" with the configured api_key\n - call_llm(provider=\"deepseek\") returns response.choices[0].message.content\n - call_llm(provider=\"deepseek\") raises RuntimeError if DEEPSEEK_API_KEY unset\n - call_llm(provider=\"deepseek\") still raises ImportError if openai pkg missing\n - make_scorer default model is now \"deepseek-v4-pro\" (NOT \"claude-sonnet-4-6\")\n - Anthropic guard still fires when provider=\"anthropic\" explicitly chosen\n\nRun:\n cd ~/Desktop/nuzantara-wt-evoskill-phase1 && python3 -m pytest scripts/test_call_llm_deepseek.py -v\n\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport os\nimport sys\nfrom pathlib import Path\nfrom unittest.mock import AsyncMock, MagicMock, patch\n\nimport pytest\n\nREPO_ROOT = Path(__file__).resolve().parent.parent\nVENDOR_EVOSKILL = REPO_ROOT / \"vendor\" / \"evoskill\"\nsys.path.insert(0, str(VENDOR_EVOSKILL))\n\n\ndef _reload_shared():\n \"\"\"Force-reload src.cli.shared to pick up patched env.\"\"\"\n if \"src.cli.shared\" in sys.modules:\n del sys.modules[\"src.cli.shared\"]\n from src.cli import shared # noqa: F401 — side-effect import\n return shared\n\n\n# ---------- infer_provider ----------\n\n\ndef test_infer_provider_deepseek_v4_pro_routes_to_deepseek():\n \"\"\"The exact model name we ship in evolver.toml MUST NOT fall through\n to the upstream `return \"anthropic\"` final branch.\"\"\"\n shared = _reload_shared()\n assert shared.infer_provider(\"deepseek-v4-pro\") == \"deepseek\"\n\n\ndef test_infer_provider_deepseek_prefix():\n \"\"\"`deepseek/some-future-model` style routes to deepseek.\"\"\"\n shared = _reload_shared()\n assert shared.infer_provider(\"deepseek/v5-preview\") == \"deepseek\"\n\n\ndef test_infer_provider_deepseek_chat_routes_to_deepseek():\n \"\"\"Even the deprecated `deepseek-chat`/`deepseek-reasoner` aliases\n (CLAUDE.md notes they were deprecated 2026-07-24) still route to\n deepseek if someone uses them, so the failure mode is a clean\n DeepSeek API 4xx — not a confusing anthropic-hardrule raise.\"\"\"\n shared = _reload_shared()\n assert shared.infer_provider(\"deepseek-chat\") == \"deepseek\"\n assert shared.infer_provider(\"deepseek-reasoner\") == \"deepseek\"\n\n\ndef test_infer_provider_no_regression_other_providers():\n \"\"\"Pre-existing routes (claude/gpt/gemini) must stay intact.\"\"\"\n shared = _reload_shared()\n assert shared.infer_provider(\"claude-sonnet-4-6\") == \"anthropic\"\n assert shared.infer_provider(\"gpt-5-mini\") == \"openai\"\n assert shared.infer_provider(\"o1-preview\") == \"openai\"\n assert shared.infer_provider(\"gemini-3.1-pro-preview\") == \"google\"\n assert shared.infer_provider(\"anthropic/claude-opus-4-7\") == \"anthropic\"\n assert shared.infer_provider(\"openrouter/google/gemini-pro\") == \"openrouter\"\n\n\ndef test_infer_provider_unknown_model_falls_back_to_anthropic():\n \"\"\"Phase 1 keeps the upstream fallback so genuinely-unknown models\n hit the loud hard-rule raise in call_llm rather than silently\n routing to DeepSeek (which would then 401).\"\"\"\n shared = _reload_shared()\n assert shared.infer_provider(\"totally-unknown-model-xyz\") == \"anthropic\"\n\n\n# ---------- _normalize_provider_model ----------\n\n\ndef test_normalize_provider_model_strips_deepseek_prefix():\n shared = _reload_shared()\n assert (\n shared._normalize_provider_model(\"deepseek\", \"deepseek/v4-pro\")\n == \"v4-pro\"\n )\n\n\ndef test_normalize_provider_model_passthrough_no_prefix():\n shared = _reload_shared()\n assert (\n shared._normalize_provider_model(\"deepseek\", \"deepseek-v4-pro\")\n == \"deepseek-v4-pro\"\n )\n\n\ndef test_normalize_provider_model_no_regression_other_providers():\n shared = _reload_shared()\n assert (\n shared._normalize_provider_model(\"openrouter\", \"openrouter/foo\")\n == \"foo\"\n )\n assert (\n shared._normalize_provider_model(\"openai\", \"openai/gpt-5\")\n == \"gpt-5\"\n )\n\n\n# ---------- call_llm deepseek branch ----------\n\n\ndef _build_mock_openai_client(content_returned: str = \"0.0\") -> MagicMock:\n \"\"\"Build a mock matching openai.AsyncOpenAI shape.\"\"\"\n mock_choice = MagicMock()\n mock_choice.message.content = content_returned\n mock_response = MagicMock()\n mock_response.choices = [mock_choice]\n\n mock_completions = MagicMock()\n mock_completions.create = AsyncMock(return_value=mock_response)\n mock_chat = MagicMock()\n mock_chat.completions = mock_completions\n mock_client = MagicMock()\n mock_client.chat = mock_chat\n return mock_client\n\n\n@patch.dict(os.environ, {\"DEEPSEEK_API_KEY\": \"sk-test-1234\"})\ndef test_call_llm_deepseek_routes_to_deepseek_base_url():\n \"\"\"call_llm(provider='deepseek') must construct openai.AsyncOpenAI\n with base_url='https://api.deepseek.com/v1' and the env-resolved\n API key. This is the entire integration contract.\"\"\"\n shared = _reload_shared()\n\n mock_client = _build_mock_openai_client(\"1.0\")\n fake_openai_module = MagicMock()\n fake_openai_module.AsyncOpenAI = MagicMock(return_value=mock_client)\n\n with patch.dict(sys.modules, {\"openai\": fake_openai_module}):\n result = asyncio.run(\n shared.call_llm(\"deepseek\", \"deepseek-v4-pro\", \"test prompt\")\n )\n\n assert result == \"1.0\"\n\n # AsyncOpenAI constructor was called with the right base_url + key\n construct_call = fake_openai_module.AsyncOpenAI.call_args\n assert construct_call.kwargs[\"base_url\"] == \"https://api.deepseek.com/v1\"\n assert construct_call.kwargs[\"api_key\"] == \"sk-test-1234\"\n\n # chat.completions.create was called with the right model + max_tokens\n create_call = mock_client.chat.completions.create.call_args\n assert create_call.kwargs[\"model\"] == \"deepseek-v4-pro\"\n assert create_call.kwargs[\"max_tokens\"] == 16\n assert create_call.kwargs[\"messages\"] == [\n {\"role\": \"user\", \"content\": \"test prompt\"}\n ]\n\n\n@patch.dict(os.environ, {\"DEEPSEEK_API_KEY\": \"sk-test-5678\"})\ndef test_call_llm_deepseek_strips_deepseek_slash_prefix():\n \"\"\"If caller passes `deepseek/deepseek-v4-pro`, the upstream API\n must receive the bare model name (DeepSeek API doesn't know about\n the OpenRouter-style `provider/` prefix).\"\"\"\n shared = _reload_shared()\n\n mock_client = _build_mock_openai_client()\n fake_openai_module = MagicMock()\n fake_openai_module.AsyncOpenAI = MagicMock(return_value=mock_client)\n\n with patch.dict(sys.modules, {\"openai\": fake_openai_module}):\n asyncio.run(\n shared.call_llm(\"deepseek\", \"deepseek/v4-pro\", \"x\")\n )\n\n create_call = mock_client.chat.completions.create.call_args\n assert create_call.kwargs[\"model\"] == \"v4-pro\"\n\n\ndef test_call_llm_deepseek_raises_when_api_key_missing(monkeypatch):\n \"\"\"ensure_provider_api_key must raise RuntimeError when\n DEEPSEEK_API_KEY is unset.\"\"\"\n monkeypatch.delenv(\"DEEPSEEK_API_KEY\", raising=False)\n shared = _reload_shared()\n\n with pytest.raises(RuntimeError, match=\"API key not configured\"):\n asyncio.run(shared.call_llm(\"deepseek\", \"deepseek-v4-pro\", \"x\"))\n\n\n# ---------- Anthropic guard regression ----------\n\n\ndef test_call_llm_anthropic_still_raises_import_error():\n \"\"\"Phase 1 patches MUST NOT regress the Phase 0 R3 fix that\n anthropic provider raises ImportError BEFORE auth lookup.\"\"\"\n shared = _reload_shared()\n\n with pytest.raises(ImportError) as exc_info:\n asyncio.run(\n shared.call_llm(\"anthropic\", \"claude-sonnet-4-6\", \"x\")\n )\n\n msg = str(exc_info.value)\n assert \"BANNED\" in msg or \"CLAUDE.md hard rule\" in msg\n assert \"ANTHROPIC_API_KEY\" not in msg or \"BANNED\" in msg\n\n\n# ---------- make_scorer default model ----------\n\n\ndef test_make_scorer_llm_default_model_is_deepseek_v4_pro():\n \"\"\"L9 fix: default scorer model must NOT be claude-sonnet-4-6\n (which routes to anthropic→ImportError). It must be\n deepseek-v4-pro so an unconfigured scorer works out of the box.\"\"\"\n shared = _reload_shared()\n\n # Build a minimal ProjectConfig with empty scorer overrides\n from src.cli.config import (\n DatasetConfig,\n EvolutionConfig,\n HarnessConfig,\n ProjectConfig,\n ScorerConfig,\n )\n\n cfg = ProjectConfig(\n harness=HarnessConfig(name=\"deepseek\", model=\"deepseek-v4-pro\"),\n evolution=EvolutionConfig(),\n dataset=DatasetConfig(),\n scorer=ScorerConfig(type=\"llm\"), # no model / no provider\n )\n\n # Inspect the closure: rather than invoking the scorer (which would\n # need a real LLM call), patch call_llm with a probe that captures\n # the (provider, model) it was invoked with.\n captured: dict = {}\n\n async def probe(provider, model, prompt):\n captured[\"provider\"] = provider\n captured[\"model\"] = model\n return \"1.0\"\n\n with patch.object(shared, \"call_llm\", side_effect=probe):\n scorer_fn = shared.make_scorer(cfg)\n score = scorer_fn(\"q\", \"p\", \"g\")\n\n assert score == 1.0\n assert captured[\"model\"] == \"deepseek-v4-pro\"\n assert captured[\"provider\"] == \"deepseek\"\n\n\n# ---------- evolver.toml end-to-end via infer_provider ----------\n\n\ndef test_infer_provider_matches_evolver_toml_scorer_provider():\n \"\"\"Belt-and-suspenders: load the actual Phase 1 evolver.toml\n and verify that infer_provider(scorer.model) matches\n scorer.provider exactly. Prevents silent drift between the\n config file and the call_llm dispatch.\"\"\"\n from src.cli.config import load_config\n\n cfg_path = (\n REPO_ROOT / \"agent-library\" / \".evoskill\" / \"config.toml\"\n )\n cfg = load_config(config_path=cfg_path)\n\n shared = _reload_shared()\n inferred = shared.infer_provider(cfg.scorer.model or \"\")\n assert inferred == cfg.scorer.provider, (\n f\"evolver.toml says provider={cfg.scorer.provider!r} but \"\n f\"infer_provider({cfg.scorer.model!r}) routes to {inferred!r}\"\n )\n"}}, "research/operations/specs/L5.2-server-side-enforcement-2026-05-26.md": {"435945f89392b92ca558834d695b57058c697670": {"t": 1786068561.942116, "n": 19159, "h": "2cedfd6a9a7ebb2d", "c": "---\nspec_id: L5.2\ntitle: Server-Side Enforcement — required hot-zone check + bot privilege downgrade + audit-append-only\ndate: 2026-05-26\ndomain: operations\nstatus: ready-for-implementation\npriority_change: ELEVATED — panel iter-2 (2026-05-26) ha invertito priorità vs L5.1 (L5.2 deve shippare PRIMA)\nprerequisites:\n - PR #852 (L1 worktree broker) merged to main\n - PR #853 (L2 redis lease registry) merged to main\nparent_research:\n - research/operations/2026-05-26-gap23-panel-synthesis.md\n - research/operations/specs/L5.1-L5.2-panel-iter2-synthesis-2026-05-26.md (iter-2 verdict)\n - research/operations/2026-05-25-sota-workflow-gap-analysis-and-l5-spec.md\npriority: P0\npanel_iter2_verdict: APPROVE_WITH_MINOR_AMENDMENTS (Gemini + DeepSeek 2/2 convergent, Codex abstain)\npanel_amendments_applied:\n - L2.A1 (Gemini): rollout priority — Phase 1+2 enforce PRIMA o contemporaneo a L5.1\n - L2.A2 (Gemini): CODEOWNERS self-modification protection nella GitHub Action\nempirical_motivation:\n - Branch protection `enforce_admins=true` ma `required_pull_request_reviews=NONE` (verificato 2026-05-26)\n - Husky pre-commit/pre-push/post-commit zero detection di `HUSKY=0` bypass (verificato 2026-05-26)\n - Cicatrix W34 documenta `HUSKY=0 git push origin HEAD:main` usato per workaround SIGPIPE\n - Sessione 026f2cd1 (2026-05-26 03:39): PR auto-mergiata via whitelist senza review durante hook setup\n - Same trust domain: Claude bot + Antonello + Codex sulla stessa workstation, stessi token (no real independence)\n---\n\n# L5.2 — Server-Side Enforcement (post-Panel)\n\n## Status post panel iter-2 (2026-05-26)\n\n**Panel verdict**: APPROVE_WITH_MINOR_AMENDMENTS (Gemini + DeepSeek 2/2 convergent).\n\n**Priorità invertita** vs sessione 026f2cd1: L5.2 deve shippare PRIMA di L5.1. Razionale (Gemini): \"Costruire i recinti (L5.2) ha la priorità rispetto all'addestrare i cani (L5.1)\". L5.1 iter-2 ha 7 difetti residui (R1-R7) → richiede iter-3, NON è ready-for-implementation.\n\n**2 amendments applicati**:\n\n- **L2.A1**: Phase 1+2 enforce-mode PRIMA o contemporaneamente a L5.1 (sezione 7 rivista).\n- **L2.A2**: CODEOWNERS self-modification protection esplicita nella GitHub Action (sezione 2.4 nuova).\n\n## TL;DR\n\nL5.1 chiude la disciplina worktree client-side. **L5.2 chiude il gap server-side**:\n\n1. **Bot privilege downgrade** — Claude bot non deve avere merge rights su main.\n2. **Required hot-zone GitHub Action** — replica lease-check + lint-launchagents server-side al merge.\n3. **CODEOWNERS scope-ristretto** — review obbligatoria SOLO su file hot-zone, SOLO per PR bot.\n4. **Audit append-only** — bypass events (HUSKY=0, --no-verify, direct push) loggati su GitHub issue + Telegram per soli hot-zone events.\n\n**Filosofia**: client hooks = ergonomia + early warning. CI server-side = enforcement non-bypassable.\n\n**Kill switch globale**: `L5_2_SERVER_ENFORCEMENT_ENABLED=false` (env workflow).\n\n## Motivazione empirica\n\n| Evidenza | Source | Implicazione |\n| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------- |\n| `enforce_admins=true` + `required_pull_request_reviews=NONE` | `gh api repos/.../branches/main/protection` 2026-05-26 14:08 WITA | Admin (incluso bot) può mergiare PR critical senza review umana |\n| Husky scripts (pre-commit 8385B + pre-push 1659B + post-commit 487B) zero `HUSKY=0` detection | `grep -rn HUSKY .husky/` 2026-05-26 14:08 WITA | Bypass silenzioso lease-check + lint-launchagents + test |\n| W34 cicatrix scar | `HUSKY=0 git push origin HEAD:main` workaround SIGPIPE | Bypass empiricamente usato in produzione |\n| Sessione 026f2cd1 commit `b8df2f996` cancella `.github/CODEOWNERS` (87 entries) | git log worktree feat/ai-agent-workflow-spec-2026-05-25 | \"Require code owner reviews\" è inutile senza CODEOWNERS |\n| Same trust domain (Codex) | Antonello + Claude bot + Codex stessa workstation, stessi token keychain | Bot reviewer LLM non è indipendenza forte |\n\n## Architettura\n\n### Tre layer enforcement\n\n```\n┌────────────────────────────────────────────────────────────────┐\n│ Layer 1 — Client hooks (husky) │\n│ Pre-commit lease-check + pre-push tests = ERGONOMIA │\n│ Bypass possibile via HUSKY=0 — accettato come trade-off │\n└────────────────────────────────────────────────────────────────┘\n ↓\n┌────────────────────────────────────────────────────────────────┐\n│ Layer 2 — Server-side required check (L5.2 NEW) │\n│ GitHub Action replica lease-check + lint-launchagents al merge │\n│ Non bypassable (required + fail-closed + expected source app) │\n└────────────────────────────────────────────────────────────────┘\n ↓\n┌────────────────────────────────────────────────────────────────┐\n│ Layer 3 — Branch protection + CODEOWNERS scope-ristretto │\n│ Required review SOLO per PR bot + hot-zone │\n│ dismiss_stale + require_latest_push │\n└────────────────────────────────────────────────────────────────┘\n```\n\n### Componenti\n\n#### Componente 1 — Bot privilege downgrade (P0.1)\n\n**Stato attuale**: ipotetico Claude bot ha admin → può `merge_pull_request` + bypass branch protection.\n\n**Action**:\n\n- Audit `gh api orgs/Balizero1987/teams` + `gh api repos/Balizero1987/Teman2/collaborators` per identificare tutti gli admin.\n- Se Claude bot esiste come account separato → downgrade da `admin` a `write` o `triage`.\n- Se Claude opera con token di Antonello → creare account bot dedicato `nuzantara-bot[bot]` con permessi minimi (read + write su PR, NO merge, NO admin).\n- Documentare in `~/.nuzantara-secrets.env` quale token usare per quale ruolo.\n\n**Failure mode evitato**: bot+admin merge same actor (Codex Q1).\n\n#### Componente 2 — Required hot-zone GitHub Action (P0.2)\n\n**File**: `.github/workflows/hot-zone-enforcement.yml`\n\n**Trigger**: `pull_request` events + `merge_group` events (per merge queue).\n\n**Logica**:\n\n1. Checkout diff base SHA (NON branch HEAD — anti `pull_request_target` trap).\n2. Per ogni file in diff, classifica: `hot-zone` (migrations / sentinel / dlq / LaunchAgent wrappers / .github/workflows / auth / billing / pricing) vs `cold`.\n3. Se `hot-zone` files presenti:\n - Connect Redis lease registry (production credentials da `secrets.REDIS_URL_RO`).\n - Verifica nessun lease attivo su file da altro agent task-id.\n - Re-esegue `scripts/lint_launchagents.sh` se LaunchAgent files toccati.\n - Re-esegue `scripts/lint_migration_numbers.py` (W41 lint) se migrations toccate.\n - Verifica `required_approving_review_count >= 1` su PR.\n4. **Fail-closed** se Redis non raggiungibile (hot-zone files presenti). **Graceful warn** se Redis non raggiungibile (solo cold files).\n5. Output: required check status `hot-zone-enforcement` con `conclusion: success | failure | neutral`. Mai `skipped`.\n\n**Critical contracts**:\n\n- **C1**: workflow DEVE girare anche su PR che modificano `.github/workflows/` stessi (anti workflow-self-modification trap, Codex Q6 missing failure mode).\n- **C2**: required check `expected source app: github-actions` (anti CI spoofing, Codex Q4 missing failure mode).\n- **C3**: NO `paths:` filter — workflow gira SEMPRE, decide internamente cosa controllare (anti skip-success trap, Codex Q5 missing failure mode).\n- **C4**: `dismiss-stale-reviews` + `require-latest-push-approval` configurati su branch protection (anti approval stale race, Codex Q3 missing failure mode).\n- **C5**: per merge queue, check ricalcola su latest base SHA (anti lease stale race, Codex Q8 missing failure mode).\n\n**Empirical motivation**: cicatrix W34 documenta bypass HUSKY=0 in produzione. Senza C1-C5, ogni patch a workflow stesso o ogni PR con stale approval può bypassare.\n\n#### Componente 2.5 — CODEOWNERS self-modification protection (L2.A2, panel iter-2 amendment)\n\n**File**: aggiungere step al workflow `hot-zone-enforcement.yml` (Componente 2).\n\n**Logica**:\n\n```yaml\n- name: Detect CODEOWNERS self-modification\n if: contains(steps.changed-files.outputs.all_changed_files, '.github/CODEOWNERS')\n run: |\n echo \"🚨 CRITICAL: PR modifies .github/CODEOWNERS\"\n # Telegram alert INDIPENDENTE dal lease check (cf L2.A2)\n curl -s -X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage\" \\\n -d \"chat_id=${{ secrets.TELEGRAM_OWNER_CHAT_ID }}\" \\\n -d \"text=🚨 PR #${{ github.event.pull_request.number }} modifies CODEOWNERS by ${{ github.actor }}: ${{ github.event.pull_request.html_url }}\"\n # Forza failure se actor non è Antonello human\n if [ \"${{ github.actor }}\" != \"Balizero1987\" ]; then\n echo \"❌ CODEOWNERS modification by non-human actor — BLOCK\"\n exit 1\n fi\n```\n\n**Razionale (Gemini panel iter-2 Q9)**: bot ridotto a `write` può ancora auto-modificare `.github/CODEOWNERS` rimuovendo Antonello dai ruleset all'interno del proprio commit. Senza questo gate esplicito, il bot può self-elevate il proprio scope di review eliminando i path protetti. Alert Telegram fires anche se actor è human (audit cognitivo + paper trail).\n\n**Failure mode chiuso**: bot self-elevates by removing CODEOWNERS protection on hot-zone paths.\n\n#### Componente 3 — CODEOWNERS scope-ristretto (P1.1)\n\n**File**: `.github/CODEOWNERS` (ripristino con scope ridotto vs versione cancellata in `b8df2f996`).\n\n**Contenuto** (esempio):\n\n```\n# CODEOWNERS — hot-zone only (post-L5.2 ripristino)\n# Scope ristretto: review obbligatoria SOLO su questi path per PR di bot/Dependabot.\n\n# Migrations\napps/backend-rag/backend/db/migrations_v2/*.sql @Balizero1987\nscripts/lint_migration_numbers.py @Balizero1987\n\n# Auth / billing / pricing\napps/backend-rag/backend/services/auth/ @Balizero1987\napps/backend-rag/backend/services/billing/ @Balizero1987\napps/backend-rag/backend/services/pricing/ @Balizero1987\n\n# LaunchAgent wrappers\ninfra/launchagents/*.sh @Balizero1987\nscripts/lint_launchagents.sh @Balizero1987\n\n# CI/CD workflows + protection\n.github/workflows/*.yml @Balizero1987\n.github/CODEOWNERS @Balizero1987\n\n# Sentinel + DLQ\nscripts/nuzantara-sentinel.py @Balizero1987\nscripts/dlq_autopilot.py @Balizero1987\n```\n\n**Branch protection update**:\n\n- `required_approving_review_count: 1`\n- `require_code_owner_reviews: true`\n- `dismiss_stale_reviews: true`\n- `require_last_push_approval: true`\n- **Bypass actors**: solo Antonello (human) per break-glass + per scope NON-hot-zone (path filtering via CODEOWNERS).\n\n**Effetto**: PR umane su file cold = no review needed (single-dev throughput intatto). PR bot su hot-zone = review obbligatoria. PR umane su hot-zone = review obbligatoria solo se CODEOWNERS match (Antonello rivede se stesso = no-op, ma forza una pausa cognitiva).\n\n#### Componente 4 — Audit append-only + Telegram (P1.2)\n\n**Mechanism A — Server-side audit GitHub issues**:\n\n- GitHub Action `on: push` su main rileva commit signature anomala (no `Hook-Verified` trailer OR trailer forgeable → re-run server-side per validare).\n- Append issue body con event log structured: `{timestamp, actor, sha, files_changed, bypass_signal, source}`.\n- Issue dedicata `[AUDIT] Workflow discipline bypass log` evergreen, write-only.\n\n**Mechanism B — Telegram real-time SOLO hot-zone**:\n\n- Trigger: hot-zone files modified + bypass signal detected (server-side replay fail) + direct push main + workflow self-modification.\n- Payload: 1-line alert via `sentinel_lib/alerter.py` retry pattern (W55 cicatrix).\n- **NO Telegram per**: HUSKY=0/--no-verify su feature branch (alert fatigue, Gemini Q4).\n\n**Mechanism C — Digest settimanale (P2.2)**:\n\n- Cron lunedì 08:00 WITA: summary 7-day bypass events.\n- Format: bypass count by file zone + actor + intent (commit msg analysis).\n- Pipe via Telegram digest channel.\n\n**Canary obbligatorio (Codex missing failure mode #14 — alert blind spot)**:\n\n- Daily canary: Telegram bot health-check + GitHub issue write permission verify.\n- Se fail 2 ticks consecutivi → fallback alert via email Antonello.\n\n## Failure modes addressed (vs L5.1 + brief originale)\n\n| Failure mode | Brief originale | L5.2 |\n| ---------------------------- | ------------------------------ | --------------------------------------------------------- |\n| HUSKY=0 client bypass | Detect lato husky (IMPOSSIBLE) | Server-side replay required check ✅ |\n| --no-verify bypass | Non mappato | Server-side replay ✅ |\n| Direct push main | Non mappato | Branch protection + Telegram alert ✅ |\n| Bot+admin same actor merge | Non mappato | P0.1 privilege downgrade ✅ |\n| Workflow self-modification | Non mappato | C1 workflow gira anche su PR che lo modifica ✅ |\n| CI status spoofing | Non mappato | C2 expected source app ✅ |\n| Path filter skip-success | Non mappato | C3 no paths filter ✅ |\n| Approval stale race | Non mappato | C4 dismiss-stale + require-latest-push ✅ |\n| Lease stale/race merge time | Non mappato | C5 ricalcolo su latest base SHA ✅ |\n| Same trust domain LLM review | Opzione A originale | P0.1 bot non admin + non bot-approves-bot ✅ |\n| Redis fail-open | Non mappato | Fail-CLOSED su hot-zone ✅ |\n| `git commit --amend` bypass | Non mappato | Server-side replay (vede diff finale, non amend) ✅ |\n| Rename file critical | Non mappato | Hot-zone classifier su path + diff content ✅ |\n| HUSKY_SKIP_INSTALL / CI=true | Non mappato | Server-side replay ✅ |\n| Attestation forgeability | Hook-Verified trailer | Re-run server-side, no trust attestation ✅ |\n| Alert blind spot | Non mappato | Canary daily + email fallback ✅ |\n| Break-glass abuse | \"Admin can bypass\" implicit | TTL + label/reason + actor allowlist + immediate alert ✅ |\n\n## Open questions per panel iter-2\n\n**Q1**: P0.1 downgrade Claude bot — esiste già un account bot separato OR Claude opera con token Antonello? (Empirical audit needed).\n**Q2**: Required check `expected source app: github-actions` — GitHub Actions vs GitHub App custom — quale boundary contro CI spoofing è realmente strong?\n**Q3**: Fail-CLOSED su Redis — quanto tempo è tollerabile downtime Redis prima che PR si accumulino? Backup plan se Redis down 4h?\n**Q4**: CODEOWNERS auto-referenziale Antonello → Antonello — è cargo cult o \"pausa cognitiva\" reale?\n**Q5**: Merge queue (GitHub native) vs custom required check — quale gestisce meglio lease race al merge time?\n**Q6**: Trappole specifiche / failure mode che ho dimenticato di mappare oltre i 15 sopra?\n\n## Rollout plan (revised post panel iter-2 — L2.A1 priority elevation)\n\n**IMPORTANT (L2.A1 panel amendment)**: L5.2 Phase 1+2 deve raggiungere `enforce-mode` PRIMA o contemporaneamente al rollout di L5.1 enforce. L5.2 funziona da safety net assoluta durante la validazione empirica di L5.1 (sub-agent inheritance pendente). L5.1 iter-2 status `pending-iter-3` non blocca questo rollout.\n\n1. **Phase 0** (pre-rollout, 2-3h): empirical audit `gh api` per stato attuale admin/permissions/protection + Redis credentials in GitHub secrets + verify Telegram bot/owner chat IDs accessible da Actions.\n2. **Phase 1** (week 1, 4-6h): ship Componente 1 (bot downgrade). Verify zero merge da bot via `gh api` audit periodico. **Gate**: 0 merge bot-originate in 48h prima procedere Phase 2.\n3. **Phase 2** (week 2-3, 12-16h): ship Componente 2 (required hot-zone Action) + Componente 2.5 (CODEOWNERS self-mod protection) in `monitor-mode` (`continue-on-error: true`, output solo logs). Raccogliere 1 settimana data empirica su false positive / true positive. **Gate**: <5% false-positive rate, 0 missed hot-zone bypass.\n4. **Phase 3** (week 4, 8-12h): flip Componente 2 + 2.5 a `enforce-mode`. Ship Componente 3 (CODEOWNERS ristretto) + branch protection update (`required_approving_review_count=1` con `require_code_owner_reviews` + `dismiss_stale_reviews` + `require_last_push_approval`). **Gate**: empirical observation 48h post-flip senza regressione.\n5. **Phase 4** (week 5-6, 6-8h): ship Componente 4 (audit append-only GitHub issue + Telegram hot-zone). Canary live + digest cron settimanale.\n6. **Phase 5** (week 7+, observation): 30 giorni monitoring. Rollback automatico se >5% PR umane bloccate per false positive. Trigger spec L5.1 iter-3 design ONLY dopo L5.2 Phase 3 enforce-mode stable (panel-dictated priority order).\n\n**Kill switch globale**: `L5_2_SERVER_ENFORCEMENT_ENABLED=false` env nel workflow → workflow ritorna `neutral` invece di failure. Permette emergency disarm senza modificare branch protection.\n\n**Interaction effect L5.1**: L5.1 iter-2 può girare in monitor-mode (Phase 1 probe, no block) in PARALLELO con L5.2 Phase 1-3. Probe collects data per iter-3 design (sub-agent inheritance, tool coverage). L5.1 enforce-mode DOPO L5.2 Phase 3 stable.\n\n## Backward compat\n\n- Escape `L5_2_SERVER_ENFORCEMENT_ENABLED=false`.\n- Break-glass label `break-glass:emergency` su PR + commit message intent marker (`emergency:`, `hotfix:`, `cicatrix-fix:`).\n- Break-glass richiede: label + issue link + TTL 24h + actor allowlist (solo Antonello human) + immediate Telegram alert + audit issue append.\n\n## Estimated effort\n\n- Phase 0: 2-3h empirical audit\n- Phase 1: 4-6h (audit + downgrade + verification)\n- Phase 2-3: 12-16h (GitHub Action + tests + monitor + enforce flip)\n- Phase 4: 6-8h (audit issue + Telegram pipeline + canary)\n- Phase 5: ongoing observation\n- **Total**: ~30-40h spread over 6-8 settimane\n\n## Sister specs\n\n- **L5.1**: client-side enforcement (worktree isolation hook). Status: `draft`, 4 panel defects pending fix.\n- **L5.2** (this): server-side enforcement (CI required check + bot downgrade + audit).\n- **L5.3** (future): merge queue + monorepo branch protection at scale (if team grows beyond single-dev).\n"}}}}