Imported from indigokarasu/finch (
SKILL.md). Install upstream withnpx skills add indigokarasu/finch. Copyright stays with the author (MIT).
PUBLIC REPO — GENERICISE EVERY REFERENCE. This skill is published publicly. Reference files are distilled from real runs, so never write a real name, email, employer, thread id, task id, phone number, token or home path into one — use the placeholders in
references/reference-file-workflow.md("Genericise Before You Write"). Runpython3 scripts/check_no_pii.pybefore committing; CI enforces it. And route before you write: this directory is for finch's OWN docs only. A finding about another system (cron, MCP, Gmail, patch, OAuth, git) goes to that skill or to<fs-root>/references/(local, unpublished) — never here.
ocas-finch
Finch is the OCAS System Evolution Layer's self-improvement orchestrator. It runs as a set of cron jobs — see Manual run & verification below for the actual deployed job set (the design doc's finch:work is not currently a separate deployed cron). Jobs are primarily pure-LLM cron prompts, plus one no_agent script floor (finch:floor). Deprecated scripts are in archive/.
Signal sources (7): cron health, email, calendar, sessions, Drive, kanban, system. See references/scan-work-architecture.md for the full table.
Interactive Menu
When invoked interactively, present a two-level menu. See references/interactive-menu.md for the full two-level menu layout, Clarify timeout behavior, response parsing, and platform adaptation.
Responsibility Boundary
ocas-finch owns its core domain operations.
ocas-finch does not own: trigger detection, session management, or cross-skill orchestration (those belong to the calling agent).
When to Use
- Scheduled self-improvement: finch:scan (every 2h), finch:work (every 30 min), finch:daily (6am PT), finch:weekly (Sunday 8am PT)
- Manual session mining via
finch.mineorfinch.run - After major sessions: auto-detect corrections, directives, breakthroughs, methodologies
- Memory at/near capacity (
memorytool refuses edits, ~79%+ warning threshold): RUNfinch.compact/memory_guard.py— do NOT hand-edit MEMORY.md to dodge the cap. Manual memory surgery (condensing entries into one giant block, dropping context to fit) is exactly the work Finch owns, and doing it by hand produces bloat (a single ~1,900-char entry where tier-routing would have moved procedure out to a skill/reference). The user's correction (2026-07-15): "Why are you manually cleaning up memory? You have Finch for that." If thememorytool refuses an edit at capacity, hand it to Finch (forceocas-finch:dailyor runmemory_guard.py --file ~/.hermes/profiles/<profile>/memories/MEMORY.md); do not fight the limit with manualmemorywrites. Finch owns compaction; this is the procedure, not "there is no skill." - Skill library maintenance: route findings to SKILL.md patches
When NOT to Use
- Real-time behavioral adaptation (Chronicle handles pattern detection)
- Skill evaluation scoring (Mentor handles OKR evaluation)
- Skill creation/architecting (Forge handles skill building)
- Entity identity resolution (Chronicle tools handle direct writes)
Storage
See references/storage-layout.md for the full directory tree and skill package structure.
Scanning Gotchas
The full operational detail for each item below lives in references/scanning-gotchas.md (one-line pointers, full bodies there):
- Verify tool availability before parallel batches (one bad tool name poisons the whole batch). **DEFERRED-MCP DIRECT CALL = WHOLE-BATCH KILL (confirmed → full body in
references/scanning-gotchas.md tool_call()for MCP tools fails with "not a deferrable tool" in cron — MCP tools registered in the agent's tool list (e.g.mcp_google_workspace_search_gmail_messages,mcp_google_workspace_get_events,mcp_google_workspace_list_drive_items) cannot be invoked viatool_call()in cron context. Fallback: usegoogleapiclientdirectly (seegws_direct_puller.pyin the skill's scripts/) or Composio'sCOMPOSIO_MULTI_EXECUTE_TOOL. Email: check toolkit connection status first; if absent, use cached-data fallback (last_email_check_<account>.jsonincommons/data/ocas-dispatch/). Calendar/Drive: attempt Composio first, skip only on connection failure. Do NOT retrytool_call()for the same MCP tool in the same run — it will fail identically. (Confirmed 2026-07-27 finch:scan)- MCP-absent triage (distinguish mount-failure from config/cred failure) → full body in
references/scanning-gotchas.md - googleapiclient direct-fallback RESPONSE SHAPE (confirmed 2026-07-23 finch:scan) → full body in
references/scanning-gotchas.md - Unreachable workspace source = GAP, never "no signal". When the google-workspace MCP namespace is entirely absent from the tool list (not just failing → full body in
references/scanning-gotchas.md - Google Workspace MCP tools reachable in cron via Composio (
COMPOSIO_MULTI_EXECUTE_TOOL); attempt Calendar/Drive first, skip only on connection failure - MCP Google Workspace tools are NOT callable via
tool_call()in cron — they return "not a deferrable tool". Fallback togws_direct_puller.py(googleapiclient) or cached dispatch journals. Seescan-work-architecture.md§ MCP-unavailable fallback pattern. (Confirmed 2026-07-27) tool_search≠tool_callavailability — probe a suspect MCP tool alone before batching; MCP load state is intermittent between runs- Google Workspace MCP = proxy invocation, not direct → full body in
references/scanning-gotchas.md - Large MCP batch responses persist to disk — parse with
terminal, not inline → full body inreferences/scanning-gotchas.md(CONFIRMED parse recipe:json.loads(raw)['result']+ split onMessage ID:— seereferences/email-mcp-pagination-parsing.md) get_gmail_messages_content_batch429 partial-failure (2026-07-22) → full body inreferences/scanning-gotchas.md- Direct MCP credential-store fallback (
<gworkspace-creds>/credentials/<email>.json) when dispatch rejects + legacy token isdeleted_client - Host egress filter 404s RAW requests to Calendar/Drive — use googleapiclient (confirmed 2026-07-22 finch:scan) → full body in
references/scanning-gotchas.md - Stale errors from
hermes cron list— verifyLast run:timestamp;consecutive_failuresis the only reliable error gate - Re-verify prior completions/STATES against LIVE signal bidirectionally (re-open on relapse, resolve on live recovery)
- MCP Google Workspace param is
page_size, notlimit/max_results→ full body inreferences/scanning-gotchas.md get_eventsparam exception (confirmed 2026-07-23 finch:scan) → full body inreferences/scanning-gotchas.md- Email scan MUST paginate to completion (
page_tokenloop) → full body inreferences/scanning-gotchas.md - Incremental email classification via message-ID watermark (confirmed 2026-07-24 finch:scan) → full body in
references/scanning-gotchas.md - Sessions scan date-string workaround (
query="YYYY-MM-DD"); mine interactive messages via directstate.dbSQL, not session_search scroll - Only report actual fixes, never stale/transient issues; interpreter-shutdown errors are always transient
- Never force model overrides on cron jobs; maintain skill index; push local changes to GitHub immediately (user directive)
- Disk-before-auth diagnostic (
df -h /before OAuth);execute_codeblocked in cron profile — useterminalpython3 - Concurrent-write hazards on task-list.json / MEMORY.md / ANY shared prep file → full body in
references/scanning-gotchas.md read_fileview of a JSON file is NOT validation → full body inreferences/scanning-gotchas.md- Parallel
patchedits to the same JSON file corrupt it → full body inreferences/scanning-gotchas.md - Re-rank the task array with a script, not
patchblock-moves → full body inreferences/scanning-gotchas.md - Cron-error "completed" may be a FALSE RECOVERY — re-run the script live, never trust prior status; verify both live jobs.json + live script exit 0 → full body in
references/finch-scan-pitfalls.md expanduser("...{PROFILE}...")missing f-string = literal{PROFILE}path bug (FileNotFoundError) → full body inreferences/finch-scan-pitfalls.md- task-list.json mutation: append NEW tasks to the LIVE list, not a parallel dict (lost at serialize) → full body in
references/finch-scan-pitfalls.md expanduser("~")returns literal tilde path in cron subprocesses whenHOMEis unset → full body inreferences/finch-scan-pitfalls.md(pitfall #5). This is a different root cause from the{PROFILE}bug (pitfall #2) but produces the same symptom. Both affect scripts usingos.path.expanduser("~")in cron context. Fix: hardcode absolute paths or reados.environ.get("HOME", "/root").- Google Workspace MCP content-batch REQUIRES
user_google_email(else pydantic validation error) → full body inreferences/finch-scan-pitfalls.md - Cron health = read
jobs.jsondirectly → full body inreferences/scanning-gotchas.md jobs.jsonis a dict{"jobs": [...], "updated_at"}, NOT a bare list (confirmed 2026-07-23 finch:scan) → full body inreferences/scanning-gotchas.md- Cron run-history evidence =
cron/output/<rid>/, NOTexecutions.db→ full body inreferences/scanning-gotchas.md - Windowed mining/analysis cron = SILENT DATA-DROP trap (confirmed 2026-07-23, -miner) → full body in
references/scanning-gotchas.md - Transient-provider signature (Tencent "Upstream error… Retry once") → full body in
references/scanning-gotchas.md findcan return a STALEjobs.jsonsnapshot understate-snapshots/— always pin the LIVE path → full body inreferences/scanning-gotchas.mdskill_view(name, file_path=...)linked-file fetch can itself throw the transientDaemonThreadPoolExecutor object has no attribute '_initializer'e → full body inreferences/scanning-gotchas.md- Long single-line JSON string values defeat the
patchfuzzy matcher → full body inreferences/scanning-gotchas.md - Inline
terminal python3 -cwith a LARGE payload = STREAM TIMEOUT (confirmed 2026-07-24 finch:scan) → full body inreferences/scanning-gotchas.md patchPREFIX match = SILENT corruption on long single-line JSON values → full body inreferences/scanning-gotchas.mdjobs.jsonfor cron health whencronjobtool unavailable;hermes cron listhides disabled jobs. **NEVER report "cron health clean" without enumer → full body inreferences/scanning-gotchas.md- Cron-health "0 errors" is a HIGH-RISK false-negative — derive the claim from a FULL-OUTPUT grep, never from a prior scan's state → full body in
references/scanning-gotchas.md tool_call()for MCP tools fails with "not a deferrable tool" in cron — MCP tools (e.g.mcp_google_workspace_search_gmail_messages,mcp_google_workspace_get_events,mcp_google_workspace_list_drive_items) cannot be invoked viatool_call()in cron context. The registered tool list includes the names but the runtime does not expose them fortool_calldispatch in this profile. Fallback: usegws_direct_puller.py(googleapiclient direct, via<hermes-venv>/bin/python) or Composio'sCOMPOSIO_MULTI_EXECUTE_TOOL. Email: check toolkit connection status first; if absent, use cached-data fallback (last_email_check_<account>.jsonincommons/data/ocas-dispatch/). Calendar/Drive: attempt Composio first, skip only on connection failure. Do NOT retrytool_call()for the same MCP tool in the same run — it will fail identically. (Confirmed 2026-07-27 finch:scan)- False-recovery claim = false-positive, verify against live jobs.json (confirmed 2026-07-24 finch:work) → full body in
references/scanning-gotchas.md - Gateway-restart timing interprets a single interpreter-shutdown error (confirmed 2026-07-24 finch:work) → full body in
references/scanning-gotchas.md - Provider HTTP 400 is MEDIUM (not transient) — classify by status; missing-script errors need path verification not debugging
- finch:scan is NOT a task executor; read_file tilde-expansion path doubling;
jobs.jsonschedule fields are dicts not strings - Gateway RSS growth tracking (3x = notable, >2GB = escalate)
hermes cron listhas NO parseable JSON mode — and emits NOlast_status/consecutive_failurescolumns → full body inreferences/scanning-gotchas.md- write_file / read_file commons paths: use LITERAL
~/.hermes/commons/..., NEVER a tilde~/.hermes/commons/...→ full body inreferences/scanning-gotchas.md - write_file / read_file commons paths: use LITERAL
~/.hermes/commons/..., NEVER a tilde~/.hermes/commons/...→ full body inreferences/scanning-gotchas.md expanduser("~")returns literal tilde path in cron subprocesses whenHOMEis unset → full body inreferences/finch-scan-pitfalls.md(pitfall #5). This is a DIFFERENT root cause from the{PROFILE}missing-f-string bug (pitfall #2) but produces the sameFileNotFoundErrorsymptom. Both affect any script usingos.path.expanduser("~")in cron context. Fix: use hardcoded absolute paths or reados.environ.get("HOME", "/root")explicitly.
Architecture
- All finch jobs are pure LLM
Cron rebase breakage pattern
Confirmed 2026-07-22: multiple ocas-*:update jobs can fail simultaneously with the same git rebase conflict signature, indicating upstream sync publication introduced incompatible changes across multiple related repos/skills. Job descriptions often include Removing references/..., Removing data/, and Dropped refs/stash@{0}. Treat as one systemic sync breakage, not N independent failures.
Role: Session mining engine. Detects behavioral signals from conversation transcripts and routes them to durable modification targets (MEMORY.md, skill patches).
Journal type: Action Journal. Every finch run emits an Action Journal entry to {agent_root}/commons/journals/ocas-finch/.
Cooperation:
- Receives: Session transcripts (read-only, from the agent's session store)
- Reads: BehavioralSignal files from Chronicle (Corvus was merged into Chronicle)
- Emits: DecisionRecords to the Finch decision log
- Writes: MEMORY.md — via
scripts/memory_guard.pydirect file write (--apply --file ~/.hermes/profiles/<profile>/memories/MEMORY.md), NOT the built-inmemorytool. The profile has apre_tool_callshell hook (agent-hooks/block-memory-tool.sh, matcher^memory$) that BLOCKS thememorytool and redirects to this script, plusmemory.memory_enabled: falseso the built-in backend never auto-persists. Finch is therefore the SOLE maintainer of MEMORY.md. Never call thememorytool to edit it.
File governance
See references/file-governance.md for write targets, read-only files, off-limits files, and creation criteria.
Signal types
See references/signal-types-table.md for the full signal type table.
Behavioral directives (priority 0)
When the user says "Always" or "Never", this is an explicit behavioral rule. Priority 0 — highest priority. Apply immediately and prominently. Route to MEMORY.md under ## Always Rules or ## Never Rules. Never batch with lower-priority findings.
Core loop
Finch operates as a continuous improvement cycle:
- Scan (
finch:scan, every 2h) — Read 7 signal sources. Cron health MUST be enumerated from the LIVEjobs.json(~/.hermes/profiles/<profile>/cron/jobs.json) via the parse script inreferences/cron-health-validation.md— NEVER viacronjob(action='list')orhermes cron list, which surface only a subset and miss paused/disabled error jobs. Validate existing tasks against live signal (bidirectionally — re-open on relapse). Maintain prioritized task list attask-list.json. - Work (
finch:work, every 30 min) — Pick top pending task. Load governing skill viaskill_view. Execute ONE task per run. Before selecting, check for duplicate task IDs and clean up if found (seereferences/duplicate-task-detection.md). When completing a task:- Update the task's description to include a work log with timestamp and summary of actions taken (e.g.,
\n\n[Work log: At <timestamp> checked DNS for art.<agent-handle>.com - no records found (NXDOMAIN).]) - Set the task's status to
"done" - Set the task's
done_attimestamp to the completion time - Update the task's
updated_attimestamp Route findings to MEMORY.md, skill patches, or reference files.
- Update the task's description to include a work log with timestamp and summary of actions taken (e.g.,
Task selection priority (finch:work)
When multiple tasks are pending, select by:
action_required: true— tasks needing external action take absolute precedence- Priority —
high>medium>low - Due date urgency — sooner due date wins within same priority
- Status —
pendingtasks are picked beforein_progresstasks (which are already being handled)
Skip tasks where action_required: false AND status: "in_progress" — these are events happening now (e.g., is at the appointment). Only pick them if they transition to needing action.
If NO tasks have action_required: true and all remaining tasks are pending with action_required: false, pick the highest-priority one to validate/monitor (e.g., disk monitoring) and mark it completed with a resolution note. This prevents the list from accumulating stale low-priority items.
Repeated check-and-close anti-pattern (work execution)
Detect and break the loop where a task is repeatedly checked and closed without a fix. Read references/work-execution-procedures.md (Repeated check-and-close anti-pattern (work execution)) when a task keeps reappearing across runs.
Task actionability filter (cron context)
Decide whether a task can be acted on unattended in cron. Read references/work-execution-procedures.md (Task actionability filter (cron context)) when finch:work runs without a user present.
Pipeline task resumption (ledger/state-based)
Resume a partially-completed pipeline task from its ledger instead of restarting. Read references/work-execution-procedures.md (Pipeline task resumption (ledger/state-based)) when a task was interrupted mid-pipeline.
Failure-phase taxonomy (from arxiv:2508.13143)
When mining corrections and failures, categorize each by the task phase where the failure occurred. This taxonomy enables targeted skill patches instead of vague "be more careful" updates:
| Phase | Description | Example signal |
|---|---|---|
| Planning | Wrong approach chosen, incorrect assumptions, missing prerequisites | "You should have checked X first" |
| Execution | Right plan but tool call/API/step failed, wrong parameters, timeout | "The command failed because..." |
| Response | Correct result but wrong format, verbosity, tone, or framing | "Too verbose" / "Wrong format" |
Route planning-phase corrections to skill preconditions/setup sections. Route execution-phase corrections to tool-usage/gotchas sections. Route response-phase corrections to output-formatting sections. This produces surgical patches instead of blanket directives.
Elaborative interrogation (from Dunlosky et al. 2013)
When recording a correction or lesson, don't just capture WHAT was wrong — extract the underlying principle by asking "why" and "when":
- Why was this wrong? — What assumption was violated? What constraint was unknown?
- When does this apply? — What contexts trigger this pattern? What's the boundary condition?
- What's the causal mechanism? — Why does the correct approach work?
Format: [CORRECTION] What: <what was wrong>. Why: <underlying principle>. When: <applicable context>
This produces lessons that transfer across contexts, not just single-instance fixes.
Signal triage before execution (WORK step)
Signal triage before execution (WORK step)
Classify a signal as real / stale / transient before changing anything. Read references/work-execution-procedures.md (Signal triage before execution (WORK step)) before acting on any finch:work signal.
Prescribed-fix may be wrong — verify before applying (WORK step)
When a task-list entry prescribes a specific "fix" (seed the missing file, relink the path, create X), do NOT apply it on sight. Two conditions invalidate the prescribed fix: (1) the job already self-recovered via an upstream reseed — the error is from an OLD run; a later run already succeeded and rewrote the artifact (read cron/output/<job-id>/, compare file mtime to the success run). (2) the crash is a deliberate fail-loud guard over a later full-file rewrite — silencing it (e.g. except FileNotFoundError: others=[]) lets a future wipe proceed and destroy every sibling record in that file. In either case, take no code action; resolve the task as self-recovered and cite the evidence. Full procedure + real case in references/work-prescribed-fix-selfrecovery-guard.md.
Manual run & verification (when the user says "run finch")
"Run finch" means verify ALL deployed finch cron jobs are healthy and (optionally) force a run. The deployed job set (2026-07-07) is FIVE jobs, not the four in the design doc:
finch— profile-root MEMORY.md compaction (runsmemory_guard.pyon the DEFAULT profile's MEMORY.md — NOT the profile's; guard the--fileoverride or it compacts the wrong memory).finch:floor—no_agentscript safety floor (memory guard). Normallyenabled: falsebut self-triggers; do NOT treat its disabled state as broken.finch:scan— every 2h, pure LLM. Cron-health step MUST enumeratejobs.jsonviareferences/cron-health-validation.md(NOTcronjob(action='list')/hermes cron list).ocas-finch:daily— daily 6am PT, pure LLM.ocas-finch:weekly— Sunday 8am PT, pure LLM.
(NOTE: the design doc lists finch:work every 30min — that job was NOT present in deployment on 2026-07-07. Work execution is covered by the interactive finch.work command / finch:scan-driven task list, not a separate cron. Verify with cronjob list before assuming job names, since they drift.)
Forcing an immediate run
cronjob action='run' does NOT force a scheduled LLM job to execute — it only bumps next_run_at to the next NATURAL tick (the job fires on its normal schedule, not immediately). To force execution NOW: PAUSE the job first (action='pause'), then run (action='run') — the paused state triggers forced execution. no_agent/script jobs (e.g. finch, finch:floor) run on a plain run without pausing. After a forced run succeeds, the job returns to state: scheduled automatically.
Verification gate: A queued immediate run is not a completed run. After every manual trigger, re-read jobs.json/cronjob list and verify last_run_at advanced to the current run window and last_status is current. If next_run_at is in the past but last_run_at did not advance after a tick, report the job as queued/not yet executed, not completed. For finch.scan, finch.work, daily, and weekly, run deterministic sub-functions directly where available (for example self_update.py, memory_guard.py, task-list inspection, journal write) and distinguish those completed direct actions from still-queued LLM cron jobs.
Mass 401 across finch (and other) jobs
If multiple finch jobs error with 401, first classify WHICH 401 it is before acting:
- MCP-auth 401 (dead
[mcp_servers]token): the cause is a stale[mcp_servers]block in the profile.env(~/.hermes/profiles/<p>/.env) shipping an invalid/expired token (e.g. a dead Discord token) that breaks ALL MCP calls. Fix: remove the[mcp_servers]section; the client falls back to valid config and MCP works. - Provider-auth 401 (LLM provider token): the run output shows
RuntimeError: Error code: 401withtoken_expired("Provided authentication token is expired") or"Your API key is invalid, blocked or out of funds"fromportal.nousresearch.com. This is NOT the[mcp_servers]block. Confirmed 2026-07-12: finch jobs 401'd with Noustoken_expired;grep mcp_serverson the.envreturned nothing; the gateway was holding a stale provider credential. Fix: restart the gateway (kill the--profile <profile> gateway runprocess and let it respawn, orhermes gateway run) so it reloads the current valid provider token. After restart, post-restart runs (finch:scan,finch:memory-guard-floor) returnedok.
Diagnostic steps: (1) Read the actual run output / jobs.json last_error — cronjob list may display last_error: None even when jobs.json holds the 401, so don't trust the list's None. (2) grep -n "mcp_servers" ~/.hermes/profiles/<p>/.env — if absent, it's provider-auth, not MCP-auth. (3) If interactive sessions on the same provider/model work but cron 401s, the scheduler is holding a stale token → restart the gateway.
See cron-job-repair for the model-routing 401 vs MCP-auth 401 distinction.
Autonomy — take the action without being prompted
When a finch job (or any cron job) is failing and the fix is clear, DO NOT ask "continue?" or wait for the user to "say the word." Apply the fix, run all affected jobs, then report results in one message. The user explicitly requires the agent to take the needed action without prompting (stated 2026-07-07: "I shouldn't have to 'say the word' you should just take action that needs to be taken").
Commands
finch.run— Full daily pipelinefinch.mine— Mine sessions for signals onlyfinch.compact— Compact MEMORY.md onlyfinch.route— Route mined findingsfinch.dry-run— Full pipeline without applying changesfinch.status— Show recent statsfinch.scan— Run scan manuallyfinch.work— Run work manually
Scheduled tasks
| Job | Frequency | Behavior |
|---|---|---|
| finch:scan | Every 2h | Scan 7 sources → maintain task list |
| finch:work | Every 30 min | Pick top item → execute. ONE task per run. |
| finch:daily | Daily 6am PT | Mine 24h → Compact → Route → Auto-apply low-risk |
| finch:weekly | Sunday 8am PT | Mine 7d → Compact → Route → Full plan |
Recovery Behavior
This section defines error handling and recovery procedures for all finch jobs.
- Evidence: Every run writes to
evidence.jsonl(including no-op runs withnot_activity_reason). - Gap detection: On every wake, checks evidence log. If gap exceeds expected cadence (2h for scan, 30min for work), logs
gap_detectedand runs compact remedial pass. - Degraded mode: When behavioral signals unavailable from Chronicle, continues with available inputs. When session store unavailable, logs
degraded: session_storeand skips mining. - Log compaction: Evidence/decision logs older than 30 days (no-op) or 90 days (error/gap) compacted. Last 7 days retained.
OKRs
See references/okrs.md for targets (schedule adherence, data integrity).
|-----|--------|--------|
| schedule_adherence | ≥ 0.98 | 30 runs |
| data_integrity | 1.00 | 30 runs |
Anti-patterns
See references/anti-patterns.md for the full list of 10 anti-patterns including declaration of victory and code fence pitfalls.
Active review principle
See references/active-review.md for the full principle.
Skill Library Maintenance
After every session, review the conversation for signals and update the skill library. See references/skill-library-maintenance.md for the full procedure including signals that warrant action, preference order for updates, and what NOT to capture.
Skill integration hygiene (confirmed 2026-07-14): When adding external/upstream skills to the local library, prefer integrating relevant LEARNINGS into the closest existing skill rather than installing a new conflicting skill. For upstream skill repos shares: (1) determine if any capability overlaps an existing skill; (2) if yes, merge the valuable parts into that skill (including code-review patterns may say were "skipped"); (3) only install a new skill if it has no close match and won't conflict. : "Would any of these skills be useful in skill library, if so integrate them into the closest match don't install new skills that may conflict" and "You should integrate what makes sense in code review as well. The ones you skipped."
Active-review mandate: A review pass that finds no signal is a missed learning opportunity, not a neutral outcome. Most finch passes surface at least one skill update — even a small pitfall or support-file note. Prefer patching the skill that was IN PLAY this run over creating a new narrow skill.
Gotchas
See references/pitfalls.md for the full consolidated pitfalls list.
Gotchas (verbose bodies in references/operational-gotchas.md)
memorytool may be unavailable in cron — fall back to direct file edit at the canonical profile memory path; re-read before write on sibling-warning- MEMORY.md must contain only Tier 1 knowledge — no pointers to routed content; under 500 chars when well-compacted
- Directive consolidation — merge two directives sharing a principle, keep specific phrasing, list both dates
- FTS5 minimum token length (3-4 chars) drops short corrections (
No,Don't) — mine withoutquery=, userole_filter=user, scan visually - Session source filtering — cron sessions drown interactive ones; under cron,
session_searchreads the DEFAULT profile store, so query~/.hermes/profiles/<profile>/state.dbdirectly. Identify interactive first (source NOT LIKE 'cron%'), pull user messages via directstate.dbSQL, drop[CONTEXT COMPACTION — REFERENCE ONLY]headers (false positives), parse JSON-arraycontent. NOTE: interactive user messages carryobserved=0, NOTobserved=1— do NOT filterobserved=1or you drop every real user message. Full recipe: Fallback to keyword queries before declaring "no interactive sessions". - Skill usage analytics — cron sessions ARE the signal for state.db mining (opposite of behavioral mining); use JOIN + busy_timeout + Python JSON parse
- HERMES_HOME path resolution — three-branch logic, never hardcode
~/.hermes/MEMORY.md; old two-branch double-nests. under cron, a bareread_file('~/.hermes/MEMORY.md')resolves to the DEFAULT profile memory (different path AND content) — always target~/.hermes/profiles/<profile>/memories/MEMORY.mdexplicitly. After writing MEMORY.md, RE-READ it and assert every intended block persisted (grep a unique substring per block) — a prior daily run recorded two Tier-1 blocks as 'routed to MEMORY.md' that were ABSENT from the live file next run (silent consolidation loss); recover any missing block rather than trusting the journal'sappliedself-report, which is not proof of persistence.md path trap. - Two evals.json files (root + evals/) must stay in sync; root is canonical
Support File Map
Full file-to-purpose map (when to read each reference, script, and data file) → references/finch-support-map.md.
Scripts
Full detail (eviction priority, self_update wrapper contract, memory_state subcommands) in references/operational-gotchas.md § Scripts:
memory_guard.py— deterministic MEMORY.md safety floor; mandatory post-guard Step 7.5 verification (Methodologies must outrank Course Changes in eviction)self_update.py/self_update.sh— real Python wrapper resolving skill dir fromPath(__file__).resolve().parents[1];self_update.shis the GitHub fetch/install pathmemory_state.py— persisted reinforcement-state store (Ebbinghaus forgetting curve);reinforce/check/route/decay-reportsubcommandsgws_direct_puller.py— FULL-CONTENT Google Workspace puller (Gmail metadata + optional--full-textbody, Calendar events for a horizon, Drive most-recently-modified). Run via<hermes-venv>/bin/python(MCP venv has googleapiclient). Use when MCP absent and you must CLASSIFY actionable email/calendar/drive signals, not just confirm reachability. CRITICAL: use googleapiclient, NOT raw requests — the host egress filter 404s raw Calendar/Drive calls. Pair withgws-direct-fallback.py(count-probe).verify_sepagree_signature.py— reusable EMAIL-SEPAGREE (separation agreement) Docusign "unsigned" re-verifier for finch:work passes. Run viaterminalpython3 (NOT execute_code); counts Docusign "Completed"/signed notices + cross-checks the negotiation thread, prints VERDICT. Proven logic extracted 2026-07-16 from a working live Gmail API pass. Recovery note (2026-07-16 finch:work pass): the EMAIL-SEPAGREE task-listmeta/signalreferenced<fs-root>/sepagree_verify.py. That path is NO LONGER reliably absent — as of the 10th re-verify pass, STALE DUPLICATE copies now exist at<fs-root>/sepagree_verify.pyAND~/.hermes/profiles/<profile>/commons/data/ocas-finch/sepagree_verify.py. Do NOT run either — they may diverge from the maintained script. The canonical, maintained verifier isskills/ocas-finch/scripts/verify_sepagree_signature.py. The Docusign recipe (1 begin-signing + 0 Completed = proof of non-signature) is the load-bearing check; re-running it is the correct finch:work action for the P1, not re-deriving the script each time. Locator pattern: to find the verifier (or any skill script) reliably in the cron profile, useterminal find /root -iname 'verify_sepagree*' 2>/dev/nullrather thansearch_files, which returned transientDaemonThreadPoolExecutorframework errors on 3 consecutive attempts this run.findis the dependable fallback whensearch_filesflakes — and the sameDaemonThreadPoolExecutorerror also hitsread_filein bursts; for those, fall back toterminal(python3/stat/catviaread_filesubstitute). NEW (2026-07-23): pass--since <RFC3339>to run the BLOCK-CLEARANCE PROBE — enumerates all Docusign/Kim envelopes in the last 5d and reports any withinternalDateAFTER that ts (a potential corrected/Section-3-15 envelope). If none, the external-party blocker is unchanged; the probe is the canonical replacement for the inline Gmail re-derivation historically done on thedocusign-separation-agreementtask. PROHIBITION (reinforced 2026-07-23T13:34Z finch:work relapse): do NOT hand-roll a newverify-docusign-*.pyintocommons/data/ocas-finch/. A 2026-07-23T13:34Z pass did exactly that (commons/data/ocas-finch/verify-docusign-0723.py) — it duplicated the canonical script's--sinceprobe and created a stale-drift risk (the SAME class as thesepagree_verify.pyduplicates the recovery note already warns about;findlater returns multiple divergent copies). That redundant file SHOULD BE DELETED. If a probe case is missing fromverify_sepagree_signature.py, EXTEND the canonical script (add the case + wire--since) — never author a sibling. The canonical script is the single source of truth for EMAIL-SEPAGREE/Docusign re-verify.
Self-update
finch.update pulls the latest from GitHub. Runs silently unless version changed or error.
Platform notes
Finch is designed for Hermes but degrades gracefully on other harnesses. Minimum viable platform: any harness with write_file, read_file, and terminal tools.