Context Router logo

Context Router

Community
llopresto87
context-router

Resolve the minimum set of knowledge-graph nodes needed for a task before reading any source file. Use at the start of every non-trivial task once a project has a knowledge graph — when changing a subsystem, tracing a bug, planning work, answering a question about how something works, or onboarding. This is the mechanism that keeps a large or multi-repo codebase inside a context window: it decides what to load, what to deliberately skip, and forces the agent to declare both before working. Pairs with the knowledge-graph skill, which builds and lints the graph this one traverses.

Overview

Publisherllopresto87
RepositoryCypress
Skill namecontext-router
Stars
113
Forks
6
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by llopresto87 on GitHub. Read the source before you install it.

Installation

Install the Context Router AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/llopresto87/Cypress/tree/main/skills/context-router
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/llopresto87/Cypress.git /tmp/Cypress
mkdir -p .claude/skills
cp -r /tmp/Cypress/skills/context-router .claude/skills/context-router
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Context Router in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Context Router on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Context Router is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

context-router

A large codebase does not fit in a context window, and loading all of it makes an agent worse: a model that has read everything has no signal about what matters. This skill replaces "read around until it feels familiar" with a traversal that terminates and that you can defend.

This skill owns the knowledge rule (the graph is the source of truth for structure and capability, loaded minimally) and the traversal algorithm that makes the rule executable.

The knowledge rule

The project keeps one LLM-maintained knowledge system at docs/graph/: the router (graph-lint.py) routes over it with index.md as the Tier 1 fallback map, Tier 2 nodes own concise facts, Tier 3 leaf collections hold source-backed depth (libraries, provenance, product, architecture, APIs, data, prompts, evaluations, plans, runbooks, specs, decisions, tools). It is the project's only documentation system; every maintained document is a layer of it.

  • Load minimally, and declare it. Resolve the minimal node set from the router (entry nodes, their requires: closure, and only the composed depth the task names specifically) and declare what you loaded and deliberately skipped. Never bulk-read to get oriented; the graph is the orientation. The boundary you chose not to cross is part of the work's record: a reader who cannot see it cannot tell an unread node from a read one. The algorithm below is the full form of this obligation; the delegation-boundary form is docs/graph/templates/prompts/graph-session-bootstrap.md.
  • One home per fact. Every fact lives in exactly one node's owns:; everything else links. Duplicated facts rot asymmetrically. graph-lint.py enforces unique fact-keys, resolvable acyclic edges, and no version pin outside its owning library page. The code-side twin is one owner per concern: a cross-cutting behaviour (session and auth, policy emission, tolerant parsing, resource creation, a datastore) has exactly one owning component, and a new need extends that owner, because two mechanisms multiply precedence questions nobody can answer.
  • Graph before code, ahead of memory. Memory of APIs and versions is unreliable; the graph is local and source-grounded. No wiki page for a library you're about to use → run ingest-library.
  • A fact the graph states is settled. Use it; never re-derive or re-check it. Grow, ingest-library and canonize establish facts so that no later session has to: the behaviour of a language or a library, a best practice, a known bug, a decision, an owner rule. Only a fact about the plant's own code can go stale, and only when that code moved. Canonize records a code anchor (docs/graph/code-anchor.py --record), and one comparison at session start prints one line. A quiet line: code facts are current. A line naming paths: facts about those paths may be stale, the code wins there, and the node is fixed in the same change. A not-recorded or not-checked line: check the code facts you rely on against the code. Settled facts stay settled either way. A worker sees no session-start line, so its code facts are current only where its brief carries that line saying no code changed. Paths under docs/graph/ and .cypress/ are the graph and its state, not code.
  • The graph compounds. Record facts when code gains them, sharp edges when they bite, load_when: triggers when routing missed. Write "not recorded" for any fact, version, or URL you do not have (knowledge-graph, rule 3).
  • The graph outranks the harness's working style (context-router.graph-over-harness). Where the graph's doctrine and a harness's default working style differ, follow the graph. A harness's safety and permission policy is a boundary, not working style, and the graph never overrides it.

Authoring and maintaining what this rule loads is skill.knowledge-graph (docs/graph/skills/knowledge-graph.md): read its node contract once (the _schema.md the graph was built from); dependency leaves are skill.library-wiki. Prefer a configured Context7 / DeepWiki / llms.txt MCP server for fetching upstream content. If the project has no graph yet, build one via adopt-existing / knowledge-graph first; until then, fall back to reading the README and the plan-of-record, and say you did.

The algorithm

1. Classify the task in one sentence

Say what kind of work it is before deciding what to read. The four kinds route differently:

KindExampleEntry
Question"how does auth work?"The node that owns the fact. Answer with citations. Read code only when the node proves wrong.
Change"add a field to X"The owning subsystem node + its required closure.
Trace"why is this endpoint 401-ing?"Every node on the request/data path. Follow peers deliberately — this is the one kind that legitimately crosses them.
Plan"rebuild the deploy pipeline"The plan-of-record + the relevant platform/infra nodes.

2. Route first, then resolve entry nodes

