Skip to main content
Anona’s GitHub App connects a repository to a memory space, in both directions. In. Capture records what a pull request settled — its title, body and review verdicts on merge, or a whole thread when somebody asks for it — into the space the repository is bound to. Mention the app in a pull request or an issue and it answers from that space, under an audience rule that never posts memory into a thread a wider audience can read. Out. The knowledge pull request takes what the space has accumulated and writes it into one file in the repository — the AGENTS.md or CLAUDE.md your coding agents already read — as a pull request your team reviews and merges. It is off until you choose that file.

Install

  1. Open Integrations in the dashboard.
  2. On the GitHub row, choose Connect.
  3. Approve the install, and grant it the repositories you want.
Installing captures nothing. Until you bind a repository, the app does nothing anywhere. Only an owner or an admin can connect or disconnect an installation. Binding a repository is open to any member, because you can only bind a space you can already reach.

Bind a repository

On the GitHub row, open Bind a repository:
string
required
One of the repositories you granted the installation. Fixed once bound — a repository’s id is what identifies the binding, so changing it would create a second binding rather than move this one.
string
required
Which memory space this repository writes to and reads from.
string
required
The key the app presents. Revoking it stops capture for this repository. Only live keys are offered.
string
Read only captures nothing. Read & write lets capture write into the space. A ceiling, not a grant: choosing Read & write requires that you can write to that space yourself.
string
On merge captures a pull request once it merges. On request captures a thread when somebody asks — “@anona-memory remember this”. Both does both. Described in full under Capture.
string
Which file Anona keeps a section of up to date. Off by default, and off is a perfectly good answer. See below.
Binding the same repository again edits it rather than creating a second one.

What to call it

The app answers to @anona-memory — the handle of the GitHub App itself, at github.com/apps/anona-memory. Mention it in a comment on an issue or a pull request, or in an inline review comment.
@anona is deprecated. It still works today, and it will be removed.github.com/anona is a real GitHub account belonging to somebody with no connection to Anona, so a comment written that way notifies a stranger who never asked to be in your pull request. Nothing on our side caught it: Anona matches the text of your comment and never asks GitHub who the mention resolved to, so the bot answered you correctly the whole time the notifications were going somewhere else.The old handle is kept working only so that the comments already sitting in your threads, and anything your team has already written down, do not break on the day the app was renamed. Everything below uses @anona-memory, and so should you.

Capture: what a repository writes into memory

Capture is what a bound repository puts into its space. There are two triggers, and When to capture on the binding decides which are live: merge is the default, and a Read only binding captures nothing under any of them. There is deliberately no fourth setting that captures every comment. Turning prose into memories runs on one extraction queue shared by every Anona customer, so a repository streaming its whole comment traffic through it would hold up everyone else’s ingestion and not only its own. both is as wide as this is allowed to go, and the hourly cap further down is the same constraint seen from the other end.

On merge

When a pull request merges, Anona captures its title, its body, and the conclusion of each review on it — “alice approved”, “bob requested changes: the migration has to run first”. Closing a pull request without merging captures nothing. Most closed pull requests were abandoned, and a decision nobody took is not worth keeping. It is deliberately not the whole thread. A merged pull request’s conversation is mostly process — “nit”, “done”, “rebased” — and the part that lasts is the decision it reached. Only top-level review submissions are read: never the per-line inline replies, never the general back and forth. A review that approved or requested changes is kept even when the reviewer wrote nothing, because there the verdict is itself the decision. A review that only commented, with no text, is dropped. Only what the project vouches for. GitHub stamps every pull request and review with the author’s relationship to the repository, and capture keeps only those written by the owner, a member of the organization, or a collaborator. Anything else — an outside contributor, a first-time drive-by, a relationship GitHub does not report at all — is left out. The two halves are filtered separately, which is the useful outcome: an outside contributor’s pull request that your team reviewed keeps your reviewers’ verdicts and drops the contributor’s prose. If nothing survives the filter, nothing is written and nothing is billed. This is a trust boundary rather than tidiness. On a public repository anybody can open a pull request, and captured text becomes memory the knowledge pull request may later render into a file your coding agents read as standing instruction. Capture is the last point at which it is still knowable which pull request a sentence came from, so it is the only place the filter can go. Pull requests opened by a bot are never captured — including Anona’s own knowledge pull request. Merging that one is the whole design, and ingesting it would feed a synthesis back in as fresh evidence for the next synthesis to build on.

On request — @anona-memory remember this

