Documentation

Everything Ichor does, and where it stops.

Ichor watches your coding agent and questions the moment it goes past the job you gave it. There is no task to type — it reads the job from what you already said. Five minutes to a working install, and no account anywhere.

01 · Install

Three commands, and no task to type.

You need Node 20 or newer, Docker running, and a TypeScript or JavaScript repository. Ichor is at its best on Next.js with Prisma, because it can then see your routes and your tables as well as your code.

your-repo
  npm i -g ichor-cli

  ichor init     # installs the agent hooks, registers the tools, writes the stack
  ichor up       # starts HydraDB and MinIO in Docker, bound to 127.0.0.1
  ichor watch    # reads your repo, then follows the conversation

ichor init writes into your repo and merges rather than overwrites — an existing .claude/settings.json, .codex/hooks.json or .mcp.json keeps everything it already had. It also adds .ichor/ to your .gitignore, because that is where Ichor keeps its own state.

The first read happens once: about 19 seconds for 1,362 files, and 2m 21s for the largest repository tested (7,735 files). After that, catching up on your edits takes seconds.

One database per repository. Projects never bleed into each other — that is tested with four loaded at once — but HydraDB cannot index the property Ichor uses to tell them apart, so every extra project makes queries slower. Three share one comfortably. More will not, so ichor init writes a database stack into each repo.
02 · Your first task

There is no command to describe the job.

Open Claude Code or Codex in the repo and work normally. Ichor reads the boundary from the prompt you were going to type anyway.

claude code
you  >  fix the duplicate email crash in vendor onboarding
       [ichor] task set — 14 functions, data: Vendor

       …hours later, same conversation…

you  >  now fix the rounding on billing invoices
       [ichor] new task — 3 functions, data: Invoice

That second line is the part that matters. Nobody runs a CLI command between tasks, so a boundary set at 9am would still be policing vendor code at 2pm while you are deep in billing — questioning every edit until you switch the thing off.

Ask a question and nothing moves. "Where is link expiry enforced?" changes no boundary, because you are not changing anything. ichor status shows what Ichor decided at any point: what is in scope, what it questioned, and what was written anyway.

03 · Switching jobs

What actually moves the boundary.

Every prompt is read against the boundary currently held, and lands in one of four answers. Ambiguity always resolves to change nothing, because a boundary redrawn for no reason is worse than one held a turn too long.

Your promptWhat happens
Names a path or a symbol outright The boundary is redrawn around it. "fix the retry in lib/billing/invoice.ts" — what you pointed at wins over everything else in the sentence.
Names a directory outside the boundary The boundary widens to include it, and keeps what it already had. A word that merely happens to match a folder name can never destroy the real task.
Ordinary words, already inside the boundary Nothing changes. This is most prompts, and it is the correct answer for them.
A wall of common words, or a pasted stack trace Nothing changes. A term matching a hundred and thirty-five files is telling you it is an English word, not where the work is.

ichor start "…" names the task by hand. Detection still reports what it saw, but it will never redraw a boundary you chose yourself.

04 · Reading a challenge

Every challenge cites something real.

When your agent is about to write something outside the job, it is stopped before the file exists, and told why.

the challenge
⚠ ichor: this looks like scope expansion.

  src/app/api/vendors/check-email/route.ts introduces a new POST endpoint at
  /api/vendors/check-email that reaches Vendor, but the task's existing path
  already reaches it:

    POST /api/vendors → createVendor → Vendor.email (unique)

  Why is a separate POST endpoint required?
  If it genuinely is:  ichor justify <file> "<reason>"

A path through your code, a table, a count of callers. Ichor never says "this looks unrelated" without showing what it looked at.

It is not a blocker. Your agent can answer with ichor justify and have the argument weighed, or simply explain and carry on — Ichor asks once per file. A file that was questioned and then written anyway is remembered as exactly that, and is never later quoted back as proof that something else belongs.