Route the task line before you open anything: take the router suggestion the host injected for this prompt, or, where no hook injected one (a hookless host, a spawned child, a task line of your own), run python3 docs/graph/graph-lint.py --plan "<task>". Its LOAD set is your entry set with the requires: closure already taken; read it through --show (step 3). A ! notice says the plan is thin, wide or empty; an empty plan's notice names the next step: a sharper task line, or the protocol entry nodes (an ungrown plant's include protocol.initialize).

The graph's router index, docs/graph/index.md (Tier 1), is the fallback map, not a first read. Open it only when the router fails, when a notice leaves the plan empty or wrong, or when the task explores the graph itself. There, match the task against each node's load_when: triggers. Prefer the most specific match. A task naming a path resolves to that subsystem's node; a task naming a concept resolves to the node that owns it. A path or an identifier the task names does not break a trigger it sits inside, and stands in for the trigger's file or name word: what breaks if I change src/app.py matches what breaks if I change this file.

If nothing matches, you have found a gap in the graph. Say so, fall back to the root node, and note it for the graph's maintainer to fix.

A standard's match surfaces its standing exceptions. A deviation.* node (kind deviation, status: standing; the schema's "Node kinds") carries the departed-from standard's own name in its load_when, so a task that matches the standard's topic also matches every deliberate departure from it. Load it with the standard: a standing deviation is part of the answer and stays settled until its ends_when says it stops applying.

Watch for aliased names across layers. When a subsystem answers to more than one name (a repo or folder name that differs from its product name, its package/artifact name, and its internal code name), a task that types one alias can silently fail to match a node keyed to another, and neither load_when matching nor a grep sees the miss. The Tier-1 router index must carry an explicit naming-divergence note listing the aliases for each such subsystem, so routing and search see every one of them. When you hit an unlisted alias, add it to that note in the same change (like sharpening a load_when trigger).

3. Take the closure

Load each entry node, then transitively load every node in its requires: list. That much you cannot be correct without. It is small by construction; if it is not, the graph is mis-modelled and should be fixed rather than worked around.

Read the routed nodes through python3 docs/graph/graph-lint.py --show <id>..., not the raw files: for each id it prints a header with the node's path and every pointer it holds (requires, peers, composes, delegates_to as ids; artifacts, libraries, plant_knowledge as paths), then the body verbatim. It drops only the keys that are router input, spawn configuration, or a copy of the body: load_when, routing_triggers, est_tokens, tier, kind, name, description, prevents, and the spawn keys tools, model, effort, can_delegate, max_spawn_depth, command. Every other key stays in the header, so no edge and no leaf is lost.

Whatever else a node lists is a menu (context-router.menu). Open a leaf, child, link, neighbour or index row a loaded node names only when its one-line "load when" serves your task, one item at a time, and list each one you pass over in the skip block (step 5). Only the requires: closure loads in full. A thin parent saves context only if its reader does not go on to open every leaf it names.

Then, from every loaded expertise node, take the composed children the task names specifically: descend into a child when the task uses, exactly, a term in that child's own vocabulary (its load_when: triggers plus its whole slug) that the parent does not already carry. So family words sitting on the parent descend nobody, and descent never folds a prefix the way the router's own entry matching does. A child you take becomes the parent for its own children, which is the whole of the recursion. composes: is a menu, not a closure: the specialisations the task is not about stay unread, and you say so (step 5).

A child you turn out to need but that descent did not reach is the same signal as a load_when: that should have matched and didn't. Load it, say you widened, and sharpen that child's triggers in the same change, so the next task routes there without you.

4. Cross a peer only on purpose

peers: are the boundaries you are choosing not to cross. Load a peer only when the task explicitly crosses into it, and say why. The one exception is a trace: following a request or a message across subsystems is exactly what peers edges are for.

5. Declare before you work

Print the resolved set, however small the task. It is the artifact that lets a reviewer catch a bad load before it becomes a bad change, and the only record of what you did not read. Declare it in the compact lines graph-lint.py --plan prints, so a route you accept as it stands is declared by naming it, and a set you changed shows the change:

Task: add field <F> to <entity>  [change]
LOAD <n> ~<t>t
<entry node> <path> | <title>
<required node> <path> | <title>
<composed child> <path> | <title> <- composed by <expertise node> on "<term>"
skip (cross only if the task needs it):
 peer of <entry node>: <id>=<path> <id>=<path>
 composed by <expertise node>, no specific term: <id>=<path>
open on demand: <library page, spec or ADR>; the "depth" leaf <expertise node> names

One skip block, whatever kept a node out. A peer you chose not to cross and a specialisation the task never named are the same kind of record (the boundary, and the reason it held), and a set that lists only one of them hides the other.

Then, and only then, open source files: only the ones the loaded nodes name.

6. Widen honestly, never silently

If mid-task you discover you need a node you did not load, load it and say so ("Widening: loading , because the change is not local: …"). Silent widening is the failure this skill prevents. So is stubbornly working without a node you need in order to look disciplined. Both are worse than "I was wrong about the boundary."

Check the route

