Inspiration

AI assistants have started to integrate memory features. These memories are built around one person though: your assistant learns your preferences and remembers your style, and gets better at helping you specifically.

However, a team accumulates knowledge differently. It makes micro-decisions every day: why a pattern was rejected, when a migration started, what the reasoning was behind a trade-off. Most of that knowledge lives in Slack threads, meeting notes, and hallway conversations. Some of it exists only in the moment. A team's most valuable knowledge is the knowledge that evaporates before anyone can query it.

Engineering teams run into this on almost every non-trivial change. The decisions that shaped a repository and the approaches the team already ruled out remain invisible to the coding assistant. It leads an engineer somewhere wrong with the same fluency it leads them somewhere right, and nothing in its response signals the difference.

Canon is an agent that embeds itself in the workflow and captures that knowledge automatically. It intercepts work as it happens and applies team memory on its own. When it recognizes something worth keeping, a decision made during a session, a reason given for a change, it forms a memory. The engineer keeps working. Canon keeps learning.

What It Does

You sign up for Canon, join your team, and get an API token to connect your coding assistant. Then, say you ask your coding assistant to add a feature to the notifications service. While planning, the assistant informs Canon of your ask. Canon searches its memory graph and finds an active migration moving notifications to a new service. The memories link to each other, so Canon follows the graph from that migration to the incident that prompted it: the old service dropped messages during peak traffic. It hands back a revised plan that targets the new service, names the migration and the incident, and explains why. Your coding assistant reshapes the plan before writing any code.

When a Canon run surfaces something worth remembering, it forms a memory. If it is confident about the new knowledge, it persists the node on its own. If it is unsure, it pauses and asks the engineer a direct question. The answer comes back, the agent finishes the thought and stores it, and the next person who works on something related finds it waiting.

Canon’s reasoning is streamed live both to your coding assistant as well to a dashboard on the web shared by your team. A reasoning feed shows the agent's thinking live as it works, with a custom generative UI engine transforming its activities into distinct visual components. The frontend also comes with a force-directed graph view that lets engineers wander the accumulated knowledge and inspect or modify any memory for themselves.

How I Built It

Canon is a multi-agent system built with Agent Development Kit. Gemini does the reasoning over memories held in MongoDB Atlas, accessed through the MongoDB MCP server.

The agent

A Gemini 3.1 Pro orchestrator runs a cognitive loop: perceive the request (from a client, say, your coding assistant), judge what the organization's memory says, trace the relationships that matter, reshape the plan, and remember what happened. It has several MongoDB tools, filtered through the tool_filter parameter on ADK's MCPToolset, for direct lookups and for writing new memories. For specialized work, it delegates to two subagents, each exposed as a callable tool through ADK's AgentTool. A semantic retriever, on the faster Gemini 3.1 Flash, finds relevant knowledge through hybrid search. A graph explorer, on Gemini 3.1 Pro, follows relationships to build out context.

The three agents collaborate through ADK's session state. Tenant configuration (org name, tenant ID, embedding model, graph traversal depth) is injected at session creation and read by every tool. Subagents write their findings back into state keys (retrieval_results, graph_results), so the orchestrator can reference them in its next reasoning step. It decides at runtime which specialist to call and how often. A request with no named entities skips the graph walk. One that surfaces new leads can search again. The orchestrator weighs recency and status alongside graph proximity as it reasons.

One graph

Canon's memory is a single MongoDB Atlas collection of nodes, each one a piece of organizational knowledge like a decision or the incident that prompted it, connected by edges into a graph. When the agent writes a memory, if there's a meaningful connection with other memory nodes, it wires its edges to them. When a decision replaces an earlier one, Canon atomically inserts the new node, marks the old node superseded, and deprecates it.

Two operations work that graph together: hybrid search locates entry points, graph traversal supplies context, and the agent reasons over both. Hybrid search is a $rankFusion aggregation that fuses Atlas Vector Search (cosine similarity, pre-filtered on tenant, status, and tags) with Atlas Full-Text Search. Traversal is a $graphLookup that walks edges recursively. The traversal tool dynamically crafts multi-stage pipelines, setting maxDepth limits and enriching results with graph metadata before handing them to the graph explorer subagent. Compound indexes on tenant, status, tags, and relationship fields support the queries, with TTL indexes for auto-expiring data.

