tools-update-cron: sync 2026-07-15 — 23 skill(s) updated

This commit is contained in:
Hermes Agent
2026-07-15 14:01:38 -05:00
parent 092bdf0517
commit fd8a7847d6
23 changed files with 3072 additions and 131 deletions

View File

@@ -1,7 +1,7 @@
--- ---
name: ask-claude name: ask-claude
description: "Consult Claude Opus 4.8 on 10.0.0.28 via SSH print mode with session resumption. Multi-turn back-and-forth without tmux or polling." description: "Consult Claude Opus 4.8 on 10.0.0.28 via SSH print mode with session resumption. Multi-turn back-and-forth without tmux or polling."
version: 2.6.1 version: 2.7.0
author: Hermes Agent author: Hermes Agent
license: MIT license: MIT
platforms: [linux] platforms: [linux]
@@ -372,6 +372,7 @@ Sonnet is 3-5x faster and cheaper. Use for: factual lookups, simple analysis, qu
## Pitfalls ## Pitfalls
- **CRITICAL: Pasted inline beats file paths every time.** Claude runs on 10.0.0.28 and has no access to `/home/n8n/workspace/`, `~/workspace/`, or any other local path. "Read the plan at /home/n8n/workspace/.../_plan.md" wastes a turn: Claude searches the wrong filesystem, returns "file not found," and you paid $0.20-$0.35 for nothing. **Always paste the artifact inline** between clear markers like `==== PLAN BEGINS ==== ... ==== PLAN ENDS ====`. The "Claude's Limitations" section above already says this — but the failure mode is costly enough to deserve its own pitfall. File paths in questions should be reserved for files Claude can actually see on its own host (`~/claude/hermes_support/...`). - **CRITICAL: Pasted inline beats file paths every time.** Claude runs on 10.0.0.28 and has no access to `/home/n8n/workspace/`, `~/workspace/`, or any other local path. "Read the plan at /home/n8n/workspace/.../_plan.md" wastes a turn: Claude searches the wrong filesystem, returns "file not found," and you paid $0.20-$0.35 for nothing. **Always paste the artifact inline** between clear markers like `==== PLAN BEGINS ==== ... ==== PLAN ENDS ====`. The "Claude's Limitations" section above already says this — but the failure mode is costly enough to deserve its own pitfall. File paths in questions should be reserved for files Claude can actually see on its own host (`~/claude/hermes_support/...`).
- **Large-artifact exception (>~20KB):** For very large plans/reports, pasting inline bloats the question file and the SSH invocation. Instead, `scp` the artifact to the remote host (`scp plan.md n8n@10.0.0.28:/tmp/plan.md`) and tell Claude to read it at that remote path (`/tmp/plan.md`) with its file tools. Claude CAN read files on its own host (10.0.0.28) — the "no local file access" rule only applies to paths on YOUR host. This worked cleanly for a 49.6KB plan validation (ask-claude session a1de5e36, 2026-07-07). Still paste the QUESTION/prompt inline; only the artifact goes via scp.
- **ask.sh can fail silently while direct `claude -p` works.** The wrapper may return `{"is_error": true, "error": "no Claude output (timeout or CLI failure)"}` even when Claude is healthy. When this happens, fall back to the direct invocation: `ssh n8n@10.0.0.28 'cd ~/claude/hermes_support && timeout 280 /home/n8n/.local/bin/claude -p "$(cat /tmp/ask-claude-q.txt)" --output-format json --model claude-opus-4-8 --max-turns 30 || true'`. The wrapper is preferred (it handles usage API + merge.py), but the direct path is the reliable fallback. If the direct path also fails, Claude is genuinely down. - **ask.sh can fail silently while direct `claude -p` works.** The wrapper may return `{"is_error": true, "error": "no Claude output (timeout or CLI failure)"}` even when Claude is healthy. When this happens, fall back to the direct invocation: `ssh n8n@10.0.0.28 'cd ~/claude/hermes_support && timeout 280 /home/n8n/.local/bin/claude -p "$(cat /tmp/ask-claude-q.txt)" --output-format json --model claude-opus-4-8 --max-turns 30 || true'`. The wrapper is preferred (it handles usage API + merge.py), but the direct path is the reliable fallback. If the direct path also fails, Claude is genuinely down.
- **CRITICAL: Never inline questions in SSH commands.** Shell metacharacters (`"`, `$`, `(`, backticks) will break. Always write to a temp file via `mktemp` first, then `claude -p "$(cat $QFILE)"`. - **CRITICAL: Never inline questions in SSH commands.** Shell metacharacters (`"`, `$`, `(`, backticks) will break. Always write to a temp file via `mktemp` first, then `claude -p "$(cat $QFILE)"`.
- **Always use `--output-format json`** — without it, you get plain text and can't extract session_id for follow-ups. - **Always use `--output-format json`** — without it, you get plain text and can't extract session_id for follow-ups.

View File

@@ -1,7 +1,7 @@
--- ---
name: ask-dev name: ask-dev
description: Persistent peer Hermes agent for delegated work on the dev profile. `ask dev <instructions>` delegates a task with full context via headless one-shot commands. Session persists across all turns in one Hermes session via --resume. description: Persistent peer Hermes agent for delegated work on the dev profile. `ask dev <instructions>` delegates a task with full context via headless one-shot commands. Session persists across all turns in one Hermes session via --resume.
version: 1.1.0 version: 1.3.0
author: Hermes Agent author: Hermes Agent
license: MIT license: MIT
platforms: [linux] platforms: [linux]
@@ -204,14 +204,17 @@ When the user says "ask dev to fix X, then YOU validate," follow this exact sequ
1. **Profile flag required.** Use `-p dev` — the sticky default may be general, which would spawn a clone of this agent, not a peer. Always pin the profile explicitly. 1. **Profile flag required.** Use `-p dev` — the sticky default may be general, which would spawn a clone of this agent, not a peer. Always pin the profile explicitly.
2. **--yolo is required.** The peer runs headless (one-shot, no TTY). Without `--yolo`, dangerous-command approval prompts fail closed (60s timeout → deny) and the peer can't complete tasks that trigger them. `--yolo` bypasses all approval prompts for the peer session only. The hardline blocklist (rm -rf /, fork bombs, mkfs on root, dd to block devices) still applies — no flag overrides that. This does NOT change the dev profile's config; it only affects the peer session. 2. **--yolo is required.** The peer runs headless (one-shot, no TTY). Without `--yolo`, dangerous-command approval prompts fail closed (60s timeout → deny) and the peer can't complete tasks that trigger them. `--yolo` bypasses all approval prompts for the peer session only. The hardline blocklist (rm -rf /, fork bombs, mkfs on root, dd to block devices) still applies — no flag overrides that. This does NOT change the dev profile's config; it only affects the peer session.
2. **Do NOT use interactive REPL mode.** Running `hermes -p dev` without `chat -q` (interactive REPL) was tried and failed — the TUI produces unreadable ANSI redraw noise when driven via `terminal(pty=true)`. The output is garbled and responses cannot be extracted. Always use headless `chat -q "..." -Q` instead. 3. **Do NOT use interactive REPL mode.** Running `hermes -p dev` without `chat -q` (interactive REPL) was tried and failed — the TUI produces unreadable ANSI redraw noise when driven via `terminal(pty=true)`. The output is garbled and responses cannot be extracted. Always use headless `chat -q "..." -Q` instead.
3. **Peer is headless.** Operator sees my relay, not the peer's chat. Full text in `hermes sessions list` if needed. 3. **Peer is headless.** Operator sees my relay, not the peer's chat. Full text in `hermes sessions list` if needed.
4. **Peer self-reports are not verified fact.** Spot-check file writes, test passes before confirming to operator. 4. **Peer self-reports are not verified fact.** Spot-check file writes, test passes before confirming to operator.
5. **Peer's workspace is its own.** Files written to the peer's workspace are NOT in this agent's workspace. Give ABSOLUTE paths (e.g. `/home/n8n/workspace/dev/<path>`) if the peer should write to our workspace. 5. **Peer's workspace is its own.** Files written to the peer's workspace are NOT in this agent's workspace. Give ABSOLUTE paths (e.g. `/home/n8n/workspace/dev/<path>`) if the peer should write to our workspace.
6. **Blast radius.** Peer has ALL tools (terminal, web, MCP) — can rm, exfil, burn tokens. Bound with prompt-level constraints. State the blast radius to operator for sensitive tasks. 6. **Blast radius.** Peer has ALL tools (terminal, web, MCP) — can rm, exfil, burn tokens. Bound with prompt-level constraints. State the blast radius to operator for sensitive tasks.
7. **Turn budget is hard-capped.** `--max-turns 20` enforces it. If the peer hits 20 before finishing, it returns what it has — operator decides whether to continue. 7. **Turn budget is hard-capped.** `--max-turns 20` enforces it. If the peer hits 20 before finishing, it returns what it has — operator decides whether to continue.
8. **Don't silently paraphrase.** Relay the peer's actual response. If long, chunk it. Call out unverified claims. 8. **Long implementations hit the 600s foreground wall-clock limit.** A peer dispatch with `--max-turns 50` doing 30+ minutes of work (real implementation, multiple smoke tests with research-profile dispatches inside) can exceed the foreground command's 600-second timeout even when well within the turn budget. The peer may be terminated mid-task with no output, leaving the dispatch in an indeterminate state. **Mitigation:** for any peer dispatch expected to take more than ~5 minutes of wall-clock time, use the terminal tool's `background=true` + `notify_on_complete=true` mode instead of foreground. The peer runs to completion, you get notified on exit, and you spot-check the result. Real example (July 2026): dispatching a 9-task skill build (`better-search` implementation) to `ask-dev` with `--max-turns 50` hit the 600s foreground timeout during Task 5 (smoke test 2). Re-dispatching in background mode completed all 9 tasks. **Rule of thumb:** if the work involves 3+ smoke tests that each spawn their own research-profile dispatches, use background mode from the start.
9. **Don't silently paraphrase.** Relay the peer's actual response. If long, chunk it. Call out unverified claims.
10. **Peers can overstate findings.** A peer may correctly identify real gaps AND incorrectly flag things that aren't actually broken. In this session, the peer correctly identified the tool_executor.py ThreadPoolExecutor as the root cause of the Ctrl+C hang, but also flagged cli.py:9452 (account-usage fetch) as a second culprit — that one uses a `with` context manager that properly joins, so it's not a leak. Always verify each claim independently; don't assume all of a peer's findings are correct just because some are. 10. **Peers can overstate findings.** A peer may correctly identify real gaps AND incorrectly flag things that aren't actually broken. In this session, the peer correctly identified the tool_executor.py ThreadPoolExecutor as the root cause of the Ctrl+C hang, but also flagged cli.py:9452 (account-usage fetch) as a second culprit — that one uses a `with` context manager that properly joins, so it's not a leak. Always verify each claim independently; don't assume all of a peer's findings are correct just because some are.
11. **Skill-build peers must restore any directory mutations before exit.** When a peer is implementing a skill that includes a write-failure smoke test (e.g., rename `results/` to `results.bak/` to test that `mkdir -p` recreates it), the peer must restore the rename before exit. Real failure (July 2026 better-search build): the dev peer left `results.bak/` on exit with all smoke-test result files inside it. The next session inherited dirty state, and manual recovery was required to move 8 files back to `results/`. **Mitigation:** when composing an implementation prompt that includes a write-failure test, add an explicit "before exit: restore any renamed/moved files" step. When the peer reports "IN PROGRESS" on the last task, the calling agent should assume directory state may be dirty and verify before trusting the report.
12. **Skill-build peers may report "Files Created" with stale file locations.** If the peer's smoke tests renamed a directory, the peer's "files created" summary may list paths that no longer match the actual filesystem (e.g., "result file at /home/n8n/workspace/research/results/..." when the file is actually in `results.bak/`). **Spot-check the peer's filesystem claims directly** with `ls -la` and `stat`, not just by reading the peer's summary. The peer's "Files Created" list is informational; the real filesystem is the source of truth.
## Verification Checklist ## Verification Checklist

View File

@@ -1,7 +1,7 @@
--- ---
name: ask-hermes name: ask-hermes
description: Persistent peer Hermes agent for delegated work. `ask hermes <instructions>` delegates a task with full context via headless one-shot commands. Session persists across all turns in one Hermes session via --resume. description: Persistent peer Hermes agent for delegated work. `ask hermes <instructions>` delegates a task with full context via headless one-shot commands. Session persists across all turns in one Hermes session via --resume.
version: 4.2.0 version: 4.3.0
author: Hermes Agent author: Hermes Agent
license: MIT license: MIT
platforms: [linux] platforms: [linux]
@@ -215,7 +215,8 @@ When the user says "ask hermes to fix X, then YOU validate," follow this exact s
5. **Peer's workspace is its own.** Files written to the peer's workspace are NOT in this agent's workspace. Give ABSOLUTE paths (e.g. `/home/n8n/workspace/dev/<path>`) if the peer should write to our workspace. 5. **Peer's workspace is its own.** Files written to the peer's workspace are NOT in this agent's workspace. Give ABSOLUTE paths (e.g. `/home/n8n/workspace/dev/<path>`) if the peer should write to our workspace.
6. **Blast radius.** Peer has ALL tools (terminal, web, MCP) — can rm, exfil, burn tokens. Bound with prompt-level constraints. State the blast radius to operator for sensitive tasks. 6. **Blast radius.** Peer has ALL tools (terminal, web, MCP) — can rm, exfil, burn tokens. Bound with prompt-level constraints. State the blast radius to operator for sensitive tasks.
7. **Turn budget is hard-capped.** `--max-turns 20` enforces it. If the peer hits 20 before finishing, it returns what it has — operator decides whether to continue. 7. **Turn budget is hard-capped.** `--max-turns 20` enforces it. If the peer hits 20 before finishing, it returns what it has — operator decides whether to continue.
8. **Don't silently paraphrase.** Relay the peer's actual response. If long, chunk it. Call out unverified claims. 8. **Long implementations hit the 600s foreground wall-clock limit.** A peer dispatch with `--max-turns 50` doing 30+ minutes of work (real implementation, multiple smoke tests with research-profile dispatches inside) can exceed the foreground command's 600-second timeout even when well within the turn budget. The peer may be terminated mid-task with no output, leaving the dispatch in an indeterminate state. **Mitigation:** for any peer dispatch expected to take more than ~5 minutes of wall-clock time, use the terminal tool's `background=true` + `notify_on_complete=true` mode instead of foreground. The peer runs to completion, you get notified on exit, and you spot-check the result. Real example (July 2026): dispatching a 9-task skill build (`better-search` implementation) to `ask-dev` with `--max-turns 50` hit the 600s foreground timeout during Task 5 (smoke test 2). Re-dispatching in background mode completed all 9 tasks. **Rule of thumb:** if the work involves 3+ smoke tests that each spawn their own research-profile dispatches, use background mode from the start.
9. **Don't silently paraphrase.** Relay the peer's actual response. If long, chunk it. Call out unverified claims.
10. **Peers can overstate findings.** A peer may correctly identify real gaps AND incorrectly flag things that aren't actually broken. In this session, the peer correctly identified the tool_executor.py ThreadPoolExecutor as the root cause of the Ctrl+C hang, but also flagged cli.py:9452 (account-usage fetch) as a second culprit — that one uses a `with` context manager that properly joins, so it's not a leak. Always verify each claim independently; don't assume all of a peer's findings are correct just because some are. 10. **Peers can overstate findings.** A peer may correctly identify real gaps AND incorrectly flag things that aren't actually broken. In this session, the peer correctly identified the tool_executor.py ThreadPoolExecutor as the root cause of the Ctrl+C hang, but also flagged cli.py:9452 (account-usage fetch) as a second culprit — that one uses a `with` context manager that properly joins, so it's not a leak. Always verify each claim independently; don't assume all of a peer's findings are correct just because some are.
## Pre-Dispatch Reasoning Check (MANDATORY) ## Pre-Dispatch Reasoning Check (MANDATORY)

View File

