Inspiration

This year's challenge describes context as knowing "what's already been decided". On many teams that knowledge exists but sits in the wrong place. A developer picks up a ticket without seeing the Confluence page that settled how it should be built, so they either build without it or make the decision again. Recall was built to close that gap: the decision should come to the work, without anyone having to search for it.

What it does

An issue panel in Jira. With no prompt, the panel shows:

  • the recorded decision that matches the issue, with a link and its first sentence
  • what is really blocking the issue
  • other connected work

A Recall agent in Rovo. Ask in plain language:

  • "Has the team already decided how webhook retries should work, and why?" The agent searches Confluence, reads the page, quotes its decision and reason, and links to it, so the answer can be checked in one click.
  • "What is blocking this issue right now?" The agent reads the issue's links in the right direction. An issue this one blocks is not reported as a blocker.
  • "Why did the team move invoice exports to a nightly batch?" That page records no reason, so the agent says so instead of offering one.

How it's built

Recall is a Forge app on the Node.js 24 runtime, with these modules:

  • a rovo:agent with two action modules, find-decisions and issue-context
  • a jira:issuePanel built with UI Kit, using a resolver

find-decisions runs a CQL search, then reads up to three matching pages through the Confluence REST API. Search excerpts are only a few words, so the page text is what lets the agent state the real decision. issue-context reads the issue's status, assignee and links from the Jira REST API, and sorts its links by direction.

Every Jira and Confluence call uses api.asUser(), so requests run with the signed-in person's permissions. Recall stores no data and declares no outside services.

The pure logic (page text extraction, CQL building, link classification, choosing the decision line) lives in its own module, covered by 13 unit tests that run with node --test.

Challenges

  • Keeping answers honest. In early testing the agent had only page titles and short excerpts, and one answer included a reason the page never gave. The fix had three parts: read the actual page text, instruct the agent to quote the page's sentences word for word, and test it with a page that has no reason on it.
  • Permissions. A search made with the app's identity returned nothing, while the same search as the user found the page. Moving every call to asUser() fixed that, and also means Recall works within each person's existing access.
  • Link direction. A first version matched the word "block" and so counted "blocks" as a blocker. Direction-aware classification and regression tests fixed it.

Accomplishments

  • The panel surfaces a decision with no prompt, right beside the work.
  • Answers quote the page and link to it, so a reader can check them.
  • On a ticket that both blocks one issue and is blocked by another, the agent named only the real blocker in the recorded tests.
  • Forge logs confirmed the actions ran during testing, rather than the model answering from memory.

What we learned

Trust in an agent comes from being able to check it. Instructions make a language model far more careful, but they aren't a guarantee, so the citation matters as much as the answer. A test page with nothing to find also reveals more than a page with a good answer on it.

What's next

  • Better matching. The panel currently looks for pages with "decision" in the title that contain the issue's most distinctive words.
  • Support for decisions recorded in Jira comments and other linked work.
  • Testing the declared rovo:mcp module, so the same tools can reach other agents.
Share this project:

Updates

Submission history