VerdictMeaning
EXPECTEDThis is the job. Silent.
CONNECTEDNot the job, but genuinely joined to it — one call away, or working on the same table. Silent.
NOT_JUDGEDNot a file Ichor reads, so it has no opinion. Silent, and honest about why.
SUSPICIOUSOutside the job, with evidence. You get asked.
HUMAN_REVIEWIchor cannot tell. It asks you rather than deciding.
05 · Commands

Running it, and asking it things.

Running it

CommandWhat it does
ichor initInstall the hooks and tools in this repo, and write the database stack.
ichor upStart HydraDB and MinIO locally.
ichor watchRead the codebase and follow the conversation. The usual way to run it.
ichor start "…"Name the task by hand. Detection still reports, but never redraws a boundary you chose.
ichor statusWhat is in scope, what was questioned, what was written anyway.
ichor stopEnd the task and stop watching.
ichor keyStore your own OpenRouter key, or show whether one is set. Optional — see below.
ichor downStop the database, from any directory. --wipe also deletes the graph — that discards every project stored in it, not just this one, and Ichor says so before doing it.

Asking it things

The same answers your agent gets from the MCP tools, in a shell. These work whether or not the workspace is trusted, which the MCP path does not.

CommandWhat it answers
ichor find <words…>Where does this live? A plain description instead of a grep pattern.
ichor impact <symbol>What else is affected if this function or type changes.
ichor callers <symbol>What calls this function, and from which endpoints.
ichor paths <model>How the app reaches a table, and through what. --route narrows it.
ichor check <file>Is this file part of the current task, and why.
ichor justify <file> "…"Argue that a file outside the task is genuinely required, and have it weighed.
06 · Tools for your agent

Half of Ichor is not policing at all.

These exist because your agent is about to grep for something the graph already knows. In a real session it searched for five words, got 116 hits, and read six entire files to work out where the task lived. Ichor had the answer in 14ms.

ToolWhat it answers
ichor_findWhere does X live? Describe it in plain words — no need to guess a name.
ichor_impactWhat breaks if I change this? Callers, endpoints, tables at stake.
ichor_callersWho reaches this function, and from which endpoints.
ichor_pathsHow the app reaches a table, and through what.
ichor_task_statusIs there an active task, and what is in it.
ichor_get_scopeThe task neighbourhood, with distances.
ichor_check_changeClassify a file before writing it.
ichor_explainWhy this verdict, with the paths behind it.
ichor_request_scope_expansionArgue for the boundary to grow.

These search structure, not text: they rank the repository's own declarations and skip matches in comments and strings. They match names, not meaning — a description sharing no vocabulary with the code will not find it.

07 · The optional key

Ichor works fully without one.

Every boundary, every challenge, every piece of evidence and all nine tools your agent can call need no key and make no outbound request. Your code never leaves your machine.

A key adds exactly one thing. When your agent argues that an expansion is genuinely necessary, that argument can be weighed against the evidence the graph produced. Without a key, an argument Ichor cannot verify comes to you instead of being granted — which is a reasonable default, not a broken feature.

optional
  ichor key sk-or-…        # get one at https://openrouter.ai/keys
  ichor key                 # is one set, and where did it come from?
  ichor key --remove        # delete it

The key is checked with OpenRouter before it is stored, so a truncated paste is caught immediately rather than turning into a feature that quietly does nothing.

Where it goes. ~/.ichor/credentials.json, in your home directory — deliberately outside every repository, so it cannot be committed by accident. An exported ICHOR_OPENROUTER_KEY always wins over the stored one, which is what you want in CI.

Cost is bounded by design. The Judge is never consulted on an ordinary edit — only when an argument has actually been made — and it is capped per task and per file, so a long session cannot run away with your credit.

08 · Environment

None of these are required.

Ichor picks sensible values and ichor init writes the rest.

