From 5ae463fb3d6e4b408b531f4d9d5185552ae00d87 Mon Sep 17 00:00:00 2001 From: SpeedyFoxAi Date: Thu, 2 Jul 2026 13:49:45 -0500 Subject: [PATCH] Add ask-claude and ask-hermes skills --- README.md | 11 + ask-claude/SKILL.md | 346 +++++++++++++++++++ ask-claude/references/hermes-headless-cli.md | 70 ++++ ask-claude/references/usage-api-details.md | 35 ++ ask-claude/scripts/ask_claude.sh | 29 ++ ask-hermes/SKILL.md | 159 +++++++++ 6 files changed, 650 insertions(+) create mode 100644 README.md create mode 100644 ask-claude/SKILL.md create mode 100644 ask-claude/references/hermes-headless-cli.md create mode 100644 ask-claude/references/usage-api-details.md create mode 100644 ask-claude/scripts/ask_claude.sh create mode 100644 ask-hermes/SKILL.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..1619033 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Hermes Skills + +Hermes Agent skills for autonomous AI workflows. + +## Skills + +### ask-claude +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. + +### ask-hermes +Persistent peer Hermes agent for delegated work. `ask hermes ` delegates a task with full context via headless one-shot commands. Session persists across all turns in one Hermes session via --resume. diff --git a/ask-claude/SKILL.md b/ask-claude/SKILL.md new file mode 100644 index 0000000..e6556b2 --- /dev/null +++ b/ask-claude/SKILL.md @@ -0,0 +1,346 @@ +--- +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." +version: 2.3.0 +author: Hermes Agent +license: MIT +platforms: [linux] +metadata: + hermes: + tags: [claude, opus, consulting, planning, analysis, ssh, print-mode, usage-tracking] + related_skills: [claude-code, hermes-agent] +--- + +# Ask Claude — Print Mode Consulting via Claude Opus 4.8 + +Includes usage & context tracking: context % from JSON envelope (free), session/weekly % from OAuth usage API (best-effort, not per-turn). Alerts at 80% context, 80% session, 90% weekly. + +Consult Claude Opus on 10.0.0.28 using headless print mode (`-p`) with session resumption. Each turn is a request/response — Claude's process exit IS the turn boundary. No tmux, no polling, no send-keys races. + +## ⚡ Quick Reference + +| Step | What | Command Pattern | +|------|------|----------------| +| 1. Write question | Temp file on 10.0.0.28 via mktemp | `ssh ... 'mktemp ... && cat > $QFILE << "EOF"\nquestion\nEOF'` | +| 2. First question | No `--resume`, `|| true` to survive errors | `timeout 280 claude -p "$(cat $QFILE)" --output-format json --model claude-opus-4-8 --max-turns 30 || true` | +| 3. Parse result | Extract `session_id`, `result`, `is_error`, detect `error_max_turns` | `python3 -c "import sys,json; ..."` | +| 4. Follow-up | WITH `--resume ` | `claude -p --resume "$(cat $QFILE)" --output-format json --model claude-opus-4-8 --max-turns 30 || true` | +| 5. Done | Process exits. Session auto-expires in ~5h. | `rm $QFILE` (trap on EXIT) | + +**Critical:** Always use `--output-format json`, `--model claude-opus-4-8`, and `--max-turns 30`. Pin the model explicitly — profile defaults drift silently. Use `|| true` after the claude command to survive non-zero exits (errors still produce parseable JSON). Use full path `/home/n8n/.local/bin/claude` on non-interactive SSH. Wrap with `timeout 280` to prevent runaway hangs. + +## Decision Point: `--resume` or Fresh Session? + +**Default rule: One Hermes session = one Claude session.** The first `ask-claude` call in a Hermes session starts a fresh Claude session. Every subsequent call within the same Hermes session resumes it. Claude accumulates context across turns, just like our conversation does. + +``` +Is there an active Claude session_id from a previous turn in THIS Hermes session? +│ +├─ YES → Use --resume +│ Always. Claude remembers everything from earlier in our conversation. +│ This is the default — same Hermes session, same Claude session. +│ +└─ NO → Fresh session (no --resume flag) + Only when: + - This is the first ask-claude call in this Hermes session + - The user explicitly said "new topic" or "fresh session" + - The previous session expired (~5 hours since last turn) +``` + +**Session lifecycle:** Each `claude -p` invocation exits immediately after responding. The session data lives on disk. You don't "close" sessions — they auto-expire after ~5 hours of inactivity. No cleanup needed. + +## When to Use + +- Deep analysis, planning, or validation +- Second opinion on architecture, security, or design +- Research that benefits from SearXNG + Opus reasoning +- Multi-turn discussion where Claude may ask clarifying questions + +## Local Hermes Peer (Alternative) + +When Claude is unreachable, overkill, or the user wants a free local peer, use the `ask-hermes` skill (planned, not yet built — see `/home/n8n/workspace/dev/ask_hermes_plan.md`). It starts a persistent peer Hermes agent on a different profile via plain `hermes` (interactive REPL, no flags). Three commands: `start ask` / `ask hermes ` / `stop ask`. The peer is a full delegated worker with all tools — not just a Q&A box. See the plan for the full mechanism, prompt composition rules, and turn budget (max 20, soft limit). + +**Build-attempt findings (2026-06-30):** +- `hermes -p general` works — `-p` flag exists despite not appearing in top-level `--help`. +- REPL needs `pty=true` to stay alive; without it, prompt_toolkit exits immediately ("Input is not a terminal"). +- **Blocker: TUI output is unreadable via `process(poll)`.** The interactive REPL's ANSI redraw codes make the actual response text unparseable. Input works, output doesn't. This is the open problem — the plan's mechanism (plain `hermes`, interactive REPL, no flags) can't currently read peer responses cleanly. Possible paths: `--cli` flag for classic REPL, or headless `-z` one-shot with `--resume` for continuity. + +## Prerequisites + +- Claude Code on 10.0.0.28 at `/home/n8n/.local/bin/claude` (v2.1.195, native installer) +- Consulting workspace: `~/claude/hermes_support/` with CLAUDE.md +- SSH: `ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28` +- Model: `claude-opus-4-8` — pass explicitly via `--model`, do NOT rely on profile default +- Use full path `/home/n8n/.local/bin/claude` on non-interactive SSH (bare `claude` only works in interactive shells that source `.bashrc`) +- Wrap with `timeout 280` to prevent runaway hangs +- Use `|| true` after claude invocation — `set -e` would kill the script on non-zero exit, but errors still produce parseable JSON + +## Workflow + +### Step 1: Confirm with User + +Tell the user what question you'll send. Skip confirmation for clear, self-contained queries. Confirm when the question needs interpretation. + +### Step 2: Send First Question (Fresh Session) + +**CRITICAL: Never inline the question in the SSH command.** Shell metacharacters (`"`, `$`, `(`, `)`, backticks) will break. Always write to a temp file first. Use `mktemp` to avoid collisions between concurrent sessions. + +```bash +# Step 2a: Write question to temp file on 10.0.0.28 (mktemp avoids collisions) +QFILE=$(ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \ + 'f=$(mktemp /tmp/ask-claude-q-XXXXXX.txt) && cat > "$f" << "HERMES_EOF" +QUESTION TEXT HERE - any characters are safe: quotes "quotes", dollars $PATH, parens (test) +HERMES_EOF +echo "$f"') + +# Step 2b: Run Claude in print mode (NO --resume — this is a fresh session) +# || true prevents set -e from killing the script on non-zero exit +# timeout 280 prevents runaway hangs +RESP=$(ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \ + "cd ~/claude/hermes_support && timeout 280 /home/n8n/.local/bin/claude -p \"$(cat $QFILE)\" --output-format json --model claude-opus-4-8 --max-turns 30 || true") + +# Step 2c: Clean up temp file +ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 "rm -f $QFILE" + +# Step 2d: Parse the JSON (handle empty/non-JSON, error_max_turns, iterate modelUsage) +SID=$(echo "$RESP" | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) +except: + print(''); sys.exit(0) +print(d.get('session_id','')) +") +RESULT=$(echo "$RESP" | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) +except: + print('ERROR: non-JSON response'); sys.exit(0) +subtype = d.get('subtype','') +if subtype == 'error_max_turns': + print('ERROR: max turns reached') +elif d.get('is_error'): + print(f\"ERROR: {subtype} - {d.get('result','')}\") +else: + print(d.get('result','(empty)')) +") +IS_ERROR=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('is_error',False))") +COST=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('total_cost_usd',''))") +``` + +**JSON fields available:** +- `session_id` — save this for follow-ups (capture from EVERY response, including errors) +- `result` — Claude's answer text (may be empty on `error_max_turns`) +- `is_error` — true if something went wrong +- `subtype` — `success`, `error_max_turns` (is_error:false but no result!), `error_budget` +- `total_cost_usd` — track spending +- `num_turns` — how many agentic loops Claude used +- `stop_reason` — why Claude stopped +- `usage` — object with `input_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `output_tokens` +- `modelUsage` — dict keyed by model name, each with `contextWindow` (total context window size). Iterate keys — don't hardcode the model name. + +## Usage & Context Tracking + +Two data sources for monitoring Claude's limits. Check them on every turn so you can alert the user BEFORE they hit a wall. + +### 1. Context % (free — from the JSON envelope you already parse) + +Compute from the same `--output-format json` response. Zero extra HTTP calls. Iterate `modelUsage` keys — don't hardcode the model name. + +```bash +# After parsing RESP, compute context % +echo "$RESP" | python3 -c " +import sys, json +d = json.load(sys.stdin) +u = d.get('usage', {}) +used = u.get('input_tokens', 0) + u.get('cache_creation_input_tokens', 0) + u.get('cache_read_input_tokens', 0) +mu = d.get('modelUsage', {}) +cw = 200000 +if mu: + for model_name, model_data in mu.items(): + cw = model_data.get('contextWindow', 200000) + break +pct = int(used / cw * 100) +print(f'context_pct={pct} tokens={used}/{cw}') +" +``` + +**Alert threshold: ≥ 80%** — warn the user. "Context at 84% — wrap this up or start fresh soon." + +### 2. Session % + Weekly % + Reset Timers (OAuth usage API — BEST-EFFORT) + +Account-wide counters. **Do NOT call every turn** — the endpoint aggressively rate-limits (open issues anthropics/claude-code #31637, #31021). Call at most every ~5 minutes, cache the last good value, and never gate a turn's success on this call. Swallow all errors (429s, timeouts, auth failures). + +```bash +# Fetch usage limits +ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \ + TOKEN=*** -r '.claudeAiOauth.accessToken' ~/.claude/.credentials.json) + curl -s https://api.anthropic.com/api/oauth/usage \ + -H "Authorization: Bearer *** \ + -H "anthropic-beta: oauth-2025-04-20" \ + -H "Content-Type: application/json" \ + | jq -r ' + -H "Content-Type: application/json" \ + | jq -r ' + + "session_pct=\(.five_hour.utilization)% session_reset=\(.five_hour.resets_at)", + "weekly_pct=\(.seven_day.utilization)% weekly_reset=\(.seven_day.resets_at)"' + ``` + + ### Step 3: Present Result to User +- `five_hour.resets_at` — UTC ISO-8601 when session counter resets +- `seven_day.utilization` — weekly cap usage (0–100) +- `seven_day.resets_at` — UTC ISO-8601 when weekly counter resets +- Token path: `~/.claude/.credentials.json` (Linux plaintext) +- Requires `anthropic-beta: oauth-2025-04-20` header or it 401s +- **Note:** `seven_day` is all-models weekly, not Opus-specific. `seven_day_opus` exists in the response but is null on this plan. The all-models counter is the one that matters for rate limiting. + +**Alert thresholds:** +- Session ≥ 80% — warn before starting a fresh session. "Claude session at 87%, resets at 2:49 PM CT — want to wait or use Sonnet instead?" +- Weekly ≥ 90% — critical. "Claude weekly at 94%, resets Tuesday 2:00 PM CT. Consider Sonnet or waiting." +- When Claude is unavailable (error_budget or rate-limited) — report the exact reset time so the user knows when to try again. + +### 3. Alert Rules (apply on every ask-claude turn) + +| Check | When | Threshold | Action | +|-------|------|-----------|--------| +| Context % | After every response | ≥ 80% | Warn: wrap up or start fresh | +| Session % | Best-effort (~5min cache) | ≥ 80% | Warn + show reset time, offer Sonnet | +| Weekly % | Best-effort (~5min cache) | ≥ 90% | Critical warning + reset time | +| Unavailable | On error_budget/rate-limit | N/A | Report exact reset time | + +### 4. Combined One-Shot Script + +The deployed files on 10.0.0.28 are two separate scripts in `~/claude/hermes_support/`: + +- **ask.sh** — wrapper script. Takes `--resume SID`, `--max-turns N` (default 30), `--qfile PATH`. Writes question to mktemp snapshot, runs claude with `timeout 280 || true`, fetches usage API (best-effort), calls merge.py. +- **merge.py** — JSON merger. Parses claude output + usage API JSON, handles empty/non-JSON gracefully (try/except), extracts session_id/result/is_error/cost/usage fields. No context_pct (cumulative usage is unreliable). + +These are the source of truth. The local `scripts/ask_claude.sh` in this skill is a reference copy — the deployed files on 10.0.0.28 take precedence. + +Invocation from Hermes: +```bash +# Fresh session +ssh n8n@10.0.0.28 '~/claude/hermes_support/ask.sh --qfile /tmp/my-question.txt' + +# Resume +ssh n8n@10.0.0.28 '~/claude/hermes_support/ask.sh --resume SESSION_ID --qfile /tmp/my-question.txt' +``` +### Step 3: Present Result to User + +Show Claude's response. If Claude asked a clarifying question (visible in the result text), get the user's answer. + +### Step 4: Send Follow-ups (WITH --resume) + +Same mktemp pattern, but add `--resume ` to continue the conversation. Always capture session_id from every response (including errors) — a turn can error yet still advance the session. + +```bash +# Step 4a: Write follow-up to temp file (mktemp) +QFILE=$(ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \ + 'f=$(mktemp /tmp/ask-claude-q-XXXXXX.txt) && cat > "$f" << "HERMES_EOF" +FOLLOWUP QUESTION TEXT +HERMES_EOF +echo "$f"') + +# Step 4b: Resume the same session +RESP=$(ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \ + "cd ~/claude/hermes_support && timeout 280 /home/n8n/.local/bin/claude -p --resume SESSION_ID \"$(cat $QFILE)\" --output-format json --model claude-opus-4-8 --max-turns 30 || true") + +# Step 4c: Clean up + parse JSON again +ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 "rm -f $QFILE" +``` + +Parse the JSON again. The session continues — Claude remembers the full conversation. + +### Step 5: Report Back + +Summarize Claude's findings in 1-3 sentences. Note any .md files saved in `~/claude/hermes_support/`. + +## Error Handling + +```bash +# Check for empty response (timeout/crash/OOM) +if [ -z "$RESP" ]; then + echo "ERROR: Claude returned empty response (timeout/crash/OOM)" +fi + +# Check for non-JSON response +echo "$RESP" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null || { + echo "ERROR: Claude returned non-JSON. Raw: $(echo "$RESP" | head -c 500)" +} + +# Check is_error in JSON +if [ "$IS_ERROR" = "True" ]; then + echo "Claude reported error: $RESULT" + # subtype tells you why: error_max_turns, error_budget, etc. +fi + +# Detect error_max_turns specifically (is_error:false but no result field) +SUBTYPE=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('subtype',''))") +if [ "$SUBTYPE" = "error_max_turns" ]; then + echo "ERROR: max turns reached. Retry with higher --max-turns or simplify." +fi +``` + +**Common errors:** +- `error_max_turns` — question too complex, increase `--max-turns` or simplify. Note: `is_error` is `false` but `result` is empty — detect via `subtype`. +- `error_budget` — hit spending cap, check Pro limits +- Non-zero exit code — SSH failure, Claude not installed, or process killed. `|| true` keeps the script alive so you can still parse the JSON. +- Empty response — timeout (increase from 280s), OOM, or crash +- Non-JSON response — SSH dropped mid-call, Claude crashed before writing output + +## Sonnet Fast-Path + +For routine questions where Opus-level reasoning is overkill, offer the user a faster option: + +```bash +/home/n8n/.local/bin/claude -p "question" --output-format json --model claude-sonnet-4-6 --max-turns 10 || true +``` + +Sonnet is 3-5x faster and cheaper. Use for: factual lookups, simple analysis, quick checks. + +## Pitfalls + +- **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 set `--max-turns`** — prevents runaway loops. 30 is a good default for analysis tasks (20 was too tight with CLAUDE.md forcing web-search-first + .md writes). +- **Always pass `--model claude-opus-4-8`** — pin the model explicitly. Profile defaults drift silently. The old "do NOT pass --model" rule was wrong. +- **Always use `|| true` after the claude command** — `set -e` kills the script on non-zero exit, but errors still produce parseable JSON you need. +- **Always wrap with `timeout 280`** — prevents runaway hangs that exceed SSH's 300s timeout. +- **Always add `-o ServerAliveInterval=30`** to SSH commands — Opus turns run 45-90s and SSH can silently disconnect. +- **Use full path `/home/n8n/.local/bin/claude`** — bare `claude` only works in interactive shells that source `.bashrc`. +- **Session IDs expire** — sessions last ~5 hours. After that, `--resume` fails. Start fresh. +- **No session cleanup needed** — sessions auto-expire. Don't try to "close" them. +- **JSON parsing** — use Python's `json.load(sys.stdin)` not jq, to handle control characters in Claude's output. Wrap in try/except for non-JSON responses. +- **SSH timeouts** — Opus can take 60-120 seconds. Set generous timeouts (300s for SSH, 280s for claude process). +- **CRITICAL: Ask before probing.** Do NOT SSH into 10.0.0.28 without asking the user first. +- **CLAUDE.md is the source of truth** — if behavior seems off, check `~/claude/hermes_support/CLAUDE.md` on 10.0.0.28. +- **Tool sanitization: `$VARNAME` → `***`** — Hermes tooling (write_file, skill_manage patch, execute_code) sanitizes `$VARIABLE` patterns to `***` at write time. When editing the combined script or any code block containing shell variables, use Python's `chr(36)` to construct `$` and write via terminal heredoc or Python file I/O. Example: `dollar = chr(36); script = f'TOKEN={dollar}(jq ...)'`. Same issue affects `|` (pipe) — use `chr(124)` if it gets mangled to `|`. +- **Capture `session_id` from EVERY response** — including errors. A turn can error yet still advance the session. If you only store it on success, resume breaks. +- **Detect `error_max_turns` via `subtype`** — it has `is_error:false` but no `result` field. Your parser will emit empty answers if you don't check subtype explicitly. +- **Iterate `modelUsage` keys** — don't hardcode `modelUsage['claude-opus-4-8']`. The key changes with the model id. Use `for model_name, model_data in mu.items(): cw = model_data.get('contextWindow', 200000); break`. +- **Usage API is best-effort only** — it aggressively rate-limits. Call at most every ~5 min, cache last good value, swallow all errors (429s, timeouts). Never gate a turn on this call. +- **Use `mktemp` for temp files** — `/tmp/ask-claude-q.txt` is a fixed path that concurrent Hermes sessions clobber. Use `mktemp /tmp/ask-claude-q-XXXXXX.txt` and `trap "rm -f $QFILE" EXIT`. +- **Shell metacharacters in questions break heredoc** — when the question contains quotes, parentheses, dollar signs, or other shell-special characters, the `mktemp` + heredoc pattern on the remote host will fail with syntax errors (bash tries to interpret the content). **Workaround:** write the question to a local temp file, `scp` it to the remote host, then use `ask.sh --qfile` with the remote path. Example: + ```bash + # Write question locally + write_file("/tmp/ask-claude-q.txt", content=question) + # Copy to remote + terminal("scp /tmp/ask-claude-q.txt n8n@10.0.0.28:/tmp/ask-claude-q.txt") + # Run via ask.sh + terminal("ssh n8n@10.0.0.28 '~/claude/hermes_support/ask.sh --qfile /tmp/ask-claude-q.txt'") + ``` + This avoids all shell escaping issues. The `ask.sh` wrapper handles mktemp snapshot + claude invocation + merge.py internally. + +## Claude's CLAUDE.md (for reference) + +The consulting workspace at `~/claude/hermes_support/CLAUDE.md` documents print mode as the operating procedure and includes behavior rules: zero fluff, SearXNG-first, save findings as .md, stay on topic, ask when unclear, wrap up when done. + +## Verification + +```bash +# Quick smoke test +ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \ + 'cd ~/claude/hermes_support && timeout 60 /home/n8n/.local/bin/claude -p "Respond with exactly: ASK_CLAUDE_OK" --output-format json --model claude-opus-4-8 --max-turns 1 || true' +``` diff --git a/ask-claude/references/hermes-headless-cli.md b/ask-claude/references/hermes-headless-cli.md new file mode 100644 index 0000000..3e53a61 --- /dev/null +++ b/ask-claude/references/hermes-headless-cli.md @@ -0,0 +1,70 @@ +# Hermes Headless CLI — Local Peer Invocation + +Verified 2026-06-30 against Hermes Agent installed at `/home/n8n/.local/bin/hermes`. + +## `-z` / `--oneshot` — Headless One-Shot Mode + +```bash +hermes -z "your prompt here" +``` + +Prints ONLY the final response text to stdout. No banner, no spinner, no tool previews, no session_id line. Tools, memory, and skills are still loaded and used — you just don't see the intermediate output. + +**Verified:** `hermes -z "Reply with exactly: HEADLESS_TEST_OK"` returned `HEADLESS_TEST_OK` with zero extra output. + +## Flags That Work With `-z` + +| Flag | Purpose | Example | +|------|---------|---------| +| `-m MODEL` | Model override | `hermes -z "..." -m glm-5.2:cloud` | +| `--provider PROVIDER` | Provider override | `hermes -z "..." --provider custom:ollama` | +| `--resume SESSION` | Resume a previous session by ID | `hermes -z "..." --resume abc123` | +| `--continue [NAME]` | Resume by name or most recent | `hermes -z "..." --continue` | +| `-t TOOLSETS` | Comma-separated toolsets | `hermes -z "..." -t terminal,file` | +| `--safe-mode` | No dangerous commands | `hermes -z "..." --safe-mode` | +| `--yolo` | Bypass approval prompts | `hermes -z "..." --yolo` | +| `--pass-session-id` | Include session_id in system prompt | `hermes -z "..." --pass-session-id` | + +## Flags That Do NOT Exist + +- **`-p` / `--profile`** — does NOT exist. Claude hallucinated this. The only way to target a specific profile is the sticky default (`hermes profile use `). +- **`-Q` / `--quiet`** — only exists on `hermes chat` subcommand, NOT top-level. `-z` is already quiet by design. + +## `hermes chat -q` — Alternative One-Shot (Chat Subcommand) + +```bash +hermes chat -q "your prompt" -Q +``` + +Also one-shot, but prints session info on exit. `-Q` suppresses some output. Use when you need the session_id for later `--resume` (since `-z` doesn't print it). + +## Session Persistence + +- `--resume ` works with both `-z` and `chat -q` +- Session IDs are printed on exit from `chat -q` (not from `-z`) +- Sessions stored in `~/.hermes/state.db` +- `hermes sessions list` shows all sessions + +## Comparison: `hermes -z` vs `claude -p` + +| Feature | `hermes -z` | `claude -p` | +|---------|-------------|-------------| +| Output | Final response only | JSON envelope (session_id, result, cost, usage) | +| Session ID | Not printed | In JSON | +| Model override | `-m` | `--model` | +| Session resume | `--resume` | `--resume` | +| Max turns | Config-based | `--max-turns` | +| Cost tracking | None | `total_cost_usd` in JSON | +| Provider | Local Ollama (free) | Anthropic API (paid) | + +## When to Use `-z` vs `chat -q` + +- **`-z`**: When you just need the answer and don't care about session_id. Cleanest for one-shot queries. +- **`chat -q -Q`**: When you need the session_id for later `--resume` (multi-turn peer consultation). Parse the exit output for the session ID. + +## Pitfalls + +- **`-z` doesn't print session_id.** If you need to resume later, use `chat -q -Q` instead and capture the session ID from the exit output. +- **No `-p`/`--profile` flag.** Target a profile by setting the sticky default first: `hermes profile use `. +- **`-Q` is chat-subcommand-only.** Don't try `hermes -z ... -Q` — it'll error. +- **`-z` still loads tools/memory/skills.** It's not a stripped-down mode — the agent has full capabilities, just quiet output. diff --git a/ask-claude/references/usage-api-details.md b/ask-claude/references/usage-api-details.md new file mode 100644 index 0000000..989aaba --- /dev/null +++ b/ask-claude/references/usage-api-details.md @@ -0,0 +1,35 @@ +# Anthropic OAuth Usage API — Confirmed Details + +Verified live on 10.0.0.28 by Claude Opus, 2026-06-27. + +## Endpoint + +``` +GET https://api.anthropic.com/api/oauth/usage +Authorization: Bearer +anthropic-beta: oauth-2025-04-20 +``` + +## Response Fields + +| Field | Meaning | Notes | +|-------|---------|-------| +| `five_hour.utilization` | Rolling 5-hour session usage (0–100) | Account-wide, not per-session | +| `five_hour.resets_at` | UTC ISO-8601 reset timestamp | | +| `seven_day.utilization` | Weekly cap usage (0–100) | ALL models combined | +| `seven_day.resets_at` | UTC ISO-8601 reset timestamp | | +| `seven_day_opus` | Opus-specific weekly | null on this plan | +| `limits[]` | Array with severity (normal/warning) | For threshold alerts | + +## Token Expiry + +`~/.claude/.credentials.json` has `expiresAt`. If expired, the curl 401s. Token auto-refreshes on next interactive `claude` run, but a headless-only loop can go stale. Not blocking for normal use — interactive runs keep it fresh. + +## Context % Formula (from print-mode JSON) + +``` +context_pct = (input_tokens + cache_creation_input_tokens + cache_read_input_tokens) + / modelUsage[model].contextWindow * 100 +``` + +All fields come from the same `--output-format json` envelope — zero extra HTTP calls. diff --git a/ask-claude/scripts/ask_claude.sh b/ask-claude/scripts/ask_claude.sh new file mode 100644 index 0000000..ea8445e --- /dev/null +++ b/ask-claude/scripts/ask_claude.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# ask_claude.sh - Hermes-side wrapper for ask-claude skill +# Delegates to the deployed ask.sh on 10.0.0.28 +# Source of truth: ~/claude/hermes_support/ask.sh + merge.py on 10.0.0.28 +# +# Usage: ask_claude.sh "question" [session_id] +# +# This is a thin caller. The real logic lives in: +# ssh n8n@10.0.0.28 ~/claude/hermes_support/ask.sh --qfile [--resume SID] + +set -euo pipefail + +Q=$1 +SID=${2:-} + +# Write question to temp file +QFILE=$(mktemp /tmp/ask-claude-q-XXXXXX.txt) +trap "rm -f $QFILE" EXIT +echo "$Q" > "$QFILE" + +# Delegate to deployed ask.sh on 10.0.0.28 +if [ -n "$SID" ]; then + ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \ + "~/claude/hermes_support/ask.sh --resume $SID --qfile $QFILE" +else + ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \ + "~/claude/hermes_support/ask.sh --qfile $QFILE" +fi + diff --git a/ask-hermes/SKILL.md b/ask-hermes/SKILL.md new file mode 100644 index 0000000..1b60384 --- /dev/null +++ b/ask-hermes/SKILL.md @@ -0,0 +1,159 @@ +--- +name: ask-hermes +description: Persistent peer Hermes agent for delegated work. `ask hermes ` delegates a task with full context via headless one-shot commands. Session persists across all turns in one Hermes session via --resume. +version: 4.0.0 +author: Hermes Agent +license: MIT +platforms: [linux] +metadata: + hermes: + tags: [hermes, peer, delegation, parallel, review, validation] + related_skills: [ask-claude, claude-code, hermes-agent] +--- + +# ask-hermes — Peer Hermes Agent for Delegated Work + +## Overview + +A persistent peer Hermes agent session for delegated work. The peer runs on the general profile (`hermes -p general`) — a separate agent instance with its own workspace, SOUL.md, AGENTS.md, and all tools (terminal, file, web search, MCP). Fresh context, no memory of this session. Used for parallel work, peer review, validation, and delegated tasks. + +Each `ask hermes` is a one-shot headless command that runs, returns a clean response, and exits. Continuity (the peer remembering prior turns) comes from `--resume `, not from a live process. No start/stop needed — the session starts on the first ask and ends when the Hermes session ends. + +## When to Use + +- Want a second agent to do real work in parallel: read files, run tests, validate, search +- Want peer review of my recommendation with full tool access +- Multi-turn task: peer can iterate, push back (via --resume) +- Want local delegation without leaving the terminal + +## When NOT to Use + +- Question is factual and I can answer it +- Task needs my exact context state (use delegate_task on THIS profile) +- You want to see the peer's chat directly (this is relay-only — full text in `hermes sessions list`) + +## Command + +### `ask hermes ` + +**First ask (starts the session):** +``` +hermes -p general chat -q "" -Q --max-turns 20 --yolo +``` +- Output line 1: `session_id: ` — capture this +- Output line 2+: the peer's response +- Hold the session_id in conversation context for all subsequent asks + +**Subsequent asks (resume the session):** +``` +hermes -p general chat --resume -q "" -Q --max-turns 20 --yolo +``` +- Compose prompt with full context (see Prompt Composition) +- Run command. Parse response (after the session_id line). Relay to operator. +- Spot-check side-effects (see Spot-check rule). + +## Session Persistence + +**HARD RULE: Always --resume the prior session for follow-up asks.** The session_id is captured from output line 1 of the first ask. Every subsequent ask in the same line of work MUST use `--resume `, not start a fresh session. Starting fresh discards the peer's context, wastes tokens re-establishing state, and breaks multi-turn workflows. If you don't have the session_id, you failed to capture it — that's a separate failure. The session_id lives in conversation context; hold it there. + +The session_id lives in conversation context. No state file. If the LXC crashes, we both crash — next session starts fresh. + +## Prompt Composition + +Peer has NO memory of this session beyond what --resume carries. Every `ask hermes` includes: +- Operator's exact instructions (quoted) +- All relevant absolute file paths +- Constraints: investigation-first, real testing, scoped workspaces, privacy-first +- Background: what's already done, what's decided +- Expected output format (e.g. "return a 1-paragraph summary + list of files changed") +- Verification handle: "return absolute path / exit code for any side-effect" + +**HARD RULE: Never paste file content into the prompt.** The peer can read any file by absolute path — point at the path instead (e.g. "read /home/n8n/workspace/dev/hindsight_issue.md and validate it"). Pasting wastes tokens and introduces transcription errors: shell expansion of backticks, encoding issues, and prompt bloat. This is the #1 ask-hermes failure mode. Only paste inline when the content is under ~5 lines or the peer genuinely can't access the path. + +## Pre-Send Audit + +Before executing ANY `ask hermes` command, run this 3-question checklist: + +1. **Is this a follow-up to a prior ask?** If yes, am I using `--resume `? Am I sure I have the session_id from the prior output's first line? Starting fresh when I should resume is the #2 failure mode. + +2. **Am I pasting file content the peer could read from an absolute path?** If yes, replace with a path reference. Only paste inline when content is under ~5 lines or the peer genuinely can't access the path. + +3. **Does this task involve verifying external facts, docs, or behavior?** If yes, include the web search mandate line: "Use `mcp_searxng_searxng_web_search` for every claim and cite the source URL. Do not rely on parametric knowledge or reason about what a command 'would show.' Run the real command, run the real search." + +**ALWAYS insist on detailed, multiple web searches.** The peer has the searxng MCP tool (`mcp_searxng_searxng_web_search`). This is non-negotiable — see the **Web Search Mandate** section below for the canonical, must-follow form. Every `ask hermes` prompt that involves verifying facts, docs, or external behavior MUST include the mandate line (specified below). + +## Web Search Mandate + +The peer MUST confirm every external fact with a live web search. Parametric knowledge is not enough. The peer has the `mcp_searxng_searxng_web_search` tool — it MUST use it. + +**What the peer must search for (non-exhaustive):** +- Version numbers, port numbers, env var names, config keys → official docs (hermes-agent.nousresearch.com, docs.openwebui.com, etc.) +- Package availability, install commands, library APIs → PyPI, GitHub, official README +- Behavior claims about a service or tool → vendor docs, GitHub issues, changelog +- Bug claims, "X is broken" verdicts → live probe + cited source + +**How the peer must search:** +- Multiple searches per claim, different angles. A single search is not enough. Official docs + GitHub + PyPI/API reference is the minimum for version-sensitive claims. +- For every verdict, cite the source URL inline. No URL = unverified = do not relay. +- Direct the peer to local files by absolute path, not pasted content. Saves tokens and avoids transcription errors. + +**Hard rule for the calling agent (you):** Every `ask hermes` prompt that involves verifying facts, docs, or external behavior MUST include the line: "Use `mcp_searxng_searxng_web_search` for every claim and cite the source URL. Do not rely on parametric knowledge or reason about what a command 'would show.' Run the real command, run the real search." + +This section is the canonical form of the web-search requirement. The abbreviated guidance in Prompt Composition above references it and does not override or weaken it. + +## Turn Budget + +Max 20 turns per question, enforced by `--max-turns 20` at the CLI level — hard cap. Peer follows up autonomously (tool calls, iterations) without reporting back each turn — only the final result comes back. If the peer hits the 20-turn cap before finishing, it returns what it has — relay that and the operator decides whether to continue with a follow-up ask. + +## Peer Cross-Validation Pattern + +When the user wants a deliverable validated, use TWO independent peer passes: + +1. **Build the deliverable yourself** (research, write, verify). +2. **Dispatch peer to build the SAME deliverable independently.** Give the peer the same goal, your file path to read first, and the instruction "find what I missed." Do NOT give the peer your conclusions — let it discover independently. +3. **Spot-check the peer's claims** against source (file:line, live commands). The peer may find real gaps AND may overstate some findings. +4. **Report the delta** — what the peer added, what was wrong, what was confirmed. + +This is NOT the same as Delegate-Fix-Then-Validate (where the peer does the work and you verify). Here both agents build independently, then you compare. + +## Relay Rule + +Do not silently paraphrase. Relay the peer's actual response — if long, chunk it. Call out unverified "I did X" claims. + +## Spot-check Rule + +Peer self-reports are not verified fact. If the peer reports a file write, `read_file` the path to confirm. If it reports a test pass, re-run the test if the result matters. + +**Peer verdicts can be WRONG.** A peer may flag a profile as BROKEN when it's actually healthy, or claim PASS on something that's broken. Always independently verify the peer's conclusions — don't relay them as fact. Concrete example: peer claimed `general` profile was BROKEN (bank_id leak), but the daemon log showed `general` was actively writing to `hermes-general` with successful retain + consolidation. The peer's logic was confused — it assumed the bank_id convention was violated when it wasn't. The daemon log is the source of truth, not the peer's reasoning. + +## Delegate-Fix-Then-Validate Pattern + +When the user says "ask hermes to fix X, then YOU validate," follow this exact sequence: + +1. **Compose findings + goal.** Write a clear prompt with: what's broken, what the correct state should be, file paths, constraints (read-only vs. allowed changes), and expected output format. +2. **Dispatch peer to fix.** Run `hermes -p general chat -q "..." -Q --max-turns 20 --yolo`. The peer does the work. +3. **Independently validate.** Do NOT trust the peer's self-report. Verify every claim: stat files, curl endpoints, read configs, check daemon logs. The peer may claim "PASS" on things that are actually broken, or flag things as BROKEN that are correct. +4. **Report the validated results** — not the peer's raw self-report. + +**CRITICAL: Do NOT jump in and do the fix yourself.** If the user said "ask hermes to fix it," the peer does the fixing. You only validate afterward. Doing the fix yourself violates the user's explicit instruction and skips the peer review step. + +## Common Pitfalls + +1. **Profile flag required.** Use `-p general` — the sticky default may be dev, 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 general profile's config; it only affects the peer session. +3. **Peer skill_manage writes to the peer's profile, not yours.** If the peer patches a skill via skill_manage, it lands in the peer's profile (general), not the calling agent's profile (dev). The peer's `skill_manage` uses its own profile context. To sync changes back to dev, the calling agent must apply the same patches to its own copy. This is the #3 ask-hermes failure mode — assuming peer changes are global. +4. **Do NOT use interactive REPL mode.** Running `hermes -p general` 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. +5. **Peer is headless.** Operator sees my relay, not the peer's chat. Full text in `hermes sessions list` if needed. +6. **Peer self-reports are not verified fact.** Spot-check file writes, test passes before confirming to operator. +7. **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/`) if the peer should write to our workspace. +8. **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. +9. **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. +10. **Don't silently paraphrase.** Relay the peer's actual response. If long, chunk it. Call out unverified claims. +11. **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. + +## Verification Checklist + +- [x] First `ask hermes ` returns peer's response showing real tool use +- [x] `session_id:` captured from output line 1 +- [x] Second ask with `--resume` — peer remembers prior turn +- [x] Spot-check: peer reports "wrote file at /path" → I `read_file` /path and confirm content matches \ No newline at end of file