Commit Graph
457 Commits
Author SHA1 Message Date
知微 c01c6cd266 merge: dev-spec v2.1 numbering fix 2026-07-20 21:21:27 +08:00
知微 2b79c1dc44 chore: sync 2026-07-20 21:21:25 +08:00
hmo 60dbb64f92 docs(dev-spec): v2.1 — fix section numbering + outdated refs
Zhiwei flagged: '十条红线' but she counted 14. Real issues found:
- duplicate section numbers: two '三、' (验证闭环 + 自检体系矩阵),
  two '四、' (开发流程 + 部署环境) -> renumbered 一~七 sequentially
- doc index said '含五条红线' (stale from v1) -> '含十条红线'
- F 小节 still used old Tier1/Tier2 framing -> aligned to L0/L1-L2
  with pointer to the L0-L4 matrix section
- version bump v2.0 -> v2.1
Minimal edit: no content changes beyond numbering/consistency.
2026-07-20 21:21:21 +08:00
知微 5ab63b5c9d merge: zhiwei reply doc 2026-07-20 20:57:24 +08:00
hmo 94883539de docs: reply to zhiwei on cron error status accuracy 2026-07-20 20:57:20 +08:00
知微 ad6a416ef4 merge: watchdog agent.log check 2026-07-20 20:56:05 +08:00
知微 c971d6bced chore: watchdog fix 2026-07-20 20:55:34 +08:00
hmo 10a37f10f9 fix(watchdog): gateway session check now uses agent.log scan, not live LLM ping
Gateway看门狗-知微 was erroring (exit -15): its check_session_health did a
live LLM ping with 25s timeout. Cold-start LLM latency is 20-100s so the
ping always timed out -> false '不健康' verdict -> false gateway restart
-> and each 10-min run burned 22k tokens.

Now uses xmpp_logger._scan_agent_log (zero cost, reads real call results):
- ok if last real call succeeded
- unhealthy only if last call explicitly failed
- idle (no recent calls) counts as healthy
Verified: watchdog job now status=ok.

Also: triggered all 6 weekend 'Blocked' jobs via hermes cron run — all
now status=ok, proving the hardlink fix holds.
2026-07-20 20:55:30 +08:00
知微 e5b0335f93 merge: zhiwei briefing doc 2026-07-20 20:37:21 +08:00
知微 dae60bb92f chore: sync before briefing merge 2026-07-20 20:37:20 +08:00
hmo c42769f293 docs: zhiwei briefing on 2026-07-20 system changes 2026-07-20 20:36:50 +08:00
知微 86a93d30e6 merge: deploy-time auto hardlink repair 2026-07-20 20:27:17 +08:00
知微 9f7198dc9c chore: deploy pipeline auto-sync 2026-07-20 20:27:15 +08:00
hmo 9a359f49bd feat(deploy): automatic hardlink repair built into deployment pipeline
User insight: hardlink breakage only happens at deploy time (scp file
replacement / git checkout-merge), so detection must be welded INTO the
deploy pipeline, not left to daily audit.

Three automatic layers, no reliance on discipline:
1. systemd path watcher (profile-scripts-sync.path): watches
   deploy/profile-scripts/ directory, auto-fires sync_profile_scripts.sh
   on any change. Verified: fires within 4s of file replacement, logs to
   gateway/logs/link_sync.log (runs as hmo user)
2. git hooks (.git/hooks/post-merge + post-checkout on 246 repo):
   auto re-link after git operations
3. Manual fallback: sync_profile_scripts.sh (now self-logging)

dev-spec red line #6 updated: SSOT rule now documents the three layers
and states breakage only happens at deploy time.
2026-07-20 20:27:11 +08:00
知微 e69109fde9 merge: L0-L4 self-check architecture 2026-07-20 19:40:24 +08:00
知微 135bfced5a chore: deployed L0-L4 self-check system 2026-07-20 19:40:23 +08:00
hmo 08eef1e181 feat(self-check): L0-L4 layered self-check architecture with LLM auto-repair
User directive: daily not weekly; clear responsibilities per layer with no
overlap; functional criteria (does the function WORK) not process liveness;
problems get FIXED via LLM with file-and-report discipline (act first,
report after); plus a meta-layer watching the watchers; deeply integrated
into F健康.

