Branch-based decision memory for AI agents — stores choices, alternatives, and regret.
Project description
The memory layer your AI agents actually need.
Not what was said — but what was chosen, what it cost, and what to do differently next time.
The problem with AI memory today
Every major memory system stores what happened: messages, chunks, embeddings, facts.
None of them store why a decision was made — or what it cost.
That means every time your agent faces the same situation, it starts from zero. It can't learn from its own mistakes. It has no way to say: "Last time I chose this, it cost me 3 points. Don't do it again."
ForkLedger fixes this.
What ForkLedger stores
situation before the choice
↓
[ branch A ] [ branch B ✓ chosen ] [ branch C ]
↓
observed outcome
↓
regret vector — what each path cost relative to the best
Over time, ForkLedger learns which branches produce the lowest regret under similar conditions — and surfaces that knowledge when it matters.
Install
pip install forkledger # core — zero dependencies
pip install forkledger[mcp] # + MCP server (Claude Desktop, Cursor, VS Code)
pip install forkledger[api] # + REST API (FastAPI)
pip install forkledger[embeddings] # + semantic similarity
pip install forkledger[all] # everything
60-second demo
from forkledger import ForkLedgerEngine, ForkRecord, Branch, OutcomeEstimate
engine = ForkLedgerEngine("decisions.db", backend="sqlite")
# Record a decision and its outcome
engine.add_record(ForkRecord(
fork_id = "trade-001",
pre_state = {"market": "BTC", "signal": "breakout", "volume": "high"},
trigger = "RSI crossed 70 with volume confirmation",
possible_branches = [Branch("enter"), Branch("wait"), Branch("short")],
chosen_branch = "enter",
realized_value = 2.4,
estimated_outcomes = [
OutcomeEstimate("wait", estimated_value=0.0),
OutcomeEstimate("short", estimated_value=-1.5),
],
confidence = 0.8,
tags = ["crypto", "momentum"],
))
# Update with actual result after the fact
engine.update_outcome("trade-001", realized_value=1.9)
# Ask: given this new situation, what should I do?
recs = engine.recommend(
current_state = {"market": "ETH", "signal": "breakout", "volume": "high"},
)
# → [{"branch": "enter", "score": 0.82}, ...]
# What patterns have emerged from repeated decisions?
policies = engine.policies()
# → [{"recommended_branch": "enter", "win_rate": {"enter": 0.72}, ...}]
# Win rates across all historical decisions
win_rates = engine.win_rates()
# CFR-style accumulated regret (time-decay weighted)
regret = engine.accumulated_regret()
# Full audit trail with effective weights
trail = engine.audit_trail()
MCP Server — works with Claude Desktop, Cursor, VS Code
pip install forkledger[mcp]
Add to your Claude Desktop config (~/.config/claude/claude_desktop_config.json):
{
"mcpServers": {
"forkledger": {
"command": "forkledger",
"args": ["mcp", "--store", "/path/to/decisions.db", "--backend", "sqlite"]
}
}
}
Your Claude agent can now:
record_decision(...)— store a decision with alternatives and outcomeget_recommendation(...)— ask what worked in similar past situationsupdate_outcome(...)— update realized value after observing resultsget_policies(...)— see distilled patterns from repeated decisionslist_decisions(...)— browse decision historymemory_stats()— store health and statistics
No code required. Works with any MCP-compatible client.
REST API
pip install forkledger[api]
forkledger serve --backend sqlite --store decisions.db
# → http://localhost:8000/docs (Swagger UI)
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check |
GET |
/stats |
Store statistics |
GET |
/forks |
List (filterable by tags, confidence) |
POST |
/forks |
Add one fork |
POST |
/forks/bulk |
Bulk add |
PATCH |
/forks/{id}/outcome |
Update realized value |
DELETE |
/forks/{id} |
Delete |
DELETE |
/forks/expired |
Purge expired |
POST |
/recommend |
Get branch recommendations |
POST |
/rank |
Rank forks by similarity |
GET |
/policies |
Distilled policies |
GET |
/win-rates |
Win rates per branch |
GET |
/audit |
Decision audit trail |
GET |
/export |
Export all as JSON |
Docker
docker run -p 8000:8000 -v $(pwd)/data:/data \
ghcr.io/sliper82/forkledger:latest
# or with compose:
docker compose up
Dashboard
pip install forkledger[dashboard]
forkledger dashboard --store decisions.db --backend sqlite
# → http://localhost:8501
Visualize decision history, win rates, accumulated regret, and policies.
CLI
forkledger add decisions.json # load from file
forkledger recommend --state '{"signal":"breakout"}' # get recommendation
forkledger update-outcome trade-001 1.9 # update outcome
forkledger policies --min-support 3 # distilled patterns
forkledger win-rates # branch win rates
forkledger accumulated-regret # CFR-weighted regret
forkledger audit --limit 20 # decision trail
forkledger stats # store health
forkledger serve --backend sqlite --port 8000 # REST API
forkledger mcp --backend sqlite # MCP server
What's unique about ForkLedger
| Concept | Description |
|---|---|
| Regret vector | Computed automatically. regret(branch) = best_value − branch_value. Zero = best choice. |
| Fuzzy policy clustering | Groups similar (not just identical) states. Real-world agent states are never perfectly equal. |
| Confidence decay | Exponential time-weighting. Old evidence fades. Configurable half-life. |
| CFR-style accumulation | Weighted regret accumulates across history per branch, similar to Counterfactual Regret Minimization. |
| Branch win rates | How often each branch was the best available choice. |
| Outcome update | Update realized values after observing results. Regret is recomputed automatically. |
| Zero dependencies | Core library requires nothing. SQLite is stdlib. All extras are opt-in. |
How it compares
→ See COMPARISON.md for a full side-by-side with Mem0, Hindsight, LangMem, and mcp-memory.
Short version: ForkLedger does not compete with those systems — it complements them. They store facts. ForkLedger stores decisions.
Architecture
ForkLedgerEngine
├── storage/
│ ├── JsonForkStore — flat file, zero deps
│ ├── SqliteForkStore — indexed, atomic, production
│ └── PostgresForkStore — cloud-native, pgvector
├── counterfactual.py — regret + decay + CFR accumulation
├── retrieval.py — scoring + optional embeddings
├── policy.py — fuzzy + exact policy distillation
├── mcp_server.py — MCP server (8 tools)
├── api.py — FastAPI REST (14 endpoints)
├── cli.py — CLI (18 commands)
└── dashboard/app.py — Streamlit UI
Retrieval scoring:
| Signal | Weight |
|---|---|
| State similarity | 45% |
| Constraint match | 20% |
| Recency | 15% |
| Confidence | 10% |
| Regret salience | 10% |
Benchmark results
ForkLedger Benchmark — 300 records, 80 queries
Load: 2.19s (137 rec/s)
Latency: p50=5.6ms p95=25.9ms
Precision@1: 100.0%
Policy accuracy: 4/4 = 100.0%
Run yourself:
python benchmarks/locomo_benchmark.py --records 500 --queries 100
Integrations
LangChain
pip install forkledger[langchain]
from forkledger.integrations.langchain import ForkLedgerMemory
memory = ForkLedgerMemory(
store_path="decisions.db",
backend="sqlite",
agent_id="my-agent",
domain="research", # pre-tuned scoring weights
)
# Record decisions manually
memory.record_decision(
fork_id="dec-001",
situation={"task": "research", "signal": "mixed"},
trigger="Conflicting sources",
chosen="verify",
alternatives=["fast-publish", "wait"],
outcome=1.8,
)
# Or use as drop-in with ConversationChain
from langchain.chains import ConversationChain
chain = ConversationChain(llm=llm, memory=memory)
AutoGen
pip install forkledger[autogen]
from autogen import AssistantAgent
from forkledger.integrations.autogen import ForkLedgerHook
hook = ForkLedgerHook(
store_path="decisions.db",
backend="sqlite",
agent_id="assistant",
domain="code",
)
# Attach to any AutoGen agent
assistant = AssistantAgent("assistant", llm_config=llm_config)
hook.attach(assistant) # injects decision context into every message
# Or use standalone
hook.record(
situation={"task": "web_research", "sources": "multiple"},
trigger="Conflicting data",
chosen="verify_cross_reference",
alternatives=["use_first", "skip"],
outcome=2.1,
)
recs = hook.recommend({"task": "web_research", "sources": "multiple"})
Roadmap
- SQLite + JSON backends
- REST API (FastAPI)
- MCP server (Claude Desktop, Cursor, VS Code)
- Fuzzy policy clustering
- Confidence decay + CFR-style accumulation
- Branch win rates + audit trail
- PostgreSQL + pgvector backend
- Streamlit dashboard
- Benchmark suite
- LangChain official memory adapter (
ForkLedgerMemory) - AutoGen integration (
ForkLedgerHook,ForkLedgerGroupChatManager) - PyPI release —
pip install forkledger
Contributing
git clone https://github.com/sliper82/forkledger
cd forkledger
pip install -e ".[dev]"
pytest
python benchmarks/locomo_benchmark.py
Issues, PRs, and ideas welcome. See CONTRIBUTING.md.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file forkledger-0.4.1.tar.gz.
File metadata
- Download URL: forkledger-0.4.1.tar.gz
- Upload date:
- Size: 46.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd0adc81693b93b4d6fb344218e283d719fbf7355ce7225b30479ac2c8ca7ce4
|
|
| MD5 |
0febd0ede69037bc2329e635ed3d1873
|
|
| BLAKE2b-256 |
0b4e77e145f763d391ef8768480c7c8736e5c40813095717988e0ef47fa0ca6b
|
File details
Details for the file forkledger-0.4.1-py3-none-any.whl.
File metadata
- Download URL: forkledger-0.4.1-py3-none-any.whl
- Upload date:
- Size: 46.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b11d1f2ff5184692914e58ee6a1e00f666d4d3af1a94024404bf614c2a6baeb
|
|
| MD5 |
a8d66785bd4b03395c44d97c506023f5
|
|
| BLAKE2b-256 |
c0e591f8572b866e690a0873011b5c3edc094272ef59df368ca2a1d23e660782
|