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.
1 line
292 KiB
JSON
1 line
292 KiB
JSON
{"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"}}}} |