VariableDefaultPurpose
ICHOR_OPENROUTER_KEYYour OpenRouter key. OPENROUTER_API_KEY and OPENROUTER_KEY are also read. Beats the stored key.
ICHOR_JUDGE_MODELopenai/gpt-5-miniWhich model weighs an argument. A cheap fallback is always tried second.
ICHOR_JUDGE_MAX_PER_TASKboundedHow many times one task may consult the Judge.
ICHOR_JUDGE_MAX_PER_FILEboundedHow many times one file may.
ICHOR_JUDGE_TIMEOUT_MS20000Give up on the model and fall back to the graph-only verdict.
ICHOR_HYDRA_URLbolt://127.0.0.1:7687Where HydraDB is listening.
ICHOR_HYDRA_TOKENgeneratedAuth token for the local database. Written by ichor up.
ICHOR_HYDRA_NAMESPACEdefaultNamespace inside HydraDB.
ICHOR_DEBUG1 mirrors the hook log to stderr while you work.
09 · Troubleshooting

When something looks wrong.

Ichor said nothing at all

It fails open on purpose: no task, no database, a parse error, a timeout — every one of those allows the edit. A tool that blocks work when it breaks gets uninstalled within the hour.

So it always writes down why. .ichor/hook.log in your repo has one line per decision, and ichor status shows the current state. A turn where the graph could not be reached says so to your agent as well, rather than leaving the session quietly unwatched.

"HydraDB is not answering on the Bolt port"

ichor up, and give Docker a moment. ichor status will tell you once it is reachable. After a container restart the first query can be slow enough to time out; the next one succeeds.

The agent still asks permission for Ichor's own tools

Claude Code ignores permissions.allow from a project's settings until the workspace is trusted, and untrusted is the default for a fresh clone. Open claude interactively in the repo once and accept the trust dialog. The hooks and every verdict work either way — and the shell commands in section 05 answer the same questions without any of this.

It did not notice when I switched tasks

Name the file or the folder you mean. An ordinary sentence of common words matches thousands of files on a large repo, so it cannot say where you have moved to. "fix the retry in backend/src/services/x/y.ts" is noticed; "fix the retry" is not. ichor start "…" always works.

Codex runs no hooks

Codex asks you to trust a hook file the first time it sees one, and codex exec cannot show that prompt — so in non-interactive mode it silently runs none. Use interactive codex and approve it once. That is Codex's behaviour rather than Ichor's, but it is worth knowing before concluding the integration is broken.

It questioned something it should not have

Measured on 30 real commits from a real product, that happens on about one edit in eight. Answer with ichor justify, or tell your agent why and carry on — Ichor asks once per file. If a boundary is plainly wrong, ichor start "…" names the task yourself and detection stops redrawing it.

10 · Limits

What it cannot do.

A tool that claims to know where a task belongs should be precise about where it is blind.

  • TypeScript and JavaScript only. Python is next. Nothing else is claimed.
  • It understands one stack deeply. Next.js App Router routes and Prisma models are what it reads. Pages Router routes and other ORMs are not in the graph yet, so on those codebases Ichor sees the calls but not the endpoints or the tables — and ichor paths answers from the endpoints it does have.
  • It still questions about one edit in eight that you were right to make. Measured, not estimated. Naming the file in your prompt is what makes the boundary precise: on the 1,362-file repository, 18 files of guesswork down to the 1 you pointed at.
  • A shell write is challenged after it lands, not before. Ichor hooks the agent's edit tools, so a file written by cat >, a heredoc or your own editor has no pre-write hook. At the end of the command Ichor reads what changed on disk and asks then — before anything is built on top of it.
  • Static analysis, and it says so. Dynamic dispatch and runtime-constructed calls cannot be seen, so results are a floor and never a ceiling. A call Ichor cannot resolve is counted and reported, never guessed — on a 1,362-file repo that was 7,178 call sites it declined to invent an edge for.
  • Ichor can be wrong. The boundary is an expectation, not a fact. When it cannot verify a justification it asks you rather than deciding — and it never blocks a prompt.

Smaller ones, for completeness: a retrieval call gives up after 1.5 seconds and hands the question to your agent's own search; the graph is rebuilt between turns, so within a single turn Ichor can be a few edits behind; and two agent sessions in one checkout share a boundary, with the newer taking it over.