I Spent 3 Hours Fixing a 15-Minute Setup
Making Claude-Cognitive Actually Work: A Multi-Project Setup Guide
I recently tried setting up claude-cognitive, a working memory system for Claude Code that promises context persistence across sessions. The 15-minute setup guide looked straightforward enough. Three hours later, I finally had it working—but only after discovering several assumptions baked into the default configuration that don't work for most real-world use cases.
Here's what I learned and how I fixed it.
What Claude-Cognitive Does
Before diving into the fixes, let's understand what this tool actually provides:
- Attention-based context routing: Files you mention become "hot" (fully injected into context), then decay to "warm" (headers only), then "cold" (evicted)
- Co-activation: Related files boost each other—mention authentication, and the multitenancy docs warm up too
- State persistence: Your attention scores survive across conversation turns
- Pool coordination: Multiple Claude instances can coordinate via shared state
The system hooks into Claude Code's UserPromptSubmit event to inject relevant documentation before each prompt. In theory, this gives Claude "working memory" about your project.
Problem 1: The Scripts Look in the Wrong Place
After following the setup guide exactly, I ran a test:
echo '{"prompt":"How does authentication work?"}' | python3 ~/.claude/scripts/context-router-v2.py
Nothing. No output. No attention state header.
Digging into the script, I found this:
# Line 452 in the original context-router-v2.py
docs_root = Path(os.environ.get("CONTEXT_DOCS_ROOT", str(Path.home() / ".claude")))
The router looks for documentation in ~/.claude/systems/, ~/.claude/modules/, etc.—the global Claude directory. But the setup guide has you create documentation in your project's .claude/ directory.
The Fix
Make the script prefer project-local docs:
# Priority: CONTEXT_DOCS_ROOT env > project .claude/ > global ~/.claude/
if os.environ.get("CONTEXT_DOCS_ROOT"):
docs_root = Path(os.environ["CONTEXT_DOCS_ROOT"])
elif Path(".claude").exists():
docs_root = Path(".claude")
else:
docs_root = Path.home() / ".claude"
Now the router automatically uses whichever .claude/ directory exists in your current working directory.
Problem 2: Hardcoded Keywords for Someone Else's Project
Even after fixing the docs path, nothing activated. Checking the injection log:
Stats: Hot=0, Warm=0, Cold=4
Activated files: none
The script found my files but couldn't activate them. Why?
The KEYWORDS dictionary—which maps trigger words to documentation files—was hardcoded for the author's specific project:
# Original keywords (lines 79-197)
KEYWORDS: Dict[str, List[str]] = {
"systems/server-one.md": [
"server-one", "gpu", "local model", "inference",
"vram", "cuda", "nvidia-smi"...
],
"systems/server-two.md": [
"server-two", "edge", "sensory", "perception", "layer 0"...
],
"modules/custom-module.md": [
"custom", "trajectory", "prediction", "state machine"...
],
# ... 100+ more project-specific keywords
}
The script contained over 100 keywords mapped to files that don't exist in my codebase. No matter what I typed, nothing activated because my project has completely different files and terminology.
The Fix: Config-Driven Keywords
Rather than edit the script every time I switch projects, I refactored it to load keywords from a project-local config file:
def load_project_config() -> Tuple[Dict[str, List[str]], Dict[str, List[str]], List[str]]:
"""
Load keywords, co-activation, and pinned files from project config.
Returns (keywords, co_activation, pinned_files) tuple.
"""
config_paths = [
Path(".claude/keywords.json"), # Project-local
Path.home() / ".claude/keywords.json", # Global fallback
]
for config_path in config_paths:
if config_path.exists():
try:
with open(config_path) as f:
config = json.load(f)
return (
config.get("keywords", {}),
config.get("co_activation", {}),
config.get("pinned", [])
)
except (json.JSONDecodeError, IOError):
continue
return ({}, {}, [])
# Load at module level
KEYWORDS, CO_ACTIVATION, PINNED_FILES = load_project_config()
Now each project gets its own .claude/keywords.json:
{
"keywords": {
"systems/development.md": [
"development", "dev", "local", "localhost",
"database", "migration", "test", "debug"
],
"systems/production.md": [
"production", "prod", "deploy", "hosting", "ci/cd"
],
"modules/feature-a.md": [
"feature-a", "component", "service", "handler",
"your-specific-terms", "class-names", "function-names"
],
"integrations/external-api.md": [
"external-api", "webhook", "api-key", "integration"
]
},
"co_activation": {
"modules/feature-a.md": ["systems/development.md"],
"integrations/external-api.md": ["modules/feature-a.md"]
},
"pinned": ["systems/development.md"]
}
Problem 3: Silent Failures
The most frustrating part of debugging was that the router fails silently. If no files reach HOT or WARM status, it outputs nothing:
# Line 492 in original
if stats["hot"] > 0 or stats["warm"] > 0:
print(output)
This makes sense in production—you don't want noise when there's nothing relevant. But during setup, you have no idea if the hook is even running.
Debugging Tip
The script logs everything to ~/.claude/context_injection.log. Check it:
tail -50 ~/.claude/context_injection.log
You'll see exactly what's happening:
================================================================================
[2026-01-08T08:41:36.818653] Turn 15
Prompt (first 100 chars): How does authentication work?...
Stats: Hot=0, Warm=0, Cold=4
Total chars: 144
Activated files: none
================================================================================
The Final Architecture
After all fixes, here's what a properly configured multi-project setup looks like:
~/.claude/
├── scripts/ # Global (shared across all projects)
│ ├── context-router-v2.py # Modified to load project config
│ ├── pool-auto-update.py
│ ├── pool-loader.py
│ ├── pool-extractor.py
│ └── pool-query.py
├── settings.json # Global hooks configuration
└── (empty systems/modules/integrations - not needed)
~/projects/your-project/.claude/
├── keywords.json # Project-specific keywords & co-activation
├── attn_state.json # Attention state (auto-generated)
├── CLAUDE.md # Project overview
├── systems/
│ ├── development.md # Dev environment docs
│ └── production.md # Production infrastructure
├── modules/
│ ├── feature-a.md # Core feature documentation
│ └── feature-b.md # Another feature
└── integrations/
└── external-api.md # Third-party integration docs
Writing Effective Documentation
The context router's value comes from the documentation you feed it. Here's what makes docs useful for this system:
Keep Files Focused
Each file should cover one system, module, or integration. The router injects entire files when they're HOT—a 500-line mega-doc will blow your context budget.
Front-Load the Important Stuff
WARM files only get their first ~25 lines injected. Put the most useful information at the top:
# Feature Name
**Purpose:** One-line description of what this does
**Entry Point:** `src/services/FeatureService.ts`
**Status:** Active
## Quick Reference
**Key Files:**
- `src/services/FeatureService.ts` - Main logic
- `src/controllers/FeatureController.ts` - API endpoints
**Endpoints:**
- `POST /api/feature/action` - Do the thing
- `GET /api/feature/:id` - Get the thing
Choose Keywords Carefully
Keywords are matched against the lowercased prompt. Include:
- Technical terms (
api,webhook,middleware,database) - File/class names (
UserService,OrderController) - Common phrasings (
how does X work,set up Y)
Avoid overly generic words that would trigger false positives.
Is It Worth It?
After all this setup, does claude-cognitive actually help?
Yes, but with caveats.
When configured properly, asking "How does authentication work?" now gives Claude immediate context about my JWT implementation, tenant membership model, and test accounts. That's genuinely useful for complex codebases.
But the setup cost is significant. You need to:
- Write project documentation (which you should do anyway)
- Curate keywords that match how you actually ask questions
- Maintain the docs as your project evolves
For small projects, this is overkill. For larger codebases where you're constantly context-switching between subsystems, the attention-based routing can save real time.
Quick Start for Your Project
If you want to try this modified setup:
1. Clone and install scripts:
git clone https://github.com/GMaN1911/claude-cognitive.git ~/.claude-cognitive
cp ~/.claude-cognitive/scripts/*.py ~/.claude/scripts/
chmod +x ~/.claude/scripts/*.py
2. Apply my patches to ~/.claude/scripts/context-router-v2.py:
Replace the docs_root logic (~line 450):
if os.environ.get("CONTEXT_DOCS_ROOT"):
docs_root = Path(os.environ["CONTEXT_DOCS_ROOT"])
elif Path(".claude").exists():
docs_root = Path(".claude")
else:
docs_root = Path.home() / ".claude"
Replace the KEYWORDS/CO_ACTIVATION section (~line 68-174) with the load_project_config() function shown above.
3. Create project config:
mkdir -p your-project/.claude/{systems,modules,integrations}
# Create keywords.json with your project's keywords
# Create documentation files
4. Configure hooks in ~/.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [
{"type": "command", "command": "python3 ~/.claude/scripts/context-router-v2.py"}
]
}]
}
}
5. Set your instance ID:
echo 'export CLAUDE_INSTANCE=A' >> ~/.zshrc
Conclusion
Claude-cognitive is a clever system with a flawed default configuration. The core ideas—attention decay, co-activation, tiered context injection—are sound. But the implementation assumes you're running a specific project and storing everything globally.
With a few patches to support project-local configuration, it becomes genuinely useful for managing context across large codebases. The setup investment pays off when you stop needing to re-explain your architecture every few prompts.
I've opened a PR to add per-project keyword configuration to the main repository. Hopefully this makes the tool more accessible to those who routinely work outside of a monorepo.