Architecture (responsibility matrix in dev-spec.md):
- L0 agents_health_check (5min): port/HTTP/DB liveness + auto_heal executor
- L1 functional_health_check (15min trading): per-module FUNCTIONAL
  criteria — output freshness/validity per REGISTRY (live_prices/market_
  snapshots/mtf_cache/macro_context/bot/LLM/cron engine), not process alive
- L2 system_hygiene_audit (daily 08:20, was weekly): divergence/hardlink/
  zombie/orphan/dead-cron/db-freshness
- L3 self_repair (30min): reads L1/L2 failures -> LLM diagnoses -> executes
  WHITELISTED repair actions directly (rerun_script/restart_service/
  sync_links/switch_llm_key/none) -> repair_log.jsonl + XMPP report.
  Max 2 repairs/module/day anti-loop. LLM unavailable -> rule fallback.
- L4 meta_watchdog (hourly): checks L0-L3 output freshness + L3 cron
  registration + XMPP bridge; direct XMPP alert as last resort

Retired (overlap): Cron监护-高频 (cron_watchdog -> L3), 全局cron健康监控
(cron_health_monitor -> L1).

Dashboard: mofin_health.py now emits self_check section (functional/meta/
hygiene/recent_repairs); mofin_health.html new '🩺 自检体系' tab rendering
L4 layers, L1 module checks, L2 issues, L3 repair history.

E2E verified: stopped xmpp bot -> L1 flagged fail -> systemd recovered ->
L3 LLM correctly diagnosed 'none needed' and logged; rerun_script whitelist
path executes real scripts successfully; meta_watchdog all-green after fix.
2026-07-20 19:39:58 +08:00
知微 c545a82023 merge: hygiene enforcement + full cleanup 2026-07-20 19:05:08 +08:00
知微 54c48dc5d7 chore: deployed cleanup + hygiene system 2026-07-20 19:05:03 +08:00
hmo 4f83ee8a01 feat(hygiene): anti-redundancy enforcement — spec rules + weekly audit
Root cause analysis of the 2026-07-20 redundancy incident:
1. No single-source-of-truth rule -> same file legitimately lived in 4+
   locations, diverging silently
2. Relative path resolution (Path(__file__).parent/'data') -> each
   hardlinked copy of mofin_db.py pointed to a DIFFERENT database
3. 'Backup habit' left .bak/legacy files in production dirs, which
   monitoring then scanned and reported as false alarms
4. Half-done migrations: DB tables created but old JSON writers/readers
   stayed (price_events), old files stayed
5. Dead modules never got buried: xiaoguo 'dead' but bot ran 8 days
   as root eating 2.5GB
6. Monitoring checked 'does it exist' not 'is it alive' -> stale file
   mtime reported as 'pipeline stalled 14 days' (false alarm)
7. No 'system hygiene' as a check category at all

Prevention implemented:
- dev-spec.md v2.0: 五条红线 -> 十条红线
  #6 single source of truth (hardlink only, no independent copies)
  #7 absolute data paths only (no __file__-relative data resolution)
  #8 no backups/legacy in production data dirs (archive immediately)
  #9 dead module burial checklist (6 mandatory steps)
  #10 monitor liveness (DB table freshness) not existence
- File Location Constitution: canonical location per content type
- NEW system_hygiene_audit.py: weekly Monday 07:30 cron checking
  diverged copies / broken hardlinks / zombie processes / orphan data
  files / dead cron scripts / DB freshness -> hygiene_report.json + XMPP
- specs/hygiene.json: module spec per red line #1
- Verified: audit found 5 real issues on first run, all fixed, re-run clean
2026-07-20 19:04:05 +08:00
知微 7f3ff66be4 merge: retire price_events.json 2026-07-20 18:10:40 +08:00
知微 4383c384be chore: deployed JSON retirement 2026-07-20 18:10:35 +08:00
hmo d5b8bec897 refactor: retire price_events.json completely — DB is the only store
User directive: no JSON, retire it fully, fix all related code.