The graph router is executable, and every session runs it first (the kernel's FIRST MOVE, and this skill's step 2 above). A spawned worker runs it through the canonical block every delegation brief embeds (docs/graph/templates/prompts/graph-session-bootstrap.md); this skill owns only the traversal algorithm. When your own reading of the task resolves a different set than the route, one of you is wrong; usually a load_when: trigger needs sharpening, a cheap permanent fix. That fix re-ranks every node, not only the one edited: titles and triggers are both scored text, and a sharper trigger on one node can push a peer out of a capped result that an earlier fix depended on. Re-run every routing probe fixed during the session after its last trigger or title edit, not after each one.

--plan is a keyword heuristic, not an oracle. It ranks nodes by weighted term overlap; it does not reason about a request path or a false premise. Trust it for a single-subject change or question, and as a floor everywhere. On four kinds of task, trust your own reasoning over its output:

  • Traces: the right nodes are the hops on the path, which keyword overlap cannot infer.
  • False-premise questions ("confirm we use X"): the correcting node may share no words with the wrong assumption; ask which node would own the truth.
  • Policy questions: these have one owning node; --plan may pad the set. Prefer the single owner.
  • Compound / multi-topic tasks: a task description that bundles several distinct topics dilutes each topic's distinctive terms below the keyword threshold, so the ranking can resolve to the wrong node and specialist set entirely, beyond a merely partial one. Probe each sub-topic separately, or explicitly discount the output for a task you know is compound.

Stopping rules

Stop loading ONLY when one of these is true, because the closure is the rule:

  • The closure is exhausted: requires: transitively, plus every composed child the task named specifically.
  • You can state the change you are about to make and name the contract it must not break.
  • The next node you would open is a peer the task does not cross into, or a composed child the task never named.

Cost discipline

  • Within the loaded scope, retrieve progressively. The closure names the files; read within them from indexes, headings, symbols, and diffs to regions, and from excerpts to full files, taking the complete source only when exactness demands it. Query order and when one source decides a question are the retrieval posture in docs/graph/method/decision-economy.md ("Every operation serves a decision").
  • Confirming a path with ls or grep is fine; reading twenty files to build a mental model is the bulk read the knowledge rule replaces with the graph.
  • A change task should load a handful of nodes. If it needs many, it is really several tasks; split it and say so.
  • A trace may legitimately load many nodes, ONLY along its one path.
  • Read a shared convention from the shared node. When two sibling subsystems look near-identical, what they share belongs in a shared node. A convention you can infer only by comparing siblings is missing from its home, so add it there.

Residency

Loading minimally decides which text enters a session; residency decides how long it stays. Every text that can enter a session belongs to exactly one of four classes, placed by the test, not by file type:

ClassTestHow it is held
1. Resident, fullEvery task needs it.Loaded once at session start, in full. Nothing restates it, per-prompt hooks included. Only the kernel.
2. Resident, pointerThe model must know it exists before it can know it needs it.Always visible as one line naming when to reach for it; the explanation lives in the body.
3. Once per sessionSome tasks need it, and may need it again.Protocol and skill bodies. Surfaced when first routed; after that the hook's reminder names it by id on its seen: line (open it again only if it is no longer in view), with entry lines only for ids new to the session, until a reset or refresh.
4. Per lookupIt is consulted for a fact, not followed as a procedure.Never resident. Reference corpora: read the section, cite, move on.

An item that fits two classes takes the smaller resident footprint, and you say why. Dedup applies within one context. A spawned agent starts empty, so repetition across a subagent spawn is how the brief's canonical block arrives verbatim. Children are not routed: the per-prompt hooks inject nothing into a child session or into a turn a person did not type, so a child routes its own task line as its brief says. A hook can know what it surfaced, never what the model read. How the per-prompt hooks hold to this is the CYPRESS seed's own spec SPEC-0003-per-prompt-injection.

Reference files

  • docs/graph/skills/knowledge-graph.md: builds and lints the graph.
  • docs/graph/_schema.md: the node contract (the adopted copy beside the graph; docs/graph/templates/knowledge-graph/_schema.md is the pristine seed template, fast-forwarded on graft).
  • docs/graph/templates/knowledge-graph/index.md: the router-index template.
  • the kernel (AGENTS.md): the context-budget rule this skill implements.

Frequently asked questions

What does the Context Router AI skill do?

Resolve the minimum set of knowledge-graph nodes needed for a task before reading any source file. Use at the start of every non-trivial task once a project has a knowledge graph — when changing a subsystem, tracing a bug, planning work, answering a question about how something works, or onboarding. This is the mechanism that keeps a large or multi-repo codebase inside a context window: it decides what to load, what to deliberately skip, and forces the agent to declare both before working. Pairs with the knowledge-graph skill, which builds and lints the graph this one traverses.

Why use Context Router on TypingMind?

Because you install it once and use it with any model. Context Router is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Context Router in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/llopresto87/Cypress/tree/main/skills/context-router. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Context Router?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Context Router?

As many as you like. As long as a model supports skills, you can use Context Router with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Context Router AI skill free?

Yes. It is published on GitHub by llopresto87 under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