klava-personal
Use Vadim's Klava context stack, Obsidian vault, vadimgest, and autonomy rules.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Use Vadim's Klava context stack, Obsidian vault, vadimgest, and autonomy rules.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | klava-personal |
| description | Use Vadim's Klava context stack, Obsidian vault, vadimgest, and autonomy rules. |
Use this skill whenever a request depends on Vadim's personal context, active projects, deals, relationships, tasks, Telegram routing, Klava cron migration, or source-backed memory.
Primary map: /srv/codex-klava/data/hermes/knowledge/vadim-info-map.md.
scripts/klava-recall "query" for the named person, company, deal, project, prior discussion, email, or message. This must be the first retrieval tool call. Do not use file search, repo grep, session memory, daily memory, the Working Set, or Obsidian first./srv/codex-klava/data/hermes/knowledge/vadim-info-map.md only if source routing or a fallback is needed./home/codex/.klava/memory/, then the matching Obsidian note, to reconcile the raw evidence with current State + Log./srv/codex-klava/data/hermes/SOUL.md when operating rules or autonomy boundaries are relevant.cd /srv/codex-klava/repos/claude
scripts/klava-recall "query"
scripts/klava-recall "exact phrase" --sources telegram,signal,gmail,bee --full
scripts/klava-recall "query" -s telegram
The wrapper uses bakeneko first from the Mac and falls back to the local index only when the server is unavailable. Set KLAVA_RECALL_LOCAL_ONLY=1 only for explicit offline diagnostics.
When a Mac-side recall result points to /srv/codex-klava/...#L<N>, that path is on bakeneko. Open it directly with ssh bakeneko "sed -n '<N>p' /srv/codex-klava/..."; do not try the server path on the Mac first.
For heartbeat-style intake:
SOURCES=$(find -L /srv/codex-klava/data/vadimgest/sources -maxdepth 1 -name '*.jsonl' ! -name 'browser.jsonl' ! -name 'xnews.jsonl' -printf '%f\n' | sed 's/\.jsonl$//' | sort | paste -sd, -)
env -u PYTHONPATH VADIMGEST_CONFIG=/srv/codex-klava/data/vadimgest/config.yaml VADIMGEST_DATA_DIR=/srv/codex-klava/data/vadimgest /srv/codex-klava/venvs/vadimgest/bin/vadimgest read --consumer intake --sources "$SOURCES" -f md --context 3 --limit 200
Heartbeat excludes only browser and xnews. All other vadimgest edge/local sources (signal, whatsapp, dayflow, imessage, hlopya, etc.) must be triaged. If a source prints ... +N more, do not commit that source to the end; process it through a source-specific/backfill batch first.
Use this order unless the task says otherwise:
Raw-source priority for recall is Telegram, Signal, Gmail, Bee, then other relevant sources. Absence from Obsidian is not evidence of absence. Scoring decides what is promoted into State + Log; all raw records remain in the append-only lake.
When user asks whether a day/session got processed and routed properly, run this 5-point check and only then report:
vadimgest, MyBrain, daily memory, Hermes cron state.vadimgest read --consumer intake --limit 5 and check whether it returns "No new data since last checkpoint" (cursor current = loop has been processing) OR a massive backlog going months back (cursor stale = loop has NOT processed, regardless of cron last_status). This is the ground truth.stats: confirm totals changed and source lines are increasing.checkpoints/<consumer>.json: confirm intake/consumer cursors moved forward and mtime is recent.state.json: confirm total_records and per-source last_ts are coherent.read --consumer intake -f md --raw --limit ...: confirm sample rows appear for expected chats/sources.last_status: ok alone. A job can exit 0 without ever advancing the intake cursor. The consumer cursor position is the only reliable signal.$SOURCES (add ! -name 'whatsapp.jsonl'). Leave for backfill 0649b859d63d. 📋 references/whatsapp-backlog-hard-exclude.md. Claude may be Feb 2026 Клавдия backlog — fast-path. Signal/Hlopya accumulate — triage from file end; Hlopya content always empty, use notes/transcript. 📋 references/per-source-delta-audit-patterns.md.[SILENT] does NOT mean cursor is current. Always re-run vadimgest read --consumer intake ... --limit 200 in the current session.📋 references/vadimgest-embedding-scope.md — embedded source list (hlopya/granola/signal/bee added Jun 27), hybrid search usage, and the Klava infra session triage rule (infra-only sessions = daily memory note, no Obsidian writes).
📋 references/per-source-delta-audit-patterns.md — fast delta check script (run FIRST when >200 records); Codex historical backlog pattern; Signal dedup by period_end; WhatsApp exclusion. Also: calendar checkpoint lag (59K phantom records), mid-session new records, meeting note content field.
📋 references/vadimgest-checkpoint-boundary-false-positive.md — "N new records" when checkpoint.line == file total lines = boundary artifact, not genuinely new. Verify total_lines - checkpoint_line == 0.
last_status..md files in key areas (People, Inbox, Vox Lab, Klava/Ops, Life).src: or equivalent provenance fields after significant contact/information changes.output/<job_id>/ is being checked, always use the newest file by mtime; stale/older truncated files can persist and should not be treated as current run state./home/codex/.klava/memory/YYYY-MM-DD.md) has the day’s summary block(s)./srv/codex-klava/data/hermes/cron/jobs.json for enabled jobs./srv/codex-klava/data/hermes/cron/output/<job_id>/ for successful run markers.skills.write_approval / memory.write_approval and any pending payloads under /pending/memory and /pending/skills.pending/skills is empty but data exists in backup directories, treat that as a queue location migration event, not “nothing to do.”/pending/memory, evaluate every payload in this run and mark each one as apply, reject, or retire. Unresolved payloads must be listed explicitly in the heartbeat report.HEARTBEAT_OK only when both payload queues are empty (or already fully resolved) and there is no new intake work to process.references/heartbeats/pending-governance-runbook.md for detailed examples.references/codex-infra-ops-session-triage.md — Codex infra/ops sessions ("Back up sessions", "Inspect Hetzner disks", "Grant Codex full access to bakeneko") — recognition, grouping, and note-home rules.
📋 references/codex-session-triage-patterns.md — session-specific heartbeat CLI/artifact behavior, consult references/hermes-heartbeat-cli-quirks.md before concluding PASS/PARTIAL. Also covers: parallel audit dispatch, second wave, scheduling loop closure, ops defer-to-next-cycle, server-side active session triage (ID format codex_<uuid>_<thread>_<turn> vs Mac-local session_MacBook-Pro-Vadim.local_*), user prompt extraction from rollout JSONL.
📋 references/codex-session-blocked-recovery-patterns.md — stalled/no-output sessions: English-coach gate on pitch drafts, Tailscale SSH auth-link resolution for DF machines, agent_turn field layout, checkpoint semantics, "Чат осуждения" group triage.Response truncated/output length limit/No fresh heartbeat artifact, treat the cycle as Partial, then do a bounded re-run with compact report surface and re-check artifact freshness rather than assuming no-op.references/heartbeats/heartbeat-production-output-reduction-runbook.md.hermes cron run ff50067ec588 --accept-hookshermes cron list --all to confirm the job is still enabled/scheduled and inspect Last run + Next run.jobs.json entry for ff50067ec588 (enabled, scheduled, state)/srv/codex-klava/data/hermes/cron/output/ff50067ec588/*.md mtime and status lineFAILEDResponse truncated / output length limitreferences/hermes-cron-output-truncation-runbook.md.No new data since last checkpoint. then HEARTBEAT_OK is allowed with no side-effect writes.On codex-klava, direct live vadimgest sync is enabled for Telegram, Obsidian, Codex, and Slack. Other sources may be indexed but should be treated as historical or edge-synced unless a health check proves they are fresh.
/home/codex/.klava/memory/YYYY-MM-DD.md.When you read a MyBrain note using terminal("cat '/path/to/note.md'") on codex-klava, the read_file tool (or the terminal output format) prepends line numbers to every line in N|content format:
1|---
2|handle: ""
3|email: "a.a.pokras@gmail.com"
...
78|- **misha_bilokur_visit:** Sasha's high-school friend...
79|
80|## Log
This causes str.replace() to silently fail — your old_string targets clean text, but the file on disk starts every line with N|. The ## Log heading appears as 80|## Log, so content.find('## Log') returns -1.
Fix: strip line numbers before any string operations.
import re
raw = open('/srv/codex-klava/data/MyBrain/People/Person.md', 'r').read()
lines = raw.split('\n')
clean_lines = []
for line in lines:
m = re.match(r'^\d+\|(.*)$', line)
clean_lines.append(m.group(1) if m else line)
content = '\n'.join(clean_lines)
# Now str.replace() works correctly on `content`
content = content.replace(old_text, new_text, 1)
from hermes_tools import write_file
write_file('/srv/codex-klava/data/MyBrain/People/Person.md', content)
Why this happens: read_file adds line numbers to its output format. When you use terminal("cat ...") the raw terminal output passes through the same display layer. open() on the actual file path gives you the raw content — use open() for mutation work, not terminal("cat ...").
Verification: after stripping, run content.find('## Log') and content.find('## State') — both should return non-negative positions. If either returns -1 on a note that has those sections, the stripping step failed.
Session example (2026-06-21): Sasha Pokras note — content.find('## Log') returned -1 because terminal("cat ...") output had 80|## Log. Stripping line numbers resolved it.
The patch tool rejects old_string / new_string that contain \" (backslash-escaped double quotes) with error:
Escape-drift detected: old_string and new_string contain the literal sequence '\\\"' but the matched region of the file does not.
This fires when the file has plain " (unescaped) but the tool call serializes them as \". Fix: use execute_code with str.replace() on the raw file content instead. Additional Unicode/em-dash and scope pitfalls: references/obsidian-str-replace-pitfalls.md. Duplicate facts-touched from multiple replace() calls on same anchor: references/str-replace-pitfalls-addendum.md.
from hermes_tools import terminal, write_file
r = terminal("cat '/path/to/note.md'")
content = r['output']
new_content = content.replace(old_string_exact, new_string_exact, 1)
write_file('/path/to/note.md', new_content)
The str.replace() approach bypasses the serialization layer entirely. Use it any time the target text contains double-quoted strings (common in State bullets with Vadim's direct quotes).
Companion pitfall: the patch tool also warns "file was modified by sibling subagent" when you write a file you never called read_file on.
📋 references/venv-python-break-recovery.md — when execute_code fails with No such file or directory: venv/bin/python, Hermes venv symlinks are broken. Switch to terminal() immediately and fix symlinks.
Each execute_code block is a fresh Python interpreter. Variables, imports, and computed values from block N are completely gone in block N+1. Always do read + transform + write in ONE block. Splitting them guarantees NameError. Sessions that got this wrong: 2026-06-20, 2026-06-29 (Natuska note — split write_file to second block → crash → wasted one full retry).
terminal("cat large_file.md") silently truncates its output at the tool's ~50K byte cap. If you do content = result['output'] and then write_file(path, content), you write a truncated file with no error.
Failure pattern:
# WRONG — truncates silently if file > 50KB:
result = terminal("cat '/path/to/large_file.md'")
content = result['output'] # may be only 47K chars of a 97K file
write_file('/path/to/large_file.md', content) # DESTROYS ~50K chars
Fix: use Python's open() directly inside execute_code:
from hermes_tools import write_file
content = open('/path/to/large_file.md', 'r').read() # always reads full file
new_content = content.replace(old, new, 1)
write_file('/path/to/large_file.md', new_content)
Why it's dangerous: files with Cyrillic text output ~50K bytes ≈ 47K chars, which looks complete. The tool returns no error or truncation marker. Files >50KB that contain mostly ASCII will truncate at a clean-looking word boundary.
Incident: klava-personal SKILL.md (97K chars) was truncated to 47K chars on 2026-06-27. Skill lost 29 sections covering critical heartbeat triage rules. Had to restore from session context.
Inside execute_code, the hermes_tools return different keys depending on the tool:
# read_file → result['content'] (NOT result['output'])
result = read_file("/path/to/file.md")
text = result['content'] # correct
# text = result['output'] # KeyError — wrong key
# terminal → result['output']
result = terminal("some command")
text = result['output'] # correct
Additionally, read_file returns a dedup response when the file was already read earlier in the same session. When deduped, the dict has status: 'unchanged' and no content key at all — accessing result['content'] raises KeyError. Recovery: fall back to terminal('cat /path/to/file') and use result['output'].
result = read_file("/home/codex/.klava/memory/2026-06-20.md")
if result.get('status') == 'unchanged':
# read_file deduped — fall back to terminal
result = terminal('cat /home/codex/.klava/memory/2026-06-20.md')
content = result['output']
else:
content = result['content']
This bites during daily memory appends mid-session: first read_file call caches the file, the append call finds status: unchanged, then result['content'] raises KeyError.
Do NOT use heredoc or echo >> file in terminal() for multi-line appends — the shell expansion and quoting reliably break on this server. Use execute_code with read_file + write_file instead:
from hermes_tools import read_file, write_file
existing = read_file("/home/codex/.klava/memory/2026-06-20.md")
new_content = existing['content'] + "\n## New Block\n...\n"
write_file("/home/codex/.klava/memory/2026-06-20.md", new_content)
This is the only reliable pattern for daily memory appends on codex-klava.
Alternative: open() directly in execute_code. Within execute_code, Python's built-in open() also works safely and avoids the read_file dedup issue entirely:
from hermes_tools import write_file
existing = open('/home/codex/.klava/memory/2026-06-20.md', 'r').read()
write_file('/home/codex/.klava/memory/2026-06-20.md', existing + "\n## New Block\n...\n")
Use open() when you're mid-session and read_file is likely to return status: unchanged (i.e., you already read the file earlier this session). open() always reads from disk — no caching.
Creating a new day's file (first heartbeat of the day). When the heartbeat runs early and the target date file doesn't exist yet, open() will raise FileNotFoundError. Use try/except to seed the file:
from hermes_tools import write_file
today = "2026-06-22"
mem_path = f"/home/codex/.klava/memory/{today}.md"
try:
existing = open(mem_path, 'r').read()
except FileNotFoundError:
existing = f"# {today}\n"
write_file(mem_path, existing + "\n## Heartbeat ...\n")
This handles both the "appending to existing file" and "creating a new day's file" cases in one block.
Full catalog of recurring session failure modes (self-improvement detour, planning-only Codex session, interrupted multi-task session) with detection scripts and triage rules: references/session-antipatterns.md.
When vadimgest read returns hundreds of new Telegram records, many will be from the Клава chat. Filter early: [09:xx] Клава: ⏳ Working / 💻 terminal / 📚 skill_view = noise; [09:xx] Vadims Casecnikovs: = signal. See references/intake-noise-patterns.md for WeightLossBot/Fit Mentor bot reminders, Клава system pings, English quiz notifications.
Sub-case: automated system status pings. Клава sends periodic pings: System Status OK Daemons: + TG Gateway + Hermes Dashboard .... Always noise — skip, no tasks. Escalate only if a daemon status changes (new failure or recovery). Recurring same-daemon-down for 2-3 cycles = known ongoing, skip silently. "Vadimgest (300601 new)" in these pings = cumulative index total since last reset, NOT 300K new records this cycle. The (+N) delta per source is the actual new count. Do not treat large running totals as a triage backlog.
Sub-case: interrupted multi-task session — only the completed task is in git. When Клава handles a session with 2+ tasks and the second task ends with a clarifying question ("What do you want to replace it with?") but the session then closes, the picture is:
Detection pattern: scan the session log lines for Клава: messages that contain a question directed at Vadim followed by no Vadim reply. If the last agent message ends with "?" or "which one?" or "what do you want to replace it with?" — the session ended before Vadim answered.
Triage rule for this sub-case:
Session example (2026-06-21): Клава removed Astrum.trade (done, pushed). Then asked "What do you want to replace it with?" for the tagline "Building at the intersection of AI and fintech." — session ended. Phrase still live at line 39 of app/page.tsx. Surfaced as PENDING in heartbeat.
When a contact explicitly asks Клава/Hermes not to reveal information to Vadim, honor it:
last_contact only).NDA on a specific sentence: Contacts sometimes flag individual facts within a message as NDA — e.g. "The last sentence is NDA" or "don't share this part." In that case: log everything else normally in the Log entry; for the flagged fact, include it in the note body with an explicit [NDA: do not share externally] marker rather than omitting it. The person note is private Obsidian — capturing the fact with NDA marking is correct. What you must NOT do: include the NDA-marked detail in a heartbeat report, a Deck card, a task title, or any surface Vadim might forward.
Follow-up cycle handling: If in a later cycle the same contact reveals "that was a joke / по приколу написал," do NOT retroactively reconstruct the original withheld content (you still don't know it). Instead: log the meta-event ("privacy request was a joke, contact was surprised Klava honored it") and update last_contact. The original non-disclosure decision was correct — don't second-guess it just because the requester revealed it was a test. The content remains unlogged unless the contact themselves shares it openly in a new message.
Vadim often plays mobile games (Clash of Clans, etc.) with the same contacts he discusses Vox/data work with. Within a single chat thread, messages may pivot between:
Disambiguation rule: when a contact asks about "юзеры" (users), "сливаются" (leaking), or "подозрительных личностей" (suspicious people) after a game-chat exchange, check which context it belongs to:
[SECURITY signal] if: (a) Vadim himself says there's a leak, (b) the contact says they received suspicious contact from a known Vox product, or (c) the OSINT bot returns a red flag (see Vox Compliance Bot section).Session example (2026-06-20): Igor Blink — game chat → "Ебать мне начало писать подозрительных личностей" + Vadim "Нет" = Igor getting spam on his own TG/game account. Not a Vox incident. Logged as casual data brainstorm + game chat extension.
Vadims Casecnikovs: lines inside ### Клава that answer a Клава clarifying question contain durable facts (fund names, check sizes, deal context). Write them to the relevant entity note. Full pattern + post-fumble fundraising debrief shape: references/klava-bot-reply-intel-pattern.md.
When vadimgest read returns a batch where every new record is a Клава/bot system status ping, skip triage: commit cursor, run cron health, append daily memory, report HEARTBEAT_OK. Exception: Vadims Casecnikovs: reply lines inside Клава disqualify this — unless Клава already picked it up (delegate_task/⏳ Working same minute). See references/concurrent-session-triage.md.
When a Codex session's output (e.g. code stats, analysis results) is then pasted or paraphrased by Vadim into a Telegram chat in the same heartbeat window, you will see both a Codex record AND a TG record with the same underlying data. Triage rule:
Session example (2026-06-27): session 019f080d ("Посчитай строки кода", vox-harbor) generated code stats Vadim then shared with Sasha Pokras at 07:56-08:13 UTC. Codex record = no-write fast path. Sasha TG exchange = single Log entry on Sasha's note.
When vadimgest read returns 2–N records and they are ALL turns of the same Codex session (identical session_id), treat this as a single no-write fast path:
Scale note: sessions can generate 10–20+ continuation turns between heartbeat cycles (e.g., session 019ed978 generated 19 new records across a single heartbeat window). The presence of many records does NOT mean more triage work — if all share a session_id, it's the same fast-path regardless of count.
Sub-case: same session_id, different phase names across cycles. A long-running session can change its title across phases while retaining the same session_id. Example: session 019ee16c appeared across many cycles as "OB/GYN P4 readiness counts" (readiness phase) and later as "Beamforming" (supervision phase). Both are the same session. The title change does NOT trigger a new Obsidian write — check session_id identity first, not the title. Only write if the new phase produced a genuinely new outcome not yet in the existing Log entry.
session_id. If created_at spread is within 30 minutes, treat as one session.Sub-case: multi-day session with turns on both sides of the checkpoint.
A Codex session can start on day N, be logged in daily memory, and then generate new turns on day N+1 that appear fresh in the intake. The checkpoint line (positions.codex.line) may land mid-session. Pattern to handle:
_line of the new records vs checkpoints/intake.json positions.codex.line. Lines above the checkpoint are genuinely new.session_id was already logged in daily memory (yesterday or today), check those turns' created_at — if they're still on the same topic with no new facts (e.g. continuing the same analysis loop), treat as continuation and skip Obsidian write.Session example (2026-06-21): 019ee16c (OB/GYN P4 readiness counts) started Jun 19, 8 turns logged in Jun 20 memory. Checkpoint at line 8174. Two new turns at lines 8184-8185 (Jun 20 23:02-23:03 UTC) arrived in the Jun 21 intake. Same title/topic as prior turns — no new facts. Treated as continuation, no Obsidian write, noted in daily memory only.
Session example (2026-06-20 ~15:56 UTC): 4 records all from session 019ed978 (W&B SoTA analysis, 12:00-12:23 UTC). Same session logged in Caterpillar Study Log 2026-06-18, referenced in 10+ prior heartbeat daily memory entries. No write needed.
Key check for the multi-record case: verify len(set(r['session_id'] for r in records)) == 1 — if there are 2+ distinct session IDs in a small Codex-only batch, fall through to the standard small-batch triage instead. For sub-step outcome turns (e.g. "val is now fast enough"), daily memory only is correct — no Obsidian write needed. Full decision table: references/codex-continuation-turn-outcome-triage.md.
When vadimgest read returns exactly 1 record and it is a Codex session turn, execute this fast path:
session_id, thread_id, cwd, and the full title (the intake view truncates at ~80 chars).session_id against Obsidian. If the same session_id already appears in a Study Log / project note entry from a prior cycle, this is a continuation turn — no new write needed._line > checkpoints/intake.json positions.codex.line (see pitfall below). If equal or lower, something is wrong with the cursor; investigate before committing.Session example: session 019ed978 (W&B SoTA analysis) — already in Study Log, 6 prior daily memory entries. No write — commit + HEARTBEAT_OK. 📋 references/codex-single-record-sub-patterns.md — result-confirmation Q&A turns, ops failure noise, strftime pitfall for daily memory.
When vadimgest read returns a small batch (say, 2–15 records), use this fast path instead of a full triage sweep:
Session example (2026-06-20 07:36 UTC): 8 records = 5 Gleb TG (Toms proposed call <24h after intro, URGENT) + 3 Codex W&B continuation (already tracked). Fast path: one Gleb State+Log update, daily memory append, commit. Done.
## Telegram — scan all ### chats; skip Клава body; prioritize: active deals > personal with action > new contacts > passive personal## Signal — often contains Vox Lab / Sasha Pokras / defense contacts; high priority## Codex — agent session IDs; extract only completed sessions with meaningful outcomes## Obsidian — new/modified notes; check if they need cross-linking or triage## Gmail / ## LinkedIn — check for deal responses or cold intro repliesWhen a prior Log entry contains a "Resolved X:XX UTC:" marker (or "Closed." / "Loop closed." etc.), do NOT treat it as ground truth. Vadim often says "Сейчас сделаю" / "А это вроде бы приглашение / Сейчас сделаю" / "OK буду" — intent-to-act phrases that a prior heartbeat marked as resolution. The actual action may never have happened.
Verification rule: if a new intake message from the same contact appears to re-ask the same thing (follow-up, reminder, "скинешь?", "ну как?", "напомни"), the prior "Resolved" was premature. Immediately:
last_contact frontmatter + State bullet to the new message date.Intent-to-act phrases that do NOT mean "done":
Only mark Resolved when: (a) the follow-through message is explicit ("Отправил" / "Done" / "Sent"), or (b) the other party confirms receipt ("спасибо, получил" / "got it").
Third–Sixth resolution states: see references/resolution-states.md for full specs, Log entry shapes, and session examples. Quick summary:
Session example (2026-06-21): Artyom's Jun 20 Claude link request — prior heartbeat marked "Resolved 20:04 UTC: Closed." → Artyom sent "скинешь?" at 21:43 Jun 21 → loop not closed, Obsidian entry corrected to "Still open."
When new intake messages continue the same conversation thread as an already-written Log entry (same day, same topic, same src chat), decide:
Sub-case: "pending reply" annotation gets resolved. When a prior Log entry ends with a note like "A reply from Vadim would be kind / loop still open / reply pending" and the next cycle delivers Vadim's actual reply, extend that existing entry rather than creating a new one. Add the reply text + its src: URI inline in the summary and mark it closed. Do NOT duplicate the src: line — just extend the summary prose with "Vadim replied at HH:MM UTC: '...' · src: <uri> · YYYY-MM-DD." This keeps the full exchange in one Log entry without a noise entry for a single-message response.
Sub-case: earlier-in-the-day request gets resolved later the same day. When a morning message asked Vadim to do something ("можешь такую ссылку еще раз скинуть?" = please resend the link) and a later-in-the-day message shows Vadim resolving it ("А это вроде бы приглашение / Сейчас сделаю"), extend the earlier Log entry's summary with "Resolved HH:MM UTC: . Closed." Update last_contact src to the later message. Same rule applies whether same cycle or different cycles. Session example (2026-06-20): Artyom's 09:08 Claude link request resolved by Vadim's 20:04 reply.
Extension pattern: content.replace(old_summary, old_summary + ". Follow-up: <new detail>", 1).
Session example: 2 Sasha messages added "digital twins of mouse/macaque brains" + "Wow, we need him" to an existing entry. Extended, not new entry — recruit signal also triggered creating Misha Bilokur note.
Sub-case: reply received but loop still open (reply is a question). When a prior entry said "X hasn't replied, loop open" and X replies with "кто?" / "когда?" / any question — do NOT close. Extend: "Follow-up HH:MM: X replied '[question].' Ball in Vadim's court — [what's needed]." See references/additive-log-patterns.md. Example (2026-06-22): Van0SS replied "кто?" to Vadim's RL env intro — loop still open, Vadim needs to name the person.
When a new message confirms or supersedes a State bullet that was written as tentative/pending (e.g., office_visit: tentatively Tuesday — not yet confirmed), the Log entry must also update the State bullet to remove the tentative qualifier and replace the old src/date. Two-step pattern:
If the State bullet was entirely superseded (e.g., office_visit moved from "tentatively Jun-17" to "confirmed Jun-20"), also update frontmatter last_contact to match.
Both heartbeat_state.json last_run and jobs.json last_run_at can carry future timestamps due to timezone offset or clock drift. Do NOT use either to infer recency. Use artifact file mtime (ls -la cron/output/ff50067ec588/) as ground truth, and vadimgest read --consumer intake to confirm cursor state. Also: daily memory files may be dated one day ahead of UTC — always use date -u and write to whichever file is already active. Full details: references/clock-skew-pitfalls.md.
A single State key must appear exactly once in ## State with exactly one · src: field. Two failure modes:
· src: X · date: note · src: Y · date in a single bullet. Collapse to most recent src in the same write pass.📋 references/state-bullet-single-src-rule.md — full patterns, fix code, session example (Ramazan Jun-25).
/srv/codex-klava/data/hermes/cron/jobs.json has been observed in two formats across deployments:
{"jobs": [...]} — object with a jobs key (confirmed 2026-06-20, 19 jobs)[...] at the top level (older deployments)Do NOT hardcode either assumption. Use the safe parse pattern that handles both:
import json, sys
data = json.load(sys.stdin)
jobs = data if isinstance(data, list) else data.get('jobs', data.get('cron_jobs', []))
for j in jobs:
if isinstance(j, dict) and j.get('enabled'):
print(j.get('id','?')[:12], '|', j.get('name','')[:40], '|', j.get('last_status','?'))
The isinstance(j, dict) guard catches accidental string iteration if structure changes. The data.get('jobs', data.get('cron_jobs', [])) chain handles both known wrapper key names.
Never-run jobs show None on all time/status fields — this is normal, not an error. A freshly-added job that has never triggered will have last_run_at: None, last_status: None, next_run_at: <first scheduled time>. Do NOT classify a None status as a failure. Example: e67d66896ba9 (English Coach Weekly Report, scheduled Monday 10:00) shows last_status: None until its first Monday run — correct state, not an alert.
Pitfall — any job field can be None for never-run or partially-initialized jobs. This includes last_run_at, last_status, next_run_at, and similar fields. Two failure modes:
None raises TypeError: 'NoneType' object is not subscriptable — wrap with str():# WRONG — crashes if last_run_at is None:
j.get('last_run_at','?')[:16]
# CORRECT:
str(j.get('last_run_at','?'))[:16]
None raise TypeError: unsupported format string passed to NoneType.__format__ — use explicit str() or avoid f-string format specs on fields that could be None:# WRONG — crashes if last_status is None:
f"{j.get('last_status','?'):8}"
# CORRECT:
str(j.get('last_status', '?')).ljust(8)
# or just:
str(j.get('last_status', '?'))
Safe pattern for printing job summaries:
for j in jobs:
if isinstance(j, dict) and j.get('enabled'):
jid = str(j.get('id', '?'))[:12]
name = str(j.get('name', ''))[:40]
status = str(j.get('last_status', '?'))
last_run = str(j.get('last_run_at', '?'))[:16]
print(f" {jid} | {status} | {last_run} | {name}")
This applies to any field on a freshly-added job that has never run, or any optional field the scheduler may not yet have populated.
📋 references/codex-session-triage-patterns.md — partial-findings sessions, "pending reply annotation gets resolved", investor-list no-action pattern, rollout JSONL extraction, commentary-only sessions.
📋 references/codex-ops-supervision-pattern.md — Ops-supervision sessions; triage as [OPS supervision].
📋 references/loop-closure-patterns.md — artifact-as-loop-closure (PDF proves a promise), Klava self-improvement session noise sub-case.
📋 references/backfill-cursor-gap-pattern.md — backfill writes Inbox triage note but does NOT commit cursor; main heartbeat must do entity writes + commit. Detection: compare Inbox note mtime vs checkpoint mtime.
read has no --commit flagvadimgest read does not accept a --commit flag. Passing it is silently ignored — the cursor does not advance. The correct two-step:
# Step 1: read and process the records
SOURCES=$(find -L /srv/codex-klava/data/vadimgest/sources -maxdepth 1 -name '*.jsonl' ! -name 'browser.jsonl' ! -name 'xnews.jsonl' -printf '%f\n' | sed 's/\.jsonl$//' | sort | paste -sd, -)
env -u PYTHONPATH VADIMGEST_CONFIG=/srv/codex-klava/data/vadimgest/config.yaml VADIMGEST_DATA_DIR=/srv/codex-klava/data/vadimgest /srv/codex-klava/venvs/vadimgest/bin/vadimgest read --consumer intake --sources "$SOURCES" -f md --context 3 --limit 200
# Step 2: after processing, commit only sources that were fully visible
env -u PYTHONPATH VADIMGEST_CONFIG=/srv/codex-klava/data/vadimgest/config.yaml VADIMGEST_DATA_DIR=/srv/codex-klava/data/vadimgest /srv/codex-klava/venvs/vadimgest/bin/vadimgest commit --consumer intake --sources "$SOURCES"
Verify the commit worked by re-running the read command. If any source printed ... +N more, remove that source from $SOURCES; process it through the heartbeat edge backfill script or a source-specific batch and commit the exact processed line instead.
Pitfall: calling read ... --commit 2>&1 looks like it might work but the flag is dropped. Always separate read from commit.
📋 references/silence-detector-control-chars.md — full pattern. Also: references/silence-detector-execute-code-pattern.md — always use /tmp script from execute_code (inline -c + f-strings breaks every session); covers find|xargs grep space-path failure (use grep -rl).
📋 references/tasks-queue-api-pitfalls.md — full verified export list, update_task does not exist (use update_task_notes instead), complete_task has no note= kwarg, create_proposal uses plan= not body=, topic-dedup behavior, and the proposal scope-drift update pattern.
📋 references/tasks-queue-api-pitfalls.md — complete_task(task_id: str, list_id: str = None) only. No note= kwarg. Also covers full API surface, update_task_notes vs update_task, and batch completion safe pattern.
During cross-link sweep (Phase 3), if a person note references a company/org as plain text (e.g. company: "Sazabi (YC)" in frontmatter, or merged with YC company Sazabi in body) but no Organizations/Sazabi.md exists, create a minimal stub. Signals:
company: field[[...]] wikilink that has no backing fileStub format — minimal but State+Log compliant:
---
tags: [organization, <domain>, <other-tags>]
status: active
last_contact: YYYY-MM-DD
---
# Company Name
One-sentence description from the context that surfaced it.
## State
- **category:** <what they do> · src: `<source-uri>` · YYYY-MM-DD
- **contact_via:** [[Person]] → [[Introducer]] · src: `<source-uri>` · YYYY-MM-DD
## Log
### YYYY-MM-DD — Org stub created from [person] intro context
- **src:** `<source-uri>`
- **mentions:** [[Person]], [[Introducer]]
- **summary:** [ORG new stub] One-sentence origin story.
- **facts-touched:** category, contact_via
After creating the stub, update the person note to replace plain text with [[OrgName]] wikilinks (both in body and State bullet).
Short Codex sessions (2-3 turns, < 5 min): check has_writes = any(k in content for k in ('write_file','patch','str_replace','create_file','apply_patch')). If False + turns ≤ 2: conceptual — pending task, not done. See references/codex-session-triage-patterns.md.
Full patterns, decision tree, and session examples: references/image-ocr-triage-patterns.md.
Sub-case A — readable financial/document screenshots (e.g. crypto deposits, receipts):
Extract key facts from OCR, write a [FINANCIAL signal] Log entry, add a crypto_screenshot (or equivalent) State bullet, leave exchange open if Vadim asked "Это что?" and sender hasn't replied. Do not create a task. Session example (2026-06-22): Dad's 500 USDC BEP20 deposit screenshots.
Sub-case B — garbled OCR (rotated/photocopied Cyrillic → Latin):
OCR content is not the signal — surrounding message context is. Write Log entry describing intent, note garbled OCR explicitly, leave open if no resolution. Session example (2026-06-20): Sasha NDA screenshot rendered as "Kak Tbl AYM@eLb...".
Quick rule: OCR legible → doc (A); OCR garbled → context-driven (B); complaint+image → complaint is signal, OCR is context (C); sender==Vadim → outgoing (D). Full specs: 📋 references/image-ocr-triage-patterns.md.
See references/jsonl-intake-pitfalls.md for: conversation type records (dominant DM format — top-level text/sender empty, messages in r['messages'] list; 2026-06-22), display truncation vs source-side truncation, _line collision (use _ingested_at >= checkpoint updated_at), and reading by source_uri.
📋 references/ambiguous-message-disambiguation.md — short bursts with no referent: trace JSONL, "?" = clarification incoming.
📋 references/source-uri-lookup-patterns.md — full patterns: source_uri construction, chat_id discovery, multi-message burst URI extraction.
📋 references/vadimgest-read-plus-n-more-pattern.md — ... +N more in vadimgest read is usually view truncation; JSONL-verify the latest record before leaving a source for backfill.
📋 references/catallax-buyer-lane-identity-resolution.md — resolve short company names ("Кrea написала") as Catallax buyer lanes; cross-write pattern (Pufit note + deal note + org stub).
When search returns empty, read raw JSONL — write multi-line lookups to /tmp scripts (inline -c fails on for loops):
script = """
import json
records = [json.loads(l) for l in open('/srv/codex-klava/data/vadimgest/sources/telegram.jsonl') if l.strip()]
chat = [r for r in records if r.get('chat','') == 'Chat Name Here']
recent = chat[-15:] if len(chat)>=15 else chat
for r in recent:
print(r.get('timestamp',''), '|', r.get('sender',''), '|', r.get('text','')[:150])
"""
with open('/tmp/jsonl_read.py', 'w') as f: f.write(script)
# terminal("python3 /tmp/jsonl_read.py")
Key fields: chat, sender, timestamp, text, meta.chat_id, meta.message_id, source_uri. Empty source_uri is common on incoming messages — construct it as vadimgest://telegram/<chat_id>_<message_id>.
Session example (2026-06-20): Igor Blink — vadimgest search empty, JSONL showed full conversation.
Brand/service marketing channels (luxury concierge, bank promos, premium card services like CinCin Exchange) are pure noise. Recognition: pitch language ("Личный консьерж", "24/7", "безграничные возможности"), sender is a brand not a person, no Vadim replies. Triage rule: skip, no write, just commit cursor.
Site deploy verify: after vadi.ms push, grep repo source — not live curl. CDN: 1-3 min. Full pattern: references/site-deployment-verification.md. Gmail/Codex triage patterns (empty-body Gmail, Mac-local commentary sessions, training-pipeline ops, Signal "+N more"): 📋 references/gmail-codex-triage-patterns.md.
The vadimgest read intake output for ## Codex entries only shows titles (truncated at ~80 chars). Look up the full title from JSONL directly — it is usually the Codex task prompt. Cross-reference with Obsidian notes to determine if the session outcome is already captured. If the Codex session was a continuation of work already logged in the current Obsidian note, no new write is needed — note it in daily memory instead.
📋 references/codex-single-record-sub-patterns.md — Q&A turns, ops noise, strftime pitfall. references/codex-session-id-lookup-pitfalls.md — daily memory grep (not Obsidian) for same-day session-ID checks — Obsidian times out 15s+; dedup; fast-path decision tree.
📋 references/codex-session-type-session-triage.md — type: session Mac-local records (prefix session_MacBook-Pro-Vadim.local_...). Read final assistant message; note in daily memory only; no Obsidian write unless significant.
Some project notes (e.g. Caterpillar Study Log, Vox deals with long history) exceed read_file's 100K char safety limit. When read_file returns "Read produced N characters which exceeds the safety limit":
# Find section line numbers
grep -n "## Study Log\|## State\|## Log\|### 2026" "/path/to/note.md" | tail -20
# Read specific section by offset
read_file(path, offset=61, limit=60) # offset = line number from grep
# Read tail for most recent entries
terminal('tail -80 "/path/to/note.md"')
Pattern: use grep -n to find the line number of the section you need, then read_file with offset=<line> and a small limit. For Study Log / recent entries, tail -80 is usually enough.
/srv/codex-klava/data/MyBrain syncs via obsidian-headless-sync from Vadim's Mac. There is no .git directory. Never attempt git -C /srv/codex-klava/data/MyBrain commit or git add — it will always fail with fatal: not a git repository. Writes via patch or write_file go directly to disk and sync outward automatically. No commit step needed or possible.
See references/log-extension-pitfalls.md for: em-dash/quote mismatch handling, safe repr()-then-replace pattern, multi-field update order, and the yap-complaint → engineering-disqualification two-cycle shape.
## Log sectionsSome person notes (e.g. Vladislav Dombrovsky) contain multiple ## Log headers — one per chat source. A patch call using old_string: "## Log\n\n### YYYY-MM-DD..." will fail with "Found N matches." Fix: include enough of the first list item after the target ## Log heading so the match is unique. Typically 2-3 lines into the first entry is sufficient:
# Too short — matches all ## Log sections:
old_string = "## Log\n\n### 2026-06-18 — Title\n"
# Sufficient — include the src line to make it unique:
old_string = "## Log\n\n### 2026-06-18 — Title\n- **src:** `tg://...`\n"
Alternatively, use grep -n "## Log" first to find which line number contains the target section, then prepend the preceding non-Log line as context anchor.
When appending a new Log entry after an existing entry's facts-touched: line, the anchor string must be unique across the whole file. Using only "- **facts-touched:** key1, key2" may work if there's one match, but it is fragile. The safe pattern anchors on both the facts-touched line AND the header of the next Log entry immediately below:
# Fragile — only works if there's exactly one matching facts-touched line:
old = "- **facts-touched:** last_contact, meeting_scheduled"
# Robust — includes the next section header to make match unambiguous:
old = "- **facts-touched:** last_contact, meeting_scheduled\n\n### 2026-06-07 — fUS for brain write"
new = "- **facts-touched:** last_contact, meeting_scheduled\n\n### 2026-06-20 — New entry\n...\n\n### 2026-06-07 — fUS for brain write"
Rule: when inserting between two Log entries, always include at least the first line of the following entry in old_string so the insertion point is pinned. This is especially important in long notes with many entries sharing similar key names.
The ## Obsidian section in vadimgest read output shows records synced from Mac (paths like /Users/vadimchashechnikov/Documents/MyBrain/...). These notes are mirrored to the server at /srv/codex-klava/data/MyBrain/... via obsidian-headless-sync.
Before creating a new note that appears in the Obsidian intake section, check whether it already exists on the server:
ls "/srv/codex-klava/data/MyBrain/People/Person Name.md"
# or
find "/srv/codex-klava/data/MyBrain/People" -name "*Keyword*"
If the file exists with the same content the prior heartbeat wrote, it is already current — no write needed. The Obsidian intake record is just the edge-sync event arriving in vadimgest after the file was written on Mac.
Pitfall: Always stat or cat the server path first — do not create duplicate notes.
Sub-case: file not yet on server (sync lag). find returns empty — obsidian-headless-sync hasn't delivered the file yet. Do NOT write based on filename inference — note expected resolutions in daily memory and defer all writes to next cycle. Report as "🔔 Meeting note pending sync." Full pattern: references/obsidian-sync-lag-triage.md.
Sub-case: Hlopya meeting notes as Obsidian intake events. Synced from Mac via obsidian-headless-sync. Triage: confirm file exists on server, read it (Action Items + Decisions are the signal), cross-write key facts to participant People notes, do NOT rewrite the meeting note itself. Participants listed in frontmatter participants: field.
Sub-case: [[People/X]] wikilink in meeting note has no backing file → search for a partial-identity note first (grep -r "identity_unresolved" People/), confirm match via Calendly URL / company / project context, update the existing note in place (do NOT create a new file, do NOT rename), resolve identity_unresolved State bullet, add meeting Log entry, also update any project Study Log with confirmed dates/milestones from the same meeting.
📋 references/hlopya-meeting-cross-write-patterns.md — full pattern with session examples (2026-06-21 equity extraction, 2026-06-23 Kevin J = Kevin Joo identity resolution).
When a new contact is flagged as a potential technical recruit (Vadim says "we need him", "can I invite them", "would be useful for Alegria/SSI work"), the person note should include these State bullets:
- **recruit_signal:** <exact Vadim quote + context> · src: `<uri>` · YYYY-MM-DD
- **applied_work_signal:** <Sasha/referrer's assessment of willingness to do applied work> · src: `<uri>` · YYYY-MM-DD
- **vadim_condition:** <any constraints Vadim set, e.g. "Lev shouldn't poach him"> · src: `<uri>` · YYYY-MM-DD
- **research_group:** <what group/lab/project they're in, and who else is there> · src: `<uri>` · YYYY-MM-DD
- **first_meeting:** <date, time, location> · src: `<uri>` · YYYY-MM-DD
Key signal phrases:
recruit_signal State bullet immediatelyapplied_work_signal📋 references/single-word-followup-pending-proposal.md — when "hey"/"привет"/"?" arrives after 7d+ gap from a contact with an open PROPOSAL, treat as follow-up, surface as action needed. Session example: Ilya HighTower "hey" Jun 29.
Some recurring contacts appear under non-obvious display names or handles. Known identities (do not re-derive).
📋 references/intake-noise-patterns.md — WeightLossBot/Fit Mentor bot reminders, Клава system pings, large Клава completed-session fast-path, active Klava mid-session detection (mid-task at heartbeat time: detect via ⏳ Working — tail, report INCOMPLETE, don't re-do in-progress work). 📋 references/bee-intake-triage-patterns.md — bee_fact/bee_conversation/bee_daily_summary triage, "Unknown source: bee" PYTHONPATH false alarm fix, sync gap detection.
Fast resolution path — frontmatter handle: field. Before doing any deduction, search the People notes for a handle: frontmatter key matching the Telegram display name. Many personal contacts are pre-resolved this way:
grep -rl 'handle: "Natuska"\|handle: Natuska' /srv/codex-klava/data/MyBrain/People/
Or search across all:
grep -r "^handle:" /srv/codex-klava/data/MyBrain/People/ | grep -i "DISPLAY_NAME"
This resolves immediately for family and close contacts (e.g., handle: "Natuska" → Наташа Чашечникова.md). Only fall through to alias deduction if grep finds nothing.
Core entries (also in the index):
People/Toms Cernavskis (roam.lol).md. Matched via phone +1 (628) 240-4507 in frontmatter.tg://artinleather/.... Any message from him is personal, low urgency unless he mentions equity, Roko, or Vox structure.handle: "Natuska".Telegram sometimes surfaces contacts under a single letter or short alias (e.g. "D", "M", "R") when the phone number isn't in Vadim's contacts under a full name. These require active identity resolution before writing to Obsidian:
/home/codex/.klava/memory/YYYY-MM-DD.md for the alias — a prior heartbeat may have already identified the person.? qualifier.People/<alias> (identity unresolved).md. Set follow_up: to tomorrow so it surfaces again next cycle. Full stub pattern: references/identity-stub-patterns.md.Session example: "D" appeared in two consecutive heartbeat cycles. First cycle: identified as "likely Danielle Strachman (1517 Fund)" from context (SF, investor, asked for calendar slots). Second cycle: confirmed by behavior (meeting arranged for next day) → wrote to Danielle Strachman note.
When ### Vox | Compliance Bot appears in the intake with messages like "Previous names: ...", "Language: ru Sex: male Age range: ..." — Vadim queried the bot with @handle. This is NOT noise. Triage as:
[04:01] Vadims Casecnikovs: @IgorBlink.osint_run State bullet with the key profile fields (age range, location, role, interests). If no, create a minimal people note with those fields.- **osint_run:** <key fields: gender, age range, location, role, interests> · src: `tg://vox-compliance-bot/YYYY-MM-DD` · YYYY-MM-DD
[SECURITY signal].Sub-case: bot returns null result ("can't gather enough information")
When the bot outputs "Unfortunately I can't gather enough information about user #XXXXXXX. Try another one." — this is still actionable:
osint_run State bullet marked null — insufficient public data.osint_run null format: - **osint_run:** null (bot: insufficient data on #XXXXXXX) · src: \tg://vox-compliance-bot/YYYY-MM-DD` · YYYY-MM-DD`Sub-case: contact forwarded by a trusted friend (not directly queried by Vadim) Sometimes a trusted contact (Vladik, Sasha, etc.) sends someone's TG ID + phone number to Vadim first, and then Vadim queries the bot with that ID. Recognition pattern:
5821420253 / telegram id 5821420253 / phone number +XX X XXXX XXXX — labeled, structured[08:11] Vadims Casecnikovs: 5821420253 to the bot
This is different from Vadim independently looking someone up. The friend is the referral source; the phone number provides a cross-platform identifier (country code often tells you something). Log as:osint_run State bullet on the unknown contact (create a stub note if none exists, title = "Unknown contact #5821420253" until identified)Vadim sometimes asks a contact to connect him to a third party on behalf of his own friends/colleagues, not for personal business reasons. Recognition pattern:
Triage rule:
[NETWORK brokering] in the Log entry (not [DEAL new] or [OPPORTUNITY]).<topic>_connection_request State bullet (e.g. rl_connection_request) pointing to the contact Vadim asked.<topic>_connection: made and add a Log entry.Session example (2026-06-20): Vadim asked Nur to connect him with "exclusive RL environments" builders — Vadim's pitch is he can connect them with his own friends in that space. rl_connection_request State bullet added. No task created.
Vadimgest occasionally delivers a record whose timestamp is days or weeks in the past — typically a media file (video, audio, image) that was written to disk late or whose sync event was delayed. Recognition signals:
[Document: ...] or media attachment, not a text messageTriage rule: treat stale-timestamp media records as no-action unless the media's content is independently significant (e.g., Vadim sent a video that was requested by someone in a later message). Steps:
timestamp field against today's date — if it's >24h stale, it's a late-sync artifact.last_contact for the person based on a late-sync timestamp. Their last_contact reflects when they actually interacted, not when the media synced.Session example (2026-06-21): Dan Klishch record appeared with timestamp 2026-06-15T03:07Z in a Jun 21 intake. It was a video.mp4 Vadim sent after confirming "Arriving in 5 min." Context from Jun 15 was a restaurant meetup — already a closed event. Skipped cleanly.
📋 references/vladik-judgment-patterns.md — Vladik covert check-ins revealed to target, over-communication toward founders.
📋 references/office-access-request-triage.md
📋 references/credential-sharing-triage.md — WiFi hotspot / device passcode / API token shared in TG: never log values, note exchange only, no task.
📋 references/meeting-prep-pattern.md — meeting prep, Nextcloud credential gap, unknown-contact stub.
📋 references/additive-log-patterns.md — reply-received-but-loop-still-open, pending-reply resolved, stale-thread short-closure (Ну да, бывает).
📋 references/team-corrects-vadim-number-pattern.md — teammates correct a Vadim-stated figure mid-thread: extend existing entry, add accuracy qualifier fact-key, no State update unless authoritative correction agreed.
📋 references/cold-contact-triage.md — unknown cold contacts (1 message, no history). Stub + identity_unresolved.
📋 references/low-signal-personal-tg-patterns.md — cold contact vs personal passive share. Sub-case 5: time-critical expiry → ⚠️ ACTION NEEDED; add <doc>_expiry State bullet; no Google Task for same-day expiry.
📋 references/scheduling-loop-edge-cases.md — wrong-timezone calendly, interview slot proposed→confirmed, social invite, counter-proposal loop.
📋 references/same-day-multi-entry-insertion.md — insertion point when note has multiple same-date Log entries; anchor ranking; new-entry vs extend.
📋 references/broadcast-style-vs-broadcast-channel.md — two-way contacts who also send broadcast-style intel shares; disambiguation rule before applying the fast path.
Some Telegram contacts (e.g. Vlad Zharoff) operate as broadcast channels — they post photos, film reviews, art projects, creative content, or news digests (#новости) to followers with no direct conversation thread. Recognition signals:
Vadims Casecnikovs: reply lines in the recent historyTriage rule: broadcast posts are always no-action. The only write is updating last_contact in frontmatter + State bullet. Do NOT create tasks, proposals, or surface in heartbeat report unless the contact switches to direct message mode (i.e., a Vadims Casecnikovs: reply appears in context).
Log entry shape for broadcast posts:
### YYYY-MM-DD — Broadcast <media type> post
- **src:** `vadimgest://telegram/<chat_id>_<msg_id>`
- **mentions:** [[Person]]
- **summary:** Posted N image(s)/video(s) at HH:MM UTC. No text / <brief text if present>. Continuation of <project name> content stream (prior: <last notable post>).
- **facts-touched:** last_contact
Same-day double-burst: When a broadcast contact posts twice in the same day and the first burst was already logged, extend the existing Log entry — update src to latest message ID, update summary to cover both bursts. Do NOT create two Log entries with the same date for broadcast-only contacts.
📋 references/broadcast-checkinquestion-media-pattern.md — "Спишь?" + immediate media = social framing for the share, not an open loop. Extend existing entry, no task.
Family and close personal contacts occasionally ask about AI tool safety, security, or tech decisions (e.g. "можно ли загружать зараженные файлы в Codex?"). Triage pattern:
[PERSONAL stable] marker — good to know mom is thinking about security, even if no action is required.The answer to "can I upload virus-infected files to an AI assistant?": yes, text/code from an infected backup is generally safe to paste (the AI can't execute it), but uploading binary executables or running infected scripts on the same machine is not. The virus lives in the executable, not in static text you paste.
When Vadim tells a personal contact he's heading to investor meetings ("Еду общаться с инвесторами") and then returns, the contact will often ask "Как прошло?" (how did it go?). This creates a soft open loop even when the prior heartbeat entry said "no action needed."
Triage rule: When the prior entry logged "heading to investors" as no-action, and a new intake message contains "Как прошло?" with no Vadim reply:
last_contact src to the new message ID.Signal phrases: "Как прошло?" / "Ну как?" / "Расскажи" after an investor/business event mention.
Session example (2026-06-21 cycle 7): Signum asked "Как прошло?" at 17:51 UTC after Vadim had said "Еду общаться с инвесторами" at 15:13. Prior entry written as no-action; new message extended same entry with "Vadim has not replied yet."
When a contact sends a time like "я где-то в 2-30 освобожусь" ("I'll be free around 2:30") in a chat where a meeting was previously arranged (e.g., Saturday lunch in daily memory, coffee confirmed in prior cycle) — this is an imminent meetup time signal, not casual chat.
Triage rule:
meeting_scheduled State bullet to the person note: <day date>, ~<time> local / <UTC> — <context> · src: <uri> · YYYY-MM-DDlast_contact src to the latest message ID.Signal phrases: "я буду в X", "освобожусь в X", "буду свободен/свободна в X", "встретимся в X?" when there's a prior meeting arrangement in context.
Arrival/en-route phrases: "Я через час буду" / "еду уже" / "буду через N минут" — these are not time proposals but arrival confirmations when a meeting was already scheduled. Update office_visit State bullet to note "en route" + update last_contact. No new task needed — meeting is already set. Extend the existing Log entry rather than creating a new one.
When Vadim forwards I-94 printouts or flight change screenshots to a contact, it encodes his own travel plans. Parse Admit Until Date (hard visa deadline), update vadim_departure_date State bullet, supersede tentative stay-plan bullets. Full pattern: references/travel-document-triage.md.
When a contact sends a share.google/... or maps.app.goo.gl/... link in Telegram with no other text, it almost always means: "here's where to meet me / come here." Context clue for triage:
meeting_scheduled or extend the last Log summary.Session example: Nur sent https://share.google/hubiCpGKDjzjjnMbf after confirming "Np! Sure" to Vadim's coffee offer → treated as meeting location → Log extended with "in-person meeting materializing."
When Vadim asks "which of these companies do we have a connection to not through Mercor/Max/Mahir/Catallax", the pattern is:
Vox Lab/Deals/max + Mahir Bansal/max + Mahir Bansal — Data Brokerage.md → ## State for Catallax carve-outs. Check schedule-c-vox-direct-carveouts-v1.md for the formal exclusion list. Companies in those carve-outs are Vox-direct by definition.Quant names (Bridgewater, Jane St, AQR, Renaissance, Two Sigma, Squarepoint, D.E. Shaw) historically have no direct buyer contact — only market map entries. Citadel is the exception.
When a new intake fact changes a field that is also in frontmatter (company, role, location, last_contact, stage, next_action, etc.), update both the State bullet and the frontmatter cache in the same write. Downstream tooling (status_collector, silence-detector, vox-crm) reads frontmatter, not State directly.
Common trigger: a contact's company or URL surfaces in a message that differs from what's in frontmatter (e.g., company: "roam.lol" in frontmatter but intake reveals areality.co as the actual current domain). Pattern:
# Read the file
result = terminal("cat '/path/to/People/Person.md'")
content = result['output']
# Update frontmatter field (top of file, between --- delimiters)
content = content.replace('company: "roam.lol"', 'company: "areality.co"', 1)
# Also update the State bullet (if it exists) or add one
content = content.replace(
'- **company_url:** roam.lol',
'- **company_url:** areality.co (also roam.lol) · src: `tg://...` · YYYY-MM-DD',
1
)
write_file('/path/to/People/Person.md', content)
Optimize messages for desired outcome - fix English, kill red flags, simulate recipient
Periodic intake pipeline - reads new data, triages, acts, updates knowledge base
State + Log convention for sourced facts. Every deal note, person note, org note follows this shape. Use when migrating notes, writing new entity notes, or auditing for unsourced claims.
Source-backed batch triage and checkpointing for grouped intake/backfill jobs.
Turn grouped source-intake batches into durable Obsidian writeback with provenance, routing, and checkpoint verification.
Process grouped calendar source records into entity notes or Inbox notes with provenance and checkpoint verification.