Changes:
- price_monitor.py: record_event writes DB only; removed EVENTS_PATH/
  load_events/save_events entirely
- strategy_feedback.py: price events read from DB only (removed JSON fallback)
- system_health_check.py: removed price_events.json from file-check list,
  DB-only event stats (was showing 0/0 due to wrong-DB resolution)
- mo_config.py: removed dead price_events_path property (no callers)
- mofin_health.py: price_events freshness reads DB table (authoritative now)
- mofin_db.py: DATA_DIR/DB_PATH now ABSOLUTE (/home/hmo/MoFin/data) —
  was relative __file__.parent, so each hardlinked copy of mofin_db.py
  resolved to a DIFFERENT database (canonical vs web-dashboard vs
  profile-local third DB with 0 rows of everything except market_snapshots).
  This fragmentation was the real cause of health checks reading empty tables.
- Unified all 4 mofin_db copies (root/scripts/deploy/profile) via hardlink
- price_events.json archived to trashbox (fully backfilled: 6353 rows in DB)

Verified:
- record_event lands in DB only, JSON not recreated
- system_health_check: 历史事件 6353 / 今日事件 2965 (was 0/0)
- strategy_feedback + price_monitor full runs clean
2026-07-20 18:10:05 +08:00
知微 73520464b6 merge: price_events DB unification 2026-07-20 17:48:27 +08:00
知微 d28a32e244 chore: deployed price_events dual-write 2026-07-20 17:47:29 +08:00
hmo efdfaf956a fix(price_events): unify event storage to DB (dual-write + backfill)
User caught the inconsistency: system claims DB-first but price events
only went to price_events.json, leaving DB table stale since Jul 6.

Root cause chain found:
- record_event() only wrote JSON, never called mofin_db.write_price_event
- price_events.code has FK -> stocks(code); events for unregistered stocks
  (new candidates, HK) silently failed INSERT and were lost to DB
- mofin_db.write_price_event swallows errors (returns False silently)

Fixes:
- record_event now dual-writes: DB (authoritative) + JSON (compat for
  legacy readers mo_config/strategy_feedback/system_health_check)
- auto-registers unknown codes into stocks table before event insert
- one-time backfill: 4064 JSON events -> DB (total 6353 rows, last=today)
- verified: record_event TEST99 lands in both DB and JSON
2026-07-20 17:46:57 +08:00
hmo bbc4ebf93d chore: investigation + notification scripts 2026-07-20 17:33:39 +08:00
知微 c02caeb1da merge: pipeline fixes + false-alarm cleanup 2026-07-20 17:32:13 +08:00
知微 c31736a38d chore: deployed pipeline fixes 2026-07-20 17:31:48 +08:00
知微 5d3b8e6fdd chore: capture deployed state 2026-07-20 17:30:43 +08:00
hmo 17305bed0b fix(pipelines): clear today's real cron errors + kill monitoring false alarms
Real errors fixed (all verified by manual run):
- price_monitor.py: shares None -> TypeError at L584 (now completes 3m7s,
  full 39-stock reassess + zone triggers + Dad push)
- market_insight.py: net_inflow None -> TypeError at L142 (now 0.3s, 5 insights)
- promote_candidates.py: add busy_timeout=30s (DB lock under concurrent writes)
- premarket_full_review.py: 12-dim analysis now detached background launch
  (was doomed by cron 120s script timeout no matter what)

Systemic:
- HERMES_CRON_SCRIPT_TIMEOUT=600 drop-in for both gateway services
  (fixes mofin_health SIGTERM, market_watch timeout, memory_guardian timeout)
- sync_profile_scripts.sh: re-hardlink deploy->profile scripts after every
  deploy (scp replaces files = new inode = broken hardlink = cron silently
  runs stale code; this caused promote to keep failing after my first fix)

Monitoring false-alarm fixes (the '花瓶' problem):
- mofin_health.py: legacy JSONs that migrated to DB (multi_tf_cache/
  macro_context/market/live_prices/price_history/macro_risk_state) no longer
  warn 'no readers'; marked as migrated
