hydra-nano 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,194 +1,122 @@
1
- # Hydra
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/rahul05ranjan/hydra/main/assets/hydra-banner.svg" alt="Hydra: small local models that take the noisy work off your coding agent" width="100%">
3
+ </p>
2
4
 
3
- Small local models that a frontier coding agent (Claude Code, Codex) hands routine repo work to, so the raw output of that work never enters the frontier model's context.
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/hydra-nano"><img src="https://img.shields.io/npm/v/hydra-nano?color=7c5cff&label=npm" alt="npm version"></a>
7
+ <a href="https://huggingface.co/rahul05ranjan/hydra-nano-git"><img src="https://img.shields.io/badge/model-Hydra%20Nano%20v2-22d3ee" alt="Hydra Nano v2 on Hugging Face"></a>
8
+ <img src="https://img.shields.io/badge/runs-100%25%20local-5eead4" alt="Runs 100% local">
9
+ <img src="https://img.shields.io/badge/node-%E2%89%A518-3c873a" alt="Node 18 or later">
10
+ <a href="https://github.com/rahul05ranjan/hydra/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
11
+ </p>
4
12
 
5
- Hydra version 0.1 provides five specialist heads powered by the local **Hydra Nano** model family (`hydra-nano:git`, `hydra-nano:os`, `hydra-nano:test`, `hydra-nano:read`, `hydra-nano:files`), also hosted on Hugging Face at [`rahul05ranjan/hydra-nano-git`](https://huggingface.co/rahul05ranjan/hydra-nano-git):
6
- 1. **git head** (`hydra-nano:git`): inspects repository history, branch sync, diffs, blame, and status.
7
- 2. **files head** (`hydra-nano:files`): fast definition-ranked code search without cluttering context.
8
- 3. **read head** (`hydra-nano:read`): extracts symbol definitions (functions, classes, methods), line ranges, or structural definition outlines across TypeScript, JavaScript, Python, Rust, and Go without dumping whole multi-thousand-line files into context.
9
- 4. **test & logs head** (`hydra-nano:test`): runs test and compiler suites (`npm test`, `pytest`, `cargo test`, `tsc`), strips framework noise and `node_modules` stacks, and returns verbatim failure diffs and source lines with the command's exit code.
10
- 5. **os head** (`hydra-nano:os`): read-only system inspection (ports, processes, disk, system info) with strict secret-blocking guardrails.
13
+ <p align="center">
14
+ <b>Your coding agent reads every line of every test log, git diff and process list it runs.<br>Hydra reads them on your machine and hands back only the lines that matter.</b>
15
+ </p>
11
16
 
12
- ## How a call works
17
+ ---
13
18
 
14
- ```
15
- agent ── hydra ask --head git "<question>" ──────▶ git head model picks probes from a menu, code runs them
16
- agent ── hydra ask --head files "<question>" ────▶ files head model picks search terms, code searches
17
- agent ── hydra read <file> [symbol|--lines] ─────▶ read head pinpoints symbol/lines, extracts outline
18
- agent ── hydra run -- <test/build-command> ──────▶ test head runs locally, digests failures & diffs
19
- agent ◀── verbatim evidence lines ──────────────── model picks lines; the agent interprets them
20
- ```
21
-
22
- - **Read-only and safe**: The model never writes an arbitrary command. It picks from fixed menus or digests command output.
23
- - **Verbatim evidence**: Results are the command's own text lines, never a paraphrase.
24
- - **Complete lists**: Git status, history, branches, remotes, stashes, tags and worktrees retain every returned record. Explicit commit counts override the model's count (maximum 50).
25
- - **Pass-through**: Output under 800 characters is handed over whole with zero model calls.
26
- - **Preserved exit codes**: `hydra run -- <cmd>` preserves `<cmd>`'s exit code, so agents know immediately if a build or test failed.
27
-
28
- On Windows, OS inspection uses fixed read-only netstat, tasklist and PowerShell CIM commands, plus Node system/memory APIs. Process output is capped at 30 records and is not sorted by CPU usage. Failed probes are marked in the returned evidence. Code search ranks current source ahead of archived evaluation results while keeping those results searchable.
29
-
30
- ## Try it
31
-
32
- Needs Node 18 or later and [llama.cpp](https://github.com/ggml-org/llama.cpp) (the default backend). [Ollama](https://ollama.com/download) is optional; select it with `HYDRA_BACKEND=ollama`.
33
-
34
- ### Run without Ollama
35
-
36
- **Hydra Nano v2 (2026-10-14):** the installer pins the Q8_0 GGUF of a full-parameter Git fine-tune at Hub revision `8129125f` (SHA-256 `6d6542c8…`). With the current code it solves **963/1,000 sealed Git tasks (96.3%; 96.17% usage-weighted)** (927/1,000 with the code validated on Kaggle) and, on 1,000-task held-out sets, Files 98.0%, Read 96.1%, Test 94.4% and OS 100% (synthetic probe outputs). See the [model card](https://huggingface.co/rahul05ranjan/hydra-nano-git) and the receipts in [`evals/results/suite/v2-6d6542c8`](evals/results/suite/v2-6d6542c8) and [`final-git-validation`](evals/results/suite/final-git-validation/SUMMARY.json). The previous v1 artifact (87.4% sealed) stays at Hub revision `ff1c723f`.
37
-
38
- `train/publish_release.py` publishes a validated release: it checks the checkpoint and GGUF against the frozen validation receipt, uploads them with the model cards in [`hub/`](hub), and pins `runtime-manifest.json` to the new revision. The GGUF is reproducible with llama.cpp `convert_hf_to_gguf.py` at commit `a05e58c` (`--outtype q8_0`). Safetensors files cannot be passed directly to `llama-server`.
39
-
40
- Start the server in one terminal:
41
-
42
- ```powershell
43
- llama-server -m C:\models\hydra-nano-git.gguf --alias hydra-nano:git --host 127.0.0.1 --port 8080 -c 8192
44
- ```
45
-
46
- In another PowerShell terminal:
47
-
48
- ```powershell
49
- $env:HYDRA_BACKEND = 'llamacpp'
50
- $env:HYDRA_LLAMA_URL = 'http://127.0.0.1:8080'
51
- $env:HYDRA_MODEL = 'hydra-nano:git'
52
- node skills/hydra/scripts/hydra.mjs doctor
53
- node skills/hydra/scripts/hydra.mjs ask --head git "which branch am I on?"
54
- ```
55
-
56
- On macOS/Linux, set the same variables with `export HYDRA_BACKEND=llamacpp`, `export HYDRA_LLAMA_URL=http://127.0.0.1:8080` and `export HYDRA_MODEL=hydra-nano:git`.
57
-
58
- The URL is the server root, without `/v1`. `HYDRA_MODEL` must match the server alias; setting it sends all model-backed heads to that one model and does not create specialist weights. Without this override, Hydra requests its existing per-head model names; your server must serve those aliases. `hydra doctor` verifies the effective configured names. `hydra setup` with this backend prints instructions without installing or starting anything.
59
-
60
- Hydra uses llama.cpp's [schema-constrained chat API](https://github.com/ggml-org/llama.cpp/tree/master/tools/server#post-v1chatcompletions-openai-compatible-chat-completions-api), disables thinking, and limits replies to 512 tokens. The model still chooses fixed read-only probes. HTTP fixture tests and native Windows Q8 inference/bootstrap checks pass; sealed accuracy on the converted runtime is tracked separately.
19
+ ## Why Hydra
61
20
 
62
- ### Run with Ollama
21
+ Frontier coding agents pay for every token of tool output: a failing test suite, a long `git log`, a `ps` dump. Most of it is noise, and all of it fills the context window.
63
22
 
64
- Set `HYDRA_BACKEND=ollama` first (`$env:HYDRA_BACKEND = 'ollama'` in PowerShell; `export HYDRA_BACKEND=ollama` in Bash).
23
+ Hydra puts small specialist models on your own machine between the agent and that output:
65
24
 
66
- ```bash
67
- # 1. Download the small local model (1.2 GB)
68
- node skills/hydra/scripts/hydra.mjs setup
69
-
70
- # 2. Ask questions about any repo
71
- cd <any git repository>
72
- node <path>/skills/hydra/scripts/hydra.mjs ask "who changed package.json most recently and why?"
73
-
74
- # 3. Slice files, symbols, or show structural outlines
75
- node <path>/skills/hydra/scripts/hydra.mjs read src/index.ts handleRequest
76
- node <path>/skills/hydra/scripts/hydra.mjs read src/server.ts --lines 20-50
77
- node <path>/skills/hydra/scripts/hydra.mjs read src/config.py --outline
25
+ - **Less context, same answer.** A `pnpm test` run that printed ~13,807 tokens reached the agent as ~57: the failing test, the assertion diff and the source line.
26
+ - **Nothing leaves your machine.** The model runs locally on llama.cpp. Hydra's telemetry stays in `~/.hydra/ledger.jsonl`.
27
+ - **Read-only by design.** The model never writes a shell command. It picks from fixed, read-only menus; code builds and runs the command.
28
+ - **Verbatim, not paraphrased.** Hydra returns the command's own lines, so the agent sees evidence, not a summary.
78
29
 
79
- # 4. Run test and build commands through hydra
80
- node <path>/skills/hydra/scripts/hydra.mjs run -- npm test
81
- node <path>/skills/hydra/scripts/hydra.mjs run -- pytest
82
-
83
- # 5. View token savings
84
- node <path>/skills/hydra/scripts/hydra.mjs stats
85
- ```
86
-
87
- ## Install as a skill
88
-
89
- Everything an agent needs is inside `skills/hydra`:
30
+ ## Quick start
90
31
 
91
32
  ```bash
92
- npx skills add rahul05ranjan/hydra --skill hydra
33
+ npx hydra-nano
93
34
  ```
94
35
 
95
- This installs the skill instructions and bundled Node CLI. It does **not** install `llama-server`, download weights or start inference: the [skills installer](https://github.com/vercel-labs/skills) copies/links skill files, and [install lifecycle hooks](https://github.com/vercel-labs/skills/issues/1155) remain a feature request. The bundled CLI can run `read` and `search` immediately; model-backed calls need the runtime described above.
36
+ One command installs everything: a pinned llama.cpp runtime, the ~812 MB Hydra Nano model (SHA-256 checked), the Hydra skill, and the integration for each coding tool it finds.
96
37
 
97
- A complete bootstrap is provided separately:
38
+ Want the `hydra` command in your own terminal too?
98
39
 
99
40
  ```bash
100
- npx github:rahul05ranjan/hydra
41
+ npm install -g hydra-nano
42
+ hydra doctor
101
43
  ```
102
44
 
103
- It installs the skill globally for every agent it finds (Claude Code if `~/.claude` exists, Codex if `~/.codex` exists; name others with `--agent <id>`), downloads a pinned llama.cpp runtime and the approximately 812 MB Hydra Nano Git Q8 GGUF, verifies SHA-256 hashes, and writes configuration under `~/.hydra`. Node 18+ with npm is required. Windows x64/arm64 CPU, macOS x64/arm64, and Ubuntu-compatible Linux x64/arm64 runtime artifacts are supported. Windows has live verification; other platform packages have not been exercised here.
104
-
105
- For Claude Code the bootstrap also registers two Claude Code hooks in `~/.claude/settings.json`, because an agent that merely has the skill rarely uses it. A `PreToolUse` hook turns a direct test or build run (npm/pnpm/yarn test or build, jest, vitest, pytest, unittest, `node --test`, cargo, go, gradle, mvn, dotnet, make test) back with the `hydra run --` command to use instead; prefixing a command with `HYDRA_RAW=1` lets the raw log through. A `SessionStart` hook tells each new session to ask Hydra about git history and to keep Read and Grep for source files, where Hydra does not save tokens. Other settings are kept, the original file is saved as `settings.json.before-hydra`, and an invalid settings file is left untouched. Skip the hooks with `--no-hooks`; manage them later with `hydra hooks install|remove|status`.
106
-
107
- The first bootstrap shares the Git model across all model-backed heads; it does not download five specialist weights. Runtime downloads and configuration stay outside agent skill folders, so skill updates retain them. Hydra starts its managed server on `127.0.0.1:11435` when needed; it stays running until `hydra stop` or a reboot. Run the installed `scripts/hydra.mjs doctor` to check readiness. `HYDRA_HOME` changes the data/config directory; explicit backend, URL and model environment variables override managed defaults.
45
+ Requires Node 18+. Runtime packages for macOS, Linux and Windows on x64 and arm64; verified live on macOS and Windows. Prefer GitHub? `npx github:rahul05ranjan/hydra` installs the same thing.
108
46
 
109
- From a local checkout, run `node skills/hydra/scripts/install.mjs`. After skill-only installation, run the installed `scripts/install.mjs --runtime-only`. The code and immutable GGUF are published. The remote bootstrap was verified on Windows on 2026-10-12, including installed-skill readiness and model hash verification; see [the receipt](evals/results/remote-bootstrap-windows.json).
47
+ ## Works with your coding tools
110
48
 
111
- For Kaggle evaluation, put `KAGGLE_USERNAME` and `KAGGLE_API_TOKEN` in root `.env` using [`.env.example`](.env.example). `train/kaggle/control.py` loads that file, prepares private frozen inputs, and separates upload from GPU execution. `run --approved` is only for an explicitly approved 30-minute evaluation; the kernel trains nothing, verifies source/model hashes, and rejects CPU fallback. Tokens are never embedded in the uploaded source bundle.
49
+ | Tool | How Hydra joins | Status |
50
+ |---|---|---|
51
+ | **Claude Code** | Skill + hooks that route test/build runs and git questions | Tested |
52
+ | **Codex** | Skill + MCP server + `PreToolUse` hook | MCP listed by Codex; trust the hook with `/hooks` |
53
+ | **OpenCode** | MCP server + plugin | MCP connects |
54
+ | **Cursor** | MCP server + `beforeShellExecution` hook | Preview |
55
+ | **Gemini CLI** | MCP server + `BeforeTool` hook | Preview |
56
+ | **Antigravity** | Skill + MCP server | Preview |
112
57
 
113
- Or copy `skills/hydra` directly to your agent's skills directory:
114
- - Claude Code: `~/.claude/skills/hydra`
115
- - Antigravity: `~/.gemini/config/skills/hydra`
58
+ Any other tool that speaks MCP can use Hydra too: point it at `hydra mcp` (stdio).
116
59
 
117
- ## Evaluations & Benchmarks
60
+ Hydra never overwrites your settings. Every file it edits is backed up once as `<file>.before-hydra`, only Hydra's own entries change, and `hydra agents remove` takes them out again.
118
61
 
119
- ### Strict comparison against targeted commands
62
+ ## What it does
120
63
 
121
- `npm run eval:strict` creates an isolated development repository, executes 12 tasks through concise direct commands and Hydra, and checks complete source slices, file sets, ordered commit records, failure/pass facts and exit codes. It uses no sealed `git1` inputs. The latest run passed **12/12 on both routes**, but Hydra returned **866 estimated tokens versus 460** for targeted commands (**88.3% more output**). These are character-count estimates and do not measure actual frontier-agent usage.
64
+ | Head | Ask it | It returns |
65
+ |---|---|---|
66
+ | **git** | "what changed in the last commit?", "who last touched `billing.js`?" | The matching commits, diff hunks or blame lines |
67
+ | **test** | `hydra run -- npm test` (pytest, cargo, go, tsc, gradle, ...) | Failing test names, assertion diffs and source locations, with the original exit code |
68
+ | **os** | "what is listening on port 3000?" | The matching ports, processes or disk lines, with secrets blocked |
69
+ | **read** | `hydra read src/server.ts handleRequest` | One symbol, a line range or an outline |
70
+ | **files** | `hydra search parseConfig` | Definition-ranked matches |
122
71
 
123
- The older whole-file trial is retained as a historical result; its weak substring score is not answer accuracy. See `TRACKER.md` for the audit.
72
+ Through MCP, Hydra offers the three where it pays most: `hydra_git`, `hydra_run` and `hydra_os`.
124
73
 
125
- The first actual Claude Code A/B run used `claude-opus-5-5`, with **6/6 correct in each arm**. Direct tools consumed **118,215** reported input-plus-output tokens and Hydra **112,431** (**4.89% fewer**). Trace review found that the harness denied a legitimate direct `git diff-tree` attempt; permission-denial-free pairs instead consumed **2.63% more tokens with Hydra**. This six-task single-run fixture does not establish general savings. The whitelist and workspace isolation have been corrected for future runs. The [frontier report](evals/results/frontier/2026-10-10T11-08-52-051Z/SUMMARY.md) includes the limitations and links to full traces.
74
+ ## Benchmarks
126
75
 
127
- `npm run eval:frontier:prepare` prepares a six-task Claude Code A/B trial without model calls. `npm run eval:frontier` launches 12 fresh sessions after user approval, with direct tools versus explicit Hydra delegation. It records exact structured-answer checks, actual reported input/output tokens including cached input, cost, tool errors, tool calls, turns and elapsed time. Sessions use isolated copies of the fixture, the same configured frontier model, a frozen Hydra runtime, a $0.50 budget setting per session and a 10-minute overall time limit. This measures repository-inspection tasks in a controlled fixture; spontaneous skill adoption and full software-development workloads require further trials.
76
+ **Hydra Nano v2**: a full-parameter fine-tune of a 0.8B model, one set of weights for every head, run as a Q8_0 GGUF.
128
77
 
129
- `node evals/trial/analyze_frontier.mjs <run-directory>` independently checks saved answers against gold and reports overall and permission-denial-free pairs without new model calls. Fixture workspaces live under `~/.hydra/trials/frontier`, outside this project.
78
+ | Set | Tasks | Solved |
79
+ |---|---|---|
80
+ | Git (sealed test set, never used for training) | 1,000 | **96.3%** |
81
+ | Files (held out) | 1,000 | **98.0%** |
82
+ | Read (held out) | 1,000 | **96.1%** |
83
+ | Test logs (second held-out set) | 1,000 | **97.5%** |
84
+ | OS (held out, synthetic probe outputs) | 1,000 | **100%** |
85
+ | Test-log benchmark across 8 frameworks | 20 | **19/20**, 47% fewer tokens |
130
86
 
131
- ### Repeated real-source and natural-adoption trial
87
+ Receipts: [model card](https://huggingface.co/rahul05ranjan/hydra-nano-git) and [`evals/results`](https://github.com/rahul05ranjan/hydra/tree/main/evals/results).
132
88
 
133
- `npm run eval:real:prepare` freezes four function-extraction tasks from Vasuki and Dhruv CLI, independent parser gold, source hashes and the production skill. The 69-file source corpus has 9,375 lines. Three fresh sessions per task compare native tools, Hydra available without a usage instruction, and explicit Hydra delegation: **36 sessions** with a **20-minute** deadline and **$18** total configured API-cost-equivalent budget. This tests source inspection and complete function extraction.
89
+ **Where Hydra does not help, measured.** In a controlled trial of 36 Claude Code sessions extracting functions from real source code, forcing that work through Hydra used **72% more tokens** than the agent's own tools. So Hydra leaves source reading and search to the agent, and routes only the noisy work: test and build logs, git history, system inspection. Full method and numbers in [evaluations](https://github.com/rahul05ranjan/hydra/blob/main/docs/evaluations.md).
134
90
 
135
- After explicit run approval, use `npm run eval:real -- --prepared <directory>`. The runner checks startup skill discovery, unchanged sources and models, and permission errors; it preserves every factual failure. `node evals/trial/analyze_real_repo.mjs <directory>` audits saved traces without model calls. See the [predeclared protocol](evals/trial/REAL_REPO_PROTOCOL.md) for arm ordering, exact correctness and exploratory task-cluster intervals.
91
+ ## Commands
136
92
 
137
- The approved run completed **36/36 sessions**, with **12/12 correct in every arm** and no protocol violations. Native tools used **387,115** total reported tokens, skill availability **391,296 (+1.08%)**, and explicit Hydra delegation **667,303 (+72.38%)**. Claude chose Hydra in **0/12** available sessions and **12/12** forced sessions. Runtime was **513.9 seconds** and reported API-cost equivalent **$2.7095**. Token savings were absent on these four tasks. Trace audit found Python symbol slices that included unrelated code or omitted part of the function; native fallback restored complete answers. See the [full report](evals/results/real-repo/2026-10-10T11-57-49-798Z/SUMMARY.md) and [evidence audit](evals/results/real-repo/2026-10-10T11-57-49-798Z/AUDIT.md).
138
-
139
- ### 1. `evals/git1` (1000 tasks, sealed git test set)
140
-
141
- 50 generated repositories, 20 kinds of question, 10 phrasings each. Verified by git gold facts and usage weighting.
142
-
143
- - **Baseline (`qwen3.5:0.8b`)**: 70% solved (69% weighted by real usage), plan accuracy 74%, 10 errors.
144
- - **Improved with JSON prompt grounding**: **77% solved (76% weighted)**, plan accuracy **80%**, **0 errors**, runtime dropped from 21.2 min to **3.5 min**.
145
- - `staged_files`: 0% ➔ 100% (+100 points)
146
- - `message_search`: 4% ➔ 72% (+68 points)
147
- - `commit_files`: 46% ➔ 72% (+26 points)
148
-
149
- Full results in `evals/results/git1.qwen3.5-0.8b.json`.
150
-
151
- ### 2. `evals/test_logs` (20 cross-language tasks)
152
-
153
- Benchmarks failure extraction and noise reduction across Vitest, Jest, Pytest, Cargo, TSC, Go, node:test, and Mocha.
154
-
155
- - **Solved**: **100% (20/20)**
156
- - **Test Name Recall**: **100%**
157
- - **File / Line Location Recall**: **100%**
158
- - **Assertion Diff Recall**: **100%**
159
- - **Cleanliness**: **100%** (zero framework or `node_modules` stack frame pollution)
160
- - **Token Savings**: **49.6% net token reduction** overall, and **96.4% to 96.7%** on verbose test suites.
161
- - **Runtime**: **3.2s** across all 20 tasks.
93
+ ```bash
94
+ hydra ask "which branch am I on and what changed?" # git, files or os, routed by the question
95
+ hydra run -- pytest -q # a test or build run, digested
96
+ hydra os "what is using port 8080?" # read-only system inspection
97
+ hydra read src/app.py create_app # one symbol, a line range or an outline
98
+ hydra stats # tokens kept out of context
99
+ hydra improve --since 2026-10-17 # what the local telemetry says to improve
100
+ hydra agents status # Hydra in each coding tool
101
+ hydra doctor # is the local model ready?
102
+ ```
162
103
 
163
- Full results in `evals/results/test_logs.qwen3.5-0.8b.json`.
104
+ Need the raw log for one run? Prefix it with `HYDRA_RAW=1` and the hook lets it through.
164
105
 
165
- ### 3. `evals/read` (20 cross-language tasks)
106
+ ## Privacy and control
166
107
 
167
- Benchmarks targeted symbol extraction, line range slicing, and file outlines across TypeScript, JavaScript, Python, Rust, and Go to address the #1 token sink (75.5% of tool tokens).
108
+ - **Local only.** The model, the runtime and the telemetry live in `~/.hydra`. Nothing is sent anywhere.
109
+ - **Turn telemetry off** with `HYDRA_TELEMETRY=0`.
110
+ - **Skip parts of the install** with `--no-agents` (no other tools), `--no-hooks` (no hooks) or `--runtime-only` (no skill).
111
+ - **Uninstall cleanly.** `hydra agents remove` and `hydra hooks remove` undo every integration and restore your settings; `hydra stop` stops the local server; deleting `~/.hydra` removes the runtime, the model and the telemetry.
168
112
 
169
- - **Solved**: **100% (20/20)**
170
- - **Target Line Recall**: **100%**
171
- - **Cleanliness**: **100%** (zero unrelated function/code leak)
172
- - **Token Savings**: **48.1% net token reduction** overall, and **77% to 92%** on targeted line and symbol slices.
173
- - **Runtime**: **0.01s** (instantaneous local extraction).
113
+ ## Learn more
174
114
 
175
- Full results in `evals/results/read.json`.
115
+ - [How Hydra works](https://github.com/rahul05ranjan/hydra/blob/main/docs/how-it-works.md): heads, backends (llama.cpp or Ollama), telemetry, every install path and the code layout.
116
+ - [Evaluations and trials](https://github.com/rahul05ranjan/hydra/blob/main/docs/evaluations.md): how each number above was measured.
117
+ - [What real use teaches](https://github.com/rahul05ranjan/hydra/blob/main/IMPROVEMENTS.md): the open and fixed findings from Hydra's own telemetry.
118
+ - [Hydra Nano on Hugging Face](https://huggingface.co/rahul05ranjan/hydra-nano-git).
176
119
 
177
- ## Layout
120
+ ## License
178
121
 
179
- - `skills/hydra/SKILL.md`: tells an agent when to delegate and how to read the result.
180
- - `skills/hydra/scripts/hydra.mjs`: the CLI.
181
- - `skills/hydra/scripts/lib/`:
182
- - `runtime.mjs`: selectable Ollama/llama.cpp API client and per-head model configuration.
183
- - `route.mjs`: route questions between heads.
184
- - `git.mjs`: git head probes and plan step.
185
- - `files.mjs`: search and walk head.
186
- - `read.mjs`: file slice and symbol reader head.
187
- - `test_logs.mjs`: test runner and build log reader head.
188
- - `answer.mjs`: verbatim line selection and lead generation.
189
- - `ollama.mjs`: compatibility exports for existing evaluation callers.
190
- - `ledger.mjs`: local token accounting ledger.
191
- - `test/`: unit test suite (`npm test`).
192
- - `evals/git1/`: 1000-task sealed test set for git head.
193
- - `evals/test_logs/`: 20-task cross-language benchmark for test and build logs.
194
- - `evals/read/`: 20-task cross-language benchmark for file slice reading.
122
+ MIT. See [LICENSE](https://github.com/rahul05ranjan/hydra/blob/main/LICENSE).
package/package.json CHANGED
@@ -1,16 +1,26 @@
1
1
  {
2
2
  "name": "hydra-nano",
3
- "version": "0.1.0",
4
- "description": "Small local models that Claude Code and Codex hand routine read-only repo work to: git questions, test and build digests, file slicing, code search and OS inspection",
3
+ "version": "0.2.0",
4
+ "description": "Small local models that take the noisy work off your coding agent: git history, test and build digests and OS inspection run on your machine, and only the lines that matter reach Claude Code, Codex, Cursor, OpenCode, Gemini CLI or Antigravity. Skill, MCP server and hooks.",
5
5
  "keywords": [
6
6
  "claude-code",
7
7
  "codex",
8
+ "cursor",
9
+ "opencode",
10
+ "gemini-cli",
11
+ "antigravity",
12
+ "mcp",
13
+ "mcp-server",
8
14
  "agent-skill",
15
+ "ai-agents",
16
+ "coding-agent",
9
17
  "local-llm",
10
18
  "llama.cpp",
19
+ "token-saving",
20
+ "context-window",
11
21
  "git",
12
22
  "test-runner",
13
- "token-saving"
23
+ "developer-tools"
14
24
  ],
15
25
  "license": "MIT",
16
26
  "author": "rahul05ranjan",
@@ -1,22 +1,52 @@
1
1
  #!/usr/bin/env node
2
- // Claude Code PreToolUse hook (Bash): a test or build run goes through `hydra run --`, whose digest
3
- // keeps failing test names, assertion diffs and source locations and drops the rest of the log.
2
+ // Shell-command hook: a test or build run goes through `hydra run --` (or the hydra_run MCP tool),
3
+ // whose digest keeps failing test names, assertion diffs and source locations and drops the rest.
4
4
  // Prefix a command with HYDRA_RAW=1 when the raw log is really needed.
5
+ // --format picks the tool's hook protocol:
6
+ // claude, codex PreToolUse; command at tool_input.command; deny via hookSpecificOutput.
7
+ // gemini BeforeTool on run_shell_command; deny via {"decision": "deny", "reason"}.
8
+ // cursor beforeShellExecution; command at command; must always answer with a permission.
9
+ // Each decision goes to the local ledger with the hook's own time, for `hydra improve`; a command
10
+ // that simply passes is recorded without its text.
5
11
  import path from 'node:path';
6
12
  import { fileURLToPath } from 'node:url';
7
- import { testRun } from '../lib/claude_hooks.mjs';
13
+ import { rawBypass, testRun } from '../lib/claude_hooks.mjs';
14
+ import { record } from '../lib/ledger.mjs';
8
15
 
9
16
  const hydra = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'hydra.mjs');
17
+ const flag = process.argv.indexOf('--format');
18
+ const format = flag >= 0 ? process.argv[flag + 1] : 'claude';
19
+
20
+ const answers = {
21
+ claude: { pass: null, deny: reason => ({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } }) },
22
+ gemini: { pass: { decision: 'allow' }, deny: reason => ({ decision: 'deny', reason }) },
23
+ cursor: { pass: { permission: 'allow' }, deny: reason => ({ permission: 'deny', agent_message: reason,
24
+ user_message: 'Hydra sent this test/build run through hydra run to keep the full log out of context.' }) },
25
+ };
26
+ answers.codex = answers.claude;
27
+ const answer = answers[format] ?? answers.claude;
28
+
10
29
  let input = '';
11
30
  process.stdin.on('data', chunk => { input += chunk; }).on('end', () => {
12
- let command = '';
13
- try { command = JSON.parse(input).tool_input?.command ?? ''; } catch { return; }
31
+ let parsed;
32
+ try { parsed = JSON.parse(input) ?? {}; } catch {
33
+ if (answer.pass) process.stdout.write(JSON.stringify(answer.pass));
34
+ return;
35
+ }
36
+ const command = String((format === 'cursor' ? parsed.command : parsed.tool_input?.command) ?? '');
37
+ const session = parsed.session_id ?? parsed.conversation_id;
38
+ const event = (decision, withCommand = true) => ({ ts: new Date().toISOString(), kind: 'hook', hook: 'route-tests',
39
+ ...(format === 'claude' ? {} : { agent: format }), decision, ...(withCommand ? { command: command.slice(0, 300) } : {}),
40
+ ...(session ? { session } : {}), hookMs: Math.round(process.uptime() * 1000) });
14
41
  const run = testRun(command);
15
- if (!run) return;
16
- process.stdout.write(JSON.stringify({ hookSpecificOutput: {
17
- hookEventName: 'PreToolUse', permissionDecision: 'deny',
18
- permissionDecisionReason: `Test/build runs go through Hydra so the full log stays out of context. Run it as:\n` +
19
- ` node "${hydra}" run -- ${run}\n(keep any cd or env prefix before it). Hydra returns the failing tests, ` +
20
- `assertion diffs and source locations verbatim, or a short pass summary. If you truly need the raw log, ` +
21
- `prefix the original command with HYDRA_RAW=1.` } }));
42
+ if (!run) {
43
+ record(rawBypass(command) ? event('raw') : event('pass', false));
44
+ if (answer.pass) process.stdout.write(JSON.stringify(answer.pass));
45
+ return;
46
+ }
47
+ record({ ...event('deny'), run });
48
+ process.stdout.write(JSON.stringify(answer.deny(`Test/build runs go through Hydra so the full log stays out of context. Run it as:\n` +
49
+ ` node "${hydra}" run -- ${run}\n${format === 'claude' ? '' : 'or call the hydra_run tool with that command. '}` +
50
+ `(Keep any cd or env prefix before it.) Hydra returns the failing tests, assertion diffs and source locations ` +
51
+ `verbatim, or a short pass summary. If you truly need the raw log, prefix the original command with HYDRA_RAW=1.`)));
22
52
  });
@@ -1,13 +1,29 @@
1
1
  #!/usr/bin/env node
2
- // Claude Code SessionStart hook: tells the agent where Hydra pays off, so it uses it without being asked.
2
+ // Session-start hook: tells the agent where Hydra pays off, so it uses it without being asked.
3
+ // --format claude or codex answers with hookSpecificOutput; cursor with additional_context.
4
+ // The session start goes to the local ledger, so `hydra improve` can tell how many sessions used Hydra.
3
5
  import path from 'node:path';
4
6
  import { fileURLToPath } from 'node:url';
7
+ import { record } from '../lib/ledger.mjs';
5
8
 
6
9
  const hydra = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'hydra.mjs');
7
- process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext:
8
- `Hydra is installed: it runs noisy read-only commands on a small local model and returns only the lines that matter.\n` +
9
- `- For git history, diff, blame or "what changed" questions, first run: node "${hydra}" ask --head git "<question>" ` +
10
+ const flag = process.argv.indexOf('--format');
11
+ const format = flag >= 0 ? process.argv[flag + 1] : 'claude';
12
+ const mcp = format === 'claude' ? '' : ' (or the hydra_git / hydra_run MCP tools)';
13
+ const guide = `Hydra is installed: it runs noisy read-only commands on a small local model and returns only the lines that matter.\n` +
14
+ `- For git history, diff, blame or "what changed" questions, first run: node "${hydra}" ask --head git "<question>"${mcp} ` +
10
15
  `from the repository, instead of reading git log, git diff or git show output yourself. Use plain git for writes ` +
11
16
  `and for short checks such as git status --short.\n` +
12
17
  `- A hook sends test and build runs through: node "${hydra}" run -- <command>\n` +
13
- `- Keep using Read and Grep for source files; Hydra does not save tokens there.` } }));
18
+ `- Keep using your own file reading and search for source files; Hydra does not save tokens there.`;
19
+
20
+ let input = '';
21
+ process.stdin.on('data', chunk => { input += chunk; }).on('end', () => {
22
+ let start = {};
23
+ try { start = JSON.parse(input) ?? {}; } catch { /* The tool always sends JSON; record what there is. */ }
24
+ const session = start.session_id ?? start.conversation_id;
25
+ record({ ts: new Date().toISOString(), kind: 'session', ...(format === 'claude' ? {} : { agent: format }),
26
+ ...(session ? { session } : {}), ...(start.source ? { source: start.source } : {}) });
27
+ process.stdout.write(JSON.stringify(format === 'cursor' ? { additional_context: guide }
28
+ : { hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: guide } }));
29
+ });
@@ -10,11 +10,12 @@ import * as git from './lib/git.mjs';
10
10
  import * as test_logs from './lib/test_logs.mjs';
11
11
  import * as read from './lib/read.mjs';
12
12
  import * as osHead from './lib/os.mjs';
13
- import { record, stats, tokens } from './lib/ledger.mjs';
13
+ import { findings, metered, record, stats, tokens } from './lib/ledger.mjs';
14
14
  import { chat, hasModel, BACKEND, modelFor, Unavailable } from './lib/runtime.mjs';
15
15
  import { route } from './lib/route.mjs';
16
16
  import { stopLocal } from './lib/local.mjs';
17
17
  import { hookStatus, installHooks, removeHooks } from './lib/claude_hooks.mjs';
18
+ import * as integrations from './lib/integrations.mjs';
18
19
 
19
20
  const HEADS = { git, files, test: test_logs, read, os: osHead };
20
21
 
@@ -25,10 +26,14 @@ const USAGE = `usage:
25
26
  hydra run [--head test] -- <command...> run a command and digest test/build output
26
27
  hydra os [question...] inspect ports, processes, disk, system
27
28
  hydra stats token accounting for past calls
29
+ hydra improve [--since <date>] [--json] what the local telemetry says to improve
28
30
  hydra doctor check that the local model is ready
29
31
  hydra setup show runtime setup instructions
30
32
  hydra stop stop Hydra's managed llama-server
31
- hydra hooks install|remove|status Claude Code hooks that route tests and git questions here`;
33
+ hydra hooks install|remove|status Claude Code hooks that route tests and git questions here
34
+ hydra agents install|remove|status [tool...] Hydra's MCP server and hooks in codex, cursor, opencode,
35
+ gemini-cli and antigravity (default: every tool found)
36
+ hydra mcp run the MCP server on stdio`;
32
37
 
33
38
  function fail(code, message) {
34
39
  console.error(message);
@@ -47,14 +52,14 @@ async function ask(args) {
47
52
  if (head === 'git' && !repo) fail(1, 'hydra: the git head needs a git repository, and this directory is not in one');
48
53
 
49
54
  const started = Date.now();
50
- const headChat = opts => chat({ ...opts, model: modelFor(head) });
51
- const sections = await HEADS[head].gather(question, cwd, headChat);
52
- const { text, rawChars } = await answer(question, sections, headChat);
55
+ const meter = metered(opts => chat({ ...opts, model: modelFor(head) }));
56
+ const sections = await HEADS[head].gather(question, cwd, meter.chat);
57
+ const { text, rawChars } = await answer(question, sections, meter.chat);
53
58
  const raw = tokens(rawChars);
54
59
  const returned = tokens(text.length);
55
60
  const seconds = Number(((Date.now() - started) / 1000).toFixed(1));
56
61
 
57
- record({ ts: new Date().toISOString(), head, question, raw, returned, seconds });
62
+ record({ ts: new Date().toISOString(), head, question, raw, returned, seconds, modelCalls: meter.modelCalls, modelSeconds: meter.modelSeconds });
58
63
  console.log(`${text}\n\n[hydra ${head} · read ~${raw} tok locally, returned ~${returned} tok · ${seconds}s]`);
59
64
  }
60
65
 
@@ -67,12 +72,12 @@ async function runCommand(args) {
67
72
  const stdinData = readFileSync(0, 'utf8');
68
73
  if (stdinData.trim()) {
69
74
  const started = Date.now();
70
- const testChat = opts => chat({ ...opts, model: modelFor('test') });
71
- const { text, rawChars, exitCode } = await test_logs.digest({ cmd: 'pipe', out: stdinData, exitCode: 1 }, testChat);
75
+ const meter = metered(opts => chat({ ...opts, model: modelFor('test') }));
76
+ const { text, rawChars, exitCode } = await test_logs.digest({ cmd: 'pipe', out: stdinData, exitCode: 1 }, meter.chat);
72
77
  const raw = tokens(rawChars);
73
78
  const returned = tokens(text.length);
74
79
  const seconds = Number(((Date.now() - started) / 1000).toFixed(1));
75
- record({ ts: new Date().toISOString(), head: 'test', question: 'pipe', raw, returned, seconds });
80
+ record({ ts: new Date().toISOString(), head: 'test', question: 'pipe', raw, returned, seconds, modelCalls: meter.modelCalls, modelSeconds: meter.modelSeconds });
76
81
  console.log(`${text}\n\n[hydra test · read ~${raw} tok locally, returned ~${returned} tok · ${seconds}s]`);
77
82
  if (exitCode !== 0) process.exit(exitCode);
78
83
  return;
@@ -85,17 +90,19 @@ async function runCommand(args) {
85
90
  const cwd = process.cwd();
86
91
  const started = Date.now();
87
92
  const res = await run(prog, progArgs, cwd);
88
- const testChat = opts => chat({ ...opts, model: modelFor('test') });
93
+ const commandSeconds = Number(((Date.now() - started) / 1000).toFixed(1));
94
+ const meter = metered(opts => chat({ ...opts, model: modelFor('test') }));
89
95
  const { text, rawChars, exitCode } = await test_logs.digest({
90
96
  cmd: cmdArgs.join(' '),
91
97
  out: res.out,
92
98
  err: res.err,
93
99
  exitCode: res.exitCode,
94
- }, testChat);
100
+ }, meter.chat);
95
101
  const raw = tokens(rawChars);
96
102
  const returned = tokens(text.length);
97
103
  const seconds = Number(((Date.now() - started) / 1000).toFixed(1));
98
- record({ ts: new Date().toISOString(), head: 'test', question: cmdArgs.join(' '), raw, returned, seconds });
104
+ record({ ts: new Date().toISOString(), head: 'test', question: cmdArgs.join(' '), raw, returned, seconds,
105
+ commandSeconds, exit: exitCode, modelCalls: meter.modelCalls, modelSeconds: meter.modelSeconds });
99
106
  console.log(`${text}\n\n[hydra test · read ~${raw} tok locally, returned ~${returned} tok · ${seconds}s]`);
100
107
  if (exitCode !== 0) process.exit(exitCode);
101
108
  }
@@ -153,14 +160,14 @@ async function readCommand(args) {
153
160
  async function osCommand(args) {
154
161
  const question = args.join(' ').trim() || 'system overview';
155
162
  const started = Date.now();
156
- const osChat = opts => chat({ ...opts, model: modelFor('os') });
157
- const sections = await osHead.gather(question, process.cwd(), osChat);
158
- const { text, rawChars } = await answer(question, sections, osChat);
163
+ const meter = metered(opts => chat({ ...opts, model: modelFor('os') }));
164
+ const sections = await osHead.gather(question, process.cwd(), meter.chat);
165
+ const { text, rawChars } = await answer(question, sections, meter.chat);
159
166
  const raw = tokens(rawChars);
160
167
  const returned = tokens(text.length);
161
168
  const seconds = Number(((Date.now() - started) / 1000).toFixed(1));
162
169
 
163
- record({ ts: new Date().toISOString(), head: 'os', question, raw, returned, seconds });
170
+ record({ ts: new Date().toISOString(), head: 'os', question, raw, returned, seconds, modelCalls: meter.modelCalls, modelSeconds: meter.modelSeconds });
164
171
  console.log(`${text}\n\n[hydra os · read ~${raw} tok locally, returned ~${returned} tok · ${seconds}s]`);
165
172
  }
166
173
 
@@ -218,6 +225,30 @@ Estimate: output size / 4. It counts the output an agent would have read by runn
218
225
  the commands itself, and leaves out the extra turns those commands would have cost.`);
219
226
  }
220
227
 
228
+ function improve(args) {
229
+ const flag = args.indexOf('--since');
230
+ const since = flag >= 0 ? args[flag + 1] : undefined;
231
+ if (flag >= 0 && (!since || Number.isNaN(Date.parse(since)))) fail(1, 'usage: hydra improve [--since <date>] [--json]');
232
+ const report = findings(undefined, { since: since && new Date(since).toISOString() });
233
+ if (args.includes('--json')) return console.log(JSON.stringify(report, null, 2));
234
+ const lines = [`since ${report.since ?? 'the start'}: ${report.calls} calls; ${report.sessions.usingHydra} of ${report.sessions.seen} recorded sessions used Hydra`];
235
+ for (const [head, h] of Object.entries(report.perHead)) {
236
+ lines.push(`${head.padEnd(6)} ${h.calls} calls, saved ~${h.saved} tok (${Math.round(h.savedShare * 100)}%), ` +
237
+ `${h.costMore} cost more than raw, p50 ${h.p50Seconds}s, p90 ${h.p90Seconds}s, ${h.modelCalls} model calls`);
238
+ for (const row of h.costMoreExamples) lines.push(` cost more: ${row.question.slice(0, 80)} (${row.raw} -> ${row.returned})`);
239
+ }
240
+ for (const [agent, a] of Object.entries(report.agents)) {
241
+ lines.push(`agent ${agent}: ${a.calls} calls (${a.viaMcp} through MCP), ${a.denials} runs turned back, ${a.sessions} sessions started`);
242
+ }
243
+ const { denials, followedByHydra, rawBypasses } = report.hooks;
244
+ const { decisions, p50Ms, p90Ms } = report.hooks;
245
+ lines.push(`hooks ${decisions} Bash calls checked (p50 ${p50Ms} ms, p90 ${p90Ms} ms); ${denials} test/build runs turned back, ` +
246
+ `${followedByHydra} followed by a Hydra run within 5 minutes`);
247
+ for (const command of rawBypasses) lines.push(` HYDRA_RAW=1 bypass (check for a false match): ${command.slice(0, 100)}`);
248
+ for (const error of report.errors) lines.push(`error ${error.ts} ${error.command}: ${error.error}`);
249
+ console.log(lines.join('\n'));
250
+ }
251
+
221
252
  async function doctor() {
222
253
  const checks = new Map();
223
254
  const modelStatuses = await Promise.all(
@@ -268,12 +299,38 @@ const commands = {
268
299
  read: () => readCommand(rest),
269
300
  os: () => osCommand(rest),
270
301
  stats: showStats,
302
+ improve: () => improve(rest),
271
303
  doctor,
272
304
  setup,
273
305
  stop: () => console.log(stopLocal() ? 'Hydra local server stopped.' : 'No managed Hydra server is running.'),
274
306
  hooks: () => hooksCommand(rest[0]),
307
+ agents: () => agentsCommand(rest),
308
+ mcp: () => import('./mcp.mjs').then(server => server.serve()),
275
309
  };
276
310
 
311
+ function agentsCommand(args) {
312
+ const [action, ...words] = args;
313
+ const ids = words.filter(word => !word.startsWith('--'));
314
+ if (action === 'status') {
315
+ for (const row of integrations.status()) {
316
+ console.log(`${row.id.padEnd(12)} ${row.present ? 'found ' : 'not found'} mcp ${row.mcp ? 'yes' : 'no '}${'hooks' in row ? ` hooks ${row.hooks ? 'yes' : 'no'}` : ''}`);
317
+ }
318
+ console.log(`claude-code ${hookStatus().length ? 'hooks yes' : 'hooks no'} (skill and hooks; see hydra hooks status)`);
319
+ return;
320
+ }
321
+ if (!['install', 'remove'].includes(action)) fail(1, 'usage: hydra agents install|remove|status [codex cursor opencode gemini-cli antigravity] [--no-hooks]');
322
+ const scripts = action === 'install'
323
+ ? integrations.installSharedScripts(path.dirname(fileURLToPath(import.meta.url))) : integrations.sharedScripts();
324
+ const targets = ids.length ? ids : action === 'install' ? integrations.detected(scripts) : Object.keys(integrations.tools(scripts));
325
+ if (!targets.length) return console.log('No supported coding tool found (codex, cursor, opencode, gemini-cli, antigravity).');
326
+ for (const result of integrations.apply(targets, action, { scripts, hooks: !words.includes('--no-hooks') })) {
327
+ if (result.error) { console.log(`${result.id}: ${result.error}`); continue; }
328
+ console.log(`${result.id}: ${result.changes.length ? result.changes.join('; ') : action === 'install' ? 'nothing changed' : 'nothing to remove'}`);
329
+ for (const message of result.manual) console.log(` ${message.replace(/\n/g, '\n ')}`);
330
+ if (result.note) console.log(` ${result.note}`);
331
+ }
332
+ }
333
+
277
334
  function hooksCommand(action) {
278
335
  const scripts = path.dirname(fileURLToPath(import.meta.url));
279
336
  if (action === 'install') console.log(`Hydra hooks registered in ${installHooks(scripts)}.`);
@@ -286,6 +343,7 @@ try {
286
343
  if (!commands[command]) fail(1, USAGE);
287
344
  await commands[command]();
288
345
  } catch (e) {
346
+ record({ ts: new Date().toISOString(), kind: 'error', command, error: String(e?.message ?? e).slice(0, 300) });
289
347
  if (!(e instanceof Unavailable)) throw e;
290
348
  fail(2, `hydra unavailable: ${e.message}`);
291
349
  }