Local-first, team-shared memory for coding agents. Forgelore remembers fixes and dead ends, injects them only when the same error comes back, and measures whether it actually saves tokens.
A single Go binary with no runtime dependencies. It works with any agent that can run a shell command.
Status: early development. The record store, fingerprinting, the command line, the measurement ledger, hooks for four agents and the MCP server all work and are tested. Releases are published and attested; the record format and the command line may still change before
1.0. Followdocs/plan.mdfor the roadmap anddocs/compatibility.mdfor which agents are verified and which are not.
The walkthrough is at https://forgeprint.github.io/forgelore/, and in Turkish at https://forgeprint.github.io/forgelore/tr/. An agent wiring itself up wants the agents page or llms.txt.
A fix recorded while running go build is found later by go vet, in a
different file, on a different line. Real output, from v0.1.1:
$ go build ./...
# example.com/app/cmd/app
cmd/app/main.go:4:2: undefined: greet
$ forgelore recall --command "go build ./..." --error-file err.txt
1 error(s), 0 with something recorded
cd023fb609411574 undefined: greet
nothing recorded
$ forgelore record --type fix \
--title "greet lives in internal/greeter; import it" \
--command "go build ./..." --error-file err.txt
01M45XT2HS8G3PXDBRTWWV3449 .forgelore/records/01M45XT2HS8G3PXDBRTWWV3449.md
fingerprint cd023fb609411574Later, a different file, a different command:
$ go vet ./...
# example.com/app/internal/svc
vet: internal/svc/svc.go:4:2: undefined: greet
$ forgelore recall --command "go vet ./..." --error-file err2.txt
1 error(s), 1 with something recorded
cd023fb609411574 undefined: greet
fix 01M45XT2HS8G3PXDBRTWWV3449 greet lives in internal/greeter; import itThe fingerprint is the same because the file, the line and the subcommand are not part of it. The error is.
- Event-triggered, just-in-time recall. When a command fails, Forgelore fingerprints the error. If that fingerprint has been seen and solved before, it injects a one-line hint — the fix, and the approaches already known not to work. No match means no tokens spent.
- Remembers dead ends. Approaches that were tried and failed leave no trace in a codebase, yet repeated error loops burn the most tokens.
- Staged access. A small, budgeted index at session start; detail only on request. The tool design enforces it: you cannot fetch detail without an id from the index.
- Measured, not claimed. Every injected byte is recorded in a local ledger. An optional A/B mode disables injection for a share of sessions and compares token usage, so the savings figure is an observation rather than a promise.
- No background model calls. Capture is deterministic.
- No network access, no telemetry.
- Never blocks the agent. Hooks are fail-open with a hard timeout.
- Does not duplicate an agent's built-in memory features.
curl -fsSL https://raw.githubusercontent.com/forgeprint/forgelore/main/scripts/install.sh | bashIt verifies the download against the release's SHA256SUMS and refuses to
install anything that does not match. No sudo, nothing outside your home
directory, no runtime to install alongside it.
Or through npm, which installs the same binary as an ordinary dependency —
no postinstall script, nothing downloaded at install time, so
--ignore-scripts works:
npm install -g forgeloreTo build it yourself instead, ./scripts/build.sh puts a binary in dist/.
Release artifacts are built by a tag-triggered workflow and carry build provenance, so you can ask who published them rather than only whether the bytes are intact:
gh attestation verify forgelore_linux_amd64 -R forgeprint/forgeloreIt prints nothing when it succeeds; the exit status is the answer. Releases
before v0.1.4 have no attestation. SHA256SUMS still answers the integrity
half on its own, offline.
forgelore init # create .forgelore/ in your project
go build ./... 2>err.txt # something fails
# ask before trying a fix
forgelore recall --command "go build ./..." --error-file err.txt
# once you have solved it, say so
forgelore record --type fix \
--title "Import the package that defines greet" \
--command "go build ./..." --error-file err.txt
# and the next time that error appears, in any file, under any go subcommand,
# recall finds itsearch <query> returns ids and titles only; show <id> returns one record in
full. Every command takes --json. init prints the one line to add to your
AGENTS.md and does not write the file itself.
plugin/ is a Claude Code plugin that wires the hooks up: a
budgeted index at session start, a hint when a command fails with an error it
has seen, and a proposed fix when a command that was failing starts working.
Nothing is recorded without you accepting it with forgelore review.
Every hook exits successfully whatever happens. A broken store, a corrupt index or a missing directory makes Forgelore silent, never your session slower or louder.
forgelore mcp serves the Model Context Protocol on stdin and stdout, so a
client that is not Claude Code can search the same memory:
claude mcp add --transport stdio forgelore -- forgelore mcp --dir .Four tools: search, get, recall_error and propose. get only takes an
id that search or recall_error returned, so no single call can hand over
the whole store, and propose writes a candidate for forgelore review
rather than a memory.
A memory starts local. Moving one into the team scope is a step somebody takes on purpose:
forgelore promote <id> # local record becomes a team record
forgelore check # refuses to let a secret be committed
forgelore dedupe # two records for one error; retire the old oneforgelore init --with-git-hook installs a pre-commit hook that runs the
check, and refuses to overwrite a hook you already have. A record derived
from content outside the project is marked tainted and needs
--force-tainted to be shared.
Records are one file each, named by a ULID, so two people adding memories at the same time produce two files and no conflict. The index, the ledger and your local scope are never committed.
docs/team-trial.md is a protocol for trying this with
two people for a week.
docs/compatibility.md lists what is supported and,
more usefully, what has actually been checked against a running agent rather
than read in its documentation. Claude Code is verified; mappings for Codex
CLI and Copilot CLI are written but unverified. Everything else works through
the CLI today.
Every lookup is written to a local ledger, and forgelore report shows what
was spent against what it bought:
forgelore reportTwo things are compared. Repeated errors per session comes from
Forgelore's own ledger and needs nothing from your agent. Cost per session
needs the agent to report what it spent; forgelore usage --help-wiring
prints how to wire that up for Claude Code. Without it the report shows the
spending and claims no saving.
Turn on the A/B mode to get a control group — the same share of sessions get no injections at all, and see exactly what a session with no memory sees:
# .forgelore/config.yaml
measure.ab.control_percent: 20Differences that are not statistically meaningful are printed as "not meaningful", with the interval, rather than as a win. The ledger is local and never leaves the machine.
The decisions that shape this project — single static binary, pure-Go SQLite,
plain markdown as the source of truth, adapters as data rather than code — are
recorded as ADRs in docs/adr/ and in docs/plan.md.
The record file format is specified in docs/record-format.md.
Every check CI runs is a script in scripts/, runnable locally:
./scripts/test.sh # gofmt, go vet, go test
./scripts/crosscheck.sh # cross-compile all targets with CGO_ENABLED=0
./scripts/gitleaks.sh # secret scan with a pinned, checksum-verified binary
./scripts/ci.sh # all of the above, exactly what CI callsSee CONTRIBUTING.md. Commits must be signed off under the
Developer Certificate of Origin (git commit -s).
If you work with Claude Code, CLAUDE.md has the rules this project is built under.