- NEW db_freshness section: real pipeline health from DB tables
  (mtf_cache 0.4h / macro_context_log 2h / market_snapshots 2h /
  live_prices 0.4h / price_events.json 0.4h — ALL HEALTHY)
- price_events freshness reads live JSON store (DB table is legacy)
- market.json placeholder created (13+ scripts have fallback paths)

Investigation notes: wiki-self-growth 03:04 key1 429 predates full key6
activation on default gateway; current 8642 verified on key6 and working.
Weekend 'Blocked' jobs verified fixed (vacuum_state_db passes).
2026-07-20 17:30:15 +08:00
知微 4dcfee8140 merge: session work — 12-dim pipeline + all session fixes 2026-07-20 08:52:42 +08:00
知微 f946728b7d chore: latest batch_reassess/premarket (HK fix, 3600s timeout) 2026-07-20 08:51:31 +08:00
知微 0190c02064 chore: capture live morning state before session merge 2026-07-20 08:50:29 +08:00
hmo a40b97f5ca feat(analysis): systematic daily 12-dim LLM analysis for holdings+watchlist
Gap (reported by user via zhiwei): premarket full review updated technical
params but full_analysis (12-dim LLM matrix) was empty for new holdings
and stale for old ones — batch_reassess existed but was never wired into
the daily pipeline and only covered watchlist.

System fix:
- premarket_full_review.py: new Step 1.5 runs batch_reassess --type
  holding --today every trading day 08:10 (force-refresh today's analysis,
  timeout 3600s, result in summary.json)
- batch_reassess.py:
  - coverage: --type holding|watchlist|all (was watchlist-only)
  - staleness: analysis >20h stale gets refreshed (was: skip if any
    analysis exists = forever stale)
  - --today flag: force re-analyze if not reassessed since 04:00 today
  - cash/total read live from portfolio_summary (was hardcoded 321271/
    952879 from weeks ago)
  - HK stock prefix fix (5-digit codes -> hk, was sending sz00700)
- watchlist_12d_backfill.py: wrapper for hermes cron (no args support)
- cron job '批量补全九维分析-一次性' -> '自选12维分析补全-每日午间'
  (daily 12:30 weekdays, covers 109 watchlist stocks missing analysis)

Verified: 300308 got 1920-char 12-dim analysis written to DB at 08:41,
signal=观望, stop/take-profit updated.
2026-07-20 08:48:08 +08:00
hmo 0ab542678b chore: untrack runtime logs/temp and stray root index.html from 246 merge 2026-07-20 08:30:06 +08:00
hmo 4c669943bd merge: integrate 246-side commits (bot reconnect fix + docs) with session work 2026-07-20 08:28:16 +08:00
hmo 115292bb96 fix(scripts): promote UNIQUE crash + candidate_filter DB lock (morning readiness)
Pre-open error sweep (all verified by manual run):
- promote_candidates.py: INSERT OR IGNORE + only newly-added stocks count
  toward promotion/XMPP (was crashing on first duplicate, never finishing;
  now completes 40s, promoted 74 with correct skip marking)
- candidate_filter.py: PRAGMA busy_timeout=30s (was dying on transient
  'database is locked' under concurrent cron writes; now completes 9.6s)
- price_monitor.py: verified completes 1m55s (< 120s cron timeout) with
  working LLM reassess via key6
- macro_context_collector.py / divergence_detector.py: previously
  'Blocked' by symlink check, now run fine (8.7s / 1m55s)
- memory_guardian.py: completes 1m55s with key6
- preflight sync diff: historical, files now identical

Production proof: 大脑任务执行 (was 429 every 10min) now status=ok at 01:21
2026-07-20 01:34:37 +08:00
hmo 57377e9dd3 feat(auto_heal): multi-profile key switching + fix two error classes
Error investigation (from restored monitoring) found 2 root causes:

1. 'Blocked: script path resolves outside scripts dir' (5+ cron jobs):
   Jul 17 symlink refactor replaced real scripts with symlinks; the hermes
   cron scheduler's security check (Path.resolve + relative_to) rejects
   symlink escape. ALL no_agent script jobs blocked since Jul 17 23:12.
   FIX: converted 102 symlinks to hardlinks (same inode, resolve() stays
   inside scripts_dir, single-source still works). Permanent structural fix.