Comment on an issue or a pull request:
and Anona captures the whole thread: its title, its body, and every human comment on it — not only the comment that asked. A decision lives in the back and forth, and keeping the conclusion alone throws away the reason for it. Also understood: save this, record this conversation, remember the above, store this discussion, note this, and similar. The instruction has to be the whole comment — the mention, the instruction, and nothing else. A word of address may come first, so please @anona-memory save this is the same instruction; “did” is not one of those words, which is what keeps @anona-memory did we ever record that? a question rather than a silent save. It also means a comment that merely quotes a directive is not one — and it is not a question either. What counts as addressing Anona is what you typed, not what GitHub’s Quote reply button pasted in above it, so a comment that is entirely quote is met with silence: nothing was addressed to us, so there is nothing to decline and nowhere a decline would be welcome. Quote something and write your own @anona-memory … underneath, and that is a question, answered as one. Quote something and write a fresh directive underneath, and that is a save. Two things have to be true:
  • Your GitHub account is connected to Anona in the dashboard. Until it is, there is no person to attribute the memory to and no membership to check, so the directive is declined.
  • You can write to the bound space yourself. Checked at the moment of the capture rather than at bind time, so losing write access stops it immediately. The binding’s Read & write setting is a ceiling on top of that, not a substitute for it.
Anona’s own comments are never captured, and neither is any other bot’s — CI status, Dependabot, stale-bot. An answer is a synthesis of what the space already holds, and writing it back in as fresh evidence is a photocopy of a photocopy. Every human comment on the thread is kept, whoever wrote it. The vouched-for filter that merge capture applies does not run here: somebody who can write to the space asked for this thread by name, and that judgement is what the filter stands in for when nobody made one. The directive works in an issue comment and in a pull request’s conversation. It does not work in an inline review comment on a line of code.

Anona reacts to tell you what happened

Anona never comments back when you ask it to remember something — a comment on every save is noise on a page people are trying to review code on. It reacts to the comment you wrote, the way a person would: The two-step is deliberate: extraction is not instant, and 👀 is the difference between “Anona has it” and “Anona never saw it”. They accumulate. They never replace each other. GitHub can add a reaction and it can remove one, and removing is a second call that can fail and leave a stale acknowledgement standing where a person will read it as the truth — so nothing is ever removed. Read them as a trail, which is what makes 👀 on its own, 👀 👍 and 👀 😕 three distinct states rather than one that keeps being overwritten. There is no reaction meaning “still going”; the absence of the second one is that. Anona watches the extraction for ten minutes and then stops watching — sooner if the machine doing the watching is replaced in the meantime. Stopping leaves the 👀 alone, and the 👀 alone stays true — it says queued, and a write still running after ten minutes is still queued. That is the whole reason the accepted state is two reactions and not one: the honest outcome of losing track is an acknowledgement that has gone stale, where a single terminal reaction would have had to either lie or never arrive. The 😕 never says why, because a pull request thread can be the whole internet and “you do not have access to that space” would tell it that the space exists and that you cannot read it. The reasons are a binding set to capture on merge only, a read-only binding, a GitHub account that is not connected, the hourly cap, an Anona account out of credits, a revoked API key, no write access to the bound space, GitHub refusing to hand over the thread, a thread with nothing in it to save, and a write the memory engine would not take. Which one it was is in the Anona dashboard, behind your session. Reactions have an hourly allowance of their own — a hundred and twenty per installation, counted across every repository bound to it and separate from both the capture cap below and the reply budget further down. Separate so that a repository being drowned in directives cannot silence its own questions, and generous because a reaction, unlike a comment, costs a thread’s readers no attention at all. Past it Anona reacts to nothing, which looks exactly like the integration being broken: it is the one case where no reaction does not mean nothing happened. A merged pull request is captured with no reaction at all. Nobody asked for it, so there is nobody waiting to be told.

Capture never comments

The only thing Anona puts back on a thread it captured is the reaction above, and only when somebody asked. It never opens a comment to report an outcome — not a save, not a skip, not a cap. A merged pull request is captured silently, so look at the space itself to confirm one landed. Extraction is asynchronous, and a memory does not appear the instant a pull request merges.

Capturing the same thing twice does nothing

A pull request captures once; a comment captures once. A redelivery, or two events racing each other, collapse to that one capture. Editing a comment into a directive after the fact does not capture it either — only a freshly posted comment counts.

The hourly cap on captures

