{"slug": "the-graph-that-learns-building-self-improving-agent-loops", "title": "The Graph That Learns: Building Self-Improving Agent Loops", "summary": "A developer has built a prototype of a self-improving AI agent that combines agent loops with graph-based memory to accumulate knowledge from completed tasks. The system records task outcomes, file relationships, and reviewer information in a graph, enabling the agent to start future similar tasks with prior context instead of from scratch. This approach aims to give agents institutional memory, making them more efficient at solving recurring problems.", "body_md": "AI agents are getting very good at doing things.\n\nThey can read a ticket, modify code, open a pull request, query an API, send an email, and keep going until a goal is reached.\n\nBut there is a problem hiding underneath all of this:\n\n**Most agents don't actually get smarter from doing the work.**\n\nThey execute a loop, finish the task, and forget what happened.\n\nThe next time the same problem appears, they start over.\n\nThat feels wrong.\n\nA useful agent shouldn't just complete tasks.\n\nIt should **accumulate knowledge about how to complete those tasks better.**\n\nThis is where two ideas become extremely powerful when combined:\n\nAgent loops provide action. Graphs provide memory.\n\nAnd when the graph is updated by the agent's own experience, you get something much more interesting:\n\nA self-improving system.\n\nIn this post, we're going to build a small version of that idea.\n\nConsider an engineering agent with this goal:\n\n```\nFix the failing checkout test.\n```\n\nThe agent might:\n\n```\nRead issue\n    ↓\nInspect repository\n    ↓\nFind failing test\n    ↓\nInspect implementation\n    ↓\nModify code\n    ↓\nRun tests\n    ↓\nFix failure\n    ↓\nOpen pull request\n```\n\nGreat.\n\nBut tomorrow another checkout test fails.\n\nThe agent starts from scratch.\n\nIt doesn't remember:\n\nThe agent has intelligence.\n\nBut it has no **institutional memory**.\n\nThat's a huge difference.\n\nAt its simplest, an agent is not magic.\n\nIt's a loop:\n\n```\nGoal\n ↓\nObserve\n ↓\nChoose action\n ↓\nExecute\n ↓\nCheck result\n ↓\nRepeat\n```\n\nWe can write that conceptually as:\n\n``` js\nwhile (!goalComplete) {\n  const observation = observe();\n\n  const action = decide(\n    observation,\n  );\n\n  const result = execute(\n    action,\n  );\n\n  check(result);\n}\n```\n\nThis is the core of an agent.\n\nThe model provides reasoning.\n\nTools provide capabilities.\n\nThe loop provides persistence toward a goal.\n\nBut there's another component we need.\n\n**Learning.**\n\nImagine the agent completes a task.\n\nInstead of simply returning:\n\n```\nTask complete.\n```\n\nit records what happened:\n\n```\nTask:\nFix checkout timeout.\n\nObservation:\nCheckout requests were being retried twice.\n\nAction:\nChanged retry behavior in checkout.ts.\n\nResult:\nTests passed.\n\nRelated files:\npayments.ts\nretry.ts\n\nReviewer:\nmaya\n\nOutcome:\nMerged.\n```\n\nNow imagine that the next checkout problem occurs.\n\nThe agent can see this previous experience.\n\nInstead of starting from zero:\n\n```\nWhat is checkout.ts?\n```\n\nit can start with:\n\n```\nPrevious changes to checkout.ts indicate\nthat retry.ts and payments.ts are frequently\ninvolved.\n\nA previous fix modified retry behavior.\n\nLet's inspect those relationships first.\n```\n\nThat's a dramatically better agent.\n\nBut there is an important question:\n\n**How should we store the memory?**\n\nYou could dump everything into a document.\n\nSomething like:\n\n```\nPrevious task:\nCheckout timeout.\n\nFiles:\ncheckout.ts\npayments.ts\nretry.ts\n\nPeople:\nMaya\nBobby\n\nSolution:\nChanged retry behavior.\n```\n\nThis works.\n\nUntil you have 10,000 tasks.\n\nThen you have a giant pile of text.\n\nThe interesting information isn't just the facts.\n\nIt's the **relationships**.\n\nFor example:\n\n```\nTask\n │\n ├── affected → checkout.ts\n │                 │\n │                 └── related → retry.ts\n │\n ├── fixed-by → commit #823\n │\n ├── reviewed-by → Maya\n │\n └── resulted-in → merged PR\n```\n\nNow we have a graph.\n\nAnd graphs give us something extremely useful:\n\n**Traversal.**\n\nWe can ask:\n\n```\nWhat happened to this file?\n\nWho worked on it?\n\nWho reviewed those changes?\n\nWhat other files changed with it?\n\nWhich previous tasks touched this area?\n\nWhich approaches worked?\n\nWhich approaches failed?\n```\n\nThe agent doesn't need to remember everything.\n\nIt needs to know **where to look**.\n\nLet's build a small prototype.\n\nOur agent will have one goal:\n\n```\nInvestigate a failing test.\n```\n\nIt will have four tools:\n\n```\ntype Tool =\n  | \"search_code\"\n  | \"read_file\"\n  | \"run_tests\"\n  | \"inspect_history\";\n```\n\nAnd it will maintain a graph containing:\n\n```\nTask\nFile\nTest\nChange\nPerson\nOutcome\n```\n\nRelationships will include:\n\n```\nAFFECTS\nREAD\nMODIFIED\nFIXED\nFAILED\nREVIEWED\nRELATED_TO\n```\n\nThe important part is that **agent activity becomes graph data**.\n\nCreate a simple graph implementation:\n\n```\ntype NodeType =\n  | \"task\"\n  | \"file\"\n  | \"test\"\n  | \"change\"\n  | \"person\"\n  | \"outcome\";\n\ntype Relationship =\n  | \"AFFECTS\"\n  | \"READ\"\n  | \"MODIFIED\"\n  | \"FIXED\"\n  | \"FAILED\"\n  | \"REVIEWED\"\n  | \"RELATED_TO\";\n\ntype Node = {\n  id: string;\n  type: NodeType;\n  label: string;\n  metadata?: Record<string, unknown>;\n};\n\ntype Edge = {\n  from: string;\n  to: string;\n  type: Relationship;\n  metadata?: Record<string, unknown>;\n};\n```\n\nThen:\n\n```\nclass Graph {\n  private nodes = new Map<string, Node>();\n  private edges: Edge[] = [];\n\n  addNode(node: Node) {\n    this.nodes.set(node.id, node);\n  }\n\n  addEdge(edge: Edge) {\n    this.edges.push(edge);\n  }\n\n  getNode(id: string) {\n    return this.nodes.get(id);\n  }\n\n  neighbors(\n    id: string,\n    relationship?: Relationship,\n  ) {\n    return this.edges\n      .filter((edge) => {\n        if (edge.from !== id) {\n          return false;\n        }\n\n        if (\n          relationship &&\n          edge.type !== relationship\n        ) {\n          return false;\n        }\n\n        return true;\n      })\n      .map((edge) => ({\n        edge,\n        node: this.nodes.get(edge.to),\n      }))\n      .filter(\n        (result) => result.node !== undefined,\n      );\n  }\n}\n```\n\nThat's enough for our prototype.\n\nWe don't need Neo4j.\n\nWe don't need a distributed graph database.\n\nWe don't even need persistence yet.\n\nWe're trying to understand the architecture.\n\nNow let's represent an experience.\n\n```\ntype AgentExperience = {\n  task: string;\n  observations: string[];\n  actions: string[];\n  result: \"success\" | \"failure\";\n  files: string[];\n};\n```\n\nSuppose our agent successfully fixes a checkout problem.\n\nWe can record:\n\n``` js\nconst experience: AgentExperience = {\n  task: \"Fix checkout timeout\",\n  observations: [\n    \"Requests were retried twice\",\n    \"payments.ts was involved\",\n  ],\n  actions: [\n    \"Inspected checkout.ts\",\n    \"Inspected retry.ts\",\n    \"Changed retry behavior\",\n    \"Ran checkout tests\",\n  ],\n  result: \"success\",\n  files: [\n    \"checkout.ts\",\n    \"retry.ts\",\n    \"payments.ts\",\n  ],\n};\n```\n\nNow turn that experience into graph nodes.\n\n``` js\nconst taskId = \"task:checkout-timeout\";\n\ngraph.addNode({\n  id: taskId,\n  type: \"task\",\n  label: experience.task,\n});\n\nfor (const file of experience.files) {\n  const fileId = `file:${file}`;\n\n  graph.addNode({\n    id: fileId,\n    type: \"file\",\n    label: file,\n  });\n\n  graph.addEdge({\n    from: taskId,\n    to: fileId,\n    type: \"AFFECTS\",\n  });\n}\n```\n\nWe have transformed an experience into structured memory.\n\nNow let's create the actual loop.\n\n```\ntype AgentState = {\n  goal: string;\n  observations: string[];\n  actions: string[];\n  complete: boolean;\n};\n\nasync function runAgent(\n  goal: string,\n  graph: Graph,\n) {\n  const state: AgentState = {\n    goal,\n    observations: [],\n    actions: [],\n    complete: false,\n  };\n\n  while (!state.complete) {\n    const context =\n      buildContext(state, graph);\n\n    const decision =\n      await decideNextAction(\n        state,\n        context,\n      );\n\n    state.actions.push(\n      decision.action,\n    );\n\n    const result =\n      await executeTool(\n        decision.action,\n        decision.input,\n      );\n\n    state.observations.push(\n      result,\n    );\n\n    state.complete =\n      await isComplete(\n        state,\n      );\n  }\n\n  return state;\n}\n```\n\nThis is a normal agent loop.\n\nBut notice this:\n\n``` js\nconst context =\n  buildContext(state, graph);\n```\n\nThe graph is now part of the agent's observation system.\n\nThe agent doesn't just observe the repository.\n\nIt observes its **accumulated experience**.\n\nThat's the beginning of a self-improving agent.\n\nNow comes the interesting part.\n\nWhen the task finishes, we update the graph.\n\n``` js\nfunction recordExperience(\n  graph: Graph,\n  state: AgentState,\n) {\n  const taskId =\n    `task:${crypto.randomUUID()}`;\n\n  graph.addNode({\n    id: taskId,\n    type: \"task\",\n    label: state.goal,\n  });\n\n  for (const action of state.actions) {\n    const actionId =\n      `action:${crypto.randomUUID()}`;\n\n    graph.addNode({\n      id: actionId,\n      type: \"change\",\n      label: action,\n    });\n\n    graph.addEdge({\n      from: taskId,\n      to: actionId,\n      type: \"MODIFIED\",\n    });\n  }\n\n  const outcomeId =\n    `outcome:${crypto.randomUUID()}`;\n\n  graph.addNode({\n    id: outcomeId,\n    type: \"outcome\",\n    label: state.complete\n      ? \"Success\"\n      : \"Failure\",\n  });\n\n  graph.addEdge({\n    from: taskId,\n    to: outcomeId,\n    type: state.complete\n      ? \"FIXED\"\n      : \"FAILED\",\n  });\n}\n```\n\nNow the graph has changed because of the agent's experience.\n\nThat's the key.\n\nThe system isn't just reading a knowledge base.\n\n**The agent is writing back into it.**\n\nNow let's say another task appears:\n\n```\nFix checkout requests timing out.\n```\n\nBefore deciding what to do, the agent queries the graph.\n\n``` js\nfunction findRelevantExperience(\n  graph: Graph,\n  query: string,\n) {\n  const tasks =\n    graph.neighbors(\n      \"task:checkout-timeout\",\n    );\n\n  return tasks;\n}\n```\n\nIn a real system, we'd use semantic search, graph traversal, or both.\n\nThe important concept is:\n\n```\nNew task\n   ↓\nFind similar experiences\n   ↓\nTraverse relationships\n   ↓\nBuild context\n   ↓\nChoose action\n```\n\nNow the agent can start with:\n\n```\nPrevious experience:\n\ncheckout.ts\n    ↓\nretry.ts\n    ↓\nsuccessful fix\n\nPrevious action:\nChanged retry behavior.\n\nPrevious outcome:\nTests passed.\n\nPotential next action:\nInspect retry.ts first.\n```\n\nThe second agent doesn't need to rediscover everything.\n\nIt inherits the first agent's experience.\n\nWe can now expand our original agent loop.\n\nInstead of:\n\n```\nGoal\n ↓\nObserve\n ↓\nAct\n ↓\nCheck\n ↓\nRepeat\n```\n\nwe get:\n\n```\n                ┌───────────────────────┐\n                │                       │\n                ▼                       │\n              Goal                     │\n                │                       │\n                ▼                       │\n          Query the Graph              │\n                │                       │\n                ▼                       │\n            Observe                    │\n                │                       │\n                ▼                       │\n             Decide                    │\n                │                       │\n                ▼                       │\n             Execute                   │\n                │                       │\n                ▼                       │\n              Check                    │\n                │                       │\n         ┌──────┴──────┐                │\n         │             │                │\n       Failure       Success            │\n         │             │                │\n         └──────┬──────┘                │\n                │                       │\n                ▼                       │\n          Update Graph ─────────────────┘\n```\n\nThis creates an important feedback cycle:\n\n```\nExperience\n    ↓\nGraph\n    ↓\nContext\n    ↓\nBetter decision\n    ↓\nNew experience\n    ↓\nGraph\n```\n\nThat is what I mean by a **self-improving graph**.\n\nThe graph becomes a record of what the system has learned by doing.\n\nThere is one thing we absolutely cannot do.\n\nWe cannot assume every experience is correct.\n\nImagine an agent tries this:\n\n```\nAction:\nIncrease retry count from 2 → 10\n\nResult:\nTest passed.\n```\n\nThe agent records:\n\n```\nIncreasing retries fixes checkout problems.\n```\n\nBut maybe the test passed because the flaky test happened to pass.\n\nNow we've poisoned the graph.\n\nThe next agent sees:\n\n```\nHistorical knowledge:\nIncreasing retries works.\n```\n\nAnd repeats the mistake.\n\nThis is where **verification** becomes critical.\n\nInstead of recording:\n\n```\nAgent says it worked.\n```\n\nwe should record:\n\n```\nAgent changed code.\n        ↓\nTests passed.\n        ↓\nIntegration tests passed.\n        ↓\nPull request merged.\n        ↓\nNo incident occurred.\n```\n\nConfidence should increase as independent evidence accumulates.\n\nFor example:\n\n```\ntype Outcome = {\n  status: \"success\" | \"failure\";\n  confidence: number;\n  evidence: string[];\n};\n```\n\nThen:\n\n``` js\nconst outcome: Outcome = {\n  status: \"success\",\n  confidence: 0.92,\n  evidence: [\n    \"unit tests passed\",\n    \"integration tests passed\",\n    \"pull request merged\",\n  ],\n};\n```\n\nThe graph should therefore store not just knowledge.\n\nIt should store:\n\n**knowledge + evidence + confidence.**\n\nThat distinction becomes extremely important at scale.\n\nAnother subtle problem:\n\nAn agent might make 50 observations while solving a task.\n\nShould all 50 become permanent memory?\n\nProbably not.\n\nWe need to distinguish between:\n\nSomething the agent saw.\n\n```\ncheckout.ts imports retry.ts\n```\n\nSomething that happened.\n\n```\nPR #842 modified checkout.ts\n```\n\nSomething that happened after an action.\n\n```\nTests passed.\n```\n\nA relationship that is useful beyond the original task.\n\n```\ncheckout.ts frequently changes with retry.ts\n```\n\nA generalized strategy.\n\n```\nWhen checkout tests fail with timeout errors,\ninspect retry behavior first.\n```\n\nThese are different levels of information.\n\nA good learning system should progressively promote information:\n\n```\nObservation\n    ↓\nEvidence\n    ↓\nRepeated pattern\n    ↓\nValidated relationship\n    ↓\nReusable knowledge\n```\n\nThat's much safer than throwing every agent thought into a vector database and calling it memory.\n\nRAG usually looks like:\n\n```\nQuestion\n   ↓\nSearch documents\n   ↓\nRetrieve chunks\n   ↓\nSend chunks to model\n   ↓\nGenerate answer\n```\n\nThat's useful.\n\nBut it primarily answers:\n\nWhat information is relevant?\n\nA graph can answer a different question:\n\nHow are these things related?\n\nConsider:\n\n```\ncheckout.ts\n```\n\nA document search might retrieve:\n\n```\nPR #842\nPR #811\nPR #743\n```\n\nA graph can expose:\n\n```\ncheckout.ts\n   │\n   ├── modified by → PR #842\n   │                  │\n   │                  ├── authored by → Maya\n   │                  └── reviewed by → Bobby\n   │\n   ├── modified by → PR #811\n   │                  │\n   │                  └── related → retry.ts\n   │\n   └── modified by → PR #743\n                      │\n                      └── caused → checkout incident\n```\n\nNow the agent can reason over the structure.\n\nRAG gives you relevant documents.\n\nA graph can give you **contextual relationships**.\n\nThe two are not competitors.\n\nThey are extremely complementary.\n\nThis might be even more valuable than learning from success.\n\nSuppose an agent tries:\n\n```\nApproach A\n```\n\nIt fails.\n\nThen:\n\n```\nApproach B\n```\n\nIt succeeds.\n\nWe should record both.\n\n```\nTask\n │\n ├── attempted → Approach A\n │                 │\n │                 └── FAILED\n │\n └── attempted → Approach B\n                   │\n                   └── SUCCEEDED\n```\n\nNow future agents have something extremely valuable:\n\n**negative knowledge.**\n\nThey don't just know what worked.\n\nThey know what was already tried.\n\nThat prevents agents from repeatedly walking into the same hole.\n\nEventually, the architecture starts looking like this:\n\n```\n┌────────────────────────────────────┐\n│              Agent                 │\n│                                    │\n│   Goal → Plan → Act → Verify      │\n│              ↑         │            │\n│              │         ▼            │\n│          Context ← Experience      │\n└───────────────┬────────────────────┘\n                │\n                ▼\n       ┌─────────────────┐\n       │ Engineering     │\n       │ Graph           │\n       ├─────────────────┤\n       │ Code            │\n       │ People          │\n       │ PRs             │\n       │ Tests           │\n       │ Incidents       │\n       │ Decisions       │\n       │ Outcomes        │\n       │ Evidence        │\n       └─────────────────┘\n```\n\nThe agent operates on the software.\n\nThe graph observes what happens.\n\nThe graph accumulates relationships.\n\nThe agent queries those relationships.\n\nAnd the cycle continues.\n\nThis is where things get really interesting.\n\nWe often think about an AI system improving through:\n\n```\nBetter model\n↓\nMore training\n↓\nBetter weights\n```\n\nBut an agent can also improve through:\n\n```\nBetter experience\n↓\nBetter graph\n↓\nBetter context\n↓\nBetter decisions\n```\n\nNo model retraining required.\n\nThe underlying model can remain exactly the same.\n\nWhat changes is the **environment around the model**.\n\nThis is an important distinction.\n\nA powerful model with poor context can perform badly.\n\nA slightly less capable model with excellent context, tools, memory, and verification can perform surprisingly well.\n\nNow take this idea outside of one agent.\n\nImagine a company where every engineering activity contributes to the graph:\n\n```\nGitHub\n   ↓\nPull Requests\n   ↓\nCode Changes\n   ↓\nReviews\n   ↓\nDeployments\n   ↓\nIncidents\n   ↓\nFixes\n   ↓\nAgent Experiences\n```\n\nYou eventually get something much larger than a dependency graph.\n\nYou get a model of how the organization actually operates.\n\nYou can ask:\n\n```\nWho understands this system?\n\nWhy was this architecture chosen?\n\nWhat usually breaks when this service changes?\n\nWhich files are high-risk?\n\nWhich engineers have solved similar problems?\n\nWhat approaches have already failed?\n\nWhat changed after the last incident?\n\nWhat should an agent inspect before modifying this service?\n```\n\nThese questions aren't answered by source code alone.\n\nThey're answered by **relationships across engineering history**.\n\nThe prototype we built is intentionally simple.\n\nA production system could add much more.\n\nRelationships change over time.\n\n```\nPerson A understood service X in 2024.\nPerson B became the primary contributor in 2026.\n```\n\nThe graph needs to understand time.\n\nNot every relationship is equally trustworthy.\n\n```\nObserved directly\n    ↓\nHigh confidence\n\nInferred from repeated behavior\n    ↓\nMedium confidence\n\nModel-generated hypothesis\n    ↓\nLow confidence\n```\n\nThe graph can track:\n\n```\nAgent\n ↓\nTask\n ↓\nActions\n ↓\nOutcome\n ↓\nTime\n ↓\nCost\n ↓\nHuman review\n```\n\nNow you can measure which strategies actually work.\n\nA human might tell the agent:\n\n```\nDon't modify this service.\nThe dependency is intentional.\n```\n\nThat becomes knowledge.\n\nThe graph gets better.\n\nThe next agent benefits.\n\nNow imagine several specialized agents:\n\n```\nCode Agent\nSecurity Agent\nTesting Agent\nIncident Agent\nDocumentation Agent\n```\n\nAll contributing to the same graph.\n\nOne agent discovers something.\n\nAnother agent can use it.\n\nThat's when the graph becomes shared memory across an agent workforce.\n\nI think there is a fundamental shift happening in how we build software agents.\n\nThe first generation of agents was mostly about:\n\nCan the model perform the task?\n\nThe next generation is about:\n\nCan the system learn from performing the task?\n\nThose are very different problems.\n\nA single agent completing one task is useful.\n\nAn agent that improves the context available to the next agent is much more powerful.\n\nAnd an organization where thousands of engineering actions continuously improve a shared knowledge graph starts to look like something new.\n\nNot just an AI assistant.\n\nNot just a chatbot.\n\nNot just RAG.\n\nA **learning system**.\n\nThe core architecture is surprisingly simple:\n\n```\nAgent\n  ↓\nActs\n  ↓\nProduces evidence\n  ↓\nUpdates graph\n  ↓\nGraph provides better context\n  ↓\nAgent makes better decisions\n  ↓\nActs again\n```\n\nThat's the loop.\n\nAnd the loop is the product.\n\nThe biggest mistake we can make with AI agents is treating every task as an isolated conversation.\n\nSoftware isn't isolated.\n\nPeople aren't isolated.\n\nDecisions aren't isolated.\n\nFailures aren't isolated.\n\nEverything is connected.\n\nA pull request connects to files.\n\nFiles connect to services.\n\nServices connect to incidents.\n\nIncidents connect to fixes.\n\nFixes connect to engineers.\n\nEngineers connect to decisions.\n\nDecisions connect to outcomes.\n\nAnd those relationships contain something incredibly valuable:\n\n**experience.**\n\nThe job of a self-improving engineering system is to capture that experience, verify it, connect it, and make it available when the next decision needs to be made.\n\nThe model provides reasoning.\n\nThe tools provide action.\n\nThe graph provides memory.\n\nThe feedback loop provides improvement.\n\nPut those four things together and you get something far more interesting than an agent that can execute a task.\n\nYou get an agent that can **learn from doing the work.**\n\nThis is the idea behind **Helix**: an engineering intelligence layer that builds a living graph of your software, engineering activity, decisions, and evidence so AI can understand what happened before it decides what to do next.\n\nIf you're building AI agents for serious engineering work, [see what Helix is building →](https://www.helix-engineering.dev/)", "url": "https://wpnews.pro/news/the-graph-that-learns-building-self-improving-agent-loops", "canonical_source": "https://dev.to/bobbyhalljr/the-graph-that-learns-building-self-improving-agent-loops-1nf7", "published_at": "2026-08-26 13:35:44+00:00", "updated_at": "2026-08-26 13:45:13.766513+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "machine-learning", "developer-tools"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/the-graph-that-learns-building-self-improving-agent-loops", "markdown": "https://wpnews.pro/news/the-graph-that-learns-building-self-improving-agent-loops.md", "text": "https://wpnews.pro/news/the-graph-that-learns-building-self-improving-agent-loops.txt", "jsonld": "https://wpnews.pro/news/the-graph-that-learns-building-self-improving-agent-loops.jsonld"}}