2. HTTP 429 on default profile (知识研究/梦境循环/wiki-self-growth/
   evolution-pulse/大脑任务执行): default gateway used ocg-key1 (weekly
   100%). FIX: switched default profile to ocg-key6 + added missing
   provider block. LLM verified working (3.1s).

auto_heal extended to actually cover these automatically next time:
- PROFILES registry: zhiwei (8643, system svc) + default (8642, user svc)
- current_provider/switch_key/_scan_agent_log parameterized by profile
- health() now reports llm_provider_default (agent.log scan)
- auto_heal: per-profile 429 detection -> best_key -> switch_key(profile)
- _ensure_provider_block: injects missing provider credentials from
  zhiwei config (single source of truth) into target config
2026-07-20 01:12:38 +08:00
hmo a05c118cbc feat(dashboard): restore lost monitoring + add 开发原则 tab (AgentsMeeting parity)
Two regressions fixed:

1. RESTORED: original health monitoring (功能树/全部Cron/数据实体/数据流)
   was an iframe to /mofin_health.html — refactor replaced it with a
   minimal services-only panel and lost all of it. mofin_health.json
   (86KB, fresh) was still being generated the whole time.
   - 健康 tab: XMPP panel (native) + restored iframe below

2. ADDED: 开发原则 parent tab replicating AgentsMeeting structure:
   - G 规范: /api/spec renders docs/dev-spec.md + git history
   - K 测试: /api/tests renders agents_health_check report as pass/fail
   - F 健康: iframe to /mofin_health.html + link to 健康 tab
   - H 需求: /api/prd (placeholder — prd.md not yet created)
   - mdRender() ported from AgentsMeeting dashboard
2026-07-20 00:40:32 +08:00
hmo 2de527b883 fix(health): drop live LLM ping — scan agent.log instead (38s -> 0.1s)
Every /api/xmpp/health fetch ran a real LLM call (22k token system prompt
each). Dashboard refreshes every 10s -> thousands of paid LLM calls/day,
plus 38s latency hanging the health tab on '加载中...'.

LLM health is now derived from the gateway's own agent.log (zero cost,
more accurate than synthetic ping — real traffic results):
- last 'API call #N latency=Xs' -> ok
- last 'API call failed ... HTTP 429...' -> error with summary
- health() runtime 38s -> 0.1s; endpoint 38s -> 0.097s
2026-07-20 00:21:49 +08:00
hmo 058c42ce27 feat(health): recent XMPP conversation log panel + stale error fix
User requirement: health tab should show recent XMPP conversations.
- bot hooks log_xmpp on inbound (on_msg) and outbound (_deliver_loop)
  so real chats land in xmpp_messages.jsonl (was: only cron/scanner)
- index.html health tab: new '最近对话' panel (last 10 msgs, dir arrow,
  preview, status, time); refreshHealth updates it incrementally
- last_error now shows age and resolved state: once a successful
  outbound happens after an error, it's shown gray as '已恢复'
  instead of alarming red forever; unresolved errors still red
- health() status no longer degraded by errors that were later
  resolved by successful outbound
2026-07-20 00:07:28 +08:00
hmo cb334ddd54 fix(bot+auto_heal): stop auto_heal from killing bot during slow LLM calls
Root cause of 'no response': auto_heal restart_zhiwei_bot fired whenever
inbound>0 + outbound=0 + errors>0 — which is exactly the normal state while
the bot waits for a slow LLM call (agent tool-use turns take 1-10 min).
Restart killed the in-flight LLM call -> user never got reply -> next cron
cycle saw same state -> restart again. Death loop (fired 22:30,22:35,22:40,
22:45,22:50,23:35).

Fixes:
- CALL_HERMES_TIMEOUT 180s -> 600s (agent tool calls need minutes)
- health(): parse last_inbound/outbound timestamps from journal
- auto_heal: restart bot ONLY if last inbound >600s old with no outbound
  since (truly stuck), plus 600s bot-restart cooldown