Cross-cutting concerns ride in custom ADK plugins so they stay out of the agent's instructions. An AmbientContextPlugin scopes every tool call to the right tenant, stamping the active organization onto the query before it reaches the database, and ensures overall database operation request integrity.

A backend any agent can call

Canon runs behind an HTTP API. Clients authenticate with API tokens (only the SHA-256 hash is stored) and post requests to /agent/run, streaming back the agent's reasoning and final response as server-sent events. Any client that can make an HTTP call can reach Canon.

Canon's own MCP server is the first client of that API. It connects to any coding assistant such as the Gemini CLI through the Model Context Protocol, and posts to the same /agent/run endpoint. The server also manages multi-turn conversations automatically: it passes a stable session ID to the backend, which summarizes each run and feeds it into the next, so the agent remembers the thread across multiple exchanges. When the agent needs to check with the engineer before saving uncertain knowledge, the MCP server invokes the protocol's elicitation feature to surface the question directly in the IDE.

Two pieces make that integration feel native. MCP Resources load ambient context before the engineer types: canon://org/state carries the organization's active decisions and open migrations, while canon://org/momentum carries the last thirty days of change. Handy MCP Prompts allow the user to guide how their coding assistant behaves, nudging it to check memory before a non-trivial change and to record what a session decided.

Seeing the agent think

Every agent event is broadcast to multiple subscribers and persisted to MongoDB. The MCP server subscribes and uses the Model Context Protocol's progress update feature to send live status to the connected coding assistant. For anyone who wants the full reasoning trail, the web app streams the same events via SSE. When a user reconnects or refreshes, the system first replays stored events before transitioning to live subscription.

The reasoning feed renders the agent's thinking in real-time through a custom generative UI layer. A ReasoningFeedPlugin hooks after_tool_callback and fires as each tool returns, capturing searches, detected conflicts, and subagent delegations. Every SSE event is validated through Zod schemas before it reaches a component, so raw text from the stream becomes statically typed React payloads. The timeline renders distinct micro-components per event type: reasoning steps, tool invocations, human-in-the-loop prompts, and newly created nodes.

The knowledge graph page is an interactive force-graph of the whole knowledge base, filterable by tag and searchable by text, with directional particles running along the supersedes links so a retired decision visibly points at the one that replaced it. When the agent saves a node mid-session, the view animates it into place, the node appearing and its edges drawing out to connect it to its neighbors.

Harness Engineering

Tool response contract

When a tool call returns [], the LLM cannot tell bad arguments from genuinely empty results. Every tool response follows a consistent contract to resolve that ambiguity: a status, a one-line summary, and actionable next steps. Error responses use structured models with error, hint, and retry fields. When a semantic search returns empty, the harness intercepts it using ADK's after_tool_callback and adds a next_actions field with a retry instruction and broader search terms. Error paths include a root cause hint, a safe retry instruction, and an explicit stop condition so the agent knows when to abandon a line of inquiry.

Abstraction boundaries

The harness keeps the agent's action space simple and lets the infrastructure handle complexity the LLM should never see. When the agent wants to post a progress update, it calls a single emit_checkpoint tool. From the agent's perspective, it is informing the user. Behind the scenes, the harness fans that call out to every consumer: the MCP client receives the update over SSE, and the Next.js frontend renders it in the reasoning feed.

What's Next

Passive ingestion. Today memory enters the graph through the agent's own writes and the occasional confirmed save during a session. Next, Canon watches the events that already mark organizational change, a merged PR, a resolved incident, a published ADR, a migration crossing a milestone, and structures them into nodes without anyone asking.

Time-aware memory. The agent already reasons over recency and status. Adding decay, where a node's pull on retrieval softens as it ages unless something keeps it alive, would let the graph ease off knowledge that has gone quiet and keep the agent's sense of what the organization believes today clear of what it has already moved past.

Agent-to-Agent discovery. Canon's memory is useful to any agent that works on the team's codebase. ADK's to_a2a wrapper would expose Canon as a discoverable service, so other agents in the organization can query the memory graph and contribute to it without custom integration.

The engineer who hit the wall at the start still asks for a REST endpoint. The difference is that something in the room now remembers why the last person who tried it chose differently, and says so before the code gets written.

Built With

Share this project:

Updates