Each installation can capture twenty times in a rolling hour, counted across every repository bound to it and across both triggers together. It counts captures that happened. A directive that was refused — a read-only binding, an unlinked commenter — costs nothing against it, so the allowance is never eaten by people who were never going to be able to save anything anyway. The cap is real rather than arbitrary — the shared extraction queue again — and it will rise when that queue stops being shared. Twenty is twice Slack’s ten because merging is automatic: a release window produces more merges in an hour than anybody tidying up threads by hand ever produces saves. This is one of two hourly limits, and they ration different things. This one rations writing into memory. The reply budget rations comments Anona posts back, which is a different resource with a different abuser, and neither one draws on the other.

Who the memory is attributed to

If the person who merged, or who asked, has connected their GitHub account, the memory carries their Anona identity. A merge by somebody who has not connected one is recorded unattributed rather than dropped — losing it because a contributor never visited a settings page throws away something real. Merge capture also keeps running on the grant of whoever bound the repository, re-checked on every merge. If that person loses write access to the space, the repository stops capturing on merge.

Asking a question in a pull request

Mention the app in an issue or a pull request in a bound repository:
The answer is the same synthesis POST /v1/reason returns, against the space the repository is bound to. Inline review comments work too, which is where reviewers actually argue. The app answers only when mentioned. It never volunteers on an opened pull request, and it never answers another bot. There is no “Thinking…” placeholder the way there is in Slack: the thread stays quiet until the answer or the link arrives. An answer is a multi-step reasoning loop and takes tens of seconds. One question at a time per repository. Ask a second while the first is still running and it says so rather than queueing it. A Read only binding still answers questions. The role governs writing, not reading.

The audience rule

A pull request thread is read by whoever can read the repository. So an answer goes into the thread only when Anona can establish that the whole readership already holds the space: A public repository never gets an in-thread answer. Not on any roster state, and there is no configuration that changes it: a public repository has no membership to check, so nothing can make posting memory content into it safe. That is the design rather than a bug to report. An unreadable or not-yet-synced roster is also a link. A repository whose collaborator list has never been read, or whose read GitHub refused, knows nothing about who is standing in the room — and “I know of nobody outstanding” must not be confused with “nobody is outstanding”. The collaborator list is re-read when it is more than five minutes old. A repository GitHub calls internal — visible to everyone in your enterprise — gets the link too. Its readers are the enterprise rather than its collaborators, so the one list that can be checked does not describe who can see the thread. The link reply is a comment saying the answer is in Anona, and it carries nothing else: no excerpt, no first line, no “I found three memories”. Where it leads is below. There is one wording for every link-only reason. Distinguishing them — public repository, somebody outstanding, a roster that would not read — would publish the roster state into the thread. GitHub has no ephemeral comment and no direct message. “Answer privately” can therefore only mean a public comment carrying a link the reader has to authenticate to open. That is worse than Slack’s ephemeral message, and it is the best the platform offers.
“Nothing on that in this space yet” follows the audience rule too. It is the result of a search and it reports what the space holds, so on a public repository you get a link to it rather than the sentence. Whether a space has anything on a subject is the same kind of disclosure as the answer itself.
The link goes to a page in the Anona dashboard. The page is the access check, not a viewer with one bolted on. It resolves your own access to the bound space before it shows anything, the same way every other space-scoped surface in the dashboard resolves it. So forwarding the link — to a colleague, into another thread, out of the company — hands nobody anything. Somebody without the grant is told they do not have access to that answer and to ask whoever shared the link, or a space owner, to invite them. Somebody who is not signed in — the ordinary case, since the link is followed out of GitHub — is sent to sign in by whichever method their account uses, and returned to the same page afterwards. Holding the URL is not access, and that is the only reason the reply is allowed to be a public comment at all. It never names the repository, the binding or the space. It shows the question, the answer, and when it was answered. Nothing else, and that is deliberate rather than minimal: naming the space would undo the discretion the thread’s own comment is keeping, and turn a forwarded link into a way to learn which memory space a repository is wired to. The answer is stored as it was given. Memory moves, so a page opened a month later shows what the thread was discussing rather than what the same question would return today. Ask again in the thread for that. Unbinding the repository, or disconnecting the installation, retires its links. The page can no longer establish which space the answer came out of, so it says the answer is not there rather than showing it to whoever is holding the URL.

A refusal never says which refusal

Two different people get the same comment:
  • somebody who has not connected their GitHub account to Anona;
  • somebody who has, but holds no access to the space this repository reads.
