Running Attractor
Standalone repository guide · updated September 11, 2026
Run these commands from a checkout of the standalone Attractor repository.
Two modes
| Mode | Engine | State | You need |
|---|---|---|---|
| Local | claude -p | ~/.attractor/state.json | Claude Code |
| Hosted | Anthropic API | Cloudflare KV | A Worker + an API key |
Local is the one to try first. It needs no API key and no Cloudflare account, because claude -p runs against whatever login Claude Code already has. Hosted is what you deploy when you want the attractor reachable from anything, not just the machine it lives on.
Both share src/model.ts. Both engines parse generated updates through parseUpdate, which clamps numeric deltas. applyUpdate then bounds weights and applies decay. Direct callers bypass the delta clamp, and malformed values still need stronger validation. Local ingestion and hosted routes also differ in consolidation and provenance logging.
Code layout
src/
model.ts The attractor itself: entropy, trajectory, decay, update application
store.ts Where state lives -- FileStore (local) or KvStore (hosted)
engine.ts What generates updates -- ClaudeCliEngine or ApiEngine
render.ts Terminal output
cli.ts Local CLI entry point
routes.ts REST API over the model
index.ts Worker entry point
types.ts State, basins, updates
cli/attractor Hosted viewer using Bash, curl, and Python 3
web/ React canvas view with force-directed layout
docs/model.md Mechanics reference in the standalone repository
src/model.ts is the part worth reading. store.ts and engine.ts are the two seams that let the same model run locally or hosted; runs.ts adds the inspection trail for local model comparisons. This portfolio collects the mechanics, research, and running notes as companion documents.
Local mode
npm install && npm run build
cat > basins.json <<'JSON'
[
{ "label": "Research methodology", "description": "Study design, controls, what makes a result trustworthy", "keywords": ["assay", "controls"] },
{ "label": "Systems design", "description": "Where state lives and how parts connect", "keywords": ["api", "storage"] }
]
JSON
./dist/attractor.mjs seed basins.json
./dist/attractor.mjs ingest conversation.txt # or - for stdin
./dist/attractor.mjs ingest --session # your latest Claude Code session
./dist/attractor.mjs sessions # list Claude Code sessions
./dist/attractor.mjs # show current state
./dist/attractor.mjs history # weight evolution
./dist/attractor.mjs context # the system-prompt block
./dist/attractor.mjs compare --session # see comparing-models.md
./dist/attractor.mjs runs # the run log
ingest --session reads Claude Code's own transcripts from ~/.claude/projects/, so you can feed it real conversations without exporting anything. --session <filter> narrows to a project directory; sessions lists what's there.
ingest makes a summary call and an update call, plus consolidation calls when keyword-cap thresholds are reached. Use npm run example for a manual walkthrough without model calls.
| Variable | Purpose |
|---|---|
ATTRACTOR_STATE | Put the state file somewhere other than ~/.attractor/ |
ATTRACTOR_SUBJECT | Whose engagement the attractor models (used in prompts) |
ATTRACTOR_SUMMARY_MODEL / ATTRACTOR_UPDATE_MODEL | Override the default models |
ATTRACTOR_COMPARE_MODELS | Legs for compare |
How this uses Claude Code
Local mode shells out to the claude binary on your machine, so it runs under your own Claude Code login: your subscription, your quota, your machine. Nothing is proxied through anyone else's account, and there's no shared credential.
Two consequences worth knowing before you run it on a large history:
- Usage follows your Claude Code configuration and account access.
- Each call starts a process. That adds overhead. Measure latency and usage on a small sample before choosing a route for larger batches.
Making claude -p behave like an API call
This turned out to be the subtle part, and it's worth spelling out if you're building anything similar.
claude -p is not a completion endpoint. It's an agent. Left alone it carries Claude Code's own system prompt, the built-in tools, your working directory, any MCP servers, and your CLAUDE.md. Given a summarization prompt inside a code repository, a capable model may reasonably decide the helpful thing is to go read the repository, which is good agent behaviour and a broken inference call. The debugging notes describe that behavior during summarization. It motivated explicit limits on tools and inherited context rather than assuming every model would simply return text.
The wrapper passes explicit model, tool, settings, and system-prompt options to reduce environmental variation. A syntactically valid example is:
claude -p \
--model "$MODEL_ID" \
--tools "" \
--strict-mcp-config \
--setting-sources "" \
--system-prompt "Return a short summary of the supplied text."
Set MODEL_ID to an identifier supported by your installed CLI and supply the text on standard input. Record the CLI version for comparisons. These flags reduce inherited context and help make comparisons easier to interpret.
--model matters for the comparison: without it, claude -p uses whatever model your Claude Code session is set to, so summarizing could silently run on Opus.
Hosted mode
Requires Cloudflare resources, an Anthropic API key, and an ATTRACTOR_TOKEN. The standalone configuration keeps one shared state and history for the deployment.
The queue configuration only provides a producer. /trigger acknowledges enqueueing, not completed processing; a consumer and access to the referenced conversation must be supplied separately. Direct /ingest and /ingest-transcript requests update state without that queue.
KV reads and writes do not serialize overlapping updates. Treat this prototype as a single-writer integration; a shared service needs explicit coordination. See Cloudflare's KV documentation.
npm install
wrangler kv namespace create MODEL_KV # put the id in wrangler.toml
wrangler queues create attractor-jobs
wrangler secret put ANTHROPIC_API_KEY
wrangler secret put ATTRACTOR_TOKEN # every route requires this
wrangler deploy
Set ATTRACTOR_SUBJECT in wrangler.toml to whoever the attractor models; it appears in the update prompt and in the injected context block. ATTRACTOR_SUMMARY_MODEL and ATTRACTOR_UPDATE_MODEL can be set there too.
Then seed it with your starting basins:
curl -X POST "$API/api/attractor/seed" \
-H "Authorization: Bearer $ATTRACTOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"basins":[
{"label":"Research methodology","description":"Study design, controls, what makes a result trustworthy","keywords":["assay","controls","replication"]},
{"label":"Systems architecture","description":"How components fit together and where state lives","keywords":["api","storage","interfaces"]}
]}'
All seeded basins start at weight 0.5. With two or more equal-weight basins, normalized entropy starts at 1.0; a single basin returns 0.
REST API
| Method | Path | Purpose |
|---|---|---|
GET | /api/attractor | Current state |
GET | /api/attractor/history | Last 10 snapshots |
POST | /api/attractor/seed | Initialize with basins (force: true to re-seed) |
POST | /api/attractor/basins | Add a basin |
DELETE | /api/attractor/basins/:id | Remove a basin and clean up its connections |
POST | /api/attractor/ingest | Feed in a pre-written summary |
POST | /api/attractor/ingest-transcript | Feed in raw messages (summarized first) |
POST | /api/attractor/trigger | Enqueue a job; requires a separately supplied consumer |
ingest-transcript can receive messages from an application or hook. The calling application handles hook setup and supplies rendered memory to future sessions.
Terminal viewer
export ATTRACTOR_API_URL="https://your-worker.workers.dev"
mkdir -p ~/.claude
(umask 077; printf '%s\n' "$ATTRACTOR_TOKEN" > ~/.claude/attractor-token)
chmod 600 ~/.claude/attractor-token
cli/attractor # current state: weights, bars, trends, keywords
cli/attractor history # weight evolution table over time
Attractor
========================================
Phase 1 | Entropy: 0.978 | CONVERGING
Updates: 6 | Dominant: context-architecture
Context architecture 100.0% ^ [##############################] (5 convos)
-> systems-design, immunology
Systems design 80.5% ^^ [########################......] (3 convos)
-> context-architecture
Immunology 77.8% ~ [#######################.......] (3 convos)
-> context-architecture, creative-writing
Creative writing 58.8% ~ [#################.............] (1 convo)
-> immunology
Bench assays 44.7% ~ [#############.................] (0 convos)
Emerging patterns:
* emergent persistence
Timestamp Context Architec. Immunology Systems Design Creative Writing
------------------------------------------------------------------------------------------
2026-06-06 09:00 68% #######... 49% #####..... 62% ######.... 49% #####.....
2026-06-14 09:00 93% #########. 62% ######.... 70% #######... 47% #####.....
2026-06-22 09:00 96% ########## 80% ########.. 66% #######... 60% ######....
2026-06-26 09:00 100% ########## 78% ########.. 80% ########.. 59% ######....
The viewer requires Bash, curl, and Python 3. Configure the bearer token as shown above. The terminal output below illustrates the state and history views.
Web view
web/AttractorView.jsx renders the attractor as a force-directed graph on canvas: basins sized and coloured by weight, edges for connections, sparklines for trajectory. web/useApi.js calls the two GET endpoints and reads a bearer token from local storage. Integrating the view requires API routing and token setup. Do not put a shared write-capable deployment token in a public portfolio demo; use curated fixture state or a separately designed read-only service.