- slow-but-normal state now logs bot_busy_not_stuck instead of killing
2026-07-19 23:43:28 +08:00
hmo bb1529909b feat(bot): screenshot OCR pipeline + ack delay 120s
D fix: screenshots were silently dropped (empty body + OOB url).
- register xep_0066, capture msg['oob']['url'] when body empty
- also handle body-as-URL messages (some clients put URL in body)
- download from upload.yoin.fun, OCR via SenseNova (sensenova-6.7-flash-lite)
- inject OCR text as context into LLM call
- config at /home/hmo/.config/mofin/ocr_config.json (outside repo)
- replaces dead node122 GLM-OCR path (host unreachable)

A+B fix: ACK_DELAY 15s -> 120s. 15s fired on every normal LLM
cold-start (20-100s), now only signals genuine hangs.
2026-07-19 23:23:46 +08:00
hmo e0f47f9349 fix(xmpp_monitor): exclude key7(kimi) from auto-heal switch candidates
zhiwei must stay on ocg-key6 (deepseek-v4-flash) per user decision.
key7 is kimi — different model provider, incompatible model name.
If best_key returns key7, auto_heal logs skip_switch instead of switching.
2026-07-19 22:09:15 +08:00
知微 b5dade3b65 docs: 记录 XMPP bot 递归重连修复到 CHANGELOG 和 knowledge-log 2026-07-19 21:00:54 +08:00
知微 e366a6359a fix(bot): 防递归重连 guard - 防止 on_disconnect → reconnect → disconnect 无限递归导致栈溢出 2026-07-19 20:59:18 +08:00
hmo 9509b80d8b feat(xmpp_monitor): dynamic key switching + coalesce auto-heal
- xmpp_logger.py:
  - Add switch_key(key_id): switch Hermes model.provider via sed + systemctl restart
  - Add current_provider() reading model: block correctly (not just first '  provider:' line)
  - Add KEY_TO_PROVIDER mapping (AgentsMeeting key_id -> Hermes provider name)
  - Add RESTART_COOLDOWN_FILE/SEC = 180s to prevent restart loops
  - auto_heal(): detect HTTP 429 / Weekly usage limit -> call best_key() -> switch_key()
  - auto_heal(): detect timeout/error -> async systemctl restart (Popen, not blocking)
  - _verify_llm(): bump timeout 25s -> 90s (cold-start gateway takes 20-40s)
  - health(): urlopen timeout 8s -> 90s (match verify window)
  - Use sudo NOPASSWD (hmo ALL=(ALL) NOPASSWD: ALL already configured)

- agents_health_check.py:
  - Replace inline pkill+Popen restart logic (caused multiple instances) with systemctl
  - Add RESTART_COOLDOWN_FILE state to skip restart within 3 min of last
  - Call xmpp_logger.auto_heal() at end of every cron cycle
  - Both inline restart and auto_heal restarts share cooldown file

- scripts/key_status.py: Reports weekly/monthly/rolling status of all 6 OCG keys
- scripts/test_llm.py: 90s timeout test (was 15s, gateway cold-start >= 30s)
- scripts/test_production.py: smoke test on /home/hmo/MoFin/ (hardlinked to web-dashboard)

Fixes:
- Old assumption 'Cloudflare blocks Python User-Agent' was WRONG.
  True cause was HTTP 429 Weekly usage limit on key5 (12hr reset window).
  Hermes silently ignored providers.X.headers config keys; only model.default_headers works.
  Config already has model.default_headers: User-Agent: curl/8.5.0 (defense in depth).
- Multiple gateway instances were caused by 3 competing systemd units
  (hermes-gateway@.service template + hermes-gateway-zhiwei.service named).
  Masked the template unit @position-analyst and @zhiwei so only the named one wins.
2026-07-19 20:12:47 +08:00
hmo 6d66cc6cf2 feat: auto-heal — gateway down auto-restart via cron every 5min 2026-07-19 18:22:45 +08:00
hmo 04308facde feat: XMPP health auto-collection every 5min via cron pipeline 2026-07-19 15:17:08 +08:00