The same reply, word for word, pointing at the dashboard. That is deliberate: naming the second would tell everyone reading the thread that a particular space exists and that this person cannot read it. The dashboard, behind your session, says which it actually was. So do not read the generic refusal as a vague error. It is the only thing the thread is allowed to say.

When it cannot answer

These are facts about the integration rather than about the space, so they are posted in the thread whatever the repository’s visibility:

The hourly reply budget

Each installation may post sixty replies in a rolling hour, counted across every repository bound to it: answers, link replies, refusals and every message in the table above, all out of the one allowance. Past it the app goes quiet. There is deliberately no “you have hit the cap” comment. The comment is the thing being rationed, so announcing the cap to whoever is driving it would be exactly the noise the cap exists to stop. Capture can afford to say so with a 😕, because a directive is a deliberate act by somebody who already holds the space; a question is not — on a public repository anybody with a GitHub account can post one. This is the other half of the capture cap, and the two are sized against different resources. Capture’s twenty ration the shared extraction queue. These sixty ration GitHub’s rate limit and the attention of everyone watching the thread. Sixty is high because the budget binds almost entirely on the paths that cost nothing. An answered question is a multi-step reasoning loop, already serialised one at a time per repository, so an hour cannot physically hold many of them. A refusal to somebody who has not connected their account is free and can be typed as fast as anybody likes. That is the case being bounded, which is also why the budget is claimed before Anona works out who asked — a question that was never going to be answered still spends one. Per installation rather than per repository, for the same reason: somebody with several of your public repositories to comment on would otherwise get a fresh sixty in each. It is counted per gateway process, so across a deployment running several the real ceiling is a small multiple of sixty. It is a ceiling where there was none, not a precise number to plan against.

If Anona restarts mid-question

An answer is produced by a background task, and a background task dies with the machine running it. When that happens the question is left with no reply at all — no error, no timeout, nothing at all to tell it apart from having been ignored, which is the worst shape a failure on this surface can take. So every time the gateway starts it sweeps for questions that were accepted and never answered, and posts one comment into the thread that asked:
Could not answer that one. Try again in a moment.
It means the machine restarted. It does not mean your question was bad. Nothing here legitimately runs for five minutes — the reasoning loop is bounded well inside that — so anything still open when the sweep looks cannot still be running. A question Anona could not answer on its merits gets one of the replies in the table above, straight away. A question it would not answer for you gets the generic refusal, also straight away. This sentence, arriving minutes late and by itself, is the third thing: the task went with its container. Ask again. It is posted in the thread whatever the repository’s visibility, for the same reason the table above is: it carries nothing out of the space. Only questions are swept. Everything else Anona receives from a repository — a merge, a push, a label, a review request — leaves nobody waiting, and apologising in a thread where nothing was asked would be worse than saying nothing. A merged pull request that failed to capture is therefore silent here too; look at the space. If the repository cannot be reached at the moment the sweep runs — the installation removed, the repository archived, GitHub down — the question is left open and tried again at the next start, rather than written off as handled and forgotten.

Who pays

The split is not an oversight. A merge has no asker — the installation configured capture, and whoever clicked merge did not request a charge and may not know Anona exists — so the installation pays, and @anona-memory remember this follows it for consistency. A question has an asker, and every other Anona surface charges them. For an ordinary team both are the same organization.

The knowledge pull request

Once a week — or daily, or monthly, whichever you pick — Anona renders what your memory space has learned about this repository into a section of one file, and opens a pull request. You review the diff and merge it, or you close it. Nothing reaches your repository until somebody with write access merges. The point is the file your coding agents already read. A synthesis that lives only in a dashboard is one nobody’s agent sees; the same text in AGENTS.md is context on every task.

You choose the file. Anona never picks one

When you choose a repository in the bind form, Anona scans its default branch for the conventions a team might already keep:
What it finds is offered, not applied. You pick one, or you type a path of your own, or you leave it off — and off is what a binding does when you say nothing. Proposing AGENTS.md to a team who maintain a 1,600-line CLAUDE.md by hand is how a bot pull request gets closed unread, so Anona does not propose; it asks. Each file on offer says what the first pull request will do to it: Two more entries carry no promise at all, and both are deliberately still on the list. Anona cannot write here means the file is there and can never be edited — a directory by that name, a file past the size GitHub will serve as text, bytes that are not UTF-8. Could not check this file means the read itself failed, so nothing at all is known about that one. Neither is quietly dropped: a file nobody managed to read is not a repository without one, and telling you what a diff will look like is not a claim a failed read is entitled to make. If the scan cannot read the repository at all, it says so and offers a retry. That is deliberately not the same screen as a repository which keeps none of these files — “we could not look” and “there is nothing there” are different facts, and only one of them means you should type a path.

