feedback-graph
Includes Linear tickets in the post-mortem context hunt, using issue context as evidence when diagnosing missed requirements and proposing lessons.
Connects Codex via MCP and AGENTS.md guidance, ingests Codex session transcripts, and provides task context and pre-done checks for Codex sessions.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Feedback Graph
Your coding agent keeps making the same mistakes. Feedback Graph remembers the corrections.
Every time you tell Claude Code or Codex "that's not good enough" or "you missed X", the lesson dies with the session. Feedback Graph runs on your machine, reads the sessions of the project folders you choose, finds the moments you pushed back, works out what was missed and why, and, once the lesson is approved (by you, or by the learning loop once it has earned it), hands it to the next agent session before it starts and again before it says "done".
Local first. Runs in Docker with a local model (Ollama) by default. Nothing leaves your machine unless you configure a provider.
You decide what is learned. Every flagged moment waits for your confirmation, and every lesson for your approval, unless your own verdicts have earned the learning loop the right to make that kind of call (approving a lesson, confirming an Inbox item). The loop runs in
autoby default and asks you only what it cannot decide, each with why; see The learning loop.Evidence, not vibes. Each lesson links back to the feedback, the miss, its cause, the code it touched and the fix.
Works with your agents through MCP and a skill: context before a task, a check before "done".
What people said, too (opt-in). Hand it a call transcript or notes and it proposes the claims and open questions in them, with who said each, flags conflicts with what agents are told, and answers "what do we know about X". Agents see only the claims you accept; see Knowledge intake.
Apache-2.0 · Operations · Security · Changelog · Contributing · Editions · Guide
Quickstart
You need Docker, git and Ollama on the host. Node and pnpm are not needed to run it: the scripts use Node when it is there, else Docker's Node image, and ./scripts/fg runs inside the api container (Node and pnpm are for developing it). Claude Code or Codex is needed for the agent hookup in the last step. Start Ollama first (the app, or ollama serve): ollama pull talks to the running server.
git clone https://github.com/ChaosDeSelva/Feedback-Graph.git feedback-graph
cd feedback-graph
ollama pull gemma4 && ollama pull nomic-embed-text # with Ollama running
./scripts/setup.sh # asks which folders hold your projects, writes .env with generated passwords, checks Ollama,
# then offers to start the stack (docker compose up -d --build) and to connect Claude Code and Codex
./scripts/fg signin # prints a one-time link that signs your browser in to the Dashboard (http://localhost:8787 unless API_PORT moves it)In the Dashboard's Settings, add project folders and choose the projects to read. If you declined setup's start, run docker compose up -d --build; if you declined connecting agents, run ./scripts/install-agent.sh (see Connect your agents; --dry-run shows what it would change).
First value in ten minutes, with no history
You do not need past sessions or pushback to see it work. --demo adds this repo's examples/ folder as a project folder, selects demo-shop, starts the stack and seeds demo-shop's demo lessons (fg demo seed, all marked demo):
./scripts/setup.sh --yes --demo
./scripts/fg demo context # the lessons an agent is given before it edits demo-shop's routes
./scripts/fg demo check # check_before_done catches a console.log a lesson forbids, citing the line
./scripts/fg activity --project demo-shop --limit 5 # what was served and checked, and why./scripts/fg demo remove takes the demo lessons back. Your own first lesson, in a project you selected, takes four steps and no history either:
In your agent (connected by setup or
install-agent.sh), say what it got wrong and the rule, e.g. "record a miss: you added a route without a cross-tenant test; every route needs one", or type/postmortem ...in Claude Code. The agent callsreport_feedback.Inbox: confirm the item. The post-mortem runs (a minute or so with the local model).
Lessons: read the proposed lesson, edit it if needed, and approve it.
Ask the agent for work on those files:
get_task_contextserves the lesson, andcheck_before_donechecks the finished work against it.
Pushback you type in a session is picked up from its transcript on its own; the Inbox then holds it for step 2.
To feed it what people said as well, turn on knowledge intake (INTAKE_ENABLED=1 in .env, then docker compose up -d), add a call or notes and ask about it:
./scripts/fg ingest - --project shop --kind notes --wait < notes.md # read into proposed claims and questions; accept them on the Intake page
./scripts/fg know "retry policy" # what is known about it, who said it, and what is disputedSomething not working? ./scripts/doctor.sh checks Docker, ports, Ollama (including that structured output works: on macOS install the Ollama app, not the Homebrew formula), the mounts, the Codex login and GitHub and Linear, and says how to fix each failure. For an unattended install: ./scripts/setup.sh --yes --pull-models --projects ~/code [--github-token-file F] [--linear-key-file F] [--claude-token-file F].
setup.sh asks first for Project folders, comma-separated. There is no default: Enter mounts none, and you add them later in Settings. On a first run it then asks which projects to read, and offers the models, a claude setup-token (its shape checked before it is saved), the API, owner and operator tokens, the stack start and agent registration. It says every .env value it changes. On an existing install, --yes adds only a missing owner token, and only when API_TOKEN is set. A tokenless install is a read-only install for owner actions: ./scripts/setup.sh --yes --enable-auth adds all three tokens (outside a terminal --enable-auth needs --yes), then re-run ./scripts/install-agent.sh so agents get the API token.
Sign in with ./scripts/fg signin. It prints a one-time link that signs this machine's browser in as the owner: it works once, for 60 s, and only at localhost, so the owner token never leaves .env. Pasting FG_OWNER_TOKEN into the sign-in card works too. A sign-out survives API restarts, a restore from backup signs every browser out, and rotating FG_OWNER_TOKEN ends all browser sessions.
To change folders later, use Settings and rerun setup. To see the whole loop without your own projects, try the demo project.
setup.shsuggests the folders your Claude Code sessions ran in (PROJECTS_DIRS=~/code,~/work ./scripts/setup.shanswers it ahead).docker-compose.ymlmounts no project folder:setup.shwrites a generateddocker-compose.override.ymlwith exactly the folders you chose (none is fine: everything starts, with nothing to read), sodocker-compose.ymlstays as shipped. It also creates./state,~/.claude/projects(orCLAUDE_PROJECTS_DIR) and~/.codex/sessions(orCODEX_SESSIONS_DIR) if missing (Docker would create them owned by root); an empty~/.codex/sessionsdoes not count as Codex being installed. On Linux it setsHOST_UID/HOST_GIDso the containers run as you; see docs/operations.md for that and Ollama'sOLLAMA_HOST.Port 8787 taken? Set
API_PORTin.env: the UI is then athttp://localhost:<API_PORT>and the MCP server athttp://localhost:<API_PORT>/mcp.setup.shprints the right address, andinstall-agent.shregisters it (re-run it after a change, removing the old registration as it tells you).The scripts (
setup.sh,doctor.sh,fg,backup.sh,restore.sh,install-agent.sh) act only on this checkout's install. Once.envexists, itsFG_INSTANCEnames the install: they stop whenFG_INSTANCEin your shell names another one, and they ignore a shell'sCOMPOSE_PROJECT_NAMEandCOMPOSE_FILE. A second install on one machine: docs/install.md.Neo4j browser: http://localhost:7476, bolt on 7689 (
NEO4J_HTTP_PORTandNEO4J_BOLT_PORTin.envmove them; both bound to 127.0.0.1).Transcripts (
~/.claude/projectsand Codex's~/.codex/sessions) and the project folders you chose are mounted read-only; nothing else from your home folder. The bundled./examplesfolder is a project root only if you add it (setup.sh --demodoes).Nothing is read until you choose which project folders to include (Settings). The worker lists every folder under your project roots with how many Claude Code and Codex sessions ran in it, and reads only the ones you select. A folder that is not a git repo but holds repos (
~/src/github.com/org/repo,~/code/client/app) lists each repo as its own project (github.com__org__repo); sessions belong to the deepest repo they ran in. A Claude Code session started in the folder that holds your projects is that project's from the turn it starts working in one (as<id>#2when it moves on to another); one that never left that folder is listed under Outside the roots in Settings > Projects.Secrets are redacted before anything is stored or sent to a model, and secret files are never read (see SECURITY.md).
Health, backups, upgrades, running it behind TLS with a token: docs/operations.md.
Related MCP server: MIHAD
Mac app
On a Mac, a small menu bar app can watch and drive your install: a dot shows whether Feedback Graph is running (green), starting or stopping (orange), stopped (grey) or has a problem (red, with what is wrong), and its menu and window start, stop and restart it, open the dashboard signed in, pause or resume the learning loop and show the proposals waiting, run a health check (./scripts/doctor.sh, beside the Docker engine and Ollama as its last status check saw them), back up, show logs and upgrade. It needs macOS 13 or newer and the same Docker engine (OrbStack, Docker Desktop or Colima) and Ollama as the stack; it bundles neither. Build it from a checkout (the Xcode Command Line Tools, macOS 14 or newer to build):
./scripts/build-mac-app.sh # dist/Feedback Graph.app
./scripts/build-dmg.sh # dist/Feedback-Graph-<version>.dmg: open it and drag the app to ApplicationsIt uses the folder you chose (Choose Install Folder…) or that Get Feedback Graph… cloned (it clones this repository and opens Terminal on ./scripts/setup.sh); with neither, ~/Code/feedback-graph when that folder is an install. A saved folder that is no longer an install leaves it with none until you choose again. Quitting it leaves Feedback Graph running. What it runs, what it reads and how to sign a build to share: docs/install.md.
Connect your agents
Claude Code plugin (recommended for Claude Code)
The plugin in plugin/ bundles the MCP server, the skills and hooks that use the graph without being asked:
/plugin marketplace add ChaosDeSelva/Feedback-Graph
/plugin install feedback-graph@feedback-graphOn each request that asks for a change, the task's approved lessons, the past misses on the files it names and the AGENTS.md and CLAUDE.md files on their path are added to the prompt, in about 1,500 characters (a longer lesson is shown whole). The first time in a session it adds them all; after that, only a lesson, miss or instructions file not yet shown.
Before the first edit of a file, that file's lessons and past misses, when it has any.
Before it stops, once: if it changed files and has not run
check_before_donesince, it is asked to.After
/clearor a compaction, what was shown is forgotten, so it is shown again.Skills:
feedback-graph,/postmortem, andtest-from-feedback, which turns lessons and past misses into test cases for QA.
Hooks only act in folders you selected in Settings and stay silent when the Feedback Graph is unreachable. Set FEEDBACK_GRAPH_URL and FEEDBACK_GRAPH_TOKEN (your API_TOKEN) in your shell for a non-default install; FEEDBACK_GRAPH_HOOKS=off turns the hooks off. Use the plugin or install-agent.sh claude, not both. With the plugin installed, install-agent.sh registers no second server for the default install (another FG_INSTANCE still gets its own, feedback-graph-<name>, beside the plugin's): it writes this install's URL and API_TOKEN into the plugin's environment in ~/.claude/settings.json (restart Claude Code). Switching to the plugin from an earlier install-agent.sh setup: claude mcp remove feedback-graph --scope user and rm ~/.claude/skills/{feedback-graph,postmortem,test-from-feedback} first.
install-agent.sh (Codex, or Claude Code without hooks)
./scripts/install-agent.sh sets up every coding agent it finds; ./scripts/install-agent.sh claude or ... codex sets up one. It registers http://localhost:<API_PORT>/mcp from .env (8787 by default); point it at another install with FEEDBACK_GRAPH_URL=https://feedback.example.com. Without that it refuses a checkout that is not set up (no .env, or FEEDBACK_GRAPH_ENV naming a missing file), which would register the default port and repoint the skills at an unconfigured folder, and one whose install name (FG_INSTANCE) another folder's running containers use. When .env sets API_TOKEN, the MCP server is registered with it as a Bearer header. A second install on this machine (its own FG_INSTANCE) is registered as feedback-graph-<name> and leaves the first install's skill links alone.
Claude Code (
claudeon your PATH): thefeedback-graphMCP server at user scope (claude mcp add --transport http), and thefeedback-graph,postmortemandtest-from-feedbackskills linked into~/.claude/skills. Type/postmortem what it missedto record a miss. The skills are symlinks into this folder, so agit pullupdates them; if you move or delete this folder, re-run the script (or remove the links).Codex (
codexon your PATH, or aconfig.tomlorauth.jsonin~/.codex;CODEX_HOMEis respected): a[mcp_servers.feedback-graph]table in~/.codex/config.tomlwith the streamable HTTPurl(andhttp_headerscarrying the token, after which the file is made readable by you only), and the same guidance as the skill in~/.codex/AGENTS.mdbetween<!-- BEGIN feedback-graph -->and<!-- END feedback-graph -->markers. Ask Codex to record a miss for the post-mortem path.
codex exec (Codex without a person to approve) refuses MCP tool calls unless the server allows them: ./scripts/install-agent.sh codex --codex-auto-approve adds default_tools_approval_mode = "approve" to the table (in a terminal it asks); a mode you set to another value is kept, and it says so. --dry-run prints what it would change and changes nothing; --help lists the options. Re-running refreshes this install's registration with its current URL and token, rewrites the marked guidance only when needed, and backs up a Codex config once before changing it (a linked config.toml or AGENTS.md is written through; a link loop or a link into a missing folder is refused), keeping any other headers you added. It changes nothing and exits 1 when config.toml declares this install's server other than as a [mcp_servers.<name>] table, or would not parse after the change (checked with python3's tomllib, 3.11+; without it, a config.toml with multi-line strings or arrays is refused, and an edit of one without says it was not checked), or when a guidance BEGIN marker has no END marker of its own; a skill folder of your own in ~/.claude/skills is left alone and reported as skipped.
Agents and scripts
The MCP tools only read and report (with knowledge intake on, submit_notes also hands you notes to review). Decisions (choosing projects, scanning, confirming Inbox items, approving lessons) are yours in the UI. To let an agent or a script make some of them, set FG_OPERATOR_TOKEN in .env (openssl rand -hex 24) and restart. A request carrying it as a bearer may make the decisions OPERATOR_ALLOW lists; by default that leaves out deciding lessons, choosing which projects are read, deleting data (links included), settings, imports, integration keys, asking in Ask (which spends your subscription), deciding on or configuring the learning loop (loop_decide, loop_settings), and accepting claims or settling conflicts (claims_decide, conflict_decide), so what agents learn stays yours. It may clean up the runs it opened itself (fg cleanup --run <id>). Writes made with it show in Activity as operator:<name>.
./scripts/fg is a small CLI for it. It runs on the host's Node (22.18+ or 23.6+, after pnpm install), else inside the api container (docker compose exec api), and only in a checkout that is set up and runs its own containers:
./scripts/fg status --watch # what the worker is doing, models, connections
./scripts/fg projects select --key shop,api
./scripts/fg scan --all
./scripts/fg inbox list # pending items; inbox confirm|reject <id>... [--reason ...]
./scripts/fg lessons list # lessons approve|reject <id> needs lesson_decide in OPERATOR_ALLOW
./scripts/fg loop status # the learning loop; loop pause|resume|mode needs loop_settings
./scripts/fg decisions list # its audit log; approve|reject|keep|rollback|note need loop_decide--json prints raw answers; --as <name> names the agent in Activity. Reading works without the token. fg status --watch follows the live event stream. ./scripts/fg --help lists every command (status, pipeline, projects, scan, inbox, lessons, demo, map rebuild|drop, links, facts, docs, activity, sessions, evidence, cleanup, loop, decisions, know, conflicts, ingest, cloud, integrations, signin, doctor, setup); fg help <command> shows one command's options. fg decisions rollback <id> is a preview (what else it undoes, what blocks it) until you add --apply; fg decisions rollback --since 24h --apply rolls back every decision applied in that time, newest first.
From inside an agent session: with FG_OPERATOR_TOKEN set, a second MCP endpoint, http://localhost:<API_PORT>/mcp/admin, gives a coding agent the same decisions as tools: pipeline_status, list_projects, select_projects, scan_projects, inbox_list, inbox_get, inbox_decide, lessons_list, lesson_get, lesson_decide, link_decide, set_fact_relevance, cloud_sync_now, run_cleanup, session_evidence, rebuild_code_map, demo_seed, demo_remove, and for the learning loop decisions_list, decision_get, loop_status, decision_decide, decision_rollback, decision_note and loop_pause, and with knowledge intake on intake_submit, claims_list, claim_decide, questions_list, question_answer, conflicts_list and conflict_resolve. It answers only to the operator token (send it as the Bearer when you register the server). Each write tool needs its OPERATOR_ALLOW verb; the demo tools need only the operator token for demo-shop. Its lists are paged at 20 rows and its read tools are marked read-only, so Codex does not ask before each one; export FG_OPERATOR_TOKEN in the shell you start Codex from (see docs/operations.md). The regular /mcp endpoint stays read-and-report only. Headless Claude Code (claude -p) denies, never prompts, a tool it was not allowed: pass --allowedTools "mcp__plugin_feedback-graph_fg" "Bash(git diff *)" (with install-agent.sh claude instead of the plugin, mcp__feedback-graph). Testers: label requests with x-fg-run: <id> (add x-fg-sandbox: 1 to keep them away from real agents), then fg evidence <session> shows what the tool decided and fg cleanup --run <id> takes back what the run wrote. A wrong bearer is refused (401) on reads as well as writes.
For scripts: GET /api/openapi.json describes the whole local API (OpenAPI 3.1), including which routes are owner-only and which OPERATOR_ALLOW verb unlocks each. GET /api/events streams changes as server-sent events (stage, pipeline, scan, chat, activity), so a watcher does not have to poll.
How it works
Ingest: reads the Claude Code and Codex session transcripts of the folders you selected and maps each to its project folder, with the repo files each session read and edited. A project is also identified by its git remote (
origin, normalized to e.g.github.com/org/repo, credentials dropped): a second clone of a known repo in another folder is recorded under the existing project.Code map: each selected project's code is parsed (tree-sitter, no model) into files, symbols, imports, the tests covering each file and 90 days of churn, and kept current as the repo changes. Misses are linked to the files their session edited, so the graph can say which modules agents keep getting corrected on, and why.
Detect: a keyword prefilter, then a classifier (gemma4 by default), flags pushback. It handles the newest feedback first. An unusable answer is asked again strictly, then once with a short prompt; a turn given up after 3 tries is re-queued at the next worker start after the classifier model or the version changes.
Inbox: you confirm or reject each flagged moment. Nothing is learned without your confirmation, except that once your reviews have earned it, the learning loop's
triage_inboxmay reject an item or confirm a GitHub or Linear comment on its own (an item from your own sessions, or an agent's report, it only proposes to confirm). Agents can also report a miss (report_feedback, e.g. from/postmortem); a report of pushback the detector already flagged becomes part of that same Inbox item, with both sources kept. The same complaint said again in one project joins the first item: within an hour from the same session or in mostly the same words, or reworded (cosine 0.85 or closer) within 3 days.Post-mortem: works out what was missed, why (a typed cause), and which context would have prevented it. The context hunt covers code facts, uploaded docs, PR reviews and Linear tickets. The proposed lesson is scoped to the files the fix touched when the evidence supports it, and gets lesson triggers: path globs, added-line patterns and symbols, taken from the lesson itself (its paths, the file a review comment was on, the files its fix edited and the code-shaped names its rule uses), never from the code the post-mortem cited. A glob for a kind of file anywhere (
**/*.sql) is kept only when the rule says it of every such file, and is flagged trigger fires repo-wide for you; a post-mortem whose answer is still not about the flagged turn when asked again is flagged needs review: not about the flagged turn and never approved on its own. It also reads the instruction files the agent had: CLAUDE.md and AGENTS.md in the repo and each parent folder up to your project root, and Claude Code's memory notes, each labelled with whether that session's agent loads it (Claude Code: CLAUDE.md and memory; Codex: AGENTS.md).Lessons: you approve, edit (paths and triggers included) or reject them, with a note. Only approved lessons reach agents. Each time a project's code map is rebuilt, approved lessons are checked again: one whose path, trigger glob or named function no longer exists is flagged its code changed, never revoked for you.
Agents use MCP. Only
report_feedback(and, with knowledge intake on,submit_notes) writes; the other tools are marked read-only.get_task_contextserves approved lessons, past misses, code facts and doc excerpts. Given the files the agent will touch, it puts the lessons tied to those files first, adds a short code map of them (importers, tests, last miss) and lists the AGENTS.md and CLAUDE.md files on their path, nearest first. A lesson whose triggers match the files, or a code-shaped name in the task's words, is served too, saying which trigger fired.A past miss is served or judged only when it shares a file with the work or is at least
MISS_MIN_SIMILARITY(0.85) similar to it.get_module_contextdescribes one file (facts, exported symbols, importers, imports, tests, and the lessons and misses tied to it);impactlists what depends on a set of files, the tests to run, and the lessons and misses there. A file past the code map's cap (SCAN_MAX_FILES) is answered in full too, its symbols parsed on demand; a file the map does not hold at all (a new one) gets the lessons its path or a trigger names, then those close to it by meaning, marked to check.check_before_donehas the judge (CHECK_LLM) check finished work against those lessons and against lessons whose triggers fire on the diff's added lines or changed files. A lesson with no verdict counts as a gap. When a lesson requires a change to a file it names (a test, a doc) and the change leaves that file out, the gap says so ("you forgot X"). Givenfiles_changed, it also names the importers and tests of those files the change did not touch, and what the last misses there were about.search_graphfinds lessons, misses, facts and docs by meaning;get_recent_sessionsreturns session summaries (step 8);report_feedbackrecords a miss (step 4).With knowledge intake on:
what_do_we_knowanswers what you accepted about a topic,find_contradictionschecks a statement against the facts and approved lessons on its scope (no model), andsubmit_noteshands you an agent's notes as a private source to review. While a conflict is open, the lessons and facts on either side are served marked[disputed: <conflict>], with both sides.
Session summaries: each session of the selected projects gets a running summary (goal, what was done and decided, open threads, pushback, files touched, outcome). The worker keeps a cursor per session, the last turn the summary covers: once a session has been quiet for
SUMMARY_QUIET_MINUTES(orSUMMARY_MAX_DELTA_TURNSturns are waiting), only the turns after the cursor are sent, with the previous summary, and the model returns the merged one (a big delta is summarized in parts, then merged). Quiet forSESSION_IDLE_MINUTESand summarized to its last turn, a session is idle ("complete for now"); a new turn makes it active again and the summary is extended. Sessions from before this are summarized a few per cycle, newest first. Agents get them throughget_recent_sessions, andget_task_contextlists the three most recent; post-mortems read the summary of the session they look at. Summaries are redacted, and never exported or synced.Activity: every fact, code map build, flag, decision, post-mortem, summary update, served context and check is logged with who or what did it and why.
Learning loop: the worker reads the Activity log into each lesson's track record (served, judged, caught a gap, repeated) and your verdicts on its decisions into how far it may trust itself, then links repeated misses, merges duplicate lessons, re-ranks, retires, rewords, narrows and approves lessons, moves a lesson's paths when its file is renamed, triages the Inbox and, with intake on, helps with claims, by the mode you choose. Every decision is on the Decisions page with what it changed and why, and can be rolled back.
The learning loop
It runs in auto by default. In auto, housekeeping (linking repeated misses, merging duplicates, re-ranking, retiring lessons that never help) applies on its own. A change to what agents are told (approving, rejecting, rewording or narrowing a lesson, turning one into a fact) applies on its own only once your own verdicts on that type's proposals have earned it, counted per type and per confidence band (one of 5, by the call's score): at least 20 of them in the call's band, with that band's lower bound at the type's bar τ or above (0.9 for approve_lesson, which approves a proposed lesson without you), and then at most 5 such changes a day, 2 per project. Until then it proposes, and you approve or reject on the Decisions page, which opens on needs you: only what it could not decide, each with a one-line reason (always your call, not earned yet, under its bar). Every item waiting for you carries an approve score (0 to 10, the chance you approve it, ranked from your own verdicts on the most similar items you decided and pulled toward 5 while evidence is thin; it orders the list and never decides what applies on its own; see the guide). An operator token's verdicts never count.
Housekeeping also moves a lesson's paths from a renamed file's old name to its new one (
update_paths), and with knowledge intake on links claims that say the same thing and folds a duplicate claim into the one it repeats.Agent-facing, once earned:
triage_inbox(bar 0.9, from 30 of your reviews in its band) rejects Inbox items and confirms GitHub and Linear comments, never an item from your own sessions or an agent's report; and with intake onaccept_claim(bar 0.92) accepts a fact or decision claim whose quote matches its source and whose speaker you confirmed, never an opinion or an agent's notes.Always proposals: a project's own similarity floors (
tune_threshold), and with intake on suggesting how to settle a conflict, who should answer an open question, or a later claim that answers one. Settling a conflict is always yours.Two lessons of one project that are close (cosine 0.85 up to the duplicate flag's 0.92) are judged, by their words or the loop's model, and a likely duplicate is only ever proposed. Rolling back a merge, or Never merge these two on a merge decision, records the pair as not the same: the loop never proposes merging it again, and only you lift that.
observe records what it would do and changes nothing; propose asks you about everything; off stops it. Set the mode in Settings > Learning loop or with fg loop mode propose; LOOP_MODE in .env seeds it on the first start only, and LOOP_DISABLED=1 stops it whatever Settings says. Upgrading to 0.8.0 with API_TOKEN or FG_OWNER_TOKEN set, the first start seeds auto, so housekeeping starts applying on its own: to start more cautiously, put LOOP_MODE=observe or LOOP_MODE=propose in .env before that first start. An install with neither token only observes.
Knowledge intake (0.9.0, opt-in)
Hand Feedback Graph what people said, a call transcript (txt, md, vtt, srt) or notes, and it breaks it into claims (facts, decisions, plans, commitments and opinions, each with who said it, when and the quote) and the questions it left open, for you to review on the Intake page. It also checks claims, facts and approved lessons against each other and flags conflicts: two things agents may be told that disagree, shown side by side for you to say which is true. Agents only ever see the claims you accept (an accepted opinion stays attributed and is never given to them), and each conflict you settle is a decision on the Decisions page that you can roll back.
It is off by default. To turn it on, set INTAKE_ENABLED=1 in .env and run docker compose up -d; Intake
then appears in the sidebar's Review group. Add a source with Add a source on its Sources tab (paste it or pick a
.txt, .md, .vtt or .srt file), POST /api/intake or the admin MCP tool intake_submit; an agent can hand you its notes with submit_notes. It reads on the local model by default
(INTAKE_LLM), and only Ollama or claude-cli unless you widen INTAKE_PROVIDER_ALLOW.
Privacy: credentials and contact details are redacted before anything is stored or sent to a model, and speakers reach
the model only as S1, S2, ... (names spoken inside the text do reach it). Transcript text, quotes, questions and
sources are never served to agents or synced. An export carries your accepted claims, settled conflicts and who said
them, never a source's text or questions, and quotes only when asked for (GET /api/export?quotes=1). Claim facts leave
for the cloud only with SYNC_CLAIMS=true (or Settings > Cloud sync), never a private source's: a transcript or an
agent's notes is private by default.
From a terminal: fg ingest call.vtt --project k --wait (a pdf is converted on your machine with pdftotext, and a
docx, odt, rtf, html or epub with pandoc, when installed). It reads the file with the host's Node; where fg runs
inside the api container instead, pipe the text in: fg ingest - --project k < call.vtt.
Ask "what do we know about X": the Ask starter What do we know about …?, fg know "<topic>", GET /api/knowledge
or, for agents, the MCP tool what_do_we_know (accepted knowledge only) answer with the claims, facts and approved
lessons on it, who said each and when, its open questions, and what is disputed. fg conflicts --open lists the
conflicts waiting for you; you settle each on the Intake page's Conflicts tab (A is true, B is true, Both
partly or Not a conflict), and Undo on the Decisions page reopens it. Agents can check a statement first with
find_contradictions.
How to use the page: docs/guide.md. Settings, privacy, deleting and cost: docs/operations.md.
UI
Page | What |
Dashboard | The landing page: a Getting started list until its steps are done, what the worker is doing, model health, pipeline counts, repeat and catch rates, projects, hotspots, recent activity, and an Ask box |
Ask | Chat about one project, one conversation per topic ("I want to add a modal", "where would feature X go?"). Answers cite the lessons, misses, facts, docs and code they come from, show existing examples and where they are used, and end with a prompt for your agent to copy into Claude Code or Codex, with the Feedback Graph tools to use. Runs on Claude, Codex or the local model ( |
Inbox | Flagged feedback to confirm or reject ( |
Intake | With knowledge intake on: Sources (Add a source), Claims to accept or reject, open Questions, Conflicts to settle and the People who said them |
Lessons | Evidence chain per lesson, edit (rule, scope, paths, triggers) and approve or reject with a Note, plus its history |
Graph | Clickable map from feedback to miss, cause, lesson, context and fix commits (three per item at most, with how many more); Code mode draws a project's code map (hotspot files, imports, tests, and the misses and lessons on them), and a file opens its module panel |
Patterns | Misses by cause, repeated misses, weekly trend, pre-done catch rate, where agents struggle (top hotspots) |
Sessions | Each session's running summary (filter by project and active/idle), its update history, files touched, the Inbox items and lessons from it, and Rebuild summary |
Projects | Pin a project (pinned ones come first in every list), link it to the projects it belongs with (the worker suggests links from what is in them; you confirm), code map (stats, sortable hotspots with an include-tests toggle, a module panel per file linking to its lessons and Inbox items), scan code into facts (choose the model), browse facts by relevance with "how was this created?" and set a fact's relevance, upload docs and images that the worker reads into facts |
Decisions | The learning loop's audit log, opening on needs you (what it could not decide, and why): every decision with its before and after, evidence and rationale; approve or reject with a note, keep or roll back (with what else a rollback undoes), Never merge on a merge, notes, JSON and CSV export |
Learning loop | The loop at work: its mode and Pause, this round, live events, each type's bar and earned confidence, recent decisions, a lesson's track record and the holdout comparison |
Activity | The provenance log, filterable by project and kind |
How to read each page, with screenshots: docs/guide.md.
The sidebar shows whether the worker is running, and a connections list (GitHub, Linear, Claude, Codex, Ollama, cloud sync) with each one's state and a link to where it is set. Projects also has Export lessons, misses and facts (JSON) (a versioned bundle; with knowledge intake on it adds your accepted claims, settled conflicts and who said them), Import bundle (its lessons arrive proposed unless you keep them approved; revoked ones stay revoked; claims keep their status) and Delete project data…. Settings has five tabs: Projects (the project folders to read), Integrations (GitHub and Linear), Cloud sync (always shown; it says how to turn team sync on until CLOUD_URL and CLOUD_TOKEN are set, and with knowledge intake on whether claim facts are synced and name who said them), Learning loop (its mode, per-type modes and bars, budget and Pause) and Clean up.
Configuration (.env)
Variable | Purpose |
| The project folders that are mounted, comma-separated. |
| Worker pace and bounds: seconds between cycles (30); turns classified per cycle (50); files per project the code map keeps (empty: 40% of the files worth mapping, from 400 to 4000, source and the files your sessions edited first) and changed files a scan reads per run (empty: 400; a number sets both); the largest file a scan reads, in bytes (80000); and the largest source file the code map parses (1000000). Empty: the default. |
| Scans run at once, beside the worker cycle (2). Agentic scans ( |
| Sent with every Ollama call: the most tokens an answer may take (empty: each job's own budget, classify 256, relevance 1024, Ask 6144, post-mortems 3072, the learning loop's rewrites 1024, its rewrite and near-duplicate judges 256 and its replay judge 512, the intake reader 4096 and the conflict judge 512, others 2048), the context window (16384; one value for every call, so the model is not reloaded), the repeat penalty (1.1), and how long one call may take (empty: each job's own limit). Structured calls also send |
| How close (0 to 1, default 0.85) a past miss must be to the work for |
| The judge's calls at once (2; the rest wait in line), and how long one check's judge call may run, waiting included (300). A response comes back within 50 s either way, "queued" while the call runs; the same check again joins it. A call still unanswered at the limit is cancelled and the check answers "not judged" with the lessons to check by hand. |
| The model that answers Ask questions (same forms as the other jobs). Empty: |
| Seeds the project selection on the very first start only (e.g. |
| Never read, scan or serve these projects, whatever is selected. Settings shows them as excluded and cannot select them. |
| Session transcripts on the host: Claude Code's (default |
| The Docker Compose name of this install: its containers, network and volumes. Empty: |
| Host ports: the UI, API and MCP server (8787, on |
| Linux: the uid and gid the worker, api and codex-runner run as (default 1000). |
| Remove the text of unflagged turns older than this. Empty keeps everything. While summaries are on, a turn keeps its text until its session summary covers it; summaries are never removed. |
| Session summaries: the model (same specs as below; empty: the local model; |
| The learning loop. |
| The loop's model (same forms as the other jobs; empty: the local model) and its model calls a day (40); agent-facing changes it applies on its own a day (5) and per project (2); days a lesson it changed is still shared with the cloud as it was before (7); the share of lessons it never changes, to compare against (0.2, at most 0.5); and the decision types that run (comma-separated; empty: all). |
|
|
| Let agents and scripts (the |
| GitHub logins whose comments are a bot's (skipped) or an AI agent's (kept and labelled), comma-separated. Accounts GitHub marks as bots are skipped anyway. |
| Reach the API from other machines: agents use |
|
|
| Without |
| From |
| Model per job: |
| Bring-your-own keys |
| GitHub and Linear tokens. Optional: you can paste them in Settings instead. A token saved in Settings wins; the one in |
| Optional override of the repository a project's PR comments come from ( |
| Minutes between rounds of looking for links between the selected projects (30). No model: package manifests, mentions of another project's repository, and its name in several files. |
| How often each project's review comments and Linear comments are read into the Inbox (10), and how often PR discussions are refreshed as context (6). |
| Storage backend: |
| Optional team sync to a Feedback Graph Cloud org. Off unless the first two are set. See Team sync. |
| Knowledge intake, off unless |
The Codex provider uses your ChatGPT login: CODEX_AUTH_FILE (only that file is mounted). It explores a copy of the repository without secret files, in the codex-runner container when CODEX_RUNNER_URL is set.
OLLAMA_EMBED_MODEL is recorded in the graph; changing it later needs pnpm reembed (the services tell you).
GitHub and Linear (optional)
Pushback does not only happen in a session: reviewers push back in pull request reviews, and in comments on Linear issues. Connect GitHub or Linear and those comments are read into the Inbox like pushback in a transcript: the same classifier labels each one, and pushback or a correction becomes an Inbox item marked GitHub or Linear, with the author, a link to the comment, the PR or issue title, and for a review comment the file, line and diff lines. You confirm or reject it as usual; a confirmed one gets a post-mortem.
Settings > Integrations. Paste a token and press Connect: it is checked with one read-only call (the account it belongs to is shown) and saved only if it works. Or set
GITHUB_TOKEN/LINEAR_API_KEYin.env(shown as "set in .env"); a token saved in Settings wins over it, so you can replace a.envkey without editing the file. Test connection checks it again; Disconnect (after a confirm) deletes a token saved in the app and goes back to the.envone, if any.GitHub: a fine-grained personal access token with access to only your projects' repositories, and Pull requests and Issues set to Read-only. Nothing is ever written to GitHub.
Linear: a personal API key from Settings, Security and access.
Repositories are found for you. Each selected project whose origin is on github.com is linked to that repository (
GITHUB_REPOSoverrides it). Turn one off in the project table. For Linear, choose the team for each project.What is read. Every
INTEGRATION_SYNC_MINUTES(10), per project: review comments, review bodies and conversation comments on pull requests updated since the last sync, and comments on the mapped team's issues. Bots and integrations are skipped. The first sync reads the last 14 days only. Only selected projects are read.The work behind a comment. A comment is linked to the agent session that most likely did the work: a session of that project on the PR's head branch (or the Linear issue's branch) that started before the comment; else, of the sessions active in the seven days before it, the one that edited the most of the PR's files (the commented file, for a review comment). Being nearest in time is never enough, and a tie is no match. Its post-mortem then reads that session (its turns, edited files and summary) beside the comment; with no session found it works from the comment and the diff lines alone.
A token pasted in Settings is kept in state/api-settings/integrations.json (mode 600, INTEGRATIONS_FILE): never in the
database, never returned by the API, logged, exported or synced. Comment text is redacted before it is stored or sent to a
model, and calls go only to api.github.com and api.linear.app. A rejected token (revoked or expired) pauses that service
until you replace it, and a rate limit pauses it until the limit lifts; both show in Settings and in the sidebar's
connections list, which shows GitHub, Linear, Claude, Codex, Ollama and cloud sync at a glance. PR discussions are also kept
as project context for post-mortems and get_task_context, as before.
Team sync (optional)
The tool is complete on its own and sends nothing anywhere by default. Feedback Graph Cloud is a separate, hosted team product built on this core: a shared team graph, single sign-on, a team MCP endpoint and pull request checks. If your team uses it, the sync client in this repo pushes your project knowledge to your org:
In the cloud web app, create an access token with the
syncscope.Set
CLOUD_URL=https://<your cloud>andCLOUD_TOKEN=fgc_pat_...in.env, thendocker compose up -d.Press Sync now on the Cloud sync card in Settings, or set
SYNC_INTERVAL_MINUTES=60to sync on a schedule.SYNC_PROJECTS=a,bnarrows which selected projects are sent.
What is sent, per selected project: its key and git remote, its approved lessons, misses (what was missed and why),
current code facts, document titles and links, and the code map's paths with their hotspot numbers. Only lessons you
approved leave: they become the team's (or wait for an admin, if your org reviews them). A proposed lesson stays on
this machine until you approve it; SYNC_SUGGESTIONS=true sends proposed ones too, as suggestions for an admin. A
lesson you reject or move back to proposed, or anything you delete, is taken back out of the team graph on the next
sync, and so is everything sent for a project you stop syncing (unselected, excluded, or left out of SYNC_PROJECTS):
it gets one last, empty bundle. What the cloud last accepted is kept in the store, so this holds across restarts.
With knowledge intake on, the facts your accepted claims became go only with SYNC_CLAIMS=true (or its switch in Settings > Cloud sync), never a private source's and never a quote. Never sent: transcripts (including the feedback you typed), intake sources with their quotes and questions, session summaries, source code, excerpts, document text, embeddings, or
anything new from an unselected or excluded project (its last bundle names only what was already sent). Every sync is logged in Activity. Details:
docs/operations.md.
Storage
Everything the tool learns is stored in Neo4j by default, which also gives you the Neo4j browser to explore the graph.
It can run on Postgres with pgvector instead, with the same code: every repository is an interface (packages/core/src/store.ts) with a Neo4j and a Postgres implementation (packages/store-postgres), and one contract test suite runs against both in CI. Redaction, Activity entries, export and delete behave the same on either.
# in .env: STORE=postgres, POSTGRES_PASSWORD=<letters and digits> (setup.sh generates one) and COMPOSE_PROFILES=postgres
docker compose up -d --buildThe postgres profile adds a pgvector/Postgres 17 container (127.0.0.1:5434, volume postgres-data). To use your own Postgres 17 server with the vector extension available (pgvector 0.8.0 or newer; an older one is refused at startup), set DATABASE_URL=postgres://user:password@host:5432/db and leave the profile off. The schema is migrated at startup. /api/status and /api/ready report which store is in use. Switching backend starts empty: to carry your lessons and code facts over, Export on the old one and Import bundle on the new one's Projects page (see docs/operations.md). Backups in Postgres mode use pg_dump (./scripts/backup.sh does it for you).
Try it on the demo project
examples/demo-shop is a small multi-tenant API with rules in its CLAUDE.md.
./scripts/setup.sh --demo adds this repo's examples folder as a project folder and selects demo-shop (on an install whose selection was already made in Settings, select demo-shop there too); with the stack started it seeds the demo lessons, and fg demo context and fg demo check show them at work. To see the whole loop from a real session instead, with a Claude Code login:
node scripts/demo-probe.mjs prepare --apply --configure --api-url http://localhost:8787 # a dedicated, selected copy of demo-shop
./scripts/demo-pushback.sh # a real Claude session there plus a pushback turn; its edits stay in the copyThe copy goes to ~/Code/demo-shop (--folder and FG_PROBE_DIR move it), which must lie under a mounted project folder; docs/demo-probe.md has the details.
Then open the Inbox, confirm the flagged pushback, wait for the post-mortem, and approve the lesson. Ask for context on a similar task (an invoices list) and run check_before_done with a summary that skips the sort order: the lesson and the gap both show up. node scripts/demo-probe.mjs restore --apply puts the project selection back afterwards.
Develop
pnpm install
pnpm test # unit + ui
docker compose -f docker-compose.test.yml up -d --wait # the test Neo4j and Postgres
TEST_STORES=neo4j,postgres pnpm test:int # contract suites on both backends
TEST_STORES=neo4j,postgres pnpm test:coverage:all # every suite with coverage, held to the floor in vitest.config.ts
./scripts/check.sh # lint, typecheck, the coverage gate, the uid check, the UI build;
# the history check only when a `public` branch existsCI runs lint, typecheck, the coverage gate and the UI build too, always checks the pushed history, and builds both Docker images; it skips the uid check.
See the installation guide for a fresh installation, a second local instance and the small memory profile.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Team memory for AI coding agents and code reviews, reviewed by humans and served over MCP.
Behavioral drift context (Behavioral Load Map, hot paths, release brief) for coding agents, per PR.
Your team's shipping standards, org map and delivery metrics, inside your coding agent.
Project memory for coding agents: requirements, decisions, code graph and delivery telemetry.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides coding agents with durable, cross-session lessons-learned memory, enforcing that success or failure verdicts can only come from human approval, human correction, or objective metrics—never from the agent itself.Apache 2.0
- FlicenseAqualityBmaintenanceEnables coding agents to build and query a verified project memory through the MCP protocol, so skills, facts, and user preferences are captured as experience and adopted only after passing independent checks. It also briefs agents at each request, flags known pitfalls live, and runs an executable review before changes are finalized.4-
- AlicenseNot gradedqualityBmaintenanceBriefs a coding agent with the repository's conventions, architecture decisions, and proven checks before every task, and lets the agent record new lessons as work proceeds. Entries are proven by running checks and flagged as stale when the code, docs, or diagrams they govern change, so agent behavior and documentation stay from drifting.442 PyPI4MIT

Tently MCPofficial
AlicenseNot gradedqualityBmaintenanceEnables coding agents such as Claude Code and Cursor to check a team's learned decisions from pull requests, reviews, and docs before writing code, showing whether breaking them is blocking or advisory. It also provides locally computed symbol impact/blast-radius analysis so source code never leaves the machine.67 npm2MIT