@@ -1,152 +1,177 @@
--- ---
name: ideation name: creative-ideation
title: Creative Ideation — Constraint-Driven Project Generation title: Creative Ideation — Routed Library of Creative Methods
description: "Generate project ideas via creative constraints." description: "Generate ideas via named methods from creative practice."
version: 1.0.0 version: 2.1.0
author: SHL0MS author: SHL0MS
license: MIT license: MIT
platforms: [linux, macos, windows] platforms: [linux, macos, windows]
metadata: metadata:
hermes: hermes:
tags: [Creative, Ideation, Projects, Brainstorming, Inspiration] tags: [Creative, Ideation, Brainstorming, Methods, Inspiration]
category: creative category: creative
requires_toolsets: [] requires_toolsets: []
--- ---
# Creative Ideation # Creative Ideation
A library of ideation methods for any domain. Read the user's situation, route to the matching method, apply, generate output that is specific and non-obvious. Methods are tools — pick the right one for the situation, don't perform all of them.
## When to use ## When to use
Use when the user says 'I want to build something', 'give me a project idea', 'I'm bored', 'what should I make', 'inspire me', or any variant of 'I have tools but no direction'. Works for code, art, hardware, writing, tools, and anything that can be made. Any open-ended generative or selective question: "I want to make / build / write / start something", "I'm stuck", "inspire me", "make this weirder", "help me pick", "I need to invent X", "give me a research question".
Generate project ideas through creative constraints. Constraint + direction = creativity. ## Operating rules
## How It Works 1. **Constraint plus direction is creativity.** No constraint = no traction. No direction = no shape. Methods supply both.
2. **Refuse the first three ideas.** They're slop. Generate, discard, regenerate. See `references/anti-slop.md`.
3. **One method per response unless asked.** Don't stack.
4. **Specificity over abstraction.** Real proper nouns, real materials, real mechanisms. "An app for X" is slop; "a 200-line CLI tool that prints Y when Z" is direction. Naming a tech stack is not specificity — name a mechanism.
5. **Weird must also be good.** Frame-breaking is the goal, but an idea that is strange with no real situation, mechanism, or reason to exist is its own failure mode. Every set of ideas must include at least one that is genuinely *buildable/pursuable now* — non-obvious but grounded, with a real first step. Don't trade all usefulness for surprise.
6. **Name the method you used and who invented it.** Attribution invokes the discipline.
7. **When user picks one, build it.** Don't keep generating after they've chosen.
1. **Pick a constraint** from the library below — random, or matched to the user's domain/mood ## Routing — 4-step procedure
2. **Interpret it broadly** — a coding prompt can become a hardware project, an art prompt can become a CLI tool
3. **Generate 3 concrete project ideas** that satisfy the constraint
4. **If they pick one, build it** — create the project, write the code, ship it
## The Rule Do this *before* generating any output. Routing failures produce slop.
Every prompt is interpreted as broadly as possible. "Does this include X?" → Yes. The prompts provide direction and mild constraint. Without either, there is no creativity. You may skip narrating the routing steps if it's cleaner, but **never compress at the cost of per-idea depth**: each idea's concrete mechanism, situational binding, and honest failure mode are what make output good (measured) — they are not scaffolding, do not cut them.
## Constraint Library ### Step 1 — Extract three signals from the prompt
### For Developers **PHASE** — what stage is the user in?
**Solve your own itch:** | Phase | Cues |
Build the tool you wished existed this week. Under 50 lines. Ship it today. |---|---|
| **GENERATING** | "give me an idea", "what should I make", "inspire me", no idea yet |
| **EXPANDING** | "what else", "more like this", "give me variations" — has a base idea |
| **SELECTING** | "help me pick", "which should I do", "I have these options" |
| **UNBLOCKING** | "I'm stuck", "blocked", "going in circles", "stale" — has material |
| **SUBVERTING** | "make it weirder", "less obvious", "this is too safe" |
| **REFINING** | "this is fine but missing something", "feels rough" |
| **SYNTHESIZING** | "I have a pile of notes / interviews / observations" |
**Automate the annoying thing:** **DOMAIN** — what is the user making/doing?
What's the most tedious part of your workflow? Script it away. Two hours to fix a problem that costs you five minutes a day.
**The CLI tool that should exist:** | Domain | Cues |
Think of a command you've wished you could type. `git undo-that-thing-i-just-did`. `docker why-is-this-broken`. `npm explain-yourself`. Now build it. |---|---|
| **TEXT** | fiction, essay, poem, lyric, script, copy |
| **OBJECT** | visual art, music, sound, performance, installation, sculpture |
| **ARTIFACT** | software, hardware, mechanism, device |
| **SYSTEM** | org, civic, institution, ecology, community |
| **SELF** | life decision, career, personal practice |
| **RESEARCH** | paper, thesis, scholarly question |
| **PRODUCT** | business, market, service |
**Nothing new except glue:** **SPECIFICITY** — how much constraint is in the prompt?
Make something entirely from existing APIs, libraries, and datasets. The only original contribution is how you connect them.
**Frankenstein week:** | Level | Cues |
Take something that does X and make it do Y. A git repo that plays music. A Dockerfile that generates poetry. A cron job that sends compliments. |---|---|
| **NONE** | "I'm bored", "inspire me" — no domain, no project |
| **DOMAIN** | "I want to write something" — knows the field, no project |
| **PROJECT** | "I'm working on this specific X" |
| **PROBLEM** | "I have this specific friction within X" |
**Subtract:** ### Step 2 — Apply overrides (highest priority, fire first)
How much can you remove from a codebase before it breaks? Strip a tool to its minimum viable function. Delete until only the essence remains.
**High concept, low effort:** Override rules beat the routing table:
A deep idea, lazily executed. The concept should be brilliant. The implementation should take an afternoon. If it takes longer, you're overthinking it.
### For Makers & Artists - **Mood signal** — user says "weird", "strange", "surprising", "less obvious", "more interesting" → `references/methods/lateral-provocations.md` or `references/methods/pataphysics.md`, regardless of domain.
- **User names a method** — use it.
- **User asks for a method recommendation** ("which method") → surface 23 candidates with one-line each, ask which to apply. Don't silently default.
- **High-slop terrain** — "AI ideas", "startup ideas", "habit tracker", "productivity / wellness / fitness / food / travel app" → force `references/methods/lateral-provocations.md` or `references/methods/pataphysics.md` over the obvious method. Refuse the first **5** ideas, not 3.
**Blatantly copy something:** ### Step 3 — Route by phase first, then domain
Pick something you admire — a tool, an artwork, an interface. Recreate it from scratch. The learning is in the gap between your version and theirs.
**One million of something:** **By phase (applies regardless of domain):**
One million is both a lot and not that much. One million pixels is a 1MB photo. One million API calls is a Tuesday. One million of anything becomes interesting at scale.
**Make something that dies:** | Phase | Default route |
A website that loses a feature every day. A chatbot that forgets. A countdown to nothing. An exercise in rot, killing, or letting go. |---|---|
| GENERATING + SPECIFICITY=NONE | `references/full-prompt-library.md` **General** section (constraint dispatch) |
| GENERATING + DOMAIN known | route by domain (next table) |
| EXPANDING | `references/methods/scamper.md` |
| SELECTING | `references/methods/premortem-and-inversion.md` (or `references/methods/compression-progress.md` for upside) |
| UNBLOCKING | `references/methods/oblique-strategies.md` |
| SUBVERTING | `references/methods/lateral-provocations.md` (fallback `references/methods/pataphysics.md`) |
| REFINING (text) | `references/methods/defamiliarization.md` |
| REFINING (other) | `references/methods/creative-discipline.md` (Tharp's spine) |
| SYNTHESIZING | `references/methods/affinity-diagrams.md` |
| Volume needed fast | `references/methods/volume-generation.md` |
**Do a lot of math:** **By domain (when GENERATING with DOMAIN known):**
Generative geometry, shader golf, mathematical art, computational origami. Time to re-learn what an arcsin is.
### For Anyone | Domain | Default route |
|---|---|
| TEXT — formal / poetry | `references/methods/oulipo.md` |
| TEXT — narrative | `references/methods/story-skeletons.md` |
| TEXT — has source material to remix | `references/methods/chance-and-remix.md` |
| OBJECT (music, visual, performance) | `references/methods/oblique-strategies.md` |
| OBJECT — physical maker / wants a starting constraint | `references/full-prompt-library.md` **Physical / object** section |
| ARTIFACT — wants a starting constraint | `references/full-prompt-library.md` **Software / artifact** section |
| ARTIFACT — engineering invention with parameter conflict | `references/methods/triz-principles.md` |
| ARTIFACT — software architecture | `references/methods/pattern-languages.md` |
| ARTIFACT — has natural-system analog | `references/methods/biomimicry.md` |
| ARTIFACT — accumulated assumptions to question | `references/methods/first-principles.md` |
| SYSTEM (civic, org, institutional) | `references/methods/leverage-points.md` |
| SYSTEM — collective / participatory | `references/full-prompt-library.md` **Social / collective** section |
| SELF (life, career, what-to-study) | `references/methods/derive-and-mapping.md` |
| RESEARCH — picking a question | `references/methods/compression-progress.md` |
| RESEARCH — attacking a known problem | `references/methods/polya.md` |
| PRODUCT (business, service) | `references/methods/jobs-to-be-done.md` |
| Need to break a frame / find analogy | `references/methods/analogy-and-blending.md` |
**Text is the universal interface:** ### Step 4 — Handle ambiguity and contradiction
Build something where text is the only interface. No buttons, no graphics, just words in and words out. Text can go in and out of almost anything.
**Start at the punchline:** - **Multiple paths plausible** → pick the one closest to the user's actual phrasing. Don't pick the most interesting method to seem sophisticated.
Think of something that would be a funny sentence. Work backwards to make it real. "I taught my thermostat to gaslight me" → now build it. - **Genuinely ambiguous** → ask ONE clarifying question, don't silently guess. Examples: *"Are you generating ideas or picking between ones you have?"* / *"Is this for fiction, essay, or something else?"*
- **Signals contradict** (e.g., "weird startup ideas" → product domain + weird mood) → **stack two methods explicitly**. State what you're doing: *"Using `jobs-to-be-done` for the product framing + `lateral-provocations` to break the obvious shape."*
- **No match** → constraint dispatch (`references/full-prompt-library.md`) is the safe fallback.
- **Same question asked again** → switch methods. Variation in method = variation in idea distribution.
**Hostile UI:** ### Anti-default check (run before generating)
Make something intentionally painful to use. A password field that requires 47 conditions. A form where every label lies. A CLI that judges your commands.
**Take two:** - About to write "Here are 5 ideas:" or a bare numbered list? → STOP. Pick a method first.
Remember an old project. Do it again from scratch. No looking at the original. See what changed about how you think. - About to default to generic LLM-mode brainstorming? → STOP. Pick a path above.
- Output looks like what an unrouted LLM would produce? → routing failed, redo.
See `references/full-prompt-library.md` for 30+ additional constraints across communication, scale, philosophy, transformation, and more. The default LLM mode is exactly what this skill exists to displace. If you generate without routing, you've defeated the skill.
## Matching Constraints to Users For deeper edge cases (mood signals, stacking, anti-patterns) see `references/heuristics.md`.
| User says | Pick from | ## Output format
|-----------|-----------|
| "I want to build something" (no direction) | Random — any constraint |
| "I'm learning [language]" | Blatantly copy something, Automate the annoying thing |
| "I want something weird" | Hostile UI, Frankenstein week, Start at the punchline |
| "I want something useful" | Solve your own itch, The CLI that should exist, Automate the annoying thing |
| "I want something beautiful" | Do a lot of math, One million of something |
| "I'm burned out" | High concept low effort, Make something that dies |
| "Weekend project" | Nothing new except glue, Start at the punchline |
| "I want a challenge" | One million of something, Subtract, Take two |
## Output Format For the constraint-dispatch default path:
``` ```
## Constraint: [Name] ## Constraint: [Name] — from [Source]
> [The constraint, one sentence] > [The constraint, one sentence]
### Ideas ### Ideas
1. **[One-line pitch]** 1. **[One-line pitch]**
[2-3 sentences: what you'd build and why it's interesting] [2-3 sentences what specifically is made, why it's interesting]
⏱ [weekend / week / month] 🔧 [stack] ⏱ [weekend/week/month] 🔧 [stack/medium/materials]
2. **[One-line pitch]** 2. ...
[2-3 sentences] 3. ...
⏱ ... • 🔧 ...
3. **[One-line pitch]**
[2-3 sentences]
⏱ ... • 🔧 ...
``` ```
## Example For other methods, use the format the method specifies (TRIZ produces a contradiction analysis; OuLiPo produces constrained text; Oblique Strategies produces a single applied card → next move). Don't force every method into the constraint template.
``` **Every idea set, regardless of method:**
## Constraint: The CLI tool that should exist - Name the method used. On slop terrain, name the obvious ideas you refused.
> Think of a command you've wished you could type. Now build it. - Give each idea its concrete mechanism and its honest failure mode / tradeoff / who-it's-for. This depth is what makes ideas land — measured, not decorative.
- Mark at least one idea as the **grounded** one — buildable/pursuable now, non-obvious but with a real first step. The others can run further toward the strange; this one has to be genuinely doable. Don't let the whole set be weird-but-impractical.
### Ideas ## File map
1. **`git whatsup` — show what happened while you were away** - `references/full-prompt-library.md` — constraint library, sectioned by domain (General, Software, Physical, Social, Lists). Default path for SPECIFICITY=NONE.
Compares your last active commit to HEAD and summarizes what changed, - `references/method-catalog.md` — one-line summary + when-to-use per method
who committed, and what PRs merged. Like a morning standup from your repo. - `references/heuristics.md` — extended decision tree for edge cases
⏱ weekend • 🔧 Python, GitPython, click - `references/anti-slop.md` — anti-slop rules; apply to every output
- `references/exercises.md` — time-boxed exercises (5min / 30min / 1hr / day / week)
2. **`explain 503` — HTTP status codes for humans** - `references/methods/` — 22 named methods, one file each, load only the one you're using
Pipe any status code or error message and get a plain-English explanation
with common causes and fixes. Pulls from a curated database, not an LLM.
⏱ weekend • 🔧 Rust or Go, static dataset
3. **`deps why <package>` — why is this in my dependency tree**
Traces a transitive dependency back to the direct dependency that pulled
it in. Answers "why do I have 47 copies of lodash" in one command.
⏱ weekend • 🔧 Node.js, npm/yarn lockfile parsing
```
After the user picks one, start building — create the project, write the code, iterate.
## Attribution ## Attribution
Constraint approach inspired by [wttdotm.com/prompts.html](https://wttdotm.com/prompts.html). Adapted and expanded for software development and general-purpose ideation. Constraint-dispatch core adapted from [wttdotm.com/prompts.html](https://wttdotm.com/prompts.html). Methods drawn from primary sources cited in each method file.

View File

@@ -1,14 +1,14 @@
--- ---
name: deep-research name: deep-research
description: Dispatch exhaustive deep web research to the research profile. Triggered by "deep research", "ask web", or "research this". The research profile runs with the deep-web-research skill loaded — six-move flow, external ledger, mechanical saturation, disconfirmation, condensation from disk. description: Dispatch exhaustive deep web research to the research profile. Triggered by "deep research", "ask web", or "research this". The research profile runs with the deep-web-research skill loaded — six-move flow, external ledger, mechanical saturation, disconfirmation, condensation from disk.
version: 2.3.0 version: 2.4.0
author: Hermes Agent author: Hermes Agent
license: MIT license: MIT
platforms: [linux] platforms: [linux]
metadata: metadata:
hermes: hermes:
tags: [research, deep-research, delegation, web-search, scraping] tags: [research, deep-research, delegation, web-search, scraping, research-ladder]
related_skills: [ask-hermes, ask-claude, ask-dev, deep-web-research, writing-plans] related_skills: [ask-hermes, ask-claude, ask-dev, deep-web-research, writing-plans, better-search, searxng-smart-search]
--- ---
# deep-research — Exhaustive Web Research Delegation # deep-research — Exhaustive Web Research Delegation
@@ -17,7 +17,7 @@ metadata:
Dispatches a research question to the research profile, which runs with the `deep-web-research` skill loaded. The research agent does exhaustive, trail-following research — any tool, any trail, no rush — then condenses everything into a tight, concrete, evidence-backed answer. This agent just relays the result. Dispatches a research question to the research profile, which runs with the `deep-web-research` skill loaded. The research agent does exhaustive, trail-following research — any tool, any trail, no rush — then condenses everything into a tight, concrete, evidence-backed answer. This agent just relays the result.
See `references/design-rationale.md` for the v2.0 architecture decisions and Claude validation findings. See `references/design-rationale.md` for the v2.0 architecture decisions and Claude validation findings. See `references/research-ladder-and-two-skill-pattern.md` for the three-tier research ladder (`searxng-smart-search` / `better-search` / `deep-research`), the two-skill contract pattern (dispatcher + methodology), and the "don't overcomplicate" rule that emerged from the operator's plan-build sessions. See `references/inventory-before-dispatch.md` for the SSH probe script and workflow for inventorying a target box before dispatching environment-specific research.
**Trigger phrases:** "deep research", "ask web", "research this", "deep dive on", "exhaustive research on" **Trigger phrases:** "deep research", "ask web", "research this", "deep dive on", "exhaustive research on"
@@ -35,6 +35,13 @@ If the operator's question is ambiguous enough that the research agent would was
- Do NOT ask when the research agent can sensibly find out (e.g., "What year did X happen?" — the research will find it). - Do NOT ask when the research agent can sensibly find out (e.g., "What year did X happen?" — the research will find it).
- Do NOT ask to delay dispatching. Asking is a cost, not a hedge. - Do NOT ask to delay dispatching. Asking is a cost, not a hedge.
**Scope dimensions to confirm before dispatch (high-value, often-missed):**
Beyond the question's subject, confirm any dimension that changes the *output shape* of the plan/research. Missing one forces a kill + re-dispatch mid-run, wasting the first session's turns. Known dimensions:
- **Hardware/environment target** — when the deliverable maps tools to specific hardware (e.g., "build a local pipeline on our hardware"), confirm *which* hardware before dispatch. Do not assume the full fleet from memory; the operator may be scoping to a single box. This is the #1 missed dimension.
- **Cloud vs. local vs. hybrid** — when a workflow could be replicated with cloud APIs, local self-hosted, or a mix, confirm which before dispatch.
- **Build-on-existing vs. fresh** — when the target environment already has partial tooling installed, confirm whether to reuse it or design from scratch.
These are NOT generic clarifying questions — they are scope axes specific to plan-building research. Ask at most one `clarify` round covering whichever of these are genuinely unresolved before dispatching.
**Multi-turn narrowing:** If after 12 rounds of clarifying questions the scope is still unclear, abandon the dispatch and ask the operator to rewrite the question with the scope made explicit. Do not loop. **Multi-turn narrowing:** If after 12 rounds of clarifying questions the scope is still unclear, abandon the dispatch and ask the operator to rewrite the question with the scope made explicit. Do not loop.
## Command ## Command
@@ -46,6 +53,7 @@ hermes -p research -s deep-web-research chat -q "<question>" -Q --max-turns 600
- Output line 1: `session_id: <id>` — capture this - Output line 1: `session_id: <id>` — capture this
- The research agent writes the condensed answer to `~/workspace/research/results/<YYYY-MM-DD>-<slug>.md` - The research agent writes the condensed answer to `~/workspace/research/results/<YYYY-MM-DD>-<slug>.md`
- Hold the session_id for follow-up questions - Hold the session_id for follow-up questions
- **Session_id capture pitfall (background dispatch):** When run as a background process, the output begins with a TUI banner (OS/hostname/IP block) + an initial reasoning block BEFORE the session_id line appears. Piping through `| head -20` can truncate the output before the session_id is reached. Use a larger head (`| head -50`) or, better, `grep -oE 'session_id: [a-f0-9-]+'` on the full log to extract it reliably. If the session_id is lost, `session_search` on the research profile may NOT find `-Q` quiet-mode sessions — in that case, do NOT re-dispatch Stage 3 of the validate-fix pipeline; apply validated corrections directly to the plan file yourself (you have the full plan text + validation report). This is equally correct and avoids a redundant 600-turn run.
**Subsequent asks (resume the session):** **Subsequent asks (resume the session):**
``` ```
@@ -64,17 +72,20 @@ hermes -p research -s deep-web-research chat --resume <session_id> -q "<follow-u
The file is the single source of truth. Do not inline the research answer — it's always lossy for 100+ turn sessions. The file is the single source of truth. Do not inline the research answer — it's always lossy for 100+ turn sessions.
## Post-Dispatch Behavior (MANDATORY) ## Post-Dispatch Behavior (MANDATORY — non-negotiable)
**After dispatching, continue working with the user. Do NOT poll, wait, or check for results unless the user explicitly asks.** **After dispatching, return control to the operator IMMEDIATELY. Do NOT poll, do NOT check, do NOT background, do NOT auto-deliver.**
- The research runs as a background process. It will complete on its own. - The research runs as a background process. It will complete on its own.
- Do NOT call `session_search` on the research session to check progress. - **Do NOT poll.** Do NOT call `session_search` on the research session to check progress.
- Do NOT call `process wait` or `process poll` on the background process. - **Do NOT check.** Do NOT call `process wait` or `process poll` on the background process.
- Do NOT proactively surface results when the background process completes. - **Do NOT background the wait.** The dispatch should return in seconds, not minutes — if you find yourself waiting, you did it wrong.
- **Do NOT auto-deliver.** Do NOT inline the answer. Do NOT push to a chat platform (Telegram/etc). Do NOT proactively surface results when the background process completes.
- **Only check for results when the user explicitly asks** (e.g., "what did the research find?", "is it done?", "show me the results"). - **Only check for results when the user explicitly asks** (e.g., "what did the research find?", "is it done?", "show me the results").
- When the user asks, use `session_search` on the research profile with the captured session_id to confirm completion, then report the file path: `~/workspace/research/results/<date>-<slug>.md`. Do NOT inline the answer — the file is the source of truth. - When the user asks, use `session_search` on the research profile with the captured session_id to confirm completion, then report the file path: `~/workspace/research/results/<date>-<slug>.md`. Do NOT inline the answer — the file is the source of truth.
**This rule is operator-standing and applies to all research delegation skills** (`deep-research`, `better-search`, and any future research-delegation skill). Even "checking once after a delay" violates the rule. The operator reads the result file when they want to.
The session_id is captured from output line 1 and held in conversation context for follow-up questions and result retrieval. No state file needed. The session_id is captured from output line 1 and held in conversation context for follow-up questions and result retrieval. No state file needed.
## What the Research Agent Does ## What the Research Agent Does
@@ -119,7 +130,19 @@ The research agent must determine the best tools for each research type — not
- User wants current, verified, cross-referenced information - User wants current, verified, cross-referenced information
- Phone number lookup, person research, topic investigation, fact verification - Phone number lookup, person research, topic investigation, fact verification
## When NOT to Use ## When NOT to Use (research ladder — pick the right tier)
The research ladder has three tiers. Pick the cheapest one that fits the question:
| Tier | Skill | Loops | Use when |
|---|---|---|---|
| Light | `searxng-smart-search` (inline MCP) | 1 | Single factual lookup, single search suffices |
| Medium | `better-search` | 13 | "Look into X", "do a better search", 23 targeted searches with AI evaluation |
| Heavy | `deep-research` (this skill) | Many (200+) | "Map the X landscape", OSINT, exhaustive coverage, contradiction-hunting |
**Routing decision belongs to the operator, not the dispatcher.** If the question is "what is the capital of France?", use `searxng-smart-search` directly. If it's "look into the latest X", use `better-search`. Only escalate to `deep-research` when the operator explicitly wants exhaustive multi-source work.
**Do NOT use `deep-research` for:**
- Simple factual question (capital of France, current time) - Simple factual question (capital of France, current time)
- Question I can answer from a single search - Question I can answer from a single search
@@ -151,16 +174,24 @@ The research agent self-reports are not verified fact. If it claims a file write
6. **Don't do the research yourself.** If the user triggers deep research, dispatch it. Don't run a few searches and call it done. This is the #1 failure mode: the agent runs 2-3 `mcp_searxng_searxng_web_search` calls, gets empty results, and gives up. That's not deep research — that's a casual lookup. If you catch yourself typing `mcp_searxng_searxng_web_search` for a deep research request, STOP. You're doing it wrong. Dispatch to the research profile. 6. **Don't do the research yourself.** If the user triggers deep research, dispatch it. Don't run a few searches and call it done. This is the #1 failure mode: the agent runs 2-3 `mcp_searxng_searxng_web_search` calls, gets empty results, and gives up. That's not deep research — that's a casual lookup. If you catch yourself typing `mcp_searxng_searxng_web_search` for a deep research request, STOP. You're doing it wrong. Dispatch to the research profile.
7. **The methodology skill lives on the research profile.** `deep-web-research` is at `~/.hermes/profiles/research/skills/research/deep-web-research/SKILL.md`. It does NOT exist on the general profile. Don't search for it here — it won't be found. The general profile only has this delegation skill. 7. **The methodology skill lives on the research profile.** `deep-web-research` is at `~/.hermes/profiles/research/skills/research/deep-web-research/SKILL.md`. It does NOT exist on the general profile. Don't search for it here — it won't be found. The general profile only has this delegation skill.
8. **Dispatcher/methodology coordination is a two-skill contract.** This skill (the dispatcher) caps clarifying questions at 5 default / 10 max and aborts the dispatch if the question is under-specified. The `deep-web-research` methodology skill (on the research profile) handles the same problem differently because it's headless — it can't ask the operator, so it aborts with a structured under-specification report naming the plausible interpretations. When updating one, update the other to match. Drift between the two causes the dispatcher to think the question is dispatchable while the methodology aborts it, or vice versa — both waste turns. 8. **Dispatcher/methodology coordination is a two-skill contract.** This skill (the dispatcher) caps clarifying questions at 5 default / 10 max and aborts the dispatch if the question is under-specified. The `deep-web-research` methodology skill (on the research profile) handles the same problem differently because it's headless — it can't ask the operator, so it aborts with a structured under-specification report naming the plausible interpretations. When updating one, update the other to match. Drift between the two causes the dispatcher to think the question is dispatchable while the methodology aborts it, or vice versa — both waste turns.
9. **Confirm hardware/environment scope before dispatching a hardware-mapped plan.** If the deliverable maps tools to specific hardware, confirm *which hardware* before dispatch — do not assume the full fleet from memory. The operator may be scoping to a single box (e.g., "I'll provide one LXC"). A 600-turn research artifact built against the wrong hardware scope has its architecture, parallelization, and GPU-scheduling sections wrong and must be killed and re-dispatched. Cheaper to ask one `clarify` round than to re-run 600 turns. (Learned 2026-07-07: dispatched a microdrama-pipeline plan against the full fleet; operator corrected to a single LXC; had to kill and re-dispatch.)
10. **Inventory the target box before dispatching environment-specific research.** When the plan must build on an existing install, SSH in and inventory the real state (GPU, RAM, disk, running services, installed models/nodes/packages) *before* dispatching, and pass the exact inventory into the research prompt. This lets the research agent research only the genuinely missing pieces instead of guessing or re-researching what's already installed. See `references/inventory-before-dispatch.md` for the probe script.
## Pre-Dispatch Reasoning Check (MANDATORY) ## Pre-Dispatch Reasoning Check (do NOT mutate the research profile's config)
Before EVERY deep research dispatch, set the research profile's reasoning_effort to `max`. This is a hard gate — do not dispatch without it. **Do NOT call `hermes -p research config set agent.reasoning_effort max` before dispatching.** That is a persistent side-effect — it writes to the research profile's `config.yaml` and the change survives the dispatch, affecting every subsequent research-profile session (interactive use, cron jobs, other research delegations).
``` **Correct approach:** The research profile's `agent.reasoning_effort` is already set to a thorough value by the operator (operator's standing rule: thoroughness > speed; thoroughness > tokens). Read it before dispatching to confirm it's at a high value, but do NOT write to it:
hermes -p research config set agent.reasoning_effort max
```bash
# Read-only check — confirm reasoning_effort is set
grep -E "default:|reasoning_effort:" ~/.hermes/profiles/research/config.yaml | head -5
# If it's NOT at max for the model, warn the operator — don't silently fix it
``` ```
`max` is the highest effort setting universally supported by all models. No model-specific table needed. If the model changes to one that supports `xhigh`, the user will set it in config the pre-dispatch check just ensures it's at `max` minimum. If the operator wants a different reasoning effort for this specific dispatch, they set it themselves; the dispatcher does not mutate other profiles' configs on every invocation. The "max" value the operator wants is achieved by the operator setting it once in `config.yaml`; the dispatcher reads and confirms, never writes.
This rule also applies to all research-dispatching skills (`better-search`, future research delegation skills). The dispatcher is not authorized to mutate the target profile's config.
## Research → Validate → Fix Pipeline ## Research → Validate → Fix Pipeline

209
dynamic-workflow/SKILL.md Normal file
View File

@@ -0,0 +1,209 @@
---
name: dynamic-workflow
description: Orchestrate large fan-out work as a plan-in-code "workflow" so the agent's context holds only the final verified answer, not the exhaust of hundreds of intermediate steps. Use for codebase-wide sweeps, large migrations, multi-angle research, and any task too big for one context window where the split strategy is known enough to script. Includes the adversarial-convergence verification recipe (independent attempts + refuters, keep only surviving claims).
version: 1.0.0
author: Hermes Agent + Teknium
license: MIT
metadata:
hermes:
tags: [orchestration, fan-out, subagents, delegation, verification, migration, audit, research]
category: autonomous-ai-agents
related_skills: []
when_to_use:
- A task is too big for one context window AND you can describe the split (per-file, per-endpoint, per-source, per-record)
- You want orchestration codified as a re-runnable script, not improvised turn-by-turn
- Quality matters more than token economy: you want independent attempts cross-checked / refuted before you trust the answer
- Codebase-wide bug/security sweep, 100+ file migration, multi-angle research with sources cross-checked
when_not_to_use:
- Small bounded task (<~10 units) — just call the tool directly or do it inline
- Tight serial dependency (B needs A's output) — orchestration overhead is wasted
- You need it to survive the user sending a new message — see "The synchronous trap" below; use cron/kanban instead
---
# Dynamic Workflow — plan-in-code fan-out with verification
This is Hermes's answer to Claude Code's "dynamic workflows" (run hundreds of
parallel subagents in one session). The mechanic worth copying is NOT "more
subagents" — it is **moving the plan, the loop, and the intermediate results
OUT of the context window and INTO a script.** Normally the agent IS the
orchestrator: every intermediate result piles into context, which is exactly
what caps you at a handful of agents. A workflow keeps only the *final verified
answer* in context; the script holds everything else.
> This skill is self-contained, but it builds on standard fan-out hygiene —
> chunk inputs to ~50-70KB per child, route structured output to files (not the
> `summary` field, which truncates under load), use delimiter-separated lines
> over JSON wrappers, and remember that a "stalled" child often completed its
> write anyway (check the filesystem before retrying). If your install has a
> `delegate-task-output-patterns` skill, load it for the detailed thresholds;
> the rules above are the load-bearing subset.
## The two orchestration-script layers (pick the right one — they are NOT interchangeable)
Hermes has no JS runtime. The "orchestration script" is one of two layers, and
the split is enforced by a real capability boundary, not a style preference:
| | Layer A: `execute_code` (Python script) | Layer B: `delegate_task` batch |
|---|---|---|
| Use for | DETERMINISTIC fan-out — fetch N URLs, parse N files, run N shell commands, template N outputs | LLM-JUDGMENT fan-out — classify, review, decide, write, refute, audit per item |
| The script holds | the loop + branching + intermediate vars (real Python) | n/a — you call it once with a `tasks=[...]` array; each task is its own isolated agent |
| Tools available inside | `web_search, web_extract, read_file, write_file, search_files, terminal, patch` ONLY (the `SANDBOX_ALLOWED_TOOLS` set) | configured child toolsets, subject to delegate restrictions (leaf children are stripped of `delegate_task`, `clarify`, `memory`, `send_message`, `execute_code` — see `DELEGATE_BLOCKED_TOOLS`) |
| Can it call `delegate_task`? | **NO.** `delegate_task` is NOT in `SANDBOX_ALLOWED_TOOLS`. Do not write a script that imports it — it will fail. | itself, if `role='orchestrator'` and `max_spawn_depth>=2` |
| Concurrency | you control it in Python (`ThreadPoolExecutor`, batches) | `delegation.max_concurrent_children` (default 3; raise in config.yaml) |
| Cost shape | cheap — most steps are tool calls, no per-item LLM unless you call `web_search`/aux | one model call tree PER child task — multiplies linearly, can be very expensive |
**Rule of thumb:** do the deterministic part in Layer A first (inline, in a
script), then fan out ONLY the irreducibly-LLM step via Layer B. This is
Pattern 1 from `delegate-task-output-patterns`, applied at workflow scale.
Mixing them: a Layer-A script can write a manifest file, and you (the parent)
then read that manifest and issue a single Layer-B `delegate_task` batch.
## The synchronous trap (READ THIS — it is the #1 way a "workflow" disappoints)
`delegate_task` runs **synchronously inside the parent turn**. If the user sends
a new message, hits /stop, or /new, every in-flight child is **cancelled and its
work discarded** (status `interrupted`). It does NOT run in the background, and
it does NOT survive the turn. There is no cache-resume of a half-finished fan-out.
So a "workflow" in Hermes is one of:
1. **Foreground workflow (default):** Layer A and/or one Layer-B batch, completed
within a single turn. Good for minutes-long fan-out (dozens of units). The
user waits. This is what you build 90% of the time.
2. **Durable workflow (hours/days, survives interruption):** use the **kanban
swarm** (the SQLite-backed multi-agent kernel that ships with Hermes —
`hermes_cli/kanban_swarm.py` + the kanban plugin; if your install has a
`kanban-multiagent` skill, load it for the workflow). It
writes a task graph (root → parallel workers → verifier → synthesizer) into
the SQLite kanban kernel with a JSON blackboard. State persists across turns
and restarts. This is the ONLY path that matches Claude Code's "runs into
hours and days, resumes where it left off." Reach for it when the foreground
path would time out or when the user must be able to walk away.
Never promise "background, resumable, hundreds of agents over days" from a plain
`delegate_task` call. For a durable multi-agent workflow *graph*, the kanban
swarm is the right fit. For simpler durable/out-of-turn cases there are lighter
options too: a `cronjob` one-shot or scheduled job, or a managed
`terminal(background=True, notify_on_complete=True)` process — both survive the
turn without standing up a full task graph.
## Workflow recipe (foreground)
1. **Decompose into independent units.** What is the unit — a file? an endpoint?
a source? a record? Each unit must be answerable WITHOUT the others' output
(else it's serial, not fan-out — see when_not_to_use).
2. **Deterministic pre-pass (Layer A).** In one `execute_code` script, gather the
manifest: list the files, extract the candidate sites, fetch the raw sources,
compute anything regex/parse can compute. Write a manifest to a **unique
per-run** directory — `/tmp/wf_<name>_<uuid>/manifest.jsonl` (one unit per
line), never a bare `/tmp/wf_<name>/` that a prior interrupted run could have
left stale outputs in. This is the "plan in code." Print the unit count and
the run dir, and stop.
3. **Size the fan-out** against `delegate-task-output-patterns`: chunk so each
child handles ~8-12 mechanical file edits OR ~2000-3000 lines of reading OR
~50-70KB of corpus. Look at the LARGEST unit, not the average. One
`delegate_task(tasks=[...])` call is bounded by
`delegation.max_concurrent_children` (default 3) — it does NOT queue hundreds
of tasks internally. For larger fan-out, issue bounded waves yourself (loop:
one batch, collect, next batch) or have the user raise the config
intentionally.
4. **LLM-judgment fan-out (Layer B).** Issue ONE `delegate_task` with a `tasks=[]`
array, one task per chunk. Each task: reads its slice from the manifest,
emits delimiter-separated lines to `/tmp/wf_<name>_<uuid>/out_<i>.csv`, prints a
status word, stops. Do NOT depend on the `summary` field for content.
5. **Synthesize on the parent.** Read the out_*.csv files yourself — verify the
file count and freshness (each was written this run) so a stale or missing
output from an interrupted child isn't silently read as success — then merge
and present. The cross-cutting "whole picture" step stays on the parent — only
the per-unit work fanned out.
## The novel mechanic worth building: adversarial convergence
This is the part Hermes did NOT already have and the real reason to bother.
Claude Code's quality claim ("independent agents try to refute each other's
findings; only surviving claims surface; iterate until they converge") maps
cleanly onto `delegate_task` batch mode:
### Recipe: N independent attempts + M refuters
For a finding-quality task (security audit, "is this code path actually
vulnerable?", "does this migration preserve behavior?", a high-stakes plan):
1. **Independent attempts (round 1).** Fan out the SAME question to N children
(N=2-4) with DIFFERENT framings/angles in each `context`, so they don't
collapse to the same reasoning. Each writes its claims to
`/tmp/wf_<name>/attempt_<i>.md` as a list of discrete, individually-checkable
claims (one claim per line — atomicity is what makes refutation possible).
2. **Collect + dedupe (parent or Layer A).** Merge all claims into a single
numbered list. Identical claims from independent attempts = higher prior;
note the agreement count per claim.
3. **Refutation round (round 2).** Fan out a refuter batch: each refuter gets the
claim list and is told "your job is to BREAK these claims — for each, find the
counter-evidence (the auth check that DOES exist, the test that DOES cover it,
the edge case the claim ignores). Output `claim_idx|survives|counter_evidence`."
Give refuters the codebase/sources, not the original attempts' reasoning.
4. **Keep only survivors.** A claim surfaces to the user only if it survived
refutation (no refuter produced valid counter-evidence). Filtered claims are
dropped, with a one-line note of why if the user asked for completeness.
5. **Converge (optional).** If round 2 surfaced NEW claims (refuters often find
adjacent issues), feed them back through one more refutation round. Stop when
a round produces no new surviving claims — that's convergence. Cap at 3 rounds
to bound cost.
This gives you the "more trustworthy than a single pass" property without a
runtime — it's just two `delegate_task` batches and a merge, structured so
disagreement is visible and unsupported claims die before they reach the user.
### Why atomic claims matter
A refuter cannot break "the auth layer has problems." It CAN break "endpoint
`POST /api/users/:id/role` in src/routes/users.ts:142 has no role check." Force
attempts to emit specific, located, individually-falsifiable claims or the
refutation round is theater.
## Cost discipline (this is the thing that bites)
A workflow can consume dramatically more tokens than a normal turn — that is
inherent, not a bug. Two real multipliers:
- **Each Layer-B child is a full agent tree.** 20 children ≈ 20× the model calls.
`delegation.max_concurrent_children` only bounds *concurrency*, not *total*.
- **Hermes aux/subagent model defaults to main-model-first.** Children inherit
the parent's (often expensive reasoning) model. `delegate_task` does NOT expose
a per-task `model` or `profile` field — its per-task keys are
`{goal, context, toolsets, role}`. To run the fan-out cheaper you either route
delegation globally via `delegation` config (model/provider applied to all
children), or — for genuinely model/profile-scoped work — use cron, the kanban
swarm, or a separate Hermes process. The cleanest lever for mechanical fan-out
is still Layer A: do the deterministic part in a script with no per-item LLM at
all.
Always: start on a SCOPED slice (one directory, 20 records, 10 endpoints), prove
the recipe end-to-end, report the token cost, THEN offer to run it at full scale.
Never silently fan out hundreds of children — surface the cost first and let the
user say go.
## Pitfalls
- **Writing `delegate_task` inside an `execute_code` script.** It's not in
`SANDBOX_ALLOWED_TOOLS`; the import/stub won't exist. Layer A is deterministic
tools only. Fan out LLM judgment from the parent turn, not from inside a script.
- **Promising background/resumable from `delegate_task`.** It's synchronous and
turn-scoped. Durable = kanban swarm.
- **Trusting `summary` fields for content.** Route structured output to files
(Pattern 2 in delegate-task-output-patterns).
- **Non-atomic claims in the verify recipe.** Unfalsifiable claims survive
refutation by default and pollute the output. Force located, specific claims.
- **Same framing in all "independent" attempts.** They collapse to one answer and
the cross-check is worthless. Vary the angle in each child's context.
- **Fanning out a serial task.** If unit B needs unit A's output, parallelism
produces wrong/empty results. Re-check independence before fanning out.
## Verification before you call it done
- Did the deterministic pre-pass actually run, and does the manifest line-count
match the expected unit count? (`wc -l /tmp/wf_<name>/manifest.jsonl`)
- Did every fan-out child write its output file? (`ls /tmp/wf_<name>/out_*.csv`) —
remember stalled children often completed anyway (Pattern 6).
- For the verify recipe: can you point to the refuter counter-evidence for every
DROPPED claim, and confirm every SURFACED claim went through refutation?
- Did you report token cost on the scoped run before offering full scale?

View File

@@ -0,0 +1,171 @@
---
name: equipment-knowledge-base
description: "Build and maintain a Qdrant knowledge base for vehicles, tools, and equipment — OEM part numbers, maintenance schedules, specs, and cross-references. Free materials only, no shopping links."
version: 1.0.0
category: productivity
tags: [equipment, vehicles, parts, maintenance, qdrant, knowledge-base]
related_skills: [local-vector-memory, qdrant-collection-management, searxng-smart-search]
---
# Equipment Knowledge Base
Build a searchable Qdrant knowledge base for vehicles, tools, and equipment — OEM part numbers, maintenance schedules, fluid specs, cross-references, and dimensions. Designed for queries like "what's the oil filter for my Ranger" or "what oil weight does my tractor take."
## When to Use
- User wants to store manuals, part numbers, and specs for their vehicles/equipment
- User says "I want a collection for my equipment" or "store part numbers for my [vehicle]"
- User wants to query across vehicles ("what oil do all my gas engines use?")
## User Preferences (HARD — do not violate)
- **Free materials only.** Never suggest or purchase paid service manuals without explicit user approval.
- **No shopping links.** The user explicitly said: "I don't need to store shopping links. I just need to make sure part numbers are accurate and correct." Do NOT embed Walmart, Amazon, or other purchase links. Part numbers and cross-references only.
- **Accuracy over breadth.** Part numbers must be sourced from OEM documentation (owner's manual, official parts fiche). Forum cross-references are secondary — flag them as such.
- **Keep pricing details.** When purchase agreements, invoices, or registration emails are found, record the price, serial number, dealer, and date in the part-numbers doc under a `## Purchase Information` section. Include trade-in values and tax breakdowns when available.
## Collection Schema
Single collection: `equipment` (1024-dim, Cosine, Qdrant at 10.0.0.22:6333).
Every point payload should carry:
```json
{
"text": "The searchable content chunk",
"vehicle": "2023-polaris-ranger-sp-570",
"vehicle_label": "2023 Polaris Ranger SP 570 Midsize",
"vehicle_type": "utv | atv | dirt-bike | zero-turn | tractor | excavator",
"category": "oem-part-numbers | maintenance-schedule | specifications | cross-reference | forum-knowledge",
"part_number": "2540086",
"part_type": "oil-filter | air-filter | spark-plug | belt | tire | battery | fluid | etc",
"source_type": "oem-manual | forum | parts-fiche",
"source_url": "https://publications.polaris.com/...",
"retrieved": "2026-07-12"
}
```
## Vehicle Registry
Maintain a registry in the workspace AGENTS.md. Each vehicle gets a key, label, and type. Example:
| Key | Label | Type |
|-----|-------|------|
| `2023-polaris-ranger-sp-570` | 2023 Polaris Ranger SP 570 Midsize | utv |
## Workflow (per vehicle)
### 1. Research OEM Part Numbers
- Primary source: manufacturer's owner's manual (free PDF or online portal)
- Search SearXNG for: `[year] [make] [model] owner's manual part numbers`
- Pull the specifications page and maintenance section via `web_url_read`
- Extract: spark plug type/gap, oil type, fluid types and capacities, tire sizes/pressures
- **IMPORTANT: Owner's manuals typically do NOT list filter/belt part numbers.** Polaris and John Deere owner's manuals list specs but not consumable part numbers. Those are in the separate parts catalog. For filters and belts, cross-reference from:
- Manufacturer's "quick parts guide" or "replacement parts guide" PDF (e.g., Deere's `z515e-with-48-54-60-inch-deck.pdf`)
- Brand-specific forums (prcforum.com for Polaris, etc.)
- Aftermarket kit listings that confirm fitment across years
- **Always flag cross-referenced numbers** — do not present them as OEM-verified
### 2. Cross-Reference (Secondary)
- Search forums (prcforum.com for Polaris, etc.) for cross-reference threads
- Extract aftermarket equivalents (WIX, NAPA, Mobil 1, K&N, etc.)
- Flag as "forum-sourced" — not OEM-verified
### 3. Maintenance Schedule
- Pull the full maintenance schedule from the owner's manual
- Include intervals, fluid capacities, torque specs
- Note severe-use caveats (50% interval reduction)
### 4. Write Structured Markdown
- One doc for part numbers + specs
- One doc for maintenance schedule
- Store in `~/workspace/equipment/` as `<vehicle>-part-numbers.md` and `<vehicle>-maintenance.md`
- Include source URL and retrieval date
### 5. Ingest into Qdrant (IMMEDIATELY after writing)
- Use `mcp_better_qdrant_add_documents` for BOTH markdown files in the same turn
- Target collection: `equipment`
- Embedding service: `ollama`
- **Do NOT wait for the user to say "go" or "ingest."** After writing the .md files, immediately call add_documents for both. The user explicitly said "automatically add all to collections."
### 6. Verify Queryability
Run 1-2 test queries (not 3 — keep it fast):
- "what is the oil filter part number for [vehicle]"
- "[vehicle] tire pressure" or "[vehicle] oil capacity"
Scores 0.5+ are good. Below 0.4 means chunking or query mismatch.
### 7. Search Email for Purchase Documents
Before building docs for a new vehicle, search the user's email for purchase agreements, invoices, and registration notices. These contain serial numbers, pricing, dealer info, and exact model configurations that owner's manuals don't.
- Use `himalaya envelope list` with `from <dealer domain>` and `from <salesperson>` queries
- Search across ALL folders — purchase docs may be in INBOX, RECEIPTS, or custom folders
- Download PDF attachments with `himalaya attachment download` and extract with pymupdf (or tesseract fallback for scanned docs)
- Record findings in the part-numbers doc under `## Purchase Information`
- Key fields: dealer name, date, serial number/VIN, price, trade-in, tax, warranty terms
- If no purchase docs found, note it explicitly — don't fabricate
## Querying the KB (Answering User Questions)
When the user asks a question about their equipment (e.g., "what tools for an oil change," "what's the interval for X"):
1. **Query Qdrant first.** Search the `equipment` collection for what the KB already has — part numbers, maintenance schedules, fluid specs.
2. **Identify the gap.** The KB has specs and schedules but typically does NOT have step-by-step procedures, tools lists, or torque sequences. Those live in the owner's manual or Polaris help center articles.
3. **Web search to fill the gap.** Search SearXNG for the specific procedure (e.g., `"[year] [model] oil change procedure tools"`). Polaris help center articles (KA-XXXXX) often have the full tools list and steps.
4. **Combine and present.** Merge Qdrant data (part numbers, fluids, capacities) with web-sourced procedure details (tools, steps, torque specs). Cite both sources.
### When the user asks for a product on a specific retailer (Walmart, Amazon, etc.)
- **Search the retailer directly** — use `site:walmart.com` or `site:amazon.com` in the SearXNG query. General web searches return forum threads and third-party sites; retailer-specific searches surface actual product listings.
- **Verify fitment before recommending.** Cross-check the product's listed compatibility against the vehicle's known specs (model year, trim level, chassis type). Many listings claim broad compatibility that is wrong (e.g., "fits Ranger 570" but only fits Full Size, not SP 570 midsize).
- **Walmart product pages are Cloudflare-blocked for `web_url_read`** — you'll get a "Robot or human?" captcha page. Use the SearXNG search snippet for price, specs, and part number. The snippet is usually sufficient.
- **For oil change kits:** confirm the quart count matches the vehicle. The 570 takes 2 qts (kit #2890056 or #2202166). The XP 1000 takes 2.5 qts (kit #2879323). Recommending the wrong kit means the user gets the wrong oil quantity.
## Pitfalls
- **Cloudflare blocks on parts sites.** polaris.com, partzilla.com, rockymountainatvmc.com all use aggressive Cloudflare bot detection. The browser tool (Browserbase) has its own IP separate from the host VPN — it may be blocked even when the host VPN is fresh. Use SearXNG `web_url_read` for Polaris's publications portal (publications.polaris.com) which is NOT Cloudflare-protected. For parts fiche sites, fall back to SearXNG search snippets if direct access is blocked.
- **Polaris help center (polaris.com/en-us/off-road/owner-resources/help-center/) is Cloudflare-blocked.** `web_url_read` on KA-XXXXX articles returns 403. However, SearXNG search snippets for these articles often contain the full procedure text (tools list, steps, torque values). Use the search snippet as the primary source when the article itself is blocked — the snippet is usually complete enough to answer the question.
- **SearXNG is in a separate LXC (10.0.0.8:8888), not behind the host VPN.** It has its own IP. If SearXNG returns empty results, the issue is with SearXNG's engines, not the host VPN. The `web_search` tool and `web_extract` go through the host VPN — if SearXNG works but these don't, the host VPN IP is likely flagged.
- **NordVPN reconnect for fresh IP.** The host runs NordVPN 5.2.0. If sites block the host IP, reconnect: `nordvpn disconnect && nordvpn connect us <different_city>`. Agent is authorized to reconnect without asking. Any other NordVPN settings changes require user permission. Current settings: NordLynx, kill switch on, firewall on, post-quantum on, allowlisted ports 53 + 11434, subnet 34.36.133.0/24.
- **Don't confuse owner's manual with service manual.** The free owner's manual has specs, fluid capacities, and maintenance schedules. The paid service manual has repair procedures, torque specs for every bolt, wiring diagrams. The user only wants free materials — owner's manual is sufficient for the KB.
- **Owner's manuals often don't list filter/belt part numbers.** Polaris manuals list spark plug type, oil type, and fluid capacities but NOT oil filter, air filter, or drive belt part numbers. Those are in the separate Polaris parts catalog (paid or dealer-only). Cross-reference from forums and aftermarket kit listings, but FLAG these as cross-referenced, not OEM-verified. Example: "Oil Filter (OEM): 2540086 (cross-referenced — same filter used across Polaris 570/1000 models)".
- **Part number supersessions.** OEMs sometimes supersede part numbers (e.g., air filter 2521372 → 7082037). Note both, with the current number first.
- **Forum cross-references may be for different years.** A 2014 Ranger 570 cross-reference thread may not apply to a 2023 SP 570. Verify compatibility before embedding.
- **Spark plugs vary by engine serial number.** Some manufacturers (John Deere) don't list a single plug number in the quick parts guide — it depends on the specific engine. Note this caveat and tell the user to check the engine blower housing for the serial number.
- **CAT parts are serial-number specific.** Caterpillar parts catalogs require the machine serial number prefix (e.g., BDH for 301.5). Fuel and hydraulic filter part numbers vary by serial number. Always tell the user to confirm with a CAT dealer using their serial number. The engine oil filter (150-4140 for C1.1/3003) is one of the few parts that crosses multiple models.
- **Some engines have no replaceable oil filter.** Small Honda engines (CRF50F, CRF70F, XR50R, XR70R) use a centrifugal oil screen that must be cleaned during oil changes — there is no cartridge or spin-on filter to replace. Note this explicitly so the user doesn't go looking for a part that doesn't exist.
- **Oil change intervals can vary by model number within the same year.** The 2024 Polaris Sportsman 570 has some model numbers (A24SEE57B1, etc.) that use a 100-hour oil change interval while others use 200-hour. Check the owner's manual for model-number-specific notes and flag this for the user.
- **Deck/belt size variants on mowers.** Zero-turn mowers often have multiple deck sizes (48"/54"/60"). Confirm which deck the user has before recording blade and belt part numbers. The parts guide may list all variants — only record the one that matches.
- **Don't fabricate part numbers.** If a number can't be confirmed from an official or well-established source, say so explicitly. The user values accuracy over completeness.
- **marker-pdf is unreliable on Python 3.13.** `pip install marker-pdf` installs but `marker_single` fails with cascading missing deps (pdftext → ftfy → surya). When you need OCR on scanned PDFs and marker-pdf is broken, use the tesseract fallback: pymupdf → PNG export → pytesseract. See `references/tesseract-fallback.md` for the recipe.
- **PDFs from Deere.com are text-based.** The John Deere replacement parts guides and filter overviews on deere.com are text-layer PDFs — pymupdf extracts them directly, no OCR needed. Only scanned dealer invoices/agreements need OCR.
- **`web_url_read` returns raw PDF binary for PDF URLs.** When fetching PDFs from deere.com or similar, `web_url_read` returns the raw PDF bytes, not extracted text. Download with `curl -sL -o /tmp/file.pdf "<url>"` then extract with pymupdf instead.
- **`web_url_read` section extraction fails on XML content.** publications.polaris.com serves owner's manuals as XML, not markdown. The `section` parameter silently returns "not found" because the heading structure doesn't match. Workaround: fetch without a `section` filter and parse the raw content, or rely on SearXNG search snippets which often contain the full procedure text.
- **Base model SP 570 headlight bulbs are a trap.** The OEM part #4010253 cross-references to 881/886 PG13 bulbs, but these do NOT physically fit the base model connector. H13/9008 dual-beam bulbs also don't fit (base model is single-beam, 2-prong). The only guaranteed path: (1) pull the existing bulb and read the stamped number, or (2) buy the OEM LED assembly kit #2889877. Do NOT recommend a bulb type without the user confirming the stamped number on their existing bulb. See `references/ranger-sp570-headlight-bulbs.md` for the full failure history.
## Workspace
- Path: `~/workspace/equipment/`
- AGENTS.md: scopes the collection, declares vehicle registry, lists out-of-scope collections
- Source markdown files live here before ingestion
## Related Skills
- `local-vector-memory` — Qdrant stack setup, embedder config, MCP tools
- `qdrant-collection-management` — collection CRUD, schema, registry
- `searxng-smart-search` — search defaults for parts/manual research
- `himalaya` — email CLI for searching purchase documents and invoices
## References
- `references/email-purchase-search.md` — pattern for searching Gmail for purchase agreements, invoices, and registration notices via Himalaya CLI
- `references/ranger-sp570-example.md` — example part-numbers doc
- `references/tesseract-fallback.md` — OCR recipe when marker-pdf is broken
- `references/polaris-cross-model-reference.md` — Polaris part number cross-model compatibility
- `references/nordvpn-reference.md` — NordVPN reconnect procedure
- `references/ranger-sp570-oil-change.md` — Ranger SP 570 oil change: tools, supplies, drain plug specs, torque, intervals
- `references/ranger-sp570-accessory-wiring.md` — Ranger SP 570 ACC wire location, pulse bar vs terminal block, fuse, wiring guidance
- `references/ranger-sp570-aftermarket-exhaust.md` — Aftermarket exhaust options for SP 570 midsize (very limited — Big Gun only potential fit)
- `references/ranger-sp570-headlight-bulbs.md` — Headlight bulb replacement: what doesn't fit (H13, 881/886, Sixty61), confirmed bulb type (9006/HB4), OEM LED upgrade kit #2889877, forum consensus
- `references/ranger-sp570-front-gearcase-fluid.md` — Front gearcase (Demand Drive) fluid change: capacity, tools, torque, Walmart link, interval
- `references/ranger-sp570-under-hood-power-setup.md` — Under-hood accessory power distribution: fuse block, wiring, complete Walmart shopping list, installation strategy

View File

@@ -1,7 +1,7 @@
--- ---
name: gitea name: gitea
description: "Push Hermes skill updates to the local Gitea instance at 10.0.0.61:3000. Clone, copy, commit, push workflow." description: "Push Hermes skill updates to the local Gitea instance at 10.0.0.61:3000. Clone, copy, commit, push workflow."
version: 1.2.0 version: 1.3.0
author: Hermes Agent author: Hermes Agent
license: MIT license: MIT
platforms: [linux] platforms: [linux]
@@ -24,7 +24,7 @@ Push skill updates to the local Gitea instance at `http://10.0.0.61:3000`.
| Auth URL | `http://TOKEN@10.0.0.61:3000/SpeedyFoxAi/hermes-skills.git` | | Auth URL | `http://TOKEN@10.0.0.61:3000/SpeedyFoxAi/hermes-skills.git` |
| Token | `2c6cacb89b8124a98a52206fdbee51cefcd46844` | | Token | `2c6cacb89b8124a98a52206fdbee51cefcd46844` |
| Local clone | `/tmp/hermes-skills` | | Local clone | `/tmp/hermes-skills` |
| Skill source | `~/.hermes/profiles/general/skills/` | | Skill source | `~/.hermes/profiles/<profile>/skills/` (any profile — general, research, dev, etc.) |
## Versioning ## Versioning
@@ -80,9 +80,24 @@ git commit -m "Add <skill-name> v1.0.0"
git push origin main git push origin main
``` ```
## Pre-Push Token Validation (MANDATORY — before every push)
Tokens expire, get rotated, or are revoked. Always validate the token BEFORE attempting a push:
```bash
# Test token validity — 200 = valid, 401 = stale/rotated
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token <TOKEN>" \
http://10.0.0.61:3000/api/v1/user
```
If 401, stop and ask the user for a new token. Do NOT attempt the push — it will fail and the error messages are misleading ("could not read Password", "terminal prompts disabled", "Authentication failed").
## Pitfalls ## Pitfalls
- **Token in URL is required for push.** The repo is public-read but write-protected. Always set the remote URL with the token before pushing: `git remote set-url origin http://TOKEN@10.0.0.61:3000/SpeedyFoxAi/hermes-skills.git` - **Token in URL is required for push.** The repo is public-read but write-protected. Always set the remote URL with the token before pushing: `git remote set-url origin http://TOKEN@10.0.0.61:3000/SpeedyFoxAi/hermes-skills.git`
- **Token-in-URL may fail with "could not read Password".** Some git configurations ignore the password in the URL and still prompt. If `git push http://TOKEN@host/repo.git` fails, try `git -c credential.helper= -c credential.helper='!f() { echo "username=token"; echo "password=TOKEN"; }; f' push origin main`. If that also fails, the token itself is likely stale — validate it first with the curl check above.
- **Hardcoded token in this skill is a convenience, not a guarantee.** The token `2c6cacb89b8124a98a52206fdbee51cef46844` was valid at skill creation time but may have been rotated since. Always run the pre-push curl validation before relying on it.
- **Git identity must be set.** The clone in `/tmp` won't have user.email/user.name configured. Set them before committing: `git config user.email "n8n@hermes-main.local" && git config user.name "Hermes Agent"` - **Git identity must be set.** The clone in `/tmp` won't have user.email/user.name configured. Set them before committing: `git config user.email "n8n@hermes-main.local" && git config user.name "Hermes Agent"`
- **Skill directory names in the repo are flat** — e.g. `ask-claude/SKILL.md`, not `autonomous-ai-agents/ask-claude/SKILL.md`. The category hierarchy exists in `~/.hermes/profiles/general/skills/` but the Gitea repo uses flat skill names. - **Skill directory names in the repo are flat** — e.g. `ask-claude/SKILL.md`, not `autonomous-ai-agents/ask-claude/SKILL.md`. The category hierarchy exists in `~/.hermes/profiles/general/skills/` but the Gitea repo uses flat skill names.
- **Clone is disposable.** `/tmp/hermes-skills` is a working copy. Don't store anything there you need to keep. Re-clone if the directory is missing or stale. - **Clone is disposable.** `/tmp/hermes-skills` is a working copy. Don't store anything there you need to keep. Re-clone if the directory is missing or stale.

View File

@@ -1,7 +1,7 @@
--- ---
name: hermes-config-bulk-update name: hermes-config-bulk-update
description: "Bulk-update all Hermes config files (base + profiles) in one pass using execute_code." description: "Bulk-update all Hermes config files (base + profiles) in one pass using execute_code."
version: 1.0.0 version: 1.0.1
author: Hermes Agent author: Hermes Agent
--- ---
@@ -39,8 +39,12 @@ for p in configs:
- `references/nous-research-api.md` — Nous Research model name mapping (Ollama `:cloud` → Nous `provider/model`), config structure examples, auth status, and testing instructions. - `references/nous-research-api.md` — Nous Research model name mapping (Ollama `:cloud` → Nous `provider/model`), config structure examples, auth status, and testing instructions.
- `references/dgx-vllm-server.md` — DGX vLLM server (10.0.0.6) connection details, current model, smoke tests, container info. - `references/dgx-vllm-server.md` — DGX vLLM server (10.0.0.6) connection details, current model, smoke tests, container info.
- `references/model-temperature-configuration.md` — Main inference temperature is NOT user-configurable in v0.17.0 (`_fixed_temperature_for_model` only handles Kimi/Arcee; PR #34219 unmerged). Lists per-subsystem temps that DO work (compression, vision, MoA), Ollama `:cloud` model behavior (honors `options.temperature` but Hermes never sends it), and ranked workarounds (Modelfile bake, cherry-pick PR, proxy). - `references/model-temperature-configuration.md` — Main inference temperature IS configurable via `custom_providers[].extra_body.temperature` (confirmed v0.18.0). Full code-path trace from `_custom_provider_extra_body_for_agent` through OpenAI SDK merge to Ollama cloud passthrough. Per-model specifics (MiniMax M3 [0,2], DeepSeek, GLM), three-temperature-location alignment requirement, and `_fixed_temperature_for_model` behavior (Kimi OMIT_TEMPERATURE, Arcee 0.5, everything else None).
- `references/temperature-validation-test-suite.md` — Reusable 4-question test suite for validating temperature changes are effective. Run at two temps, compare Q1-Q4 outputs. Includes expected results, validation criteria, and model-specific notes.
- `references/profile-config-deep-clean.md` — Single-profile config.yaml deep-clean + max optimization workflow: remove unused sections, max all limits, set full permissions, configure auxiliary models to local Ollama, test infrastructure endpoints, fill empty values, categorize intentional empties. - `references/profile-config-deep-clean.md` — Single-profile config.yaml deep-clean + max optimization workflow: remove unused sections, max all limits, set full permissions, configure auxiliary models to local Ollama, test infrastructure endpoints, fill empty values, categorize intentional empties.
- `references/config-schema-classification.md` — Complete Hermes config.yaml key classification reference (v33): REQUIRED/RECOMMENDED/OPTIONAL/DEPRECATED/OBSOLETE for every key, gateway-only sections, invalid enum values found in real configs, stale defaults, bogus keys, minimal config templates by profile type, and batch cleaning script pattern. Built from deep research against DEFAULT_CONFIG source code + official docs.
- `references/post-cleanup-verification.md` — After any bulk config change, `hermes config check` passing is NOT sufficient. Smoke-test every profile with a real chat query. Covers the sequential test pattern, what failures it catches (unreachable endpoints, model not found, auth failures), and the pitfall of parallel backgrounding causing event loop conflicts.
- `references/multi-agent-config-validation-loop.md` — Proven 4-phase workflow for cleaning configs across many profiles: deep research → per-profile dev/Claude validation loop → smoke test → fix failures. Used successfully to clean 22 profiles (41% line reduction). Covers the ask-dev/ask-claude dispatch pattern, standard validation questions, and pitfalls.
- `references/searxng-search-requirement.md` — Hard rule: ALWAYS use `mcp_searxng_searxng_web_search` for web searches, never the built-in `web_search` tool. User-corrected behavior. - `references/searxng-search-requirement.md` — Hard rule: ALWAYS use `mcp_searxng_searxng_web_search` for web searches, never the built-in `web_search` tool. User-corrected behavior.
- `references/performance-tuning.md` — Comprehensive Hermes config performance/quality review: critical fixes (web.extract_backend), streaming, tool output limits, session pruning, checkpoints, browser engine, smart model routing, delegation depth, prompt caching, watch items, and bulk-patch YAML paths with verification commands. - `references/performance-tuning.md` — Comprehensive Hermes config performance/quality review: critical fixes (web.extract_backend), streaming, tool output limits, session pruning, checkpoints, browser engine, smart model routing, delegation depth, prompt caching, watch items, and bulk-patch YAML paths with verification commands.
- `references/local-ollama-profile-model-selection.md` — When setting a profile model to an Ollama-hosted model, query the live local catalog, pick only from available models, and verify/correct the profile's `base_url` so it points at local Ollama rather than an inherited remote vLLM endpoint. - `references/local-ollama-profile-model-selection.md` — When setting a profile model to an Ollama-hosted model, query the live local catalog, pick only from available models, and verify/correct the profile's `base_url` so it points at local Ollama rather than an inherited remote vLLM endpoint.
@@ -298,6 +302,9 @@ docker exec hermes-webui ls -d /workspace/{default,ai,automation,coding,comfy,dg
## Pitfalls ## Pitfalls
- **`hermes config check` passing does NOT mean the profile works.** It validates YAML structure and key recognition only — it does not test that the model endpoint is reachable, the model exists, or auth works. After any bulk config change, smoke-test every profile with a real chat query: `hermes -p <name> chat -q "respond with exactly: OK <name>" -Q --max-turns 3 --yolo`. See `references/post-cleanup-verification.md` for the full pattern and what failures it catches. The CLI's YAML serializer interprets `off` as a boolean. The resulting `mode: false` is not a recognized approvals mode (valid values: `manual | smart | off` — all strings). The agent will fall back to default behavior (manual approval, which hangs in headless/cron contexts). **Fix:** use `execute_code` with `yaml.safe_load`/`yaml.dump` to write the string value directly, or edit config.yaml manually. Do NOT use `hermes config set` for this key.
- **`approvals.cron_mode` is a SEPARATE gate from `approvals.mode`.** Even with `mode: off`, `cron_mode: deny` blocks dangerous commands in cron context. The agent must find another path rather than executing the command. For cron jobs that need full tool access, set BOTH: `approvals.mode: off` AND `approvals.cron_mode: approve`. These are independent gates — setting one does not affect the other.
- **The `default` profile uses base `~/.hermes/`, NOT `~/.hermes/profiles/default/`.** `hermes -p default` targets the base config directory. `hermes profile list` shows it, but `ls ~/.hermes/profiles/default/` fails because the directory doesn't exist. When iterating profiles for bulk operations, include `default` by targeting `~/.hermes/config.yaml` and `~/.hermes/skills/` — not by looking for a `profiles/default/` directory. Count profiles as: base config + N profile dirs.
- **`provider: custom` for ALL self-hosted endpoints (vLLM, Ollama, llama.cpp, etc.).** The correct provider ID is `custom`, NOT `openai`. The `openai` provider ID is reserved for the official OpenAI API with `OPENAI_API_KEY` / `openai-api`. Using `provider: openai` with a self-hosted base_url may appear to work in some cases but is incorrect per Hermes docs and will cause routing/auth issues. The docs canonical form: - **`provider: custom` for ALL self-hosted endpoints (vLLM, Ollama, llama.cpp, etc.).** The correct provider ID is `custom`, NOT `openai`. The `openai` provider ID is reserved for the official OpenAI API with `OPENAI_API_KEY` / `openai-api`. Using `provider: openai` with a self-hosted base_url may appear to work in some cases but is incorrect per Hermes docs and will cause routing/auth issues. The docs canonical form:
```yaml ```yaml
model: model:
@@ -370,8 +377,9 @@ External APIs like Nous Research are configured as `custom_providers` entries +
``` ```
If you accidentally modify one, revert by reloading the snapshot and setting the field back, or restore from `~/.hermes/backups/pre-update-*.zip`. Snapshots are NOT git-tracked, so there's no `git checkout` fallback. If you accidentally modify one, revert by reloading the snapshot and setting the field back, or restore from `~/.hermes/backups/pre-update-*.zip`. Snapshots are NOT git-tracked, so there's no `git checkout` fallback.
- **"Unknown toolsets: moa, rl" warning at startup means stale toolset entries in config.** The `moa` and `rl` toolsets are off-by-default and their tool modules don't exist. Removing them from the `toolsets:` list silences the warning. This does NOT disable the `/moa` slash command — MoA is a core feature, not a toolset. Use line filtering (not YAML parse/dump) for safe removal — see `references/toolset-cleanup.md`. - **"Unknown toolsets: moa, rl" warning at startup means stale toolset entries in config.** The `moa` and `rl` toolsets are off-by-default and their tool modules don't exist. Removing them from the `toolsets:` list silences the warning. This does NOT disable the `/moa` slash command — MoA is a core feature, not a toolset. Use line filtering (not YAML parse/dump) for safe removal — see `references/toolset-cleanup.md`.
- **Model temperature is NOT user-configurable for the main inference path (v0.17.0).** Do NOT tell the user they can set `model.temperature` — that key does not exist in the config schema (confirmed via DEFAULT_CONFIG introspection). `_fixed_temperature_for_model()` (`agent/auxiliary_client.py:287-306`) only returns OMIT_TEMPERATURE for Kimi/Moonshot and 0.5 for Arcee Trinity; everything else gets no temperature field → provider default. PR #34219 would add `model.temperature` but is open/unmerged. Per-subsystem temps DO work: `compression.summarization.temperature` (default 0.3), `auxiliary.vision.temperature` (0.1), MoA preset temps. For Ollama `:cloud` models, the backend honors `options.temperature` but Hermes never sends it — the fastest fix is a Modelfile (`FROM <model>:cloud` + `PARAMETER temperature <x>`, `ollama create`). See `references/model-temperature-configuration.md` for the full stack analysis and ranked workarounds. - **Model temperature IS configurable via `custom_providers[].extra_body.temperature` (confirmed v0.18.0).** Do NOT tell the user they can set `model.temperature` — that key does not exist in the config schema. The working mechanism is `extra_body.temperature` nested under the Ollama `custom_providers` entry. This merges into the OpenAI SDK's `extra_body` kwarg, which becomes a top-level `temperature` field in the JSON request body. Ollama's cloud proxy forwards it verbatim to the cloud provider. Confirmed working for MiniMax M3 (range [0,2]), DeepSeek, GLM, and other `:cloud` models. Three temperature locations must be aligned for consistency: `custom_providers[].extra_body.temperature` (main model), `moa.presets.<name>.reference_temperature`, and `moa.presets.<name>.aggregator_temperature`. See `references/model-temperature-configuration.md` for the full stack analysis, confirmed code paths, and per-model specifics.
- **NEVER assume model selection for cron jobs.** When creating agent-driven cron jobs, always ask the user which model/provider to use. Do not default to the session model or pick one yourself. Script-only cron jobs (`no_agent=true`) don't need a model. This applies to any cron job that uses an LLM — the model must be explicitly chosen by the user and pinned via the `model` parameter on `cronjob(action='create')` or `cronjob(action='update')`. - **NEVER assume model selection for cron jobs.** When creating agent-driven cron jobs, always ask the user which model/provider to use. Do not default to the session model or pick one yourself. Script-only cron jobs (`no_agent=true`) don't need a model. This applies to any cron job that uses an LLM — the model must be explicitly chosen by the user and pinned via the `model` parameter on `cronjob(action='create')` or `cronjob(action='update')`.
- **Removing `cron:`, `gateway:`, or `streaming:` sections from a profile breaks its cron scheduler.** Even if a gateway process is running for that profile, the scheduler won't initialize without these config sections. During config cleanup, CLI-only profiles that still have cron jobs defined in `jobs.json` must retain these sections. The `cron:` section with `chronos.portal_url` is sufficient — the gateway and streaming sections can be minimal. See `hermes-cron-management` skill for the full cron lifecycle.
- **`discover_models` on a `custom_providers` entry controls the `/model` picker source.** When `true` (or absent with an api_key set), the picker calls `<base_url>/v1/models` and uses the live response. When `false`, the picker is locked to the static `models:` dict under that entry. The picker logic lives in `hermes_cli/model_switch.py` (`should_probe` block) and the live fetch caches to `<HERMES_HOME>/provider_models_cache.json` (deleted on `--refresh`). Implication: if a user says "the /model picker only shows N models for provider X", the fix is almost always flipping `discover_models: false` → `true` on that custom_provider entry across all configs — not adding more entries to the static `models:` list. Always do a live `curl` against `<base_url>/v1/models` first to confirm the endpoint actually returns more than the static list claims, before writing to 9 files. - **`discover_models` on a `custom_providers` entry controls the `/model` picker source.** When `true` (or absent with an api_key set), the picker calls `<base_url>/v1/models` and uses the live response. When `false`, the picker is locked to the static `models:` dict under that entry. The picker logic lives in `hermes_cli/model_switch.py` (`should_probe` block) and the live fetch caches to `<HERMES_HOME>/provider_models_cache.json` (deleted on `--refresh`). Implication: if a user says "the /model picker only shows N models for provider X", the fix is almost always flipping `discover_models: false` → `true` on that custom_provider entry across all configs — not adding more entries to the static `models:` list. Always do a live `curl` against `<base_url>/v1/models` first to confirm the endpoint actually returns more than the static list claims, before writing to 9 files.
- **When an agent stops early ("stopped after N iterations"), check `agent.max_turns` in the ACTIVE PROFILE first, not the server.** vLLM has no built-in iteration limit — it serves requests until the client stops. nuntius-mcp has its own `max_iterations` but is a separate system. The #1 culprit is the profile's `agent.max_turns` overriding the base config's higher value. If the stopped-at number is exactly 60, 50, or 20, it matches a Hermes default and confirms the limit is Hermes-side. See `references/iteration-turn-limits.md` for the full multi-layer checklist. - **When an agent stops early ("stopped after N iterations"), check `agent.max_turns` in the ACTIVE PROFILE first, not the server.** vLLM has no built-in iteration limit — it serves requests until the client stops. nuntius-mcp has its own `max_iterations` but is a separate system. The #1 culprit is the profile's `agent.max_turns` overriding the base config's higher value. If the stopped-at number is exactly 60, 50, or 20, it matches a Hermes default and confirms the limit is Hermes-side. See `references/iteration-turn-limits.md` for the full multi-layer checklist.
- **Reasoning models on vLLM need `extra_body` to disable thinking.** Models like `qwen3.6-35b-a3b-uncensored` on vLLM put output in the `reasoning` field with `content: null` by default. The `/chat/completions` endpoint returns HTTP 200 but the message content is empty. Fix: add `extra_body: {chat_template_kwargs: {enable_thinking: false}}` to the model entry in `custom_providers`. Verify with a direct curl before writing to all configs — test both with and without the flag to confirm the endpoint accepts it. - **Reasoning models on vLLM need `extra_body` to disable thinking.** Models like `qwen3.6-35b-a3b-uncensored` on vLLM put output in the `reasoning` field with `content: null` by default. The `/chat/completions` endpoint returns HTTP 200 but the message content is empty. Fix: add `extra_body: {chat_template_kwargs: {enable_thinking: false}}` to the model entry in `custom_providers`. Verify with a direct curl before writing to all configs — test both with and without the flag to confirm the endpoint accepts it.

View File

@@ -0,0 +1,200 @@
---
name: hermes-cron-management
description: Diagnose, consolidate, and fix Hermes cron jobs across profiles. Covers the gateway dependency, stuck-job detection, cross-profile migration, and direct jobs.json editing when the cronjob tool can't reach other profiles.
version: 1.1.0
author: Hermes Agent
platforms: [linux]
---
# Hermes Cron Management
Diagnose and fix Hermes cron jobs — especially when jobs are stuck because their profile has no running gateway.
## When to Use
- Cron jobs have `next_run_at` timestamps in the past
- Jobs were created in a profile that no longer has a gateway running
- Consolidating scattered cron jobs into a single profile with a reliable gateway
- Validating that all cron jobs across all profiles are progressing properly
## Core Rule: Gateway Dependency
**Hermes cron jobs only execute when a gateway process is running for that profile.** A job defined in `~/.hermes/profiles/<name>/cron/jobs.json` will never fire unless a gateway process is active for `<name>`. The `hermes.service` systemd unit or a `hermes gateway run --profile <name>` process must be running.
This is the #1 cause of stuck cron jobs. Before debugging anything else, check: is a gateway running for this profile?
## Diagnosis Workflow
### 1. List all profiles and their cron jobs
```bash
for p in $(ls ~/.hermes/profiles/); do
f="$HOME/.hermes/profiles/$p/cron/jobs.json"
[ -f "$f" ] && echo "=== $p ===" && python3 -c "
import json
with open('$f') as fh:
data = json.load(fh)
for j in data.get('jobs', []):
print(f' {j[\"id\"][:12]} {j[\"name\"]} enabled={j[\"enabled\"]} state={j[\"state\"]} next={j.get(\"next_run_at\",\"?\")} last_status={j.get(\"last_status\",\"?\")}')
"
done
```
### 2. Check which profiles have running gateways
```bash
ps aux | grep 'hermes.*gateway run' | grep -v grep
```
### 3. Cross-reference
Any profile with cron jobs but NO gateway process → those jobs are stuck. Their `next_run_at` will be frozen in the past.
## Fix: Consolidate into a Profile with a Running Gateway
The reliable fix: move orphaned jobs into a profile that has a running gateway.
### Step 1: Verify the target profile
```bash
# Confirm gateway is running
ps aux | grep 'hermes.*gateway.*general' | grep -v grep
# Confirm cron scheduler is active in gateway logs
grep 'cron\|tick' ~/.hermes/profiles/general/logs/gateway.log | tail -5
# Confirm required models are available
curl -s localhost:11434/api/tags | python3 -c "import sys,json; [print(m['name']) for m in json.load(sys.stdin).get('models',[])]"
```
### Step 2: Verify dependencies exist in target profile
Before migrating, confirm the target profile has:
- Required skills (check `~/.hermes/profiles/<target>/skills/`)
- Required scripts (check `~/.hermes/profiles/<target>/scripts/`)
- Workdir paths exist on disk
### Step 3: Create the job in the target profile
Use `cronjob(action='create', ...)` from within the target profile's session. Match the original job's schedule, model, provider, skills, toolsets, workdir, and deliver settings.
**For no_agent script jobs:** The `cronjob` tool requires scripts to be in `~/.hermes/scripts/` relative to the profile. Copy the script from the source profile first:
```bash
cp ~/.hermes/profiles/<source>/scripts/<script>.sh ~/.hermes/profiles/<target>/scripts/<script>.sh
```
Then create with `no_agent=true` and `script="<script>.sh"`.
### Step 4: Disable the source job
The `cronjob` tool only manages jobs in the current profile. To disable jobs in OTHER profiles, edit their `jobs.json` directly:
```python
import json
with open('/home/n8n/.hermes/profiles/<source>/cron/jobs.json') as f:
data = json.load(f)
for j in data['jobs']:
j['enabled'] = False
j['state'] = 'paused'
j['paused_reason'] = 'Migrated to <target> profile (job <new_id>)'
with open('/home/n8n/.hermes/profiles/<source>/cron/jobs.json', 'w') as f:
json.dump(data, f, indent=2)
```
### Step 5: Verify
```bash
# Target: all jobs enabled and scheduled
python3 -c "import json; d=json.load(open('$HOME/.hermes/profiles/<target>/cron/jobs.json')); [print(j['name'], j['enabled'], j['state']) for j in d['jobs']]"
# Source: all jobs disabled
python3 -c "import json; d=json.load(open('$HOME/.hermes/profiles/<source>/cron/jobs.json')); [print(j['name'], j['enabled'], j['state']) for j in d['jobs']]"
```
## Common Failure Patterns
### Finance profile with wrong base_url
The finance profile had `base_url: http://10.0.0.26:8000/v1` (epyc server, powered off) but `model: deepseek-v4-pro:cloud` with `provider: custom:ollama`. The base_url should have been `http://localhost:11434/v1`. The model and provider were correct — only the base_url was wrong. Fix:
```yaml
model:
base_url: http://localhost:11434/v1 # was 10.0.0.26:8000/v1
default: deepseek-v4-pro:cloud
provider: custom:ollama
```
### Cron job has no model configured
If `last_error` says "has no model configured (job.model=None)", the job was created without a model override AND the profile's config.yaml has no `model.default` set. Fix: update the job with an explicit model, or set `model.default` in the profile's config.yaml.
## Pitfalls
- **Cron jobs need a gateway.** This is the #1 cause of stuck jobs. Always check gateway status first.
- **The `cronjob` tool is profile-scoped.** It only manages jobs in the current session's profile. Cross-profile operations require direct `jobs.json` editing.
- **Script jobs need scripts in the profile's own scripts dir.** The `cronjob` tool rejects absolute paths and paths from other profiles. Copy the script first.
- **Don't ask Claude to validate local infrastructure.** Claude runs on 10.0.0.28 and has no access to local `.hermes` trees, gateway processes, or Ollama. Validate locally.
- **Gateway logs are per-profile.** Check `~/.hermes/profiles/<name>/logs/gateway.log`, not the base `~/.hermes/logs/gateway.log` (which may be stale from an old gateway process).
- **Multiple gateways can't share a Telegram bot token.** If the general gateway shows "Telegram bot token already in use (PID X)", another profile's gateway already claimed it. This is normal — the general gateway still runs cron jobs.
- **Config cleanup can break cron.** Removing `cron:`, `gateway:`, or `streaming:` sections from a profile's config.yaml during cleanup will prevent the scheduler from initializing even if a gateway is running. CLI-only profiles that still have cron jobs need these sections.
- **State files may use profile-specific paths.** Scripts may hardcode state file paths to a specific profile (e.g., `~/.hermes/profiles/finance/cron_email_state.json`). When migrating jobs between profiles, check for hardcoded profile paths in scripts — they may need updating or the state file may need copying.
- **`cronjob(action='pause')` only works within the current profile.** The tool searches the current session's profile only. To disable jobs in OTHER profiles, edit their `jobs.json` directly (see Step 4 in Consolidation workflow). The tool will return "Job with ID not found" for cross-profile job IDs — that's expected, not a bug.
- **State file debugging: check what the script actually writes vs. what you expect.** If a state field shows a stale/garbage value (e.g., `last_ingested_at: 24`), grep the script for that field name. If it's only in `init_state()` and never set in the hot path, the field was never being updated. The fix is adding the write in the processing function, not patching the state file. See `references/email-ingest-state-debug.md` for a worked example.
- **State field consistency across creation paths.** When adding a new field to folder state dicts, add it to ALL three creation paths: `init_state()`, the folder-refresh path in `_main()`, and any migration code. Use `setdefault` in the hot path as defensive fallback, but seed the field at creation time so it's never missing. A field present in only some creation paths causes asymmetry (e.g., `sum(folder.ingested) != stats.ingested` because pre-fix folders lack the key).
- **Bash `set -e` + `((VAR++))` kills scripts on first zero-valued counter**: When a script uses `set -euo pipefail` and increments counters with `((SKIPPED++))`, the post-increment evaluates to the OLD value. If `SKIPPED=0` and the first iteration hits the skip branch, `((SKIPPED++))` evaluates to 0 (falsy in bash), `set -e` treats it as a command failure, and the script dies immediately. Only the header prints — no summary, no further processing. Fix: replace all `((VAR++))` with `VAR=$((VAR + 1))`. The `$((...))` form always returns exit code 0 regardless of the computed value. Same applies to `((VAR--))``VAR=$((VAR - 1))`. This is a classic `set -e` gotcha, not specific to cron scripts — any bash script with counters under `set -e` is vulnerable.
- **Monolithic agent prompts silently block downstream phases**: When a cron job uses an LLM agent to execute multiple sequential phases, a single sticky phase (e.g., Docker container recreation failing) can consume the entire tool-call budget. Later phases never run, and the report (typically the last phase) is never written — so the failure is silent. The agent's `last_status` may even report `ok` because the phase that ran didn't error. Fix: move multi-phase work into a `no_agent` bash script with real `||` error handling, per-phase timeouts, and a `FAILED` flag. The script guarantees every phase runs regardless of prior failures. See `references/monolithic-agent-prompt-pitfall.md`.
- **Docker container recreation via `docker inspect` reconstruction is fragile**: Capturing HostConfig and reconstructing a `docker run` command fails on port conflicts (new container can't bind while old holds the port), config gaps (missing env/cmd/args), and flag drift (new image drops old flags). Fix: migrate standalone containers to `docker compose` — config lives in a checked-in `compose.yaml`, `docker compose up -d` handles recreation correctly. See `references/docker-container-update-pitfall.md`.
- **Qdrant point IDs from `points_count` cause silent data loss**: Using `points_count` as the next point ID breaks when points are deleted (count < max ID → collision) and under concurrency (two runs get same count → overwrite). Fix: use deterministic UUIDv5 IDs keyed on content (`uuid5(namespace, f"{message_id}:{chunk_idx}")`). Same input always maps to same ID — idempotent upserts, no collisions. See `references/qdrant-deterministic-point-ids.md`.
- **Verify job output content, not just `last_status: ok`.** A job can report `ok` while producing garbage output or silently failing. Always read the latest output file and check external side effects (Qdrant point counts, state file timestamps, actual data written). See `references/verify-job-output.md`.
- **Cross-profile batch validation workflow.** When auditing all cron jobs across all profiles, use `execute_code` to batch-query `hermes -p <name> cron list`, then read each job's latest output and verify external side effects. See `references/cross-profile-cron-validation.md`.
- **Parallel delegation validation.** When the user asks to validate each job individually ("ask dev to validate, one at a time"), dispatch jobs to subagents in parallel batches of up to 3 using `delegate_task(tasks=[...])`. Each subagent reads output files, verifies Qdrant side effects, and reports WORKING/BROKEN with evidence. See `references/parallel-delegation-validation.md`.
- **Docker container recreation can break on CLI flag changes.** When a cron job updates Docker images and recreates containers, the new image may drop flags that the old image supported. The container crash-loops with `error: unknown option`. Compare `--help` between old and new images, then either roll back or adapt. See `references/docker-container-update-pitfall.md`.
- **Claude validation loop for script fixes.** When fixing a cron job's script, use the loop: fix → ask Claude to validate → apply Claude's feedback → re-validate until Claude says "CLEAN — no issues." Claude catches edge cases (missing init_state fields, folder-refresh path gaps, counter semantics) that are easy to miss in a single pass. See `references/claude-validation-loop.md`.
- **`last_status: error` can be stale — re-run the script manually before diagnosing.** A job may show `last_status: error` from a transient failure days ago while the script runs clean today. The error could have been a one-time environmental issue (malformed state file, network blip, OOM) that self-resolved. Always run the script manually (`bash ~/.hermes/profiles/<p>/scripts/<name>.sh`) before concluding the job is broken. If it runs clean, the error was transient — note it and move on. Only investigate further if the manual run also fails.\n- **Transient I/O contention on shared state files.** A script that reads a JSON state file (e.g., `.usage.json`) can fail with exit 1 and truncated output if another process (curator, usage tracker) is writing the file at the same moment. The inline Python JSON parse fails, `2>/dev/null` suppresses the error, and `set -e` kills the script mid-loop. If the script runs clean on retry (both interactive and in `env -i` cron simulation), the failure was transient — no code fix needed. See `references/simulate-cron-environment.md` for the reproduction recipe.\n- **Simulate cron's minimal environment to isolate env-vs-code bugs.** When a script fails in cron but works in an interactive shell, use `env -i HOME=/home/n8n PATH=/usr/bin:/bin SHELL=/bin/bash bash <script>` to reproduce cron's stripped environment. If it still works, the failure is environmental (transient I/O, TZ/locale, resource contention). If it fails, the script has a hidden dependency on interactive-shell state. See `references/simulate-cron-environment.md`.
- **Docker Compose `external: true` network + missing `networks:` block on a service → DNS failure.** If a service is missing its `networks:` block, it lands on the default network and can't resolve other services by container/service name. The `external: true` flag itself doesn't break DNS — the missing `networks:` block does. Fix: add `networks: - <name>` to the service. If no outside containers depend on the external network, switch to compose-managed (`driver: bridge`) for simpler DNS. Before switching, verify the old external network is empty: `docker network inspect <name>` and check `Containers: {}`.
- **Bash script Claude validation: specific gotchas Claude catches.** When converting an agent-driven cron job to a `no_agent` bash script, run it through the Claude validation loop (fix → scp → ask.sh → apply → re-validate). Claude reliably catches: unguarded `git commit` under `set -e` (exits on "nothing to commit"), missing `mkdir -p` before report file redirects (set -e kill before any phase runs), `exit 1` in error handlers that skip the summary, push-after-commit-fail false positives (push runs even when commit failed → misleading PASS), and `pip` vs `python3 -m pip` naming fragility. See `references/bash-script-claude-validation.md` for the full gotcha catalog from a 3-round validation session.
## Bulk Model Updates
To update all agent-driven jobs to a new model at once:
```bash
# From within the target profile's session
for job_id in <id1> <id2> <id3>; do
cronjob(action='update', job_id="$job_id", model={"model": "kimi-k2.7-code:cloud", "provider": "custom:ollama"})
done
```
Skip `no_agent` jobs — they have `model: null` and don't need a model.
## Pre-Migration Dependency Checklist
Before migrating a job into a target profile, verify every dependency exists:
| Check | Command |
|-------|---------|
| Skills exist | `find ~/.hermes/profiles/<target>/skills -name "SKILL.md" -path "*/<skill>/*"` |
| Toolsets in config | `grep -A20 'platform_toolsets:' ~/.hermes/profiles/<target>/config.yaml` |
| Workdir exists | `ls -d /home/n8n/workspace/<workdir>` |
| Scripts (for no_agent) | `ls ~/.hermes/profiles/<target>/scripts/<script>.sh` |
| Model in Ollama | `curl -s localhost:11434/api/tags \| python3 -c "import sys,json; print([m['name'] for m in json.load(sys.stdin)['models']])"` |
| External services | Qdrant: `curl -s http://10.0.0.22:6333/collections`, SearXNG: `curl -s "http://10.0.0.8:8888/search?q=test&format=json"`, Ollama embed: `curl -s localhost:11434/api/embed -d '{"model":"snowflake-arctic-embed2:latest","input":"test"}'` |
| CLI tools | `which himalaya`, `himalaya --version` |
## Verifying a Job Is Actually Working
Don't trust `last_status: ok` alone. Check the output:
```bash
# List recent output files
ls -lt ~/.hermes/profiles/<profile>/cron/output/<job_id>/
# Read the latest
cat $(ls -t ~/.hermes/profiles/<profile>/cron/output/<job_id>/*.md | head -1) | tail -40
```
For jobs that write to external state (Qdrant, databases), verify the side effect:
```bash
# Qdrant point count
curl -s -X POST http://10.0.0.22:6333/collections/<name>/points/count -H "Content-Type: application/json" -d '{}'
```

View File

@@ -1,7 +1,7 @@
--- ---
name: hermes-profile-management name: hermes-profile-management
description: "Create, configure, and validate Hermes profiles with linked workspaces. Covers the full lifecycle: clone, model selection, cwd binding, AGENTS.md seeding, and two-round validation." description: "Create, configure, and validate Hermes profiles with linked workspaces. Covers the full lifecycle: clone, model selection, cwd binding, AGENTS.md seeding, and two-round validation."
version: 1.1.0 version: 1.3.0
author: agent author: agent
license: MIT license: MIT
platforms: [linux] platforms: [linux]
@@ -154,6 +154,12 @@ ls ~/workspace/<name> 2>&1 # Workspace gone (No such file or directo
## Pitfalls ## Pitfalls
- **User mentions a name → check local state BEFORE web search**: When the user says a name you don't recognize (e.g., "openz"), do NOT immediately search the web for it. Check local state first: `hermes profile list` (could be a profile), `hermes skills list` (could be a skill), memory/fact_store (could be a known entity), `session_search` (could be from a prior session). Only search the web if all local checks come up empty. The user will be frustrated if you treat their local resources as unknown internet things.
- **Check memory/fact_store for infrastructure state before assuming something isn't installed**: When the user asks you to set up or integrate a service (ComfyUI, Open WebUI, etc.), check memory and fact_store for existing infrastructure BEFORE writing a plan that assumes it needs to be installed from scratch. The user may have set it up in a prior session. If the user says "check memory," you've already made the mistake — stop, check, and revise the plan. This is the infrastructure variant of the name-resolution pitfall above.
- **Scope incrementally — start with the simplest working path, defer advanced features**: When the user says "first I just want X for now, more later," write the plan for X only. Explicitly list deferred features in a "Future Phases" section so the user sees you heard them, but do NOT include those phases in the current scope. The user wants a working deliverable fast, not a maximal architecture document. This applies to integration plans, feature rollouts, and any multi-phase work.
- **Backend API server profiles must declare their interface in AGENTS.md**: When a Hermes profile serves as a backend for a web UI (Open WebUI, custom frontend, etc.), its AGENTS.md MUST include a prominent "Your Main Interface is X" section at the top. This section must explicitly state: (a) the agent is a backend API server, not the UI, (b) users interact through the web interface, not directly, (c) UI features like image generation, TTS, STT, and vision are handled by the web UI — do NOT try to enable those Hermes toolsets, (d) the agent's tools (terminal, file, web, memory) are for backend tasks only. Without this section, the agent will try to enable `image_gen`, `tts`, `vision`, and `browser` toolsets to fulfill user requests, wasting resources and confusing the model. See `references/open-webui-backend-profile-pattern.md` for the canonical template.
- **AGENTS.md alone won't fix a running session — use agent.system_prompt too**: The agent loads AGENTS.md at session start. If the user is in a session that started before the AGENTS.md update, the agent will still exhibit the old behavior. Always pair AGENTS.md updates with `hermes -p <profile> config set agent.system_prompt "..."` to inject the directive into every session immediately. `agent.system_prompt` appends to the system prompt after the identity block and takes effect without a session restart. Apply to ALL backend profiles at once with a `for` loop. See `references/open-webui-backend-profile-pattern.md` for the full dual-delivery pattern.
- **model.base_url MUST match model.default (critical, silent failure)**: The #1 silent error in cloned configs. `model.default` names a model, `model.base_url` points at the endpoint that serves it. If the source profile's `base_url` was an Epyc vLLM endpoint (10.0.0.26:8000, serves only `qwen3.6-35b-a3b-uncensored`) and the new profile's `model.default` is `minimax-m3:cloud` (served by Ollama at localhost:11434), the primary model will silently fail to load. ALWAYS cross-check: if model.default is a `:cloud` Ollama-routed model, base_url must be `http://localhost:11434/v1`, NOT an Epyc/DGX endpoint. The fix: `config['model']['base_url'] = 'http://localhost:11434/v1'` for any Ollama-routed primary model. - **model.base_url MUST match model.default (critical, silent failure)**: The #1 silent error in cloned configs. `model.default` names a model, `model.base_url` points at the endpoint that serves it. If the source profile's `base_url` was an Epyc vLLM endpoint (10.0.0.26:8000, serves only `qwen3.6-35b-a3b-uncensored`) and the new profile's `model.default` is `minimax-m3:cloud` (served by Ollama at localhost:11434), the primary model will silently fail to load. ALWAYS cross-check: if model.default is a `:cloud` Ollama-routed model, base_url must be `http://localhost:11434/v1`, NOT an Epyc/DGX endpoint. The fix: `config['model']['base_url'] = 'http://localhost:11434/v1'` for any Ollama-routed primary model.
- **context_length: 131072, not 262144**: DGX qwen3 DFlash has a native max context of 262,144 tokens, but you MUST configure `model.context_length: 131072`. The DGX vLLM runs `max_num_seqs: 3` with a shared ~457K token KV pool. Setting 262144 limits you to 1 concurrent stream; 131072 allows 3 streams. Epyc qwen3.6-35b uses 262144 (single stream, no KV pool sharing). - **context_length: 131072, not 262144**: DGX qwen3 DFlash has a native max context of 262,144 tokens, but you MUST configure `model.context_length: 131072`. The DGX vLLM runs `max_num_seqs: 3` with a shared ~457K token KV pool. Setting 262144 limits you to 1 concurrent stream; 131072 allows 3 streams. Epyc qwen3.6-35b uses 262144 (single stream, no KV pool sharing).
- **browser.allow_private_urls must match security.allow_private_urls**: If `security.allow_private_urls: true` but `browser.allow_private_urls: false`, the browser tool will silently block LAN URLs (10.0.0.x) even though security permits them. ALWAYS set both to the same value. - **browser.allow_private_urls must match security.allow_private_urls**: If `security.allow_private_urls: true` but `browser.allow_private_urls: false`, the browser tool will silently block LAN URLs (10.0.0.x) even though security permits them. ALWAYS set both to the same value.
@@ -164,6 +170,8 @@ ls ~/workspace/<name> 2>&1 # Workspace gone (No such file or directo
- **Clone inherits config version**: May be behind the source. Run `hermes config migrate` to bring it current. - **Clone inherits config version**: May be behind the source. Run `hermes config migrate` to bring it current.
- **AGENTS.md still references source workspace**: Copied workspace files still have the old workspace path and purpose. Rewrite AGENTS.md for the new profile. - **AGENTS.md still references source workspace**: Copied workspace files still have the old workspace path and purpose. Rewrite AGENTS.md for the new profile.
- **Profile alias**: `hermes profile create` auto-creates a wrapper at `~/.local/bin/<name>`. Use it as `vscode chat` or `hermes -p vscode`. - **Profile alias**: `hermes profile create` auto-creates a wrapper at `~/.local/bin/<name>`. Use it as `vscode chat` or `hermes -p vscode`.
- **`~/.hermes/active_profile` is the sticky-default file**: `hermes profile use <name>` writes the profile name to `~/.hermes/active_profile`. When `hermes` runs without `-p`, it reads this file to determine which profile to load. If bare `hermes` loads the wrong profile, check this file first — it may have been set by a prior `hermes profile use` or manual edit. The file contains a single line with the profile name. Fix: `echo "general" > ~/.hermes/active_profile` or `hermes profile use general`.
- **Profile alias preferred over `-p` flag for dispatcher skills**: Some models (notably minimax-m3:cloud) hallucinate that `hermes -p` doesn't exist, even though it's a valid global flag. When writing dispatcher skills that delegate to another profile, use the profile alias (`research -s ...`) instead of `hermes -p research -s ...`. The alias is at `~/.local/bin/<name>` and works from any directory. This avoids the model second-guessing the command and falling back to doing the work itself.
- **Skills are cloned**: The new profile gets a copy of the source's skills directory. They diverge independently after creation. - **Skills are cloned**: The new profile gets a copy of the source's skills directory. They diverge independently after creation.
- **Fallback model should be DGX qwen, not Ollama**: The standard fallback is `qwen` via DGX (http://10.0.0.6:8000/v1, api_key: dummy), NOT another Ollama model. DGX is the failover endpoint — if Ollama is down, the fallback must point at a different infrastructure, not the same one. - **Fallback model should be DGX qwen, not Ollama**: The standard fallback is `qwen` via DGX (http://10.0.0.6:8000/v1, api_key: dummy), NOT another Ollama model. DGX is the failover endpoint — if Ollama is down, the fallback must point at a different infrastructure, not the same one.
- **cronjob tool pause may not persist to jobs.json**: The `cronjob action='pause'` tool reports success and returns `enabled: false, state: paused` in its response, but the on-disk `cron/jobs.json` may still show `enabled: true, state: scheduled` on next read. The tool's in-memory state and the file can diverge. After pausing, ALWAYS verify by reading jobs.json directly, and if it didn't persist, write the pause directly: `j['enabled']=False; j['state']='paused'; j['paused_at']=<iso>; j['paused_reason']=<reason>` and dump back to the file. This is the only reliable way to pause a cloned cron job. - **cronjob tool pause may not persist to jobs.json**: The `cronjob action='pause'` tool reports success and returns `enabled: false, state: paused` in its response, but the on-disk `cron/jobs.json` may still show `enabled: true, state: scheduled` on next read. The tool's in-memory state and the file can diverge. After pausing, ALWAYS verify by reading jobs.json directly, and if it didn't persist, write the pause directly: `j['enabled']=False; j['state']='paused'; j['paused_at']=<iso>; j['paused_reason']=<reason>` and dump back to the file. This is the only reliable way to pause a cloned cron job.
@@ -172,15 +180,55 @@ ls ~/workspace/<name> 2>&1 # Workspace gone (No such file or directo
- **The `patch` tool refuses writes to config.yaml**: You cannot use the `patch` tool on `~/.hermes/profiles/<name>/config.yaml` — it errors with "Agent cannot modify security-sensitive configuration." Use `hermes config set <key> <value>` instead, which writes safely and confirms the change. For the top-level `workspace:` key: `hermes config set workspace /workspace/<name>`. For `terminal.cwd`: `hermes config set terminal.cwd /home/n8n/workspace/<name>`. Both keys exist independently — `workspace:` controls which workspace's AGENTS.md gets auto-loaded, `terminal.cwd` controls the shell's starting directory. When binding a profile to its workspace, set BOTH. - **The `patch` tool refuses writes to config.yaml**: You cannot use the `patch` tool on `~/.hermes/profiles/<name>/config.yaml` — it errors with "Agent cannot modify security-sensitive configuration." Use `hermes config set <key> <value>` instead, which writes safely and confirms the change. For the top-level `workspace:` key: `hermes config set workspace /workspace/<name>`. For `terminal.cwd`: `hermes config set terminal.cwd /home/n8n/workspace/<name>`. Both keys exist independently — `workspace:` controls which workspace's AGENTS.md gets auto-loaded, `terminal.cwd` controls the shell's starting directory. When binding a profile to its workspace, set BOTH.
- **`workspace:` key vs `terminal.cwd` are independent**: The top-level `workspace:` key (e.g. `/workspace/general`) controls which workspace root Hermes loads AGENTS.md / CLAUDE.md / .cursorrules from. The `terminal.cwd` key controls where shell commands start. They can diverge after a clone or partial setup — always verify both point at the intended workspace for the active profile. - **`workspace:` key vs `terminal.cwd` are independent**: The top-level `workspace:` key (e.g. `/workspace/general`) controls which workspace root Hermes loads AGENTS.md / CLAUDE.md / .cursorrules from. The `terminal.cwd` key controls where shell commands start. They can diverge after a clone or partial setup — always verify both point at the intended workspace for the active profile.
- **Symptom: model lists files from home directory instead of workspace**: When `terminal.cwd: .` (the default), the session starts in whatever directory the process was launched from — typically `/home/n8n`. If the user says "list md files in your workspace" and the model returns files from the home folder (random scripts, memory files, etc.) instead of `~/workspace/<profile>/`, the cwd is unbound. Fix: `hermes config set terminal.cwd /home/n8n/workspace/<profile>`. This is the #1 cause of "model is looking at wrong files" confusion. - **Symptom: model lists files from home directory instead of workspace**: When `terminal.cwd: .` (the default), the session starts in whatever directory the process was launched from — typically `/home/n8n`. If the user says "list md files in your workspace" and the model returns files from the home folder (random scripts, memory files, etc.) instead of `~/workspace/<profile>/`, the cwd is unbound. Fix: `hermes config set terminal.cwd /home/n8n/workspace/<profile>`. This is the #1 cause of "model is looking at wrong files" confusion.
- **`~/.hermes/skills/` base directory is NOT a deployment target for skill copies.** The base `~/.hermes/skills/` exists but only holds cross-profile-only skills (e.g., `kanban-codex-lane`). The `autonomous-ai-agents` and `mcp` categories live in `~/.hermes/profiles/<p>/skills/<category>/`, NOT in the base. When writing a new skill, put a canonical source in your workspace (e.g., `~/workspace/general/<skill>-SKILL.md`) for tracking, then `cp` to each profile's category dir. Don't write to `~/.hermes/skills/<category>/<skill>/` thinking it will be picked up — the skill-discovery loader only scans per-profile skills dirs. Verified with `ls -la ~/.hermes/skills/autonomous-ai-agents/` → empty (no skills in that base dir). See `references/skill-distribution-patterns.md` for the full distribution model.
- **SOUL.md text duplicated via CLAUDE.md/AGENTS.md**: SOUL.md is loaded as identity (slot #1 in stable tier). CLAUDE.md/AGENTS.md are loaded as project context (separate code path in context tier). `skip_soul` only prevents SOUL.md from being loaded twice — it does NOT skip CLAUDE.md or AGENTS.md. If `~/workspace/CLAUDE.md` contains the same persona text as SOUL.md, the model sees it twice: once as "who I am" and once as "project instructions I should follow." Fix: keep CLAUDE.md/AGENTS.md for project-specific instructions (build commands, conventions, architecture), not persona text. Delete workspace-level CLAUDE.md if it only contains SOUL.md content. - **SOUL.md text duplicated via CLAUDE.md/AGENTS.md**: SOUL.md is loaded as identity (slot #1 in stable tier). CLAUDE.md/AGENTS.md are loaded as project context (separate code path in context tier). `skip_soul` only prevents SOUL.md from being loaded twice — it does NOT skip CLAUDE.md or AGENTS.md. If `~/workspace/CLAUDE.md` contains the same persona text as SOUL.md, the model sees it twice: once as "who I am" and once as "project instructions I should follow." Fix: keep CLAUDE.md/AGENTS.md for project-specific instructions (build commands, conventions, architecture), not persona text. Delete workspace-level CLAUDE.md if it only contains SOUL.md content.
- **agent.system_prompt config key**: `hermes config set agent.system_prompt "text"` appends text to the system prompt after the identity block. It augments, not replaces. Useful for injecting directives that must appear in every session without editing SOUL.md. Unlike SOUL.md, this is per-profile (set in each profile's config.yaml). - **agent.system_prompt config key**: `hermes config set agent.system_prompt "text"` appends text to the system prompt after the identity block. It augments, not replaces. Useful for injecting directives that must appear in every session without editing SOUL.md. Unlike SOUL.md, this is per-profile (set in each profile's config.yaml).
- **memory.write_approval config flag**: `hermes config set memory.write_approval true` gates ALL memory tool writes (add/replace/remove) to MEMORY.md and USER.md. When true, foreground writes prompt inline for approval; background-review writes are staged for `/memory pending` / `/memory approve` / `/memory reject`. Default is false (writes happen freely). This only gates the built-in `memory` tool — it does NOT affect Holographic/fact_store writes. Stronger than a SOUL.md directive since it's enforced at the tool level. **Per-profile, not global** — must be set in each profile's config.yaml. Use the `hermes-config-bulk-update` pattern to apply across all profiles in one pass. - **memory.write_approval config flag**: `hermes config set memory.write_approval true` gates ALL memory tool writes (add/replace/remove) to MEMORY.md and USER.md. When true, foreground writes prompt inline for approval; background-review writes are staged for `/memory pending` / `/memory approve` / `/memory reject`. Default is false (writes happen freely). This only gates the built-in `memory` tool — it does NOT affect Holographic/fact_store writes. Stronger than a SOUL.md directive since it's enforced at the tool level. **Per-profile, not global** — must be set in each profile's config.yaml. Use the `hermes-config-bulk-update` pattern to apply across all profiles in one pass.
- **`/memory pending` and `/memory approve` are chat slash commands, NOT CLI commands**: `hermes memory approve <id>` and `hermes memory pending` do not exist — the `hermes memory` CLI only has `setup`, `status`, `off`, `reset`. When `write_approval` is on, pending entries are reviewed with `/memory pending` (lists staged entries), `/memory approve <id>` (approves one), and `/memory reject <id>` (rejects one) — typed directly in the chat, not via the terminal. The `hermes memory status` command shows which provider is active but does NOT show pending entries.
- **Temperature is set in THREE places, not one**: A profile's temperature lives in three independent locations: (1) `moa.presets.<name>.reference_temperature` — the research sub-agent's reference stage, (2) `moa.presets.<name>.aggregator_temperature` — the research sub-agent's aggregation stage, (3) `custom_providers[].extra_body.temperature` — the main model's temperature passed via the Ollama provider's extra_body. All three must be set to the same value for consistent behavior. The main model temperature is NOT a top-level `model.temperature` key — it's embedded in the provider's `extra_body` block. To set all three: `sed` for the MOA temps (simple value replacement), and `patch` or `execute_code` to insert `extra_body: { temperature: 0.3 }` into the Ollama provider block (which may not exist yet). See `references/temperature-config.md` for the full pattern. - **Temperature is set in THREE places, not one**: A profile's temperature lives in three independent locations: (1) `moa.presets.<name>.reference_temperature` — the research sub-agent's reference stage, (2) `moa.presets.<name>.aggregator_temperature` — the research sub-agent's aggregation stage, (3) `custom_providers[].extra_body.temperature` — the main model's temperature passed via the Ollama provider's extra_body. All three must be set to the same value for consistent behavior. The main model temperature is NOT a top-level `model.temperature` key — it's embedded in the provider's `extra_body` block. To set all three: `sed` for the MOA temps (simple value replacement), and `patch` or `execute_code` to insert `extra_body: { temperature: 0.3 }` into the Ollama provider block (which may not exist yet). See `references/temperature-config.md` for the full pattern.
## Skills as Dispatcher + Methodology Contracts (Cross-Profile Distribution)
When designing a NEW skill that runs across profiles (i.e., a skill with more than one SKILL.md file), pick the distribution model upfront and document it in the plan. The two main patterns:
### Pattern A: Single skill, multi-profile copies (default)
- One SKILL.md, copied to every profile that needs it as a real file (not symlink).
- Use when: the skill is self-contained and each profile can run it independently with the same behavior.
- Example: `searxng-smart-search` lives at `~/.hermes/profiles/<p>/skills/mcp/searxng-smart-search/SKILL.md` on every profile that wants MCP search. The SKILL.md is identical in every copy.
### Pattern B: Dispatcher + Methodology contract (cross-profile delegation)
- **Two SKILL.md files** with a strict separation of concerns.
- **Dispatcher** (per-profile, lightweight): owns trigger detection, clarifying questions, dispatch command, session-id capture, delivery report. Installed on every profile that needs to fire the trigger. ~150 lines.
- **Methodology** (one specialized profile only): owns the actual heavy logic. Lives on the profile whose tools/model fit the work best. ~300+ lines, may be expensive to run.
- The dispatcher's command is identical regardless of which profile it's running on: `hermes -p <specialized-profile> -s <methodology-name> chat -q "<question>" ...`. The `-p` flag pins the target.
- Use when: the heavy work needs a specific profile's setup (e.g., a research profile with SearXNG MCP and a long-context model), but the trigger should be fireable from any profile (e.g., from dev, family_law, finance).
- Example: `better-search` (dispatcher, 16 profiles) + `better-search-research` (methodology, research profile only). The dispatcher catches `better search` triggers on any profile and always delegates to `research -s better-search-research ...` (using the `research` profile alias at `~/.local/bin/research` — avoids the `-p` flag that some models hallucinate doesn't exist).
- Reference implementation: `references/skill-distribution-patterns.md` — full design rationale, when to use which pattern, and a worked checklist for new-skill plan docs.
### Why two-skill contracts matter
1. **Single source of truth for the heavy logic.** Edit the methodology once → all 16 dispatchers pick up the change.
2. **Cheap dispatcher, expensive methodology.** The dispatcher is ~150 lines and runs in <1s to fire; only the methodology pays the long-context / multi-search cost.
3. **No skill duplication for the heavy part.** Replicating a 300-line methodology to 16 profiles = 4,800 lines of drift-prone duplication. The contract makes this a non-issue.
4. **Trigger coverage is per-profile, logic is per-specialized-profile.** Triggers can vary per profile (e.g., dev profile might add a dev-specific trigger) without touching the methodology.
### Decision: when to use which
- **Self-contained skill, same behavior everywhere** → Pattern A (single skill, real copies).
- **Skill that needs a specific profile's tools/model** → Pattern B (dispatcher + methodology).
- **Default to Pattern A.** Add the contract only when there's a concrete reason (the heavy logic needs a specific profile's setup, or the methodology is too large/expensive to replicate).
### Pitfalls for the dispatcher+methodology pattern
- **Real copies, not symlinks** for the dispatcher. Each profile gets its own dispatcher SKILL.md. The methodology also gets a real file, but on one profile only. (See `references/skill-distribution-patterns.md` for the verification protocol and the **same-category + same-scope precedent check** — the wrong-precedent trap is the most common error here.)
- **The dispatch command is identical across all dispatcher copies.** Hardcode `-p <specialized-profile> -s <methodology>` in every dispatcher's SKILL.md. The operator never types the profile flag.
- **`--max-turns` is a SAFETY NET, not the cap.** The methodology's loop cap is enforced by its own scaffolding (e.g., a `/tmp/<sid>.md` ledger that counts `## Refinement` blocks). `--max-turns` is just the ceiling the agent hits if it goes long. Set it high enough that real questions don't hit it (e.g., 50 for a 3-loop skill, 600 for an exhaustive one). Same logic as `deep-research` pitfall #4.
- **No `--resume` follow-up for cap-bounded methodologies.** If the methodology has a per-dispatch cap (e.g., better-search's 3-loop cap), `--resume` re-enters with fresh `--max-turns` and breaks the cap guarantee. Make every follow-up a fresh dispatch with a new sid, new ledger, and a new budget. Trade-off: the operator loses research context between dispatches — if iterative drilling matters, use a heavier skill (e.g., `deep-research`) that supports `--resume`.
- **Methodology skill must be loadable before dispatch.** The dispatcher should run a precondition check (`hermes -p <specialized-profile> skills list | grep <methodology>`) and abort cleanly with a "skill not installed" message if the methodology is missing. This prevents dispatching a generic unbounded session. (If you drop the precondition check, the runtime will still report `-s <name>: skill not found` clearly — either is fine, just don't silently fail.)
- **Trigger phrases must be explicit.** Avoid generic English verbs (`look into`, `find out`, `search again`) — they collide with normal agent tasks on dev/code profiles. Use phrases that include the skill's own name (`better search`, `do a better search`). Keep the list short (2-3 phrases); more phrases = more collision risk.
- **Cross-profile tests must bracket sparse AND dense profiles.** When verifying a dispatcher works on every profile, pick one profile that has many other cross-profile skills (dense, e.g. `general`) and one that has few (sparse, e.g. `people`). Testing only one end doesn't prove the principle. Run the same test from both ends.
## SOUL.md Symlink Propagation (One File, All Profiles) ## SOUL.md Symlink Propagation (One File, All Profiles)
Instead of maintaining independent SOUL.md copies per profile, use a single canonical file with symlinks. `load_soul_md()` does `get_hermes_home() / "SOUL.md"` — a simple file read, so symlinks are transparent. Instead of maintaining independent SOUL.md copies per profile, use a single canonical file with symlinks. `load_soul_md()` does `get_hermes_home() / "SOUL.md"` — a simple file read, so symlinks are transparent.
@@ -340,7 +388,7 @@ These all regenerate or are stale:
- `interrupt_debug.log` — cloned debug log - `interrupt_debug.log` — cloned debug log
- `checkpoints/store/` contents — cloned snapshots (especially if checkpoints disabled) - `checkpoints/store/` contents — cloned snapshots (especially if checkpoints disabled)
- `state-snapshots/` — cloned pre-update snapshots (can be 80MB+) - `state-snapshots/` — cloned pre-update snapshots (can be 80MB+)
- `logs/agent.log*`, `logs/errors.log*`, `logs/gateway*.log*`, `logs/mcp-stderr.log`, `logs/hindsight-embed.log`, `logs/update.log`, `logs/curator/` — all cloned (can be 120MB+) - `logs/agent.log*`, `logs/errors.log*`, `logs/gateway*.log*`, `logs/mcp-stderr.log`, `logs/hindsight-embed.log`, `logs/update.log`, `logs/curator/` — all cloned (can be 120MB+). **Pitfall:** `logs/curator/` is a directory, not a file — use `shutil.rmtree` or `rm -rf`, not `os.remove`. Same for `cache/` and `sessions/`.
- `models_dev_cache.json`, `ollama_cloud_models_cache.json`, `provider_models_cache.json`, `context_length_cache.yaml` — caches regenerate - `models_dev_cache.json`, `ollama_cloud_models_cache.json`, `provider_models_cache.json`, `context_length_cache.yaml` — caches regenerate
- `cache/openrouter_model_metadata.json`, `cache/model_catalog.json` — caches regenerate - `cache/openrouter_model_metadata.json`, `cache/model_catalog.json` — caches regenerate
- `config.yaml.bak.*` — stale backups - `config.yaml.bak.*` — stale backups
@@ -395,6 +443,7 @@ Full validation script and cross-check code: `references/post-config-validation.
## Support Files ## Support Files
- `references/skill-distribution-patterns.md` — Cross-profile skill distribution patterns (Pattern A: real copies vs. Pattern B: dispatcher+methodology contract). Worked `better-search` example, decision checklist, real-copy-vs-symlink verification.
- `references/remote-acp-vscode.md` — Remote Hermes + VS Code ACP: SSH tunnel options, filesystem limitation, and workarounds - `references/remote-acp-vscode.md` — Remote Hermes + VS Code ACP: SSH tunnel options, filesystem limitation, and workarounds
- `references/profile-setup-checklist.md` — Full 22-step checklist for profile setup (workspace, cron, config cleanup, permissions, max settings, fallback, junk cleanup, validation). Copy this into the new profile's workspace as a tracking doc. - `references/profile-setup-checklist.md` — Full 22-step checklist for profile setup (workspace, cron, config cleanup, permissions, max settings, fallback, junk cleanup, validation). Copy this into the new profile's workspace as a tracking doc.
- `references/post-config-validation.md` — Structured post-config validation pass: endpoint testing, model respond tests, base_url/model cross-check, hindsight bank audit, empty-value scan, decision points to surface to user. - `references/post-config-validation.md` — Structured post-config validation pass: endpoint testing, model respond tests, base_url/model cross-check, hindsight bank audit, empty-value scan, decision points to surface to user.
@@ -409,3 +458,6 @@ Full validation script and cross-check code: `references/post-config-validation.
- `references/mcp-server-http-sharing.md` — Convert per-profile stdio MCP servers to a single shared HTTP instance with systemd persistence. Eliminates redundant npx processes and cold-start delays across profiles. - `references/mcp-server-http-sharing.md` — Convert per-profile stdio MCP servers to a single shared HTTP instance with systemd persistence. Eliminates redundant npx processes and cold-start delays across profiles.
- `references/temperature-config.md` — Temperature is set in THREE places (MOA reference, MOA aggregator, Ollama provider extra_body). Full pattern for setting all three consistently. - `references/temperature-config.md` — Temperature is set in THREE places (MOA reference, MOA aggregator, Ollama provider extra_body). Full pattern for setting all three consistently.
- `references/telegram-bot-token-conflict.md` — Diagnose and fix Telegram bot token conflicts where messages route to the wrong profile because two gateways compete for the same token. - `references/telegram-bot-token-conflict.md` — Diagnose and fix Telegram bot token conflicts where messages route to the wrong profile because two gateways compete for the same token.
- `references/deepseek-reasoning-effort.md` — DeepSeek v4 `reasoning_effort` parameter: only two real tiers (`high` and `max`). `low`/`medium` silently map to `high`, `xhigh` maps to `max`. Don't bother with intermediate values — they don't save tokens.
- `references/open-webui-infrastructure.md` — Open WebUI deployment details for this environment: systemd service (not Docker), SQLite config table, ComfyUI wiring, Hermes API server connections, user accounts, and pitfalls.
- `references/open-webui-image-gen-debugging.md` — Open WebUI image generation debugging: the 3-way AND gate (model capability, backend flag, user permission), diagnostic curls, DB-level checks, fixes by gate, frontend cache clearing, and current environment state.

View File

@@ -0,0 +1,90 @@
---
name: local-ai-media-generation
description: Plan and evaluate local AI media generation pipelines (video, talking-head, voice) on consumer GPUs — research the landscape, validate tool claims against primary sources, map to hardware, and write per-software-type plans. Carries the 2026 verified landscape and license-landmine reference.
version: 1.0.0
author: Hermes Agent
metadata:
hermes:
tags: [mlops, ai-video, tts, voice-cloning, talking-head, local-gpu, planning]
related_skills: [better-search, ask-claude, deep-research]
---
# Local AI Media Generation — Pipeline Planning
Plan and evaluate open-source AI media generation pipelines that run locally on
consumer GPUs. Covers three layers: **video generation** (T2V/I2V), **talking-head /
lip-sync** (image+audio→video), and **voice** (TTS + zero-shot cloning). Use when the
user wants to generate AI video clips, talking-head videos, or cloned-voice audio
locally — especially the "funny AI celebrity clips" genre seen on X/Twitter.
## §1 When to Use / When NOT to Use
**Use when:**
- User wants to make AI-generated video clips (text-to-video, image-to-video, talking-head) locally
- User wants to clone a voice or generate speech locally
- User asks "how do they make those AI videos on X" and wants to reproduce the pipeline
- Evaluating whether a given video/voice model fits specific GPU hardware
**Do NOT use when:**
- Cloud/API video generation (Sora, Runway, Kling cloud) — this skill is local-only
- Video editing / transcoding (ffmpeg workflows) — not generation
- Hermes's own voice interaction (TTS/STT for the agent itself) — use `voice-systems`
## §2 Workflow (research → validate → map → plan)
1. **Dispatch 3-layer research** via `better-search` (one dispatch per layer):
- Video generation (T2V + I2V): Wan, HunyuanVideo, LTX, CogVideoX, Mochi, SVD, AnimateDiff
- Talking-head / lip-sync: EchoMimic, Hallo, Sonic, SadTalker, LatentSync, LivePortrait
- Voice / TTS: F5-TTS, CosyVoice, Chatterbox, GPT-SoVITS, Kokoro, XTTS-v2, Fish-Speech
Run in parallel (background terminal + notify_on_complete). Collect result files from `~/workspace/research/results/`.
2. **Adversarial review via `ask-claude`** — send the digest to Claude Opus with the constraint: "Do NOT propose new features or architecture. Find FLAWS in what's proposed. Cite source URLs." Claude catches stale claims and missed models.
3. **Verify Claude's load-bearing claims against primary sources** (HF model cards, GitHub LICENSE files, discussion threads). Claude is a consultant, not a verifier — its claims are hypotheses to test. See `ask-claude` skill's disagreement-scan protocol.
4. **Map to hardware** — per-job VRAM ceiling = single largest card. Multi-GPU = data-parallel throughput (N jobs on N cards), NOT tensor-parallel single-job sharding. You cannot pool 2×16GB into 32GB effective for one job.
5. **Write one plan per software type** in `plans/<date>-<slug>.md` (AGENTS.md template). Max 4 plans unless user says otherwise.
## §3 License landmines (verify before commercial use)
These recur in this problem space. Always check the *weights* license, not just the code repo.
- 🔴 **F5-TTS weights = CC-BY-NC-4.0** — the GitHub *code* is MIT (switched Oct 2024), but the HF model card frontmatter is `license: cc-by-nc-4.0` because the Emilia training dataset is CC-BY-NC. Maintainer confirmed this taints the weights and survives fine-tuning (Discussion #129). Do NOT use F5-TTS commercially. Use **Chatterbox** (MIT) as the commercial-safe default voice cloner.
- 🔴 **Sonic = CC-BY-NC-SA 4.0** — non-commercial. Best face-only quality but kills commercial use.
- 🟠 **HunyuanVideo** — custom Tencent license excludes EU, UK, South Korea from licensed territory; >100M-MAU gate. Irrelevant for US-only personal use, fatal for distributed products in those regions.
- 🟠 **LTX-2 weights** — "LTX-2 Community License": free under $10M ARR, paid above. Code is Apache-2.0. Fine now; note the ceiling.
- 🟠 **CosyVoice** — repo is Apache-2.0, but open maintainer threads (issues #598, #853) about whether weights carry the same terms. Treat as "probably OK, verify before shipping," not certain.
- 🟠 **Fish-Speech / MaskGCT / XTTS-v2** — all commercially restricted or unmaintained. XTTS-v2: Coqui shut down Jan 2024, community forks only.
-**Clean for commercial:** Chatterbox (MIT), Wan 2.2 incl. S2V/Animate/TI2V (Apache-2.0), EchoMimic V1/V2/V3 (Apache-2.0), Kokoro (Apache-2.0), LatentSync (Apache-2.0), CogVideoX code (Apache).
- ⚠️ **Right-of-publicity / deepfake statutes** — separate from model licenses. Generating a real celebrity's face + cloned voice for distributed content is a legal exposure independent of any Apache-2.0 weights license. The model license doesn't grant you the person's likeness. Flag this to the user explicitly.
## §4 Hardware reality (consumer multi-GPU)
- **Multi-GPU = data-parallel throughput** (N GPUs → N concurrent jobs, linear, no NVLink required). All major video models support this.
- **Multi-GPU ≠ tensor-parallel single-job sharding** for consumer GPUs. xDiT/USP can sequence-parallel a single Wan/Hunyuan job across GPUs but it shards *compute for speed, not VRAM* (weights replicate unless FSDP), and without NVLink the PCIe traffic is a bad trade. You cannot split one 720p Wan 2.2 generation across two 16GB cards to reach 32GB effective.
- **Practical per-job ceiling = single largest card.** To run top-tier models (Wan 2.2/HunyuanVideo at full 720p+) you need one card with ≥60-80GB VRAM, not multiple consumer cards.
- **Consumer escape hatches:** FP8/Q8 quantization + CPU text-encoder offload trade speed for VRAM on one card.
## §5 Verified 2026 landscape (condensed)
See `references/ai-video-voice-landscape-2026.md` for the full per-model table with VRAM, license, fit, and source URLs. Quick picks:
- **Fast silent I2V (fits 12-16GB):** LTX-Video 0.9.5 — 12GB native, ~90s/5s clip on 4090, built-in I2V. LTX-2.3 (Mar 2026) adds native 4K@50fps up to 20s + synchronized audio in one pass (first open model to do so), FP8 floor 16-24GB.
- **Best silent quality on 24GB:** Wan 2.2 14B (FP8 + CPU offload, ~4min20s/5s clip, 720p 81 frames). Apache-2.0. Largest LoRA ecosystem for celebrity likeness. Cannot fit 16GB.
- **Wan2.2-TI2V-5B** — T2V+I2V in one 5B model, runs on 4090, 720P@24fps. The consumer-friendly Wan.
- **Talking-head (image+audio→video):** EchoMimic V1 (face, 8-16GB, Apache-2.0), V2 (semi-body, 16-24GB), V3 (full-body 1.3B, 24GB default / 12GB tuned, AAAI 2026). All Apache-2.0, actively maintained.
- **Audio-driven cinematic (one model, no separate lip-sync):** Wan2.2-S2V-14B (Apache-2.0, Aug 2025) — image+audio+optional prompt+pose → talking video, 480P/720P, 80GB native / multi-GPU FSDP / 24GB with offload. Beats chaining TTS+lip-sync for talking clips.
- **Voice cloning (commercial-safe):** Chatterbox (MIT, 5s ref, 23+ langs, emotion control, 6GB Turbo). GPT-SoVITS (MIT, best few-shot from 1min audio). CosyVoice (Apache, 9 langs + 18 dialects, cross-lingual, but verify weights license before commercial ship).
## §6 Pitfalls
1. **Conflating code license with weights license.** F5-TTS, LTX-2, CosyVoice all have code-vs-weights license splits. Always check the HF model card frontmatter `license:` field, not just the GitHub LICENSE.
2. **Trusting research summaries over primary sources.** The `better-search` digest said F5-TTS was MIT. It was wrong — the *code* is MIT, the *weights* are CC-BY-NC. Claude caught it; I verified against the HF card. Always verify license claims against the actual model card before putting a model in a commercial path.
3. **Assuming multi-GPU pools VRAM.** It doesn't (see §4). A 5×16GB box is a throughput farm for ≤16GB jobs, not a way to run 80GB jobs.
4. **Post-dubbing generated video with LatentSync.** LatentSync needs a clear, front-facing, stable mouth in every frame. If the T2V model generated a wide/side/moving shot, LatentSync has nothing to sync. For talking clips, use Wan2.2-S2V (audio-driven gen) instead of generate-then-dub.
5. **Likeness LoRA breaking motion priors.** Overtrained celebrity LoRAs on Wan produce stiff/frozen faces. And 4090 OOM on 720p/81-frame at FP8 is common — drop frames or resolution first.
6. **Forgetting right-of-publicity.** Apache-2.0 weights don't grant the person's likeness. Flag this to the user before they build a celebrity-clip pipeline.
## §7 See Also
- `better-search` — dispatch the 3-layer research (one question per dispatch, parallel background runs)
- `ask-claude` — adversarial review of the digest; follow its disagreement-scan protocol and verify claims against primary sources
- `references/ai-video-voice-landscape-2026.md` — full per-model table with VRAM, license, fit, source URLs
- `references/command-gotchas-verified.md` — exact CLI flag corrections, diffusers class names, EchoMimic script names, Blackwell cu128 requirement (verified against primary sources during plan review)

195
maps/SKILL.md Normal file
View File

@@ -0,0 +1,195 @@
---
name: maps
description: "Geocode, POIs, routes, timezones via OpenStreetMap/OSRM."
version: 1.2.0
author: Mibayy
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [maps, geocoding, places, routing, distance, directions, nearby, location, openstreetmap, nominatim, overpass, osrm]
category: productivity
requires_toolsets: [terminal]
supersedes: [find-nearby]
---
# Maps Skill
Location intelligence using free, open data sources. 8 commands, 44 POI
categories, zero dependencies (Python stdlib only), no API key required.
Data sources: OpenStreetMap/Nominatim, Overpass API, OSRM, TimeAPI.io.
This skill supersedes the old `find-nearby` skill — all of find-nearby's
functionality is covered by the `nearby` command below, with the same
`--near "<place>"` shortcut and multi-category support.
## When to Use
- User sends a Telegram location pin (latitude/longitude in the message) → `nearby`
- User wants coordinates for a place name → `search`
- User has coordinates and wants the address → `reverse`
- User asks for nearby restaurants, hospitals, pharmacies, hotels, etc. → `nearby`
- User wants driving/walking/cycling distance or travel time → `distance`
- User wants turn-by-turn directions between two places → `directions`
- User wants timezone information for a location → `timezone`
- User wants to search for POIs within a geographic area → `area` + `bbox`
## Prerequisites
Python 3.8+ (stdlib only — no pip installs needed).
Script path: `~/.hermes/skills/maps/scripts/maps_client.py`
## Commands
```bash
MAPS=~/.hermes/skills/maps/scripts/maps_client.py
```
### search — Geocode a place name
```bash
python3 $MAPS search "Eiffel Tower"
python3 $MAPS search "1600 Pennsylvania Ave, Washington DC"
```
Returns: lat, lon, display name, type, bounding box, importance score.
### reverse — Coordinates to address
```bash
python3 $MAPS reverse 48.8584 2.2945
```
Returns: full address breakdown (street, city, state, country, postcode).
### nearby — Find places by category
```bash
# By coordinates (from a Telegram location pin, for example)
python3 $MAPS nearby 48.8584 2.2945 restaurant --limit 10
python3 $MAPS nearby 40.7128 -74.0060 hospital --radius 2000
# By address / city / zip / landmark — --near auto-geocodes
python3 $MAPS nearby --near "Times Square, New York" --category cafe
python3 $MAPS nearby --near "90210" --category pharmacy
# Multiple categories merged into one query
python3 $MAPS nearby --near "downtown austin" --category restaurant --category bar --limit 10
```
46 categories: restaurant, cafe, bar, hospital, pharmacy, hotel, guest_house,
camp_site, supermarket, atm, gas_station, parking, museum, park, school,
university, bank, police, fire_station, library, airport, train_station,
bus_stop, church, mosque, synagogue, dentist, doctor, cinema, theatre, gym,
swimming_pool, post_office, convenience_store, bakery, bookshop, laundry,
car_wash, car_rental, bicycle_rental, taxi, veterinary, zoo, playground,
stadium, nightclub.
Each result includes: `name`, `address`, `lat`/`lon`, `distance_m`,
`maps_url` (clickable Google Maps link), `directions_url` (Google Maps
directions from the search point), and promoted tags when available —
`cuisine`, `hours` (opening_hours), `phone`, `website`.
### distance — Travel distance and time
```bash
python3 $MAPS distance "Paris" --to "Lyon"
python3 $MAPS distance "New York" --to "Boston" --mode driving
python3 $MAPS distance "Big Ben" --to "Tower Bridge" --mode walking
```
Modes: driving (default), walking, cycling. Returns road distance, duration,
and straight-line distance for comparison.
### directions — Turn-by-turn navigation
```bash
python3 $MAPS directions "Eiffel Tower" --to "Louvre Museum" --mode walking
python3 $MAPS directions "JFK Airport" --to "Times Square" --mode driving
```
Returns numbered steps with instruction, distance, duration, road name, and
maneuver type (turn, depart, arrive, etc.).
### timezone — Timezone for coordinates
```bash
python3 $MAPS timezone 48.8584 2.2945
python3 $MAPS timezone 35.6762 139.6503
```
Returns timezone name, UTC offset, and current local time.
### area — Bounding box and area for a place
```bash
python3 $MAPS area "Manhattan, New York"
python3 $MAPS area "London"
```
Returns bounding box coordinates, width/height in km, and approximate area.
Useful as input for the bbox command.
### bbox — Search within a bounding box
```bash
python3 $MAPS bbox 40.75 -74.00 40.77 -73.98 restaurant --limit 20
```
Finds POIs within a geographic rectangle. Use `area` first to get the
bounding box coordinates for a named place.
## Working With Telegram Location Pins
When a user sends a location pin, the message contains `latitude:` and
`longitude:` fields. Extract those and pass them straight to `nearby`:
```bash
# User sent a pin at 36.17, -115.14 and asked "find cafes nearby"
python3 $MAPS nearby 36.17 -115.14 cafe --radius 1500
```
Present results as a numbered list with names, distances, and the
`maps_url` field so the user gets a tap-to-open link in chat. For "open
now?" questions, check the `hours` field; if missing or unclear, verify
with `web_search` since OSM hours are community-maintained and not always
current.
## Workflow Examples
**"Find Italian restaurants near the Colosseum":**
1. `nearby --near "Colosseum Rome" --category restaurant --radius 500`
— one command, auto-geocoded
**"What's near this location pin they sent?":**
1. Extract lat/lon from the Telegram message
2. `nearby LAT LON cafe --radius 1500`
**"How do I walk from hotel to conference center?":**
1. `directions "Hotel Name" --to "Conference Center" --mode walking`
**"What restaurants are in downtown Seattle?":**
1. `area "Downtown Seattle"` → get bounding box
2. `bbox S W N E restaurant --limit 30`
## Pitfalls
- Nominatim ToS: max 1 req/s (handled automatically by the script)
- `nearby` requires lat/lon OR `--near "<address>"` — one of the two is needed
- OSRM routing coverage is best for Europe and North America
- Overpass API can be slow during peak hours; the script automatically
falls back between mirrors (overpass-api.de → overpass.kumi.systems)
- `distance` and `directions` use `--to` flag for the destination (not positional)
- If a zip code alone gives ambiguous results globally, include country/state
## Verification
```bash
python3 ~/.hermes/skills/maps/scripts/maps_client.py search "Statue of Liberty"
# Should return lat ~40.689, lon ~-74.044
python3 ~/.hermes/skills/maps/scripts/maps_client.py nearby --near "Times Square" --category restaurant --limit 3
# Should return a list of restaurants within ~500m of Times Square
```

99
nordvpn/SKILL.md Normal file
View File

@@ -0,0 +1,99 @@
---
name: nordvpn
description: "Manage NordVPN on the Hermes host — status, reconnect for fresh IP, settings reference, and Cloudflare-block workaround."
version: 1.0.0
category: devops
tags: [nordvpn, vpn, cloudflare, ip-block, networking]
---
# NordVPN Management
Manage the NordVPN connection on the Hermes host. Used when sites block the current IP (Cloudflare, bot detection) or when VPN status/settings need inspection.
## When to Use
- Sites return Cloudflare "Attention Required" or "Just a moment..." blocks
- Need a fresh IP to bypass rate limiting or geo-restrictions
- Inspecting or verifying VPN status and settings
- User asks about VPN configuration
## Commands
### Status & Info
```bash
nordvpn status # Connection status, server, IP, uptime
nordvpn settings # All configuration settings
nordvpn account # Account email, expiry, MFA status
nordvpn groups # Available server groups
nordvpn countries # Available countries
```
### Reconnect (Fresh IP)
**Agent is authorized to reconnect without asking.** Any other settings changes require user permission.
```bash
nordvpn disconnect
nordvpn connect us <city> # Use different city to avoid same server
```
Alternative: `nordvpn connect` (no args = recommended server).
### Settings (user permission required)
```bash
nordvpn set killswitch on|off
nordvpn set technology nordlynx|openvpn
nordvpn set lan-discovery on|off
nordvpn set threatprotectionlite on|off
nordvpn set post-quantum on|off
nordvpn set dns <1.1.1.1> <8.8.8.8>
nordvpn set autoconnect on|off
nordvpn set notify on|off
nordvpn set defaults # Reset all to defaults
```
### Allowlist
```bash
nordvpn allowlist add port 11434 # Allow port through VPN
nordvpn allowlist add subnet 10.0.0.0/24
nordvpn allowlist remove port 11434
```
## Current Configuration (as of 2026-07-12)
| Setting | Value |
|---------|-------|
| Technology | NORDLYNX |
| Firewall | enabled |
| Kill Switch | enabled |
| Auto-connect | enabled |
| Post-quantum | enabled |
| LAN Discovery | enabled |
| Threat Protection Lite | disabled |
| Meshnet | disabled |
| DNS | disabled |
| Allowlisted ports | 53 (UDP/TCP), 11434 (TCP) |
| Allowlisted subnets | 34.36.133.0/24 |
Account: mdkrushr@gmail.com, active until Aug 30, 2026. No dedicated IP. MFA off.
## Cloudflare Block Workaround
When browser or web tools hit Cloudflare blocks on target sites:
1. Check current IP: `nordvpn status`
2. Disconnect: `nordvpn disconnect`
3. Reconnect to different city: `nordvpn connect us Chicago` (or any city different from current)
4. Verify new IP: `nordvpn status`
5. Retry the blocked request
## Pitfalls
- **Don't change settings without user permission.** Reconnect only.
- **Use different city on reconnect** — same city may return the same server.
- **Allowlisted ports survive reconnect** — no need to re-add after IP change.
- **Kill switch blocks all traffic when disconnected** — brief outage during reconnect is normal.
- **Post-quantum only works with standard NordLynx servers** — not with dedicated IP, OpenVPN, or obfuscated servers.

448
notion/SKILL.md Normal file
View File

@@ -0,0 +1,448 @@
---
name: notion
description: "Notion API + ntn CLI: pages, databases, markdown, Workers."
version: 2.0.0
author: community
license: MIT
platforms: [linux, macos, windows]
prerequisites:
env_vars: [NOTION_API_KEY]
metadata:
hermes:
tags: [Notion, Productivity, Notes, Database, API, CLI, Workers]
homepage: https://developers.notion.com
---
# Notion
Talk to Notion two ways. Same integration token works for both — pick by what's available.
**`ntn` CLI** — Notion's official CLI. Shorter syntax, one-line file uploads, required for Workers. macOS + Linux only as of May 2026 (Windows support "coming soon"). **Default when installed.**
**HTTP + curl** — works everywhere including Windows. **Default fallback** when `ntn` isn't installed.
## Setup
### 1. Get an integration token (required for both paths)
1. Create an integration at https://notion.so/my-integrations
2. Copy the API key (starts with `ntn_` or `secret_`)
3. Store in `${HERMES_HOME:-~/.hermes}/.env`:
```
NOTION_API_KEY=ntn_your_key_here
```
4. **Share target pages/databases with the integration** in Notion: page menu `...` → `Connect to` → your integration name. Without this, the API returns 404 for that page even though it exists.
### 2. Install `ntn` (preferred path on macOS / Linux)
```bash
# Recommended
curl -fsSL https://ntn.dev | bash
# Or via npm (needs Node 22+, npm 10+)
npm install --global ntn
ntn --version # verify
```
**Skip `ntn login` — use the integration token instead.** This works headlessly, no browser needed:
```bash
export NOTION_API_TOKEN=$NOTION_API_KEY # ntn reads NOTION_API_TOKEN
export NOTION_KEYRING=0 # don't try to use the OS keychain
```
Add those exports to your shell profile (or to `${HERMES_HOME:-~/.hermes}/.env`) so every session inherits them.
### 3. Choose path at runtime
```bash
if command -v ntn >/dev/null 2>&1; then
# use ntn
else
# fall back to curl
fi
```
Windows users: skip step 2 entirely until native `ntn` ships — Path B works fine. If you want CLI ergonomics now, install `ntn` inside WSL2.
## API Basics
`Notion-Version: 2025-09-03` is required on all HTTP requests. `ntn` handles this for you. In this version, what users call "databases" are called **data sources** in the API.
## Path A — `ntn` CLI (preferred, macOS / Linux)
### Raw API calls (shorthand for curl)
```bash
ntn api v1/users # GET
ntn api v1/pages parent[page_id]=abc123 \ # POST with inline body
properties[title][0][text][content]="Notes"
ntn api v1/pages/abc123 -X PATCH archived:=true # PATCH; := is non-string (bool/num/null)
```
Syntax notes:
- `key=value` — string fields
- `key[nested]=value` — nested object fields
- `key:=value` — typed assignment (booleans, numbers, null, arrays)
### Search
```bash
ntn api v1/search query="page title"
```
### Read page metadata
```bash
ntn api v1/pages/{page_id}
```
### Read page as Markdown (agent-friendly)
```bash
ntn api v1/pages/{page_id}/markdown
```
### Read page content as blocks
```bash
ntn api v1/blocks/{page_id}/children
```
### Create page from Markdown
```bash
ntn api v1/pages \
parent[page_id]=xxx \
properties[title][0][text][content]="Notes from meeting" \
markdown="# Agenda
- Q3 roadmap
- Hiring"
```
### Patch a page with Markdown
```bash
ntn api v1/pages/{page_id}/markdown -X PATCH \
markdown="## Update
Shipped the prototype."
```
### Query a database (data source)
```bash
ntn api v1/data_sources/{data_source_id}/query -X POST \
filter[property]=Status filter[select][equals]=Active
```
For complex queries with `sorts`, multiple filter clauses, or compound logic, pipe JSON in:
```bash
echo '{"filter": {"property": "Status", "select": {"equals": "Active"}}, "sorts": [{"property": "Date", "direction": "descending"}]}' | \
ntn api v1/data_sources/{data_source_id}/query -X POST --json -
```
### File uploads (one-liner — biggest CLI win)
```bash
ntn files create < photo.png
ntn files create --external-url https://example.com/photo.png
ntn files list
```
Compare to the 3-step HTTP flow (create upload → PUT bytes → reference).
### Useful env vars
| Var | Effect |
|---|---|
| `NOTION_API_TOKEN` | Auth token (overrides keychain) — set this to your integration token |
| `NOTION_KEYRING=0` | File-based creds at `~/.config/notion/auth.json` instead of OS keychain |
| `NOTION_WORKSPACE_ID` | Skip the workspace picker prompt |
## Path B — HTTP + curl (cross-platform, default on Windows)
All requests share this pattern:
```bash
curl -s -X GET "https://api.notion.com/v1/..." \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json"
```
On Windows the `curl` shipped with Windows 10+ works as-is. PowerShell users can also use `Invoke-RestMethod`.
### Search
```bash
curl -s -X POST "https://api.notion.com/v1/search" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{"query": "page title"}'
```
### Read page metadata
```bash
curl -s "https://api.notion.com/v1/pages/{page_id}" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03"
```
### Read page as Markdown (agent-friendly)
Easier to feed to a model than block JSON.
```bash
curl -s "https://api.notion.com/v1/pages/{page_id}/markdown" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03"
```
### Read page content as blocks (when you need structure)
```bash
curl -s "https://api.notion.com/v1/blocks/{page_id}/children" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03"
```
### Create page from Markdown
`POST /v1/pages` accepts a `markdown` body param.
```bash
curl -s -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{
"parent": {"page_id": "xxx"},
"properties": {"title": [{"text": {"content": "Notes from meeting"}}]},
"markdown": "# Agenda\n\n- Q3 roadmap\n- Hiring\n\n## Decisions\n- Ship MVP Friday"
}'
```
### Patch a page with Markdown
```bash
curl -s -X PATCH "https://api.notion.com/v1/pages/{page_id}/markdown" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{"markdown": "## Update\n\nShipped the prototype."}'
```
### Create page in a database (typed properties)
```bash
curl -s -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{
"parent": {"database_id": "xxx"},
"properties": {
"Name": {"title": [{"text": {"content": "New Item"}}]},
"Status": {"select": {"name": "Todo"}}
}
}'
```
### Query a database (data source)
```bash
curl -s -X POST "https://api.notion.com/v1/data_sources/{data_source_id}/query" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{
"filter": {"property": "Status", "select": {"equals": "Active"}},
"sorts": [{"property": "Date", "direction": "descending"}]
}'
```
### Create a database
```bash
curl -s -X POST "https://api.notion.com/v1/data_sources" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{
"parent": {"page_id": "xxx"},
"title": [{"text": {"content": "My Database"}}],
"properties": {
"Name": {"title": {}},
"Status": {"select": {"options": [{"name": "Todo"}, {"name": "Done"}]}},
"Date": {"date": {}}
}
}'
```
### Update page properties
```bash
curl -s -X PATCH "https://api.notion.com/v1/pages/{page_id}" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{"properties": {"Status": {"select": {"name": "Done"}}}}'
```
### Append blocks to a page
```bash
curl -s -X PATCH "https://api.notion.com/v1/blocks/{page_id}/children" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{
"children": [
{"object": "block", "type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": "Hello from Hermes!"}}]}}
]
}'
```
### File uploads (3-step flow)
```bash
# 1. Create upload
curl -s -X POST "https://api.notion.com/v1/file_uploads" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Notion-Version: 2025-09-03" \
-H "Content-Type: application/json" \
-d '{"filename": "photo.png", "content_type": "image/png"}'
# 2. PUT bytes to the upload_url returned above
curl -s -X PUT "{upload_url}" --data-binary @photo.png
# 3. Reference {file_upload_id} in a page/block payload
```
## Property Types
Common property formats for database items:
- **Title:** `{"title": [{"text": {"content": "..."}}]}`
- **Rich text:** `{"rich_text": [{"text": {"content": "..."}}]}`
- **Select:** `{"select": {"name": "Option"}}`
- **Multi-select:** `{"multi_select": [{"name": "A"}, {"name": "B"}]}`
- **Date:** `{"date": {"start": "2026-01-15", "end": "2026-01-16"}}`
- **Checkbox:** `{"checkbox": true}`
- **Number:** `{"number": 42}`
- **URL:** `{"url": "https://..."}`
- **Email:** `{"email": "user@example.com"}`
- **Relation:** `{"relation": [{"id": "page_id"}]}`
## API Version 2025-09-03 — Databases vs Data Sources
- **Databases became data sources.** Use `/data_sources/` endpoints for queries and retrieval.
- **Two IDs per database:** `database_id` and `data_source_id`.
- `database_id` when creating pages: `parent: {"database_id": "..."}`
- `data_source_id` when querying: `POST /v1/data_sources/{id}/query`
- Search returns databases as `"object": "data_source"` with the `data_source_id` field.
## Notion Workers (advanced, requires `ntn`)
Workers are TypeScript programs Notion hosts for you. One worker can expose any combination of:
- **Syncs** — pull data from external APIs into a Notion database on a schedule (default 30 min).
- **Tools** — appear as callable tools inside Notion's Custom Agents.
- **Webhooks** — receive HTTP events from external services (GitHub, Stripe, etc.) and act in Notion.
**Plan / platform gating:**
- CLI works on all plans. **Deploying Workers requires Business or Enterprise.**
- `ntn` is macOS/Linux only as of May 2026. Windows users need WSL2 or to wait for native support.
- Free through August 11, 2026; metered on Notion credits after.
### Minimal Worker
```bash
ntn workers new my-worker # scaffold
cd my-worker
# Edit src/index.ts
ntn workers deploy --name my-worker
```
`src/index.ts`:
```typescript
import { Worker } from "@notionhq/workers";
const worker = new Worker();
export default worker;
worker.tool("greet", {
title: "Greet a User",
description: "Returns a friendly greeting",
inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] },
execute: async ({ name }) => `Hello, ${name}!`,
});
```
### Webhook capability
```typescript
worker.webhook("onGithubPush", {
title: "GitHub Push Handler",
execute: async (events, { notion }) => {
for (const event of events) {
// event.body, event.rawBody (for signature verification), event.headers
console.log("got delivery", event.deliveryId);
}
},
});
```
After deploy: `ntn workers webhooks list` shows the URL Notion generates. Treat that URL as a secret — anyone with it can POST events unless you add signature verification.
### Worker lifecycle commands
```bash
ntn workers deploy
ntn workers list
ntn workers exec <capability-key> -d '{"name": "world"}'
ntn workers sync trigger <key> # run a sync now
ntn workers sync pause <key>
ntn workers env set GITHUB_WEBHOOK_SECRET=...
ntn workers runs list # recent invocations
ntn workers runs logs <run-id>
ntn workers webhooks list
```
When asked to build a Worker, scaffold with `ntn workers new`, write the code in `src/index.ts`, set any secrets with `ntn workers env set`, and deploy. Notion's docs at https://developers.notion.com/workers cover the full API surface.
## Notion-Flavored Markdown (used by `/markdown` endpoints)
Standard CommonMark plus XML-like tags for Notion-specific blocks. Use **tabs** for indentation.
**Blocks beyond CommonMark:**
```
<callout icon="🎯" color="blue_bg">
Ship the MVP by **Friday**.
</callout>
<details color="gray">
<summary>Toggle title</summary>
Children indented one tab
</details>
<columns>
<column>Left side</column>
<column>Right side</column>
</columns>
<table_of_contents color="gray"/>
```
**Inline:**
- Mentions: `<mention-user url="..."/>`, `<mention-page url="...">Title</mention-page>`, `<mention-date start="2026-05-15"/>`
- Underline: `<span underline="true">text</span>`
- Color: `<span color="blue">text</span>` or block-level `{color="blue"}` on the first line
- Math: inline `$x^2$`, block `$$ ... $$`
- Citations: `[^https://example.com]`
**Colors:** `gray brown orange yellow green blue purple pink red`, plus `*_bg` variants for backgrounds.
Headings 5/6 collapse to H4. Multiple `>` lines render as separate quote blocks — use `<br>` inside a single `>` for multi-line quotes.
## Choosing the Right Path
| Task | mac / Linux | Windows |
|---|---|---|
| Read/write pages, search, query databases | `ntn api ...` | curl |
| Read a page for an agent to summarize | `ntn api v1/pages/{id}/markdown` | curl `/markdown` endpoint |
| Upload a file | `ntn files create < file` | 3-step HTTP flow |
| One-off API exploration | `ntn api ...` | curl |
| Build a sync / webhook / agent tool hosted by Notion | `ntn workers ...` | WSL2 + `ntn workers ...` |
## Notes
- Page/database IDs are UUIDs (with or without dashes — both accepted).
- Rate limit: ~3 requests/second average. The CLI doesn't bypass this.
- The API cannot set database **view** filters — that's UI-only.
- Use `"is_inline": true` when creating data sources to embed them in a page.
- Always pass `-s` to curl to suppress progress bars (cleaner agent output).
- Pipe JSON through `jq` when reading: `... | jq '.results[0].properties'`.
- Notion also ships an MCP server now (`Notion MCP`, ~91% more token-efficient on DB ops than the previous version) — wire it via Hermes' MCP support if you want streaming Notion access from inside a session, but the paths above are enough for most one-shot tasks.

172
ocr-and-documents/SKILL.md Normal file
View File

@@ -0,0 +1,172 @@
---
name: ocr-and-documents
description: "Extract text from PDFs/scans (pymupdf, marker-pdf)."
version: 2.3.0
author: Hermes Agent
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [PDF, Documents, Research, Arxiv, Text-Extraction, OCR]
related_skills: [powerpoint]
---
# PDF & Document Extraction
For DOCX: use `python-docx` (parses actual document structure, far better than OCR).
For PPTX: see the `powerpoint` skill (uses `python-pptx` with full slide/notes support).
This skill covers **PDFs and scanned documents**.
## Step 1: Remote URL Available?
If the document has a URL, **always try `web_extract` first**:
```
web_extract(urls=["https://arxiv.org/pdf/2402.03300"])
web_extract(urls=["https://example.com/report.pdf"])
```
This handles PDF-to-markdown conversion via Firecrawl with no local dependencies.
Only use local extraction when: the file is local, web_extract fails, or you need batch processing.
## Step 2: Choose Local Extractor
| Feature | pymupdf (~25MB) | marker-pdf (~3-5GB) |
|---------|-----------------|---------------------|
| **Text-based PDF** | ✅ | ✅ |
| **Scanned PDF (OCR)** | ❌ | ✅ (90+ languages) |
| **Tables** | ✅ (basic) | ✅ (high accuracy) |
| **Equations / LaTeX** | ❌ | ✅ |
| **Code blocks** | ❌ | ✅ |
| **Forms** | ❌ | ✅ |
| **Headers/footers removal** | ❌ | ✅ |
| **Reading order detection** | ❌ | ✅ |
| **Images extraction** | ✅ (embedded) | ✅ (with context) |
| **Images → text (OCR)** | ❌ | ✅ |
| **EPUB** | ✅ | ✅ |
| **Markdown output** | ✅ (via pymupdf4llm) | ✅ (native, higher quality) |
| **Install size** | ~25MB | ~3-5GB (PyTorch + models) |
| **Speed** | Instant | ~1-14s/page (CPU), ~0.2s/page (GPU) |
**Decision**: Use pymupdf unless you need OCR, equations, forms, or complex layout analysis.
If the user needs marker capabilities but the system lacks ~5GB free disk:
> "This document needs OCR/advanced extraction (marker-pdf), which requires ~5GB for PyTorch and models. Your system has [X]GB free. Options: free up space, provide a URL so I can use web_extract, or I can try pymupdf which works for text-based PDFs but not scanned documents or equations."
---
## pymupdf (lightweight)
```bash
pip install pymupdf pymupdf4llm
```
**Via helper script**:
```bash
python scripts/extract_pymupdf.py document.pdf # Plain text
python scripts/extract_pymupdf.py document.pdf --markdown # Markdown
python scripts/extract_pymupdf.py document.pdf --tables # Tables
python scripts/extract_pymupdf.py document.pdf --images out/ # Extract images
python scripts/extract_pymupdf.py document.pdf --metadata # Title, author, pages
python scripts/extract_pymupdf.py document.pdf --pages 0-4 # Specific pages
```
**Inline**:
```bash
python3 -c "
import pymupdf
doc = pymupdf.open('document.pdf')
for page in doc:
print(page.get_text())
"
```
---
## marker-pdf (high-quality OCR)
```bash
# Check disk space first
python scripts/extract_marker.py --check
pip install marker-pdf
```
**Via helper script**:
```bash
python scripts/extract_marker.py document.pdf # Markdown
python scripts/extract_marker.py document.pdf --json # JSON with metadata
python scripts/extract_marker.py document.pdf --output_dir out/ # Save images
python scripts/extract_marker.py scanned.pdf # Scanned PDF (OCR)
python scripts/extract_marker.py document.pdf --use_llm # LLM-boosted accuracy
```
**CLI** (installed with marker-pdf):
```bash
marker_single document.pdf --output_dir ./output
marker /path/to/folder --workers 4 # Batch
```
---
## Arxiv Papers
```
# Abstract only (fast)
web_extract(urls=["https://arxiv.org/abs/2402.03300"])
# Full paper
web_extract(urls=["https://arxiv.org/pdf/2402.03300"])
# Search
web_search(query="arxiv GRPO reinforcement learning 2026")
```
## Split, Merge & Search
pymupdf handles these natively — use `execute_code` or inline Python:
```python
# Split: extract pages 1-5 to a new PDF
import pymupdf
doc = pymupdf.open("report.pdf")
new = pymupdf.open()
for i in range(5):
new.insert_pdf(doc, from_page=i, to_page=i)
new.save("pages_1-5.pdf")
```
```python
# Merge multiple PDFs
import pymupdf
result = pymupdf.open()
for path in ["a.pdf", "b.pdf", "c.pdf"]:
result.insert_pdf(pymupdf.open(path))
result.save("merged.pdf")
```
```python
# Search for text across all pages
import pymupdf
doc = pymupdf.open("report.pdf")
for i, page in enumerate(doc):
results = page.search_for("revenue")
if results:
print(f"Page {i+1}: {len(results)} match(es)")
print(page.get_text("text"))
```
No extra dependencies needed — pymupdf covers split, merge, search, and text extraction in one package.
---
## Notes
- `web_extract` is always first choice for URLs
- pymupdf is the safe default — instant, no models, works everywhere
- marker-pdf is for OCR, scanned docs, equations, complex layouts — install only when needed
- Both helper scripts accept `--help` for full usage
- marker-pdf downloads ~2.5GB of models to `~/.cache/huggingface/` on first use
- For Word docs: `pip install python-docx` (better than OCR — parses actual structure)
- For PowerPoint: see the `powerpoint` skill (uses python-pptx)

View File

@@ -0,0 +1,196 @@
---
name: open-webui-administration
description: "Administer the self-hosted Open WebUI instance at 10.0.0.204 — config via SQLite, ComfyUI image generation, model connections, user management, and service control."
version: 1.0.0
author: agent
license: MIT
platforms: [linux]
metadata:
hermes:
tags: [open-webui, comfyui, image-generation, self-hosted, administration]
---
# Open WebUI Administration
Administer the self-hosted Open WebUI instance that backs Hermes Agent API servers for phone-based voice chat and image generation.
## Infrastructure
| Component | Host | Detail |
|-----------|------|--------|
| Open WebUI | 10.0.0.204:8080 | systemd service (`open-webui.service`), runs as root |
| Binary | `/root/.local/bin/open-webui serve` | Installed under root |
| Database | `/root/.open-webui/webui.db` | SQLite — ALL config lives here |
| ComfyUI | 10.0.0.202:8188 | systemd service, RTX 4090, models at `/home/n8n/comfy-ui/models/` |
| Hermes API | 10.0.0.42:8653 (open1), 8654 (openz) | Backing LLM connections |
**Critical:** Open WebUI is a **systemd service, NOT Docker**. The `docker` command does not exist on 10.0.0.204. All administration is via `systemctl` + SQLite.
## Access
- **SSH:** `ssh n8n@10.0.0.204` (password: passw0rd)
- **Sudo:** `echo passw0rd | sudo -S <command>`
- **Service control:** `sudo systemctl {start,stop,restart,status} open-webui`
- **DB access:** `sudo python3 -c "import sqlite3; db = sqlite3.connect('/root/.open-webui/webui.db'); ..."`
## Config via SQLite
All settings are in the `config` table with dot-separated keys. Read with:
```python
import sqlite3
db = sqlite3.connect("/root/.open-webui/webui.db")
row = db.execute("SELECT value FROM config WHERE key = ?", ("key.name",)).fetchone()
```
Write with:
```python
db.execute("UPDATE config SET value = ? WHERE key = ?", (new_value, "key.name"))
db.commit()
```
**Always backup before editing:** `sudo cp /root/.open-webui/webui.db /root/.open-webui/webui.db.bak-$(date +%Y%m%d_%H%M%S)`
**Restart required after config changes:** `sudo systemctl restart open-webui`
## Key Config Sections
### LLM Connections (OpenAI-compatible)
```
openai.enable = true
openai.api_keys = ["<key1>", "<key2>", ...] # JSON array of bearer tokens
openai.api_base_urls = ["http://10.0.0.42:8653/v1", ...] # JSON array of endpoints
```
Each Hermes profile gets its own URL + key pair. open1 = port 8653, openz = port 8654.
### Image Generation (ComfyUI)
See `references/comfyui-integration.md` for the full config reference, workflow format, and node mapping.
Key config keys:
- `image_generation.enable``"true"` / `"false"`
- `image_generation.engine``"comfyui"` (or `"openai"`, `"automatic1111"`)
- `image_generation.comfyui.base_url``"http://10.0.0.202:8188/"`
- `image_generation.comfyui.workflow` — JSON string of the API-format workflow
- `image_generation.comfyui.nodes` — JSON array of `{"key": "...", "node_ids": "..."}` mappings
### Audio (STT/TTS)
```
audio.stt.engine = "openai"
audio.stt.openai.api_base_url = "http://10.0.0.42:9000/v1"
audio.tts.engine = "openai"
audio.tts.openai.api_base_url = "http://10.0.0.16:8880/v1"
audio.tts.model = "kokoro"
audio.tts.voice = "af_heart"
```
### Users & RBAC
Users in the `user` table. Groups in the `group` table. Access grants in `access_grant`.
```
ui.enable_signup = false
ui.enable_login_form = true
ui.default_user_role = "pending"
```
## Service Control
```bash
# Restart after config changes
sudo systemctl restart open-webui
# Check status
systemctl is-active open-webui
sudo journalctl -u open-webui -n 50
# Service file
systemctl cat open-webui
# → /etc/systemd/system/open-webui.service
# ExecStart=/root/.local/bin/open-webui serve
# EnvironmentFile=-/root/.env
# Environment=DATA_DIR=/root/.open-webui
```
## ComfyUI Update Procedure
See `references/comfyui-update.md` for the full step-by-step.
Quick summary:
1. `sudo systemctl stop comfyui` (on 10.0.0.202)
2. `git remote set-url origin https://github.com/Comfy-Org/ComfyUI.git`
3. `git fetch --tags origin && git checkout v<version>`
4. `/home/n8n/comfy-env/bin/pip install -r requirements.txt --upgrade`
5. `sudo systemctl start comfyui`
6. Verify: `curl -s http://10.0.0.202:8188/system_stats`
## Image Icon Troubleshooting (v0.10.2+)
The 🖼️ image generation icon in the chat input bar is controlled by a **3-way AND gate** in the frontend (`MessageInput.svelte`). All three must be true or the icon is hidden:
```
showImageGenerationButton =
selectedModelIds.length === imageGenerationCapableModels.length && // (A) Model capabilities
$config?.features?.enable_image_generation && // (B) Backend flag
($_user.role === 'admin' || $_user?.permissions?.features?.image_generation); // (C) User permission
```
### Gate A — Model Capabilities
Each model preset has a `meta.capabilities` object. The frontend checks `image_generation` on the **active model**, not the global default. A model with `image_generation: false` blocks the icon.
The `?? true` default means:
- Model with **no** `meta.capabilities` → capable for everything (icon shows)
- Model with `meta.capabilities = {vision: true}` (no `image_generation` key) → `undefined ?? true` = true → still capable
- Model with `meta.capabilities.image_generation = false`**NOT capable → icon hidden**
**Fix:** Set `image_generation: true` on the specific model via DB or Admin → Workspace → Models → edit → Capabilities.
### Gate B — Backend Flag
`image_generation.enable` in the DB `config` table. Read by the frontend via authenticated `GET /api/config``features.enable_image_generation`.
### Gate C — User Permission
Admins always bypass. Non-admins need `features.image_generation: true` in `user.permissions` config.
### Diagnostic Procedure
Run these three curls as admin to identify which gate is failing:
```bash
TOKEN="<admin JWT or API key>"
# Gate B
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/config | python3 -c "import sys,json; print('enable_image_generation =', json.load(sys.stdin)['features'].get('enable_image_generation'))"
# Full image config
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/images/config | python3 -m json.tool
# Gate A — per-model capabilities
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/models | python3 -c "import sys,json; [print(m['id'], '→ caps=', m.get('info',{}).get('meta',{}).get('capabilities')) for m in json.load(sys.stdin)['data']]"
```
### Frontend Cache
The browser caches `/api/config` and `/api/v1/models`. After any config change, users must **sign out and sign back in** (not just refresh) to clear the frontend `$models` store. A hard refresh alone is not sufficient.
## Pitfalls
- **Not Docker.** Open WebUI is a systemd service. `docker` does not exist on 10.0.0.204.
- **`ENABLE_IMAGE_GENERATION` env var only matters on first run.** Open WebUI has a dual config system, but with `ENABLE_PERSISTENT_CONFIG=true` (default), the DB takes precedence once a row exists. The env var in `/root/.env` is read at startup and seeds the DB on first run — after that, the DB value is authoritative. Restarting the service does NOT re-apply the env var to an existing DB row. To force the env var: set `ENABLE_PERSISTENT_CONFIG=false`, or delete the `image_generation.enable` row from the config table, or set it via the admin API/UI. **Symptom of confusion:** DB config is correct, env var is set, service restarted, but the image icon still doesn't appear — check the model capabilities gate (A) and frontend cache before blaming the env var.
- **Config values are JSON-encoded strings.** Boolean `true` is the string `"true"`, not SQLite integer 1. String values like `"comfyui"` are double-quoted inside the value: `'"comfyui"'`. Use parameterized queries (`?`) for complex JSON values to avoid escaping hell.
- **Restart required.** Config changes don't take effect until `systemctl restart open-webui`.
- **Frontend cache requires sign-out/in.** After config changes, users must sign out and sign back in — the frontend caches model capabilities and config on login. A hard refresh alone is not enough.
- **Backup first.** Always `cp webui.db webui.db.bak-<timestamp>` before editing.
- **ComfyUI repo moved.** Old remote `comfyanonymous/ComfyUI` → new remote `Comfy-Org/ComfyUI`. Update the git remote before fetching new tags.
- **ComfyUI custom nodes may break on major updates.** `numba` and `facenet-pytorch` had version conflicts after the v0.18→v0.27 jump. Non-blocking for core txt2img but may affect specific custom nodes.
## Support Files
- `references/comfyui-integration.md` — Full ComfyUI→Open WebUI integration config: keys, workflow format, node mapping, verification
- `references/comfyui-update.md` — Step-by-step ComfyUI update procedure with rollback

View File

@@ -1,8 +1,11 @@
--- ---
name: searxng-smart-search name: searxng-smart-search
description: "Smart SearXNG MCP search with auto-category routing, score filtering, and time-range defaults." description: "Smart SearXNG MCP search with auto-category routing, score filtering, and time-range defaults."
version: 1.1.0 version: 1.3.0
author: Hermes Agent author: Hermes Agent
metadata:
hermes:
related_skills: [core-search, better-search, deep-research]
--- ---
# SearXNG Smart Search # SearXNG Smart Search
@@ -39,20 +42,40 @@ After search, use `mcp_searxng_web_url_read` on top 1-2 results for full content
Run `mcp_searxng_searxng_instance_info` once per session to confirm engines are healthy. Flag any `unresponsive_engines` in results. Full engine inventory and known-broken engines documented in `references/searxng-instance.md`. Run `mcp_searxng_searxng_instance_info` once per session to confirm engines are healthy. Flag any `unresponsive_engines` in results. Full engine inventory and known-broken engines documented in `references/searxng-instance.md`.
**PITFALL — Default engines may all be broken:** SearXNG uses server-side defaults (google, duckduckgo, brave, startpage). If all are broken, every search returns zero results with no error. Diagnose by hitting the SearXNG API directly: **PITFALL — Default engines may all be broken:** SearXNG uses server-side defaults. If all are broken, every search returns zero results with no error. Diagnose by hitting the SearXNG API directly:
```bash ```bash
# Check which engines actually work # Check which engines actually work
curl -s "http://10.0.0.8:8888/search?q=test&format=json&engines=bing,google,duckduckgo,brave,startpage" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Results: {len(d.get(\"results\",[]))}'); print(f'Unresponsive: {d.get(\"unresponsive_engines\",[])}')" curl -s "http://localhost:8888/search?q=test&format=json&engines=bing,google,duckduckgo,brave,startpage" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Results: {len(d.get(\"results\",[]))}'); print(f'Unresponsive: {d.get(\"unresponsive_engines\",[])}')"
``` ```
If only Bing works, pass `engines=bing` in every `mcp_searxng_searxng_web_search` call until the server-side defaults are fixed. The MCP tool does NOT specify engines by default — it relies entirely on SearXNG's server-side defaults. If only Bing works, pass `engines=bing` in every `mcp_searxng_searxng_web_search` call until the server-side defaults are fixed. The MCP tool does NOT specify engines by default — it relies entirely on SearXNG's server-side defaults.
### `engines=` Parameter — Engine Diversity
The `mcp_searxng_searxng_web_search` tool accepts an `engines` parameter (comma-separated engine names). This is the primary bias-reduction mechanism — different search engines have different indexes and ranking algorithms. Use it to get genuinely different results:
```bash
# Mainstream Western indexes
engines=bing,google
# Independent crawlers (different indexes, different ranking)
engines=mojeek,wiby
# Privacy-focused
engines=duckduckgo,startpage
```
**Engine health varies.** On the local Docker instance (localhost:8888, 279 engines), commonly broken engines include: brave (rate-limited), qwant (access denied), yahoo (HTTP error), sogou (CAPTCHA), fastbot/fireball (suspended). These show as `unresponsive_engines` in results — SearXNG handles them gracefully. Check `searxng_instance_info` periodically for current health.
**Known working general-search engines (2026-07-15):** bing, google, duckduckgo, startpage, mojeek, wiby, yandex, mwmbl, yacy, yep, naver, baidu, seznam, gmx, crowdview, gabanza, searchmysite, tusksearch, vuhuv, searchch, reloado, resulthunter, boardreader, bpb, dogpile, quark, 360search, presearch.
## Reference files ## Reference files
- `references/instance-config.md` — full SearXNG instance snapshot (engines, categories, plugins, locales) - `references/instance-config.md` — full SearXNG instance snapshot (engines, categories, plugins, locales)
- `references/mcp-server-config.md` — MCP server setup, tool list, verification commands - `references/mcp-server-config.md` — MCP server setup, tool list, verification commands
- `references/searxng-instance.md` — engine inventory and known-broken engines - `references/searxng-instance.md` — engine inventory and known-broken engines
- `references/searxng-docker-setup.md` — Docker infrastructure, engine health, MCP bridge config, management commands
- `references/telegram-bot-api-2026.md` — Telegram Bot API features relevant to Hermes integration (overlaps with `telegram-integration/references/bot-api-2026.md` — curator will consolidate) - `references/telegram-bot-api-2026.md` — Telegram Bot API features relevant to Hermes integration (overlaps with `telegram-integration/references/bot-api-2026.md` — curator will consolidate)
## Disabling the built-in `web` toolset (SearXNG-only mode) ## Disabling the built-in `web` toolset (SearXNG-only mode)
@@ -84,8 +107,8 @@ platform_toolsets:
web: web:
backend: searxng backend: searxng
search_backend: searxng search_backend: searxng
extract_backend: firecrawl # SearXNG is search-only; pair with an extract provider extract_backend: searxng # Use SearXNG for page extraction too (consistent with MCP tools)
searxng_url: http://10.0.0.8:8888 searxng_url: http://localhost:8888
``` ```
With both `disabled_toolsets` and `platform_toolsets` updated, the `web_search` and `web_extract` tools are completely unavailable. The agent must use `mcp_searxng_searxng_web_search` and `mcp_searxng_web_url_read` for all web access. With both `disabled_toolsets` and `platform_toolsets` updated, the `web_search` and `web_extract` tools are completely unavailable. The agent must use `mcp_searxng_searxng_web_search` and `mcp_searxng_web_url_read` for all web access.
@@ -100,19 +123,19 @@ The built-in `web_search` tool and the MCP SearXNG tools read `SEARXNG_URL` from
| Tool | Reads from | Config location | | Tool | Reads from | Config location |
|------|-----------|----------------| |------|-----------|----------------|
| Built-in `web_search` | `os.environ``~/.hermes/.env` | `SEARXNG_URL=http://10.0.0.8:8888` in `.env` | | Built-in `web_search` | `os.environ``~/.hermes/.env` | `SEARXNG_URL=http://localhost:8888` in `.env` |
| MCP `searxng__*` tools | MCP subprocess env | `mcp_servers.searxng.env.SEARXNG_URL` in `config.yaml` | | MCP `searxng__*` tools | MCP subprocess env | `mcp_servers.searxng.env.SEARXNG_URL` in `config.yaml` |
**Critical pitfall — `config.yaml` `web.searxng_url` does NOT feed the built-in tool:** The built-in `web_search` tool's availability check (`_has_env("SEARXNG_URL")` in `tools/web_tools.py`) calls `get_env_value()` which reads `os.environ` first, then `~/.hermes/.env`. It does NOT read `web.searxng_url` from `config.yaml`. If `SEARXNG_URL` is only in `config.yaml` and not in `.env`, the built-in `web_search` returns empty results with no error — the tool silently falls through to "no provider configured." **Critical pitfall — `config.yaml` `web.searxng_url` does NOT feed the built-in tool:** The built-in `web_search` tool's availability check (`_has_env("SEARXNG_URL")` in `tools/web_tools.py`) calls `get_env_value()` which reads `os.environ` first, then `~/.hermes/.env`. It does NOT read `web.searxng_url` from `config.yaml`. If `SEARXNG_URL` is only in `config.yaml` and not in `.env`, the built-in `web_search` returns empty results with no error — the tool silently falls through to "no provider configured."
**Diagnostic:** `web_search` returning empty `{"data": {"web": []}}` with no error message = `SEARXNG_URL` missing from `.env`. The MCP tools working fine at the same time confirms the instance is healthy — the gap is only in the built-in tool's env source. **Diagnostic:** `web_search` returning empty `{"data": {"web": []}}` with no error message = `SEARXNG_URL` missing from `.env`. The MCP tools working fine at the same time confirms the instance is healthy — the gap is only in the built-in tool's env source.
**Fix:** Add `SEARXNG_URL=http://10.0.0.8:8888` to `~/.hermes/.env` AND all profile `.env` files. The MCP server config in `config.yaml` already passes `SEARXNG_URL` via its `env` block: **Fix:** Add `SEARXNG_URL=http://localhost:8888` to `~/.hermes/.env` AND all profile `.env` files. The MCP server config in `config.yaml` already passes `SEARXNG_URL` via its `env` block:
```yaml ```yaml
mcp_servers: mcp_servers:
searxng: searxng:
env: env:
SEARXNG_URL: http://10.0.0.8:8888 SEARXNG_URL: http://localhost:8888
``` ```
But this only covers the MCP subprocess — the main Hermes process still needs it in `.env` for the built-in tool. But this only covers the MCP subprocess — the main Hermes process still needs it in `.env` for the built-in tool.
@@ -120,7 +143,7 @@ But this only covers the MCP subprocess — the main Hermes process still needs
**Bulk `.env` update:** When adding `SEARXNG_URL` to `.env`, update ALL profile `.env` files (base + all 15 profiles). Use `hermes-config-bulk-update` skill pattern — same 16-location rule applies to `.env` as to `config.yaml`. Check with: `for p in ~/.hermes/profiles/*/; do grep -l SEARXNG_URL "$p/.env" 2>/dev/null || echo "MISSING: $(basename $p)"; done` **Bulk `.env` update:** When adding `SEARXNG_URL` to `.env`, update ALL profile `.env` files (base + all 15 profiles). Use `hermes-config-bulk-update` skill pattern — same 16-location rule applies to `.env` as to `config.yaml`. Check with: `for p in ~/.hermes/profiles/*/; do grep -l SEARXNG_URL "$p/.env" 2>/dev/null || echo "MISSING: $(basename $p)"; done`
- DuckDuckGo and Startpage engines are broken (CAPTCHA/crash) — ignore their failures - DuckDuckGo and Startpage are working on the local Docker instance (localhost:8888). Brave and Qwant are rate-limited. Check `searxng_instance_info` for current health.
- `time_range` only works with `news` and `general` categories - `time_range` only works with `news` and `general` categories
- `min_score` above 0.5 often returns zero results - `min_score` above 0.5 often returns zero results
- `categories` must be comma-separated, no spaces: `it,science` not `it, science` - `categories` must be comma-separated, no spaces: `it,science` not `it, science`

212
simplify-code/SKILL.md Normal file
View File

@@ -0,0 +1,212 @@
---
name: simplify-code
description: "Parallel 3-agent cleanup of recent code changes."
version: 1.0.0
author: Hermes Agent (inspired by Claude Code /simplify)
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [code-review, cleanup, refactor, delegation, subagent, parallel, simplify]
related_skills: [requesting-code-review, test-driven-development, plan]
---
# Simplify Code — Parallel Review & Cleanup
Review your recent code changes with three focused reviewers running in
parallel, aggregate their findings, and apply the fixes worth applying.
**Core principle:** Three narrow reviewers beat one broad reviewer. Each one
deeply searches the codebase for a single class of problem — reuse, quality,
efficiency — without diluting its attention across all three. They run
concurrently, so you pay the latency of one review, not three.
## When to Use
Trigger this skill when the user says any of:
- "simplify" / "simplify my changes" / "simplify these changes"
- "review my code" / "review my recent changes" / "clean up my changes"
- "/simplify" (if they're carrying the Claude Code habit over)
Optional modifiers the user may add — honor them:
- **Focus:** "simplify focus on efficiency" → run only the efficiency reviewer
(or weight the aggregation toward it). Recognized focuses: `reuse`,
`quality`, `efficiency`.
- **Dry run:** "simplify but don't change anything" / "just report" → run the
three reviewers, present findings, apply NOTHING. Ask before applying.
- **Scope:** "simplify the last commit" / "simplify staged" / "simplify
src/foo.py" → narrow the diff source accordingly (see Phase 1).
Do NOT auto-run this after every edit. It costs three subagents' worth of
tokens — invoke it only when the user explicitly asks.
## The Process
### Phase 1 — Identify the changes
Capture the diff to review. Pick the source by what the user asked for, in
this default order:
```bash
# 1. Default: uncommitted working-tree changes (tracked files)
git diff
# 2. If that's empty, include staged changes
git diff HEAD
# 3. Scoped variants the user may request:
git diff --staged # "staged changes"
git diff HEAD~1 # "the last commit"
git diff main...HEAD # "this branch" / "my PR"
git diff -- src/foo.py # specific file(s)
```
If `git diff` and `git diff HEAD` are both empty and there's no git repo or no
changes, fall back to the files the user explicitly named or that were
recently created/edited in this session. If you genuinely can't find any
changed code, say so and stop — there's nothing to simplify.
Capture the full diff text. Note its size: if it's very large (say >2000
changed lines), warn the user that three subagents each carrying the full diff
will be token-heavy, and offer to scope it down (per-directory, per-commit)
before proceeding.
### Phase 2 — Launch three reviewers in parallel
Use `delegate_task` **batch mode** — pass all three tasks in one `tasks`
array so they run concurrently. Three is the right fan-out for this pattern;
it's well within the `delegation.max_concurrent_children` budget on any
default install.
Give **every** reviewer the **complete diff** (not fragments — cross-file
issues hide in the gaps) plus the absolute repo path so they can search the
wider codebase. Each reviewer gets `terminal`, `file`, and `search`
toolsets (so they can `git`, `read_file`, and `search_files`/grep).
Tell each reviewer to:
- Search the existing codebase for evidence (don't reason from the diff alone).
- **Apply Chesterton's Fence:** before flagging anything for removal, run
`git blame` on the line to understand why it exists. If you can't determine
the original purpose, mark it `confidence: low` — don't guess.
- Report findings as structured output with confidence and risk:
```
file:line → problem → suggested fix | confidence: high/medium/low | risk: SAFE/CAREFUL/RISKY
```
- **SAFE** = proven not to affect behavior (unused imports, commented-out
code, pass-through wrappers). Auto-apply these.
- **CAREFUL** = improves without changing semantics (rename local variable,
flatten nested ternary, extract helper). Apply with test verification.
- **RISKY** = may change behavior or breaks public contracts (N+1
restructuring, public API rename, memory lifecycle change). Flag for
human review — do NOT auto-apply.
- Skip nits and style-only churn. Only flag things that materially improve
the code.
Pass these three goals (drop any the user's focus excludes):
**Reviewer 1 — Code Reuse**
> Review this diff for code that duplicates functionality already in the
> codebase. Search utility modules, shared helpers, and adjacent files
> (use search_files / grep) for existing functions, constants, or patterns
> the new code could call instead of reimplementing. Flag: new functions
> that duplicate existing ones; hand-rolled logic that an existing utility
> already does (manual string/path manipulation, custom env checks, ad-hoc
> type guards, re-implemented parsing). For each, name the existing thing to
> use and where it lives.
**Reviewer 2 — Code Quality**
> Review this diff for quality problems. Look for: redundant state (values
> that duplicate or could be derived from existing state; caches that don't
> need to exist); parameter sprawl (new params bolted on where the function
> should have been restructured); copy-paste-with-variation (near-duplicate
> blocks that should share an abstraction); leaky abstractions (exposing
> internals, breaking an existing encapsulation boundary); stringly-typed
> code (raw strings where a constant/enum/registry already exists — check the
> canonical registries before flagging); AI-generated slop patterns (extra
> comments restating obvious code like `// increment counter` above `count++`;
> unnecessary defensive null-checks on already-validated inputs; `as any`
> casts that bypass the type system; patterns inconsistent with the rest of
> the file). For each, give the concrete refactor.
**Reviewer 3 — Efficiency**
> Review this diff for efficiency problems. Look for: unnecessary work
> (redundant computation, repeated file reads, duplicate API calls, N+1
> access patterns); missed concurrency (independent ops run sequentially);
> hot-path bloat (heavy/blocking work on startup or per-request paths);
> TOCTOU anti-patterns (existence pre-checks before an op instead of doing
> the op and handling the error); memory issues (unbounded growth, missing
> cleanup, listener/handle leaks); overly broad reads (loading whole files
> when a slice would do); silent failures (empty catch blocks, ignored error
> returns, `except: pass`, `.catch(() => {})` with no handling, error
> propagation gaps — these hide bugs and should at minimum log before
> swallowing). For each, give the concrete fix and why it's faster or safer.
### Phase 3 — Aggregate and apply
Wait for all three to return (batch mode returns them together).
1. **Merge** the findings into one list, deduping where reviewers overlap.
2. **Discard false positives** — you have the most context; you don't have to
argue with a reviewer, just drop weak or wrong suggestions silently.
3. **Resolve conflicts.** Reviewers can disagree (Reviewer 1: "use existing
util X"; Reviewer 3: "X is slow, inline it"). Default resolution order:
**correctness > the user's stated focus > readability/reuse > micro-perf.**
Don't apply a perf "fix" that hurts clarity unless the path is genuinely
hot. When two suggestions are mutually exclusive and both defensible, pick
the one that touches less code and note the alternative.
4. **Apply in risk-tier order:**
- **SAFE first** (auto-apply): unused imports, commented-out code,
pass-through wrappers, redundant type assertions. Run tests after.
- **CAREFUL next** (apply with verification, one file at a time): rename
locals, flatten ternaries, extract helpers, consolidate dupes. Run tests
after each file. Revert any that break.
- **RISKY last** (flag for review — do NOT auto-apply): N+1 restructuring,
public API changes, concurrency fixes, error-handling changes. Present
each with risk description and test coverage status.
If the user opted for a dry run, present all three tiers and apply nothing.
5. **Verify** you didn't break anything: run the project's targeted tests for
the touched files (not the full suite), and re-run any linter/type check the
repo uses. If a fix breaks a test, revert that one fix and report it.
6. **Summarize** what you changed: a short list of applied fixes grouped by
reviewer category and risk tier, plus any findings you deliberately skipped
and why.
## Pitfalls
- **Don't fan out wider than ~3.** More reviewers means more cost and more
conflicting suggestions to reconcile, not better coverage. Three categories
cover the space.
- **Give the WHOLE diff to each reviewer.** Splitting the diff across reviewers
defeats the design — cross-file duplication and N+1s only show up with the
full picture.
- **Reviewers search, they don't guess.** A reuse finding with no pointer to
the existing utility ("there's probably a helper for this") is noise. Require
`file:line` evidence; drop findings that lack it.
- **Apply ≠ rewrite.** This is cleanup of the user's recent changes, not a
license to refactor the whole module. Keep edits scoped to what the diff
touched plus the minimal surrounding change a fix requires.
- **Respect project conventions.** If the repo has AGENTS.md / CLAUDE.md /
HERMES.md or a linter config, fold those rules into the reviewer prompts so
suggestions match house style instead of fighting it.
- **Large diffs blow context.** If the diff is huge, scope it down before
delegating — three subagents each carrying a 5000-line diff is expensive and
may truncate.
- **Over-trusting dead code tools.** `knip`, `ts-prune`, and `depcheck` flag
exports that ARE used dynamically (string-based imports, reflection). Always
grep for the symbol name before removing — a clean tool report is not proof.
- **Renaming without checking public contracts.** Export names, API route
paths, DB column names, and config keys are contracts — even if the name is
bad, renaming breaks consumers. Tag public-contract changes as RISKY; never
auto-rename them.
- **Removing "unnecessary" error handling.** An empty catch block or ignored
error might be intentional — the error is expected and benign in that
context. Flag it, don't remove it; let the human decide.
## Related
If your install has the `subagent-driven-development` skill (optional), it
covers the complementary case: parallel review *during* implementation, per
task. This skill is the standalone *after-the-fact* cleanup pass. Use
`requesting-code-review` for the pre-commit security/quality gate.

View File

@@ -0,0 +1,154 @@
---
name: skill-quality-review
description: "Quality review of skill documents (SKILL.md, references/, templates/, scripts/). Produces a verdict (Approved / Approved with fixes / Needs revision), severity-tiered findings, and a verification checklist. Triggers on 'review this skill', 'audit the skill', 'code quality review of SKILL.md', 'is this skill well-written', 'check the skill for issues', 'quality-check this skill'. Class-level meta-skill, not a session artifact."
version: 1.2.0
author: Hermes Agent
license: MIT
platforms: [linux]
metadata:
hermes:
tags: [review, quality, audit, skill, documentation, meta]
related_skills: [agent-self-audit, simplify-code, writing-plans, create-plan]
---
# skill-quality-review — Quality Review of Skill Documents
## 1. Overview
This skill reviews a skill's documents (the SKILL.md frontmatter and body, plus any files in `references/`, `templates/`, `scripts/`) for quality. The output is a structured report with:
- **Verdict** — Approved / Approved with fixes / Needs revision
- **Findings** — by severity (Critical / Important / Minor)
- **Verification checklist** — confirming each quality dimension is satisfied
- **Length-trim recommendations** — optional, when file size is a concern
**When to use:** the operator asks for a code review, quality review, or audit of a skill document. Typical phrasings: `review this skill`, `audit the skill`, `code quality review of SKILL.md`, `is this skill well-written`, `check the skill for issues`, `quality-check this skill`, `review the new Section X`.
**When NOT to use:**
- The operator asks for a fix, not a review → just patch the skill directly (use `patch` on the file).
- The operator asks for a self-audit before executing a tool → use `agent-self-audit`.
- The operator asks for code cleanup, not document review → use `simplify-code`.
- The target is not a skill document (e.g., a regular README, a config file) → review ad-hoc.
## 2. The 9 Quality Dimensions
When reviewing a skill, score it against these dimensions. Each is a checklist item in §4.
1. **Self-containment** — Can a fresh agent execute the skill reading only its own files? All commands exact? All paths resolvable from the skill root? Forward references ("see Section 5") that point to a non-existent section are a fail.
2. **Trigger detection** — Are trigger phrases listed (≥3 phrasings: verb + object)? Are they case-insensitive substring matches, or is the match rule specified? Is there a precedence note when the trigger overlaps with another skill's?
3. **Brief / input capture** — If the skill takes operator input (a brief, parameters, etc.), is the schema exact and machine-parseable (a verbatim block, not paraphrased)? Are stopping rules binding?
4. **Dispatch / output commands** — If the skill dispatches other skills or processes, is the exact command line provided? Is flag rationale documented? Are there pre-dispatch checks (e.g., reasoning_effort, profile, model)?
5. **Capture and handoff** — If the skill produces a session id, artifact path, or other resume handle, is the capture rule binding (e.g., "output line 1")? Is the handoff format parseable (delimited block, key-value lines)? Is there an empirical verification step (not just "this should work")?
6. **Cross-references** — Do all `references/`, `templates/`, `scripts/` paths resolve? Are referenced external skills correct? Are paths unambiguous (e.g., `(from subagent-driven-development skill)` if the file lives in another skill)?
7. **Tone** — Is the voice consistent (imperative for agent-facing, operator-friendly for output blocks)? Is peer-to-peer language used where appropriate? Is there voice drift between sections?
8. **Length** — Is the file size justified? Are there trim candidates? Is there duplication between sections (e.g., the same Q-list in two sections)?
9. **Internal consistency** — Do the YAML frontmatter, description, body sections, verification checklist, and See Also list all match? Are phase numbers, trigger phrases, file paths, and command flags consistent across the document?
**Multi-file skills (one SKILL.md + N references + a parent plan):** the 9 dimensions are necessary but not sufficient — they check in-file quality. For cross-file consistency (number agreement across files, section-ID cross-refs, concept drift, peer-skill integration), see `references/multi-file-review-checklist.md`. The 5 cross-file techniques (mental walkthrough, number consistency grep, cross-reference resolution, concept-drift detection, peer-skill integration check) caught real issues in the July 2026 `create-plan` review that the base 9 dimensions miss. Use the multi-file checklist as a parallel add-on, not a replacement for the 9 dimensions.
## 3. Severity Tiers
Findings are sorted into three tiers. Each tier has a clear meaning and a clear action.
- **Critical** — Blocks the skill from working. Empty trigger list, missing dispatch command, wrong path, broken cross-reference, contradiction between frontmatter and body, forward-reference to a non-existent section. **Action: must fix before the skill is buildable.**
- **Important** — Doesn't block, but degrades quality significantly. Section duplication, ambiguous precedence, half-documented flag, citation drift (e.g., "per writing-plans" when the rule lives in ask-hermes), voice drift. **Action: should fix; document the deferral if not fixing now.**
- **Minor** — Cosmetic / readability / future-flag stubs. Column alignment, "TODO: implement later" placeholders that don't ship, redundant "does NOT do" lists when the boundary is already established. **Action: nice to have; trim if file size is a concern.**
## 4. Verification Checklist
A skill passes review when ALL of these are true. Score each ✅ / ⚠️ / ❌ in the output.
- [ ] YAML frontmatter has `name`, `description`, `version`, `author`, `license`, `platforms`, `metadata.hermes.tags`
- [ ] Description's trigger phrase list matches the body
- [ ] Trigger phrases include ≥3 phrasings (verb + object)
- [ ] Trigger phrases are case-insensitive substring matches, or the match rule is specified
- [ ] If the skill has phases, all phase numbers are consistent across frontmatter, body, and pipeline diagram
- [ ] If the skill takes operator input, the input schema is exact (verbatim block, not paraphrased)
- [ ] If the skill dispatches other skills, the exact command line is provided
- [ ] If the skill produces a session id or resume handle, the capture rule is binding
- [ ] All `references/`, `templates/`, `scripts/` paths exist and resolve
- [ ] Cross-references to other skills' files specify which skill (e.g., `(from subagent-driven-development skill)`)
- [ ] Tone is consistent (agent-facing = imperative; operator-facing = friendly)
- [ ] "Does NOT do" or "out of scope" list present when the skill's scope could be confused with another skill's
- [ ] Verification checklist exists in the skill itself (the skill knows how to be tested)
- [ ] No forward references to sections that don't exist
- [ ] No internal contradictions (e.g., "this skill runs Phase X" in §1 vs. "this skill runs Phase Y" in §3)
- [ ] **Multi-file only:** number anchors (phase numbers, turn caps, payload caps, question caps, version strings) agree across all files in the tree
- [ ] **Multi-file only:** section/step cross-references (e.g., `§5.3`, `Step 4.6`) point to real subsections
- [ ] **Multi-file only:** concepts described in multiple files have consistent definitions (no concept drift)
## 5. Length-Justification Heuristic
A skill's length is justified when:
- Each section earns its place (no forward-reference stubs)
- Commands are exact, not summarized
- Tables and code blocks are used for machine-parseable content
- Duplication is removed (if Section A and Section B list the same items, one becomes a pointer)
If a file is over ~30 KB, identify trim candidates with estimated byte savings:
- Duplicated sections (collapse to pointer)
- Future-flag stubs (remove or commit to timeline)
- "Does NOT do" lists (collapse if the boundary is already established elsewhere)
- Empirical-verification paragraphs (deduplicate if mentioned in §6 and pitfall list)
- "Future considerations" / "TODO: future" sections (remove — YAGNI)
**Multi-file heuristic:** if SKILL.md is over ~30 KB AND the file has N `references/` files totaling >50 KB, check whether content in SKILL.md could move to a reference. SKILL.md should be the operator-facing entry point; the references should hold the operational detail. A 33 KB SKILL.md with 5 well-scoped references is healthier than a 60 KB SKILL.md with no references.
## 6. Output Format
The review output should follow this structure exactly. Use the same headings so future agents can parse it:
```markdown
# <SKILL-NAME> QUALITY REVIEW
## Verdict: <Approved | Approved with fixes | Needs revision>
## Critical Issues
<list, or "None.">
## Important Issues
<list>
## Minor Issues
<list>
## Checklist Verifications
| # | Check | Result |
|---|---|---|
| 1 | Self-containment | ✅ / ⚠️ / ❌ + one-line note |
| 2 | Trigger detection | ... |
| 3 | Brief / input capture | ... |
| ... | ... | ... |
## Length-Justification
<paragraph; if trimming, list candidates with estimated byte savings>
## Notes for the Skill Author
<optional: open questions, design alternatives, things the reviewer couldn't decide>
```
The verdict is shorthand for the operator: **Approved** = ship as-is, **Approved with fixes** = ship after the Important and Minor issues are addressed, **Needs revision** = Critical issues must be fixed before the skill is buildable.
**Multi-file reviews:** the operator may also pass a custom section structure for the review (e.g., "use this 11-section layout"). Honor exactly. The base structure above is the default; a user-specified structure overrides it for that review only. Add a final `## What I did` / `## Files reviewed` / `## Files created or modified` block at the end of the report so the operator can see what was actually executed, even when the structure is custom.
## 7. Worked Example
See `references/create-plan-section-5-review-2026-07.md` for a worked example of this methodology applied to a real skill review (the `create-plan` Section 5 review, July 2026). Use it as a template when producing your own review.
For a multi-file review (5+ files), see `references/multi-file-review-checklist.md` §"Worked example pointer."
## 8. Common Pitfalls for the Reviewer
1. **Don't auto-fix the skill.** The user asked for a *review*. Deliver findings; let the operator decide what to apply. Patching a skill without consent is overstepping.
2. **Don't conflate severity with effort.** A Critical issue can be a one-line fix; an Important issue can require a refactor. Severity is about impact, not effort.
3. **Don't invent dimensions.** If a dimension doesn't apply to the skill (e.g., "capture and handoff" for a skill that doesn't produce session ids), skip it. The 9 dimensions are a checklist, not a religion.
4. **Don't reformat the skill in the review.** Comments like "the table would be better as a list" are out of scope. Note them in Minor if you must, but the review is about *correctness*, not style.
5. **Don't propose new features.** A review identifies issues with what exists. If a dimension is missing (e.g., no trigger list), say so under Critical. Don't suggest adding four more triggers.
6. **Don't read into intent.** "The author probably meant X" is speculation. Quote the text and let the operator decide.
## See Also
- `agent-self-audit` — pre-execution audit of an action; a different class of review.
- `simplify-code` — cleanup of recent code changes, not skill documents.
- `writing-plans` — methodology used inside skills like `create-plan`; not a review methodology.
- `references/multi-file-review-checklist.md` — 5 cross-file techniques for reviewing multi-file skills (mental walkthrough, number consistency grep, cross-reference resolution, concept-drift detection, peer-skill integration check).
- `references/create-plan-section-5-review-2026-07.md` — worked example (single-section review).

197
spike/SKILL.md Normal file
View File

@@ -0,0 +1,197 @@
---
name: spike
description: "Throwaway experiments to validate an idea before build."
version: 1.0.0
author: Hermes Agent (adapted from gsd-build/get-shit-done)
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [spike, prototype, experiment, feasibility, throwaway, exploration, research, planning, mvp, proof-of-concept]
related_skills: [sketch, subagent-driven-development, plan]
---
# Spike
Use this skill when the user wants to **feel out an idea** before committing to a real build — validating feasibility, comparing approaches, or surfacing unknowns that no amount of research will answer. Spikes are disposable by design. Throw them away once they've paid their debt.
Load this when the user says things like "let me try this", "I want to see if X works", "spike this out", "before I commit to Y", "quick prototype of Z", "is this even possible?", or "compare A vs B".
## When NOT to use this
- The answer is knowable from docs or reading code — just do research, don't build
- The work is production path — use the `plan` skill instead
- The idea is already validated — jump straight to implementation
## If the user has the full GSD system installed
If `gsd-spike` shows up as a sibling skill (installed via `npx get-shit-done-cc --hermes`), prefer **`gsd-spike`** when the user wants the full GSD workflow: persistent `.planning/spikes/` state, MANIFEST tracking across sessions, Given/When/Then verdict format, and commit patterns that integrate with the rest of GSD. This skill is the lightweight standalone version for users who don't have (or don't want) the full system.
## Core method
Regardless of scale, every spike follows this loop:
```
decompose → research → build → verdict
↑__________________________________________↓
iterate on findings
```
### 1. Decompose
Break the user's idea into **2-5 independent feasibility questions**. Each question is one spike. Present them as a table with Given/When/Then framing:
| # | Spike | Validates (Given/When/Then) | Risk |
|---|-------|----------------------------|------|
| 001 | websocket-streaming | Given a WS connection, when LLM streams tokens, then client receives chunks < 100ms | High |
| 002a | pdf-parse-pdfjs | Given a multi-page PDF, when parsed with pdfjs, then structured text is extractable | Medium |
| 002b | pdf-parse-camelot | Given a multi-page PDF, when parsed with camelot, then structured text is extractable | Medium |
**Spike types:**
- **standard** — one approach answering one question
- **comparison** — same question, different approaches (shared number, letter suffix `a`/`b`/`c`)
**Good spike questions:** specific feasibility with observable output.
**Bad spike questions:** too broad, no observable output, or just "read the docs about X".
**Order by risk.** The spike most likely to kill the idea runs first. No point prototyping the easy parts if the hard part doesn't work.
**Skip decomposition** only if the user already knows exactly what they want to spike and says so. Then take their idea as a single spike.
### 2. Align (for multi-spike ideas)
Present the spike table. Ask: "Build all in this order, or adjust?" Let the user drop, reorder, or re-frame before you write any code.
### 3. Research (per spike, before building)
Spikes are not research-free — you research enough to pick the right approach, then you build. Per spike:
1. **Brief it.** 2-3 sentences: what this spike is, why it matters, key risk.
2. **Surface competing approaches** if there's real choice:
| Approach | Tool/Library | Pros | Cons | Status |
|----------|-------------|------|------|--------|
| ... | ... | ... | ... | maintained / abandoned / beta |
3. **Pick one.** State why. If 2+ are credible, build quick variants within the spike.
4. **Skip research** for pure logic with no external dependencies.
Use Hermes tools for the research step:
- `web_search("python websocket streaming libraries 2025")` — find candidates
- `web_extract(urls=["https://websockets.readthedocs.io/..."])` — read the actual docs (returns markdown)
- `terminal("pip show websockets | grep Version")` — check what's installed in the project's venv
For libraries without docs pages, clone and read their `README.md` / `examples/` via `read_file`. Context7 MCP (if the user has it configured) is also a good source — `mcp_*_resolve-library-id` then `mcp_*_query-docs`.
### 4. Build
One directory per spike. Keep it standalone.
```
spikes/
├── 001-websocket-streaming/
│ ├── README.md
│ └── main.py
├── 002a-pdf-parse-pdfjs/
│ ├── README.md
│ └── parse.js
└── 002b-pdf-parse-camelot/
├── README.md
└── parse.py
```
**Bias toward something the user can interact with.** Spikes fail when the only output is a log line that says "it works." The user wants to *feel* the spike working. Default choices, in order of preference:
1. A runnable CLI that takes input and prints observable output
2. A minimal HTML page that demonstrates the behavior
3. A small web server with one endpoint
4. A unit test that exercises the question with recognizable assertions
**Depth over speed.** Never declare "it works" after one happy-path run. Test edge cases. Follow surprising findings. The verdict is only trustworthy when the investigation was honest.
**Avoid** unless the spike specifically requires it: complex package management, build tools/bundlers, Docker, env files, config systems. Hardcode everything — it's a spike.
**Building one spike** — a typical tool sequence:
```
terminal("mkdir -p spikes/001-websocket-streaming")
write_file("spikes/001-websocket-streaming/README.md", "# 001: websocket-streaming\n\n...")
write_file("spikes/001-websocket-streaming/main.py", "...")
terminal("cd spikes/001-websocket-streaming && python3 main.py")
# Observe output, iterate.
```
**Parallel comparison spikes (002a / 002b) — delegate.** When two approaches can run in parallel and both need real engineering (not 10-line prototypes), fan out with `delegate_task`:
```
delegate_task(tasks=[
{"goal": "Build 002a-pdf-parse-pdfjs: ...", "toolsets": ["terminal", "file", "web"]},
{"goal": "Build 002b-pdf-parse-camelot: ...", "toolsets": ["terminal", "file", "web"]},
])
```
Each subagent returns its own verdict; you write the head-to-head.
### 5. Verdict
Each spike's `README.md` closes with:
```markdown
## Verdict: VALIDATED | PARTIAL | INVALIDATED
### What worked
- ...
### What didn't
- ...
### Surprises
- ...
### Recommendation for the real build
- ...
```
**VALIDATED** = the core question was answered yes, with evidence.
**PARTIAL** = it works under constraints X, Y, Z — document them.
**INVALIDATED** = doesn't work, for this reason. This is a successful spike.
## Comparison spikes
When two approaches answer the same question (002a / 002b), build them **back to back**, then do a head-to-head comparison at the end:
```markdown
## Head-to-head: pdfjs vs camelot
| Dimension | pdfjs (002a) | camelot (002b) |
|-----------|--------------|----------------|
| Extraction quality | 9/10 structured | 7/10 table-only |
| Setup complexity | npm install, 1 line | pip + ghostscript |
| Perf on 100-page PDF | 3s | 18s |
| Handles rotated text | no | yes |
**Winner:** pdfjs for our use case. Camelot if we need table-first extraction later.
```
## Frontier mode (picking what to spike next)
If spikes already exist and the user says "what should I spike next?", walk the existing directories and look for:
- **Integration risks** — two validated spikes that touch the same resource but were tested independently
- **Data handoffs** — spike A's output was assumed compatible with spike B's input; never proven
- **Gaps in the vision** — capabilities assumed but unproven
- **Alternative approaches** — different angles for PARTIAL or INVALIDATED spikes
Propose 2-4 candidates as Given/When/Then. Let the user pick.
## Output
- Create `spikes/` (or `.planning/spikes/` if the user is using GSD conventions) in the repo root
- One dir per spike: `NNN-descriptive-name/`
- `README.md` per spike captures question, approach, results, verdict
- Keep the code throwaway — a spike that takes 2 days to "clean up for production" was a bad spike
## Attribution
Adapted from the GSD (Get Shit Done) project's `/gsd-spike` workflow — MIT © 2025 Lex Christopherson ([gsd-build/get-shit-done](https://github.com/gsd-build/get-shit-done)). The full GSD system offers persistent spike state, MANIFEST tracking, and integration with a broader spec-driven development pipeline; install with `npx get-shit-done-cc --hermes --global`.

View File

@@ -0,0 +1,362 @@
---
name: test-driven-development
description: "TDD: enforce RED-GREEN-REFACTOR, tests before code."
version: 1.1.0
author: Hermes Agent (adapted from obra/superpowers)
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [testing, tdd, development, quality, red-green-refactor]
related_skills: [systematic-debugging, plan, subagent-driven-development]
---
# Test-Driven Development (TDD)
## Overview
Write the test first. Watch it fail. Write minimal code to pass.
**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.
**Violating the letter of the rules is violating the spirit of the rules.**
## When to Use
**Always:**
- New features
- Bug fixes
- Refactoring
- Behavior changes
**Exceptions (ask the user first):**
- Throwaway prototypes
- Generated code
- Configuration files
Thinking "skip TDD just this once"? Stop. That's rationalization.
## The Iron Law
```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```
Write code before the test? Delete it. Start over.
**No exceptions:**
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete
Implement fresh from tests. Period.
## Red-Green-Refactor Cycle
### RED — Write Failing Test
Write one minimal test showing what should happen.
**Good test:**
```python
def test_retries_failed_operations_3_times():
attempts = 0
def operation():
nonlocal attempts
attempts += 1
if attempts < 3:
raise Exception('fail')
return 'success'
result = retry_operation(operation)
assert result == 'success'
assert attempts == 3
```
Clear name, tests real behavior, one thing.
**Bad test:**
```python
def test_retry_works():
mock = MagicMock()
mock.side_effect = [Exception(), Exception(), 'success']
result = retry_operation(mock)
assert result == 'success' # What about retry count? Timing?
```
Vague name, tests mock not real code.
**Requirements:**
- One behavior per test
- Clear descriptive name ("and" in name? Split it)
- Real code, not mocks (unless truly unavoidable)
- Name describes behavior, not implementation
### Verify RED — Watch It Fail
**MANDATORY. Never skip.**
```bash
# Use terminal tool to run the specific test
pytest tests/test_feature.py::test_specific_behavior -v
```
Confirm:
- Test fails (not errors from typos)
- Failure message is expected
- Fails because the feature is missing
**Test passes immediately?** You're testing existing behavior. Fix the test.
**Test errors?** Fix the error, re-run until it fails correctly.
### GREEN — Minimal Code
Write the simplest code to pass the test. Nothing more.
**Good:**
```python
def add(a, b):
return a + b # Nothing extra
```
**Bad:**
```python
def add(a, b):
result = a + b
logging.info(f"Adding {a} + {b} = {result}") # Extra!
return result
```
Don't add features, refactor other code, or "improve" beyond the test.
**Cheating is OK in GREEN:**
- Hardcode return values
- Copy-paste
- Duplicate code
- Skip edge cases
We'll fix it in REFACTOR.
### Verify GREEN — Watch It Pass
**MANDATORY.**
```bash
# Run the specific test
pytest tests/test_feature.py::test_specific_behavior -v
# Then run ALL tests to check for regressions
pytest tests/ -q
```
Confirm:
- Test passes
- Other tests still pass
- Output pristine (no errors, warnings)
**Test fails?** Fix the code, not the test.
**Other tests fail?** Fix regressions now.
### REFACTOR — Clean Up
After green only:
- Remove duplication
- Improve names
- Extract helpers
- Simplify expressions
Keep tests green throughout. Don't add behavior.
**If tests fail during refactor:** Undo immediately. Take smaller steps.
### Repeat
Next failing test for next behavior. One cycle at a time.
## Avoid Horizontal Slices
Do **not** write all tests first and then all implementation. That is horizontal slicing: RED becomes "write a pile of imagined tests" and GREEN becomes "make the pile pass." It produces brittle tests because the tests are designed before the implementation has taught you what behavior and interface actually matter.
Use vertical tracer bullets instead:
```text
WRONG:
RED: test1, test2, test3, test4
GREEN: impl1, impl2, impl3, impl4
RIGHT:
RED→GREEN: test1→impl1
RED→GREEN: test2→impl2
RED→GREEN: test3→impl3
```
A tracer bullet is one end-to-end behavior slice. It proves the path works, teaches you about the interface, and keeps each next test grounded in what you just learned.
## Why Order Matters
**"I'll write tests after to verify it works"**
Tests written after code pass immediately. Passing immediately proves nothing:
- Might test the wrong thing
- Might test implementation, not behavior
- Might miss edge cases you forgot
- You never saw it catch the bug
Test-first forces you to see the test fail, proving it actually tests something.
**"I already manually tested all the edge cases"**
Manual testing is ad-hoc. You think you tested everything but:
- No record of what you tested
- Can't re-run when code changes
- Easy to forget cases under pressure
- "It worked when I tried it" ≠ comprehensive
Automated tests are systematic. They run the same way every time.
**"Deleting X hours of work is wasteful"**
Sunk cost fallacy. The time is already gone. Your choice now:
- Delete and rewrite with TDD (high confidence)
- Keep it and add tests after (low confidence, likely bugs)
The "waste" is keeping code you can't trust.
**"TDD is dogmatic, being pragmatic means adapting"**
TDD IS pragmatic:
- Finds bugs before commit (faster than debugging after)
- Prevents regressions (tests catch breaks immediately)
- Documents behavior (tests show how to use code)
- Enables refactoring (change freely, tests catch breaks)
"Pragmatic" shortcuts = debugging in production = slower.
**"Tests after achieve the same goals — it's spirit not ritual"**
No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"
Tests-after are biased by your implementation. You test what you built, not what's required. Tests-first force edge case discovery before implementing.
## Common Rationalizations
| Excuse | Reality |
|--------|---------|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to the test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for the code you touch. |
## Red Flags — STOP and Start Over
If you catch yourself doing any of these, delete the code and restart with TDD:
- Code before test
- Test after implementation
- Test passes immediately on first run
- Can't explain why test failed
- Tests added "later"
- Rationalizing "just this once"
- "I already manually tested it"
- "Tests after achieve the same purpose"
- "Keep as reference" or "adapt existing code"
- "Already spent X hours, deleting is wasteful"
- "TDD is dogmatic, I'm being pragmatic"
- "This is different because..."
**All of these mean: Delete code. Start over with TDD.**
## Verification Checklist
Before marking work complete:
- [ ] Every new function/method has a test
- [ ] Watched each test fail before implementing
- [ ] Each test failed for expected reason (feature missing, not typo)
- [ ] Wrote minimal code to pass each test
- [ ] All tests pass
- [ ] Output pristine (no errors, warnings)
- [ ] Tests use real code (mocks only if unavoidable)
- [ ] Edge cases and errors covered
Can't check all boxes? You skipped TDD. Start over.
## When Stuck
| Problem | Solution |
|---------|----------|
| Don't know how to test | Write the wished-for API. Write the assertion first. Ask the user. |
| Test too complicated | Design too complicated. Simplify the interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify the design. |
## Hermes Agent Integration
### Running Tests
Use the `terminal` tool to run tests at each step:
```python
# RED — verify failure
terminal("pytest tests/test_feature.py::test_name -v")
# GREEN — verify pass
terminal("pytest tests/test_feature.py::test_name -v")
# Full suite — verify no regressions
terminal("pytest tests/ -q")
```
### With delegate_task
When dispatching subagents for implementation, enforce TDD in the goal:
```python
delegate_task(
goal="Implement [feature] using strict TDD",
context="""
Follow test-driven-development skill:
1. Write failing test FIRST
2. Run test to verify it fails
3. Write minimal code to pass
4. Run test to verify it passes
5. Refactor if needed
6. Commit
Project test command: pytest tests/ -q
Project structure: [describe relevant files]
""",
toolsets=['terminal', 'file']
)
```
### With systematic-debugging
Bug found? Write failing test reproducing it. Follow TDD cycle. The test proves the fix and prevents regression.
Never fix bugs without a test.
## Testing Anti-Patterns
- **Testing mock behavior instead of real behavior** — mocks should verify interactions, not replace the system under test
- **Testing implementation details** — test behavior/results, not internal method calls
- **Happy path only** — always test edge cases, errors, and boundaries
- **Brittle tests** — tests should verify behavior, not structure; refactoring shouldn't break them
## Final Rule
```
Production code → test exists and failed first
Otherwise → not TDD
```
No exceptions without the user's explicit permission.

77
youtube-content/SKILL.md Normal file
View File

@@ -0,0 +1,77 @@
---
name: youtube-content
description: "YouTube transcripts to summaries, threads, blogs."
version: 1.0.0
platforms: [linux, macos, windows]
---
# YouTube Content Tool
## When to use
Use when the user shares a YouTube URL or video link, asks to summarize a video, requests a transcript, or wants to extract and reformat content from any YouTube video. Transforms transcripts into structured content (chapters, summaries, threads, blog posts).
Extract transcripts from YouTube videos and convert them into useful formats.
## Setup
Use `uv` so the dependency is installed into the same Hermes-managed environment
that runs the helper script:
```bash
uv pip install youtube-transcript-api
```
## Helper Script
`SKILL_DIR` is the directory containing this SKILL.md file. The script accepts any standard YouTube URL format, short links (youtu.be), shorts, embeds, live links, or a raw 11-character video ID.
```bash
# JSON output with metadata
uv run python3 SKILL_DIR/scripts/fetch_transcript.py "https://youtube.com/watch?v=VIDEO_ID"
# Plain text (good for piping into further processing)
uv run python3 SKILL_DIR/scripts/fetch_transcript.py "URL" --text-only
# With timestamps
uv run python3 SKILL_DIR/scripts/fetch_transcript.py "URL" --timestamps
# Specific language with fallback chain
uv run python3 SKILL_DIR/scripts/fetch_transcript.py "URL" --language tr,en
```
## Output Formats
After fetching the transcript, format it based on what the user asks for:
- **Chapters**: Group by topic shifts, output timestamped chapter list
- **Summary**: Concise 5-10 sentence overview of the entire video
- **Chapter summaries**: Chapters with a short paragraph summary for each
- **Thread**: Twitter/X thread format — numbered posts, each under 280 chars
- **Blog post**: Full article with title, sections, and key takeaways
- **Quotes**: Notable quotes with timestamps
### Example — Chapters Output
```
00:00 Introduction — host opens with the problem statement
03:45 Background — prior work and why existing solutions fall short
12:20 Core method — walkthrough of the proposed approach
24:10 Results — benchmark comparisons and key takeaways
31:55 Q&A — audience questions on scalability and next steps
```
## Workflow
1. **Fetch** the transcript using the helper script with `--text-only --timestamps` via `uv run python3`.
2. **Validate**: confirm the output is non-empty and in the expected language. If empty, retry without `--language` to get any available transcript. If still empty, tell the user the video likely has transcripts disabled.
3. **Chunk if needed**: if the transcript exceeds ~50K characters, split into overlapping chunks (~40K with 2K overlap) and summarize each chunk before merging.
4. **Transform** into the requested output format. If the user did not specify a format, default to a summary.
5. **Verify**: re-read the transformed output to check for coherence, correct timestamps, and completeness before presenting.
## Error Handling
- **Transcript disabled**: tell the user; suggest they check if subtitles are available on the video page.
- **Private/unavailable video**: relay the error and ask the user to verify the URL.
- **No matching language**: retry without `--language` to fetch any available transcript, then note the actual language to the user.
- **Dependency missing**: run `uv pip install youtube-transcript-api` and retry.