-
-
The Decision Log: what was decided, why, and what was rejected.
-
A conflicting work item gets one comment citing the decision.
-
The issue panel shows the verdict: Conflict with DEC-1.
-
Decisions sync into the Teamwork Graph.
-
The same tools over Atlassian MCP, for Claude Code, Cursor or any MCP client.
-
The Decision Memory agent in Rovo explains the conflict.
-
One conflict engine and one shared decision log, all running on Forge.
Inspiration
Every team has this conversation: "Why are we migrating to DynamoDB? We decided against that last spring." Decisions get made in Confluence pages, Jira comments and pull request threads, and then they fade from memory. Months later someone opens a ticket that quietly reverses one. Nobody notices until review, or until production.
Codegeist asked us to use Atlassian's connected context so teams know what has already been decided. Decision Memory turns that context into a guardrail.
What it does
Decision Memory keeps a log of team decisions: what was chosen, why, and which alternatives were rejected. It checks new work against that log wherever people work:
- On every Jira work item. When an issue is created or its summary or description changes, the app checks it against active decisions. If the work contradicts one, it comments once with the decision, the reasoning behind it and the rejected options. An issue panel shows the verdict (Conflict, Related or Clear), with buttons to dismiss the warning or reopen the decision.
- In Rovo. The Decision Memory agent answers "Why did we choose our event store?" and "Does this work item contradict a past decision?". It can also pull candidate decisions from a Confluence page or Jira thread and record them once you confirm.
- In your IDE. The same tools are available over MCP, so Claude Code, Cursor or any MCP client can check a plan against team decisions before writing code.
- In the Teamwork Graph. Decisions are pushed into the graph as documents linked to their Jira work items, so search and other agents can find them.
- Decision Log page. Search, filter, revisit, supersede and settings, all in one place.
How we built it
- Forge, fully "Runs on Atlassian". The app makes no external calls.
- Conflict engine: a two-stage check.
- A fast lexical ranker (TF-IDF over unigrams and bigrams) picks candidate decisions. Matches get extra weight when the work names an option the team explicitly rejected, or shares a tag or label.
- Forge LLMs (Claude) labels each candidate as contradicts, reopens, aligns or unrelated, with a one-line reason. If the LLM is unavailable, the app falls back to the lexical verdict.
- Modules used:
rovo:agent,action×6,rovo:mcp,graph:connector,llm,trigger(Jira issue created/updated),jira:issuePanelandjira:globalPage(UI Kit). - Shared storage across products. Forge storage is isolated per installation, so the Jira and Confluence installs (and MCP calls) would each see a different log. We store decisions in Jira app properties instead, which both installs reach through
asApp().requestJira. The result is one decision log for every surface. - Tests: 24 automated tests. They cover the ranker, the verdict parsing, Decision Log search and an end-to-end flow against in-memory Forge mocks: seed, Rovo actions, Jira trigger, issue panel, Teamwork Graph sync, a second installation, and migrating old data.
Challenges we ran into
- Storage that splits. MCP calls landed on the Confluence installation and saw an empty log while Jira had five decisions. Moving to a shared Jira-backed store fixed it, with a one-time migration for data already in Forge storage.
- Undocumented shapes. The Teamwork Graph expects document categories in uppercase (
DOCUMENT). Forge LLM replies can arrive as content parts. Jira app properties come back as JSON strings. Each of these failed silently until we tested on a live site. - Precision over noise. A conflict bot that comments on everything gets muted. Rejected-alternative matching plus an LLM pass keeps the warnings rare and specific, and the app comments once per decision per work item.
Accomplishments we're proud of
- One decision log that reaches Jira, Confluence, Rovo, MCP clients and the Teamwork Graph.
- It catches the classic failure: a ticket titled "Migrate the event store to DynamoDB" is flagged within seconds, citing the decision that rejected DynamoDB and the reason why.
- The full flow was verified live on an Atlassian site: trigger, panel, Rovo agent, MCP from Claude Code, and Teamwork Graph sync.
What we learned
Forge's newer pieces (Rovo actions, rovo:mcp, the Teamwork Graph connector and Forge LLMs) let one small codebase serve every surface where people work, as long as the data layer is designed for multiple products from the start.
What's next for Decision Memory
- Bitbucket pull request checks: flag a PR whose diff contradicts a decision.
- A Confluence decision macro, so architecture decision records (ADRs) are captured automatically on publish.
- Review dates: remind owners when a decision is due to be revisited.
- Personal data reporting, then a Marketplace listing.
Built With
- confluence
- forge
- forge-llm
- javascript
- jira
- mcp
- react
- rovo
- teamwork-graph