It owns a delimited section, and nothing else

Everything Anona writes lives between two markers:
The section between the markers is replaced wholesale on every refresh. Everything outside them is never modified — not reformatted, not re-wrapped, not re-serialized. The file is cut at the two markers and the parts you own are put back exactly as they were, down to the trailing newline. Keep your own prose outside the markers and it will still be there in a year. Three consequences worth knowing:
  • A file with no markers yet gets the section appended at the end. Your existing content is the start of the new file, byte for byte.
  • A file that does not exist is created, containing only the section.
  • A file whose markers cannot be read unambiguously — one half missing, two copies, an end before a begin — is left alone, and no pull request is opened. Anona will not guess which of your paragraphs was meant to be its own. This is the one failure here that waiting does not fix, so it is also the one recorded on the binding and shown on its row in Integrations, rather than left to be inferred from a repository that went quiet.
Editing inside the markers is not forbidden, but the next refresh replaces it. That shows up as a deletion in the pull request’s own diff, in front of you, before you merge.

It is evidence, not instruction

Every entry in the section is a quotation, under a heading naming the memory model it came from — with the question that model answers on the line below, when it has one — and all of it beneath a preamble that names the space and says the whole section is evidence. A memory model may well contain a sentence like “always do X”; presented as a quoted, attributed claim it is something a reviewer can evaluate, where the same sentence in the document’s own voice is something an agent obeys. At most twenty models are rendered, and a very long one is cut off with a line saying so and pointing at the dashboard. A pull request nobody can read is a pull request that gets merged unread, which would defeat the only real protection this feature has. Read the section the way you would read a document a colleague wrote about your repository. That is what it is.

You choose how often, and it only arrives when there is something to say

Three cadences, set per repository beside the file: It is a ceiling, not a schedule. Daily does not mean a pull request a day; it means Anona may open one as often as once a day, and it opens none at all unless this repository’s memory actually changed. There is no per-merge option, and that is deliberate. The synthesis is refreshed in the background, so a pull request opened the moment a merge landed would show you the picture as it was before that merge — fresh-looking and already out of date. More importantly, the merge gate is the only thing between generated text and a file your coding agent follows, and a bot pull request that turns up on every merge is one that gets merged unread. Nothing is opened when:
  • the rendered section is unchanged since last time (there is deliberately no “last refreshed” timestamp in it, so an unchanged week produces an identical section and no pull request);
  • a previous knowledge pull request is still open — one at a time;
  • the space has nothing worth rendering yet;
  • the binding’s API key has been revoked, or the organization is out of credits.
Refreshing a memory model that has fallen behind is charged as one insights operation, to the organization that owns the installation. A quiet repository costs nothing at any cadence — there is nothing stale to refresh.

A shared space cannot have one

If the space is shared to you by another organization, the knowledge pull request is refused — the bind form explains it before you pick a file, and the API answers 403 github_knowledge_shared_space. Everyone who can read the repository would see what was written into it, which is a wider audience than the shared space’s own members, and it is not the binding organization’s decision to make. Capture is unaffected; only this feature is off. The check runs again on every pass, not just at bind time — a space can be shared after the binding was saved, and a check made only at bind time would never see it.

Turning it off

Set the field back to Off and save. The dashboard asks first, because the binding save is a full replace and “I did not touch that field” and “I turned it off” would otherwise be the same request. Turning it off stops future pull requests. It removes nothing: whatever has already been merged into your repository is yours, and stays exactly where it is. Delete the section by hand if you want it gone.

The endpoints behind the page

Everything the Integrations page does is also callable with a dashboard session token (never an API key: 401 session_required): Two more are GitHub’s to call, not yours: POST /v1/github/events receives every webhook delivery, and GET /v1/github/callback is where the browser lands after an install is approved. GET .../knowledge-files returns [] for a repository that keeps none of them, and 503 github_api_error when the repository could not be read. Those are different answers; do not render the second as the first. POST /v1/github/bindings is a full replace. Send knowledge_path and knowledge_cadence on every save — omitting the path clears it rather than leaving it alone, and omitting the cadence resets it to weekly.

Error codes

Codes are lowercase, in the standard envelope:

Disconnect

Disconnect on the GitHub row revokes the installation and removes its bindings. Your spaces, your memories and anything already merged into a repository are untouched. To stop one repository only, unbind it instead.