A Backroom That Remembers
Most agent memory demos run for an afternoon. This one has to run for thirty-eight weeks.
Eight large language models each manage a Fantasy Premier League team for a full season. Every week, each manager makes one decision that constrains all the ones after it — a transfer spent is gone, a chip played cannot be replayed, a player sold at a loss stays sold. The interesting engineering problem is not making a good pick in gameweek 2. It is making a pick in gameweek 27 that is consistent with a promise made in gameweek 1, by a model that has no memory of ever making it.
If the vocabulary here is new, what agent memory is and what a memory layer has to do covers the ground this post assumes.
Each manager is not one agent but three: a coach, an assistant manager, and the manager itself. They hold a conversation, they produce a team, and what survives that conversation into next week is the entire subject of this post.
The conversation
The three roles run in sequence inside a single decision step. Each is a separate agent with its own memory in phorvec, and all three run the same underlying model — Grok's coach is Grok, Opus's coach is Opus. Substituting one strong advisor for everybody would have been simpler and would have destroyed the comparison.
The coach speaks first
The coach sees the statistics and nothing else: form, expected goals, expected goals conceded, fixture difficulty, minutes, fitness. It ranks twenty players, five per position, and it has to justify each one.
Midfielders:
1. Palmer — 13 points, ICT 16.10 and 82 minutes at fit 100, with Chelsea's
home_difficulty 2 against Brighton the best premium fixture.
2. Szoboszlai — 0.85 xG, 0.27 xA and 1.12 xGI from 90 minutes — the strongest
underlying mid among fully fit 90-minute players — and Liverpool host Forest.
...
5. Hinshelwood — 16 points, 1.41 xG and 1.43 xGI in 63 minutes, listed despite
fit 75 and doubtful status because those numbers lead the position.
Notes:
Chelsea concentrate the week's attack: Palmer, Rogers and João Pedro all get
home_difficulty 2 against Brighton. Liverpool's Szoboszlai, Gakpo and Isak all
host Forest — three from one club is the cap.
That Notes block is the coach doing the job a spreadsheet cannot: flagging that the week's best fixtures are concentrated in two clubs, and that the squad rules cap how much of that you can buy.
The assistant answers with something legal
The assistant sees the shared block and the coach's twenty names. It does not get to browse the whole pool — deliberately, because the point of the coach is to narrow the field. It returns a complete, legal, fifteen-player sheet:

Alongside the eleven names it returns the arithmetic, which is the part that makes it an assistant rather than a list:
Transfers: out: Semenyo in: Rogers
What this costs, calculated for you:
Transfer hit: 0 points
Bank afterwards: £1.0m
This sheet breaks no rule. It is legal as it stands.
That image is rendered by the same component the episode itself uses, from the same contract — a squad is never described twice in this system, once for the video and once for a document.
Note what the assistant does with the coach's advice: Palmer was the coach's number-one midfielder and does not appear. The assistant took Rogers instead — same club, same fixture, cheaper — because the sheet has to balance and Palmer did not fit without a second sale.
The manager decides, and explains itself
Only the manager's words are ever published. It receives the coach's ranking and the assistant's sheet, and it is free to ignore both:
Semenyo at eight and a half million for two points is the hole, and I am not rolling him into City at Palace. That is three City players on a difficulty-three away day after the supporting cast already blanked. Rogers is the free transfer: Chelsea at home to Brighton is the fixture you stack, and João Pedro is already there for it. The million I bank is useful, not the reason.
I am not taking a four-point hit for Palmer. The only legal path sells Van Hecke too, and Tottenham at home to Newcastle is the one defensive fixture I actually like this week. Gameweek two is not when you burn rank for a maybe.
That second paragraph is the whole architecture justifying itself. The manager is answering the coach — Palmer was ranked first — with a reason the coach could not have known, because the coach never sees the squad. Three views of the same week, and a decision that reconciles them.
The same structure produces opposite behaviour in a different backroom. Sonnet's assistant proposed a four-point hit, selling two players to buy two, and Sonnet's manager took it:
Taking the 4-point hit stings, but it's early enough in the season that locking in two genuine upgrades outweighs a single gameweek's cost.
Same three roles, same prompt, same week. One backroom refuses a hit on principle and the other accepts one on principle, and the only variable is the model.
What is passed versus what is remembered
This is the distinction that matters over thirty-eight weeks, and it is easy to blur.
The coach's ranking and the assistant's sheet are passed — they exist inside one decision step and are consumed immediately. If they were only passed, they would vanish, and gameweek 3's coach would have no idea what gameweek 2's coach recommended or whether the manager took it.
So all three roles also write. Each role owns its own phorvec project, and all three are members of that manager's team:
fpl-team-opus (FULL sharing)
├── context_fpl_coach_opus role: coach
├── context_fpl_asst_opus role: assistant
└── context_fpl_mgr_opus role: manager
This is the same shape as the multi-agent collaboration demo — team_create, team_add_member with FULL sharing, and each agent writing through context_store without ever reading another's memory — running for a season instead of a scenario.
Eight teams, twenty-four member projects, and — asserted by a test — zero members in common between any two.
Writing to a role's store and reading it back are separate concerns, and both
name a role. store and recall take a manager and a role — never a project —
so _project_for is the only code that decides which store is opened. Symmetry
matters here: a write path that can address three stores and a read path that can
address one is a backroom whose advice nobody can consult.
And not every role should get memory. With the read path working we deliberately gave it to one of the three. The coach ranks the week's statistics and passes them on; it has nothing to be consistent with, and it is already the largest prompt by a wide margin — 32,030 tokens against the assistant's 13,900. The assistant is the role that needs to know what it already tried, so it now reads its own past sheets and nothing else. It is not handed the manager's memory, which would make it an echo rather than a third voice.
The isolation is not a nicety here, it is the experiment. Eight managers receive a byte-identical prompt; the only variables are the model and its private history. If one manager could read another's reasoning there would be no competition, just one model wearing eight faces, and the season's results would be worthless. The access-control and audit surface is what makes that boundary checkable rather than assumed.
What a decision leaves behind
Each decision is written in two shapes, because they are read by different things.
One narrative record carries the squad, the transfers and the manager's verbatim reasoning. This is what gets recalled into a later gameweek's prompt — the paragraph above about Semenyo and Palmer is, next week, something Grok's manager can be reminded it said.
Several atomic claims, one sentence each. These are the commitments:
I am holding all four chips until at least gameweek six.
I will not take a points hit for a transfer before I play my wildcard.
I am keeping Haaland and captaining him every gameweek he is fit.
I will start at most one transfer per gameweek and bank the rest.
Those four are Opus's, from gameweek 2. Every one is checkable against a decision it has not made yet. In gameweek 9, if that manager takes a points hit without having played its wildcard, that is a contradiction against a claim in its own store — and a story the show can tell.
Splitting the two shapes is not tidiness. conflict_check runs a negation pass over an item's whole text; handed a long multi-claim record it flags unrelated pairs. Measured across six pairs of ordinary, non-contradictory gameweeks: two false positives with long-form records, zero with atomic claims. The category on the write is what decides whether an item enters that comparison at all, and narrative records are deliberately excluded from it.
There is one more consumer. Because the coach and assistant write into the same team, a claim like "I will start at most one transfer per gameweek" is visible to the assistant that has to propose next week's sheet. The commitment does not only bind the manager who made it — it reaches the agent whose job is to draft the thing the manager will be asked to approve.
How recall is asked, and what happens when it is thin
Before each decision, the manager's own project is queried for its history. What comes back is not treated as a yes/no — the outcome is recorded per step, as a taxonomy:
memory_source | Meaning |
|---|---|
phorvec | Recall returned memories, and they went into the prompt. |
postgres_backfill | Recall was empty; the relational store supplied the history. |
postgres_fallback | Recall was unreachable. The outage case. |
none | Consulted; there genuinely is no history yet. |
Recording which of those four produced a prompt is what lets the system insist on the first one. A manager only decides on memory it actually recalled: if its store is not complete, the run stops and says which manager and why, before a single provider call is made. Memory that is optional in practice is memory nobody can reason about, so it is not optional here.
The layering behind it is the part we would repeat on any agent system. The relational database holds the decisions; phorvec holds the manager's memory of them. Neither is a copy of the other: one is the record of what happened, the other is what the agent can bring to mind about it. Because every memory record derives from a committed row, the store is reconstructible — a rebuild replays it through the same gateway the live path writes with, so the two cannot drift into producing subtly different documents for the same decision.
The practical effect is that retrieval quality becomes a tuning problem rather than a correctness one. A thin recall gives a weaker prompt; it can never give an invented one. That is a much better failure mode to design for than the alternative, and it is why we would put a durable source of truth under a vector store on any project where the agent's output is going to be published.
Query shape matters more as a season accumulates, which is what hybrid search with BM25 and reciprocal rank fusion exists to address, and the memory-mode benchmarks are where to start when choosing between retrieval strategies.
Before any gameweek is spent, a preflight asserts that every manager's store holds
its identity and one record for every decision it has committed, and it names every
manager at once rather than one refusal at a time. Running that before a provider
is paid — rather than reading memory_source afterwards — is the difference between
a memory system you can trust and one you find out about later. It is the same check
an operator can run by hand, sharing one implementation, so the answer cannot differ
between the two.
What should not go in memory
The most useful thing we added after two gameweeks went into the prompt and not into the store.
A manager could recall every squad it had ever picked and still not know how its season was going. "In gameweek 1 I owned player X" is not a fact anyone can act on, and storing more of them actively hurt: every extra pick record competes for the same top_k slots as the commitments, which are the part worth recalling.
So results are derived, never remembered:
## YOUR_FORM
Gameweeks played: 2. The most recent 5, newest first:
GW2: 30 points, after a 4-point hit, 5 of 8 in the league
GW1: 40 points, 2 of 8 in the league
Season so far: 66 points, currently 5 of 8.
Transfer hits: 1 in 2 gameweeks, costing 4 points in total.
Chips remaining: wildcard, bench boost, triple captain, free hit.
Around sixty tokens, and it stays that size at gameweek 38 because five weeks are listed and every week is counted. It is rebuilt from the relational store on every prompt.
The rule it follows generalises well past football: if a fact is objective, reproducible and changes over time, derive it — and store only what cannot be recomputed. Results get restated when the league corrects a scoring decision, so a stored copy would be wrong and could never be fixed, because the gateway has no delete. A manager's reasoning exists nowhere else, and is exactly what memory is for.
The practical effect is that the store stops competing with itself. Memory holds convictions; the prompt carries the scoreboard.
The thirty-eight-week problem
A two-week snapshot flatters any memory system. The real questions arrive later, and they are worth naming before they do.
The store only grows. By gameweek 38 each manager will hold thirty-eight narrative records and a few hundred atomic claims. One broad question asked against a whole season competes in the widest possible field, which is why consolidation is part of the design rather than a later optimisation — a season is exactly the horizon at which "just store everything and search it" stops working, and exactly why we asked narrow questions from the start. What we run today is the association pass, which discovers and links related records so a week's reasoning arrives with the weeks it belongs to. Commitments themselves are pinned, so nothing that has to survive to gameweek 38 is ever a candidate for compaction.
Commitments have to stay findable. "I am holding all four chips until at least gameweek six" is a claim whose whole value is being retrieved in gameweek 6, four weeks and dozens of records after it was written. Recency-weighted retrieval would bury it precisely when it becomes relevant. Claims are stored pinned for that reason.
Contradiction is content, not error. A manager that reverses a stated position is not a bug to suppress — it is the most interesting thing that can happen in an episode. The system needs to notice it, which is why decisions are written as comparable atomic claims rather than as prose nobody can diff.
Roles change and identities must not. A manager's slug is fixed for its career even if the model behind it is upgraded, because the slug is what ties it to its memory. Renaming a project mid-season would orphan everything before it.
Does it show up in the output?
One clean piece of evidence, and it is the reason the loop exists.
In gameweek 1, Opus's manager wrote that it intended to "do nothing loudly for a while". In gameweek 2 — unprompted, with no summary of week one in its instructions and no human editing anywhere in the pipeline:
I said in week one I'd do nothing loudly for a while, and this is the quietest useful move available. Zubimendi is the one man in my eleven with no path to a return. That is a swap of floor for floor and ceiling for free.
The manager is quoting a promise back at itself. Nothing in the prompt asked it to; the sentence was in its context because its own reasoning from the previous week had been written to its own project and recalled into this one.
And the promise is visible in the squad. Here is that manager's opening team, and the same team a week later:


One change. Zubimendi out, Xhaka in, no points hit, the armband where it was. That is what "do nothing loudly" looks like when a model is held to it by its own words rather than by an instruction — and it is the difference between memory that reaches a prompt and memory that merely exists.
That is the whole thing working, once, in one manager. Getting it to happen reliably for eight managers across thirty-six more gameweeks is the actual project.
Memory that knows how it turned out
Recalling a decision is not the same as learning from one. A manager that reads back "I stand still until the water is worth striking" knows what it argued; it does not know whether arguing that was a good idea.
So once a gameweek is scored, every record written about it is told the result,
through phorvec's own outcome_record — success, failure, or mixed, attached to
the exact items that produced it. The next prompt renders the verdict beside the
reasoning:
- Gameweek 1: I stand still until the water is worth striking. Opening week,
everyone reaches for the shiny thing...
RESULT: mixed. Gameweek 1: 34 net points, finishing 6 of 8. Season total 34.
Two things made that cheap. The store hands back an id when it writes, so an outcome attaches to the record itself rather than to a second copy of it kept somewhere else. And the classification is by rank within the field, not a points threshold: fifty is a good week when the field averages forty and a poor one when it averages sixty, and what a manager needs to learn from is whether it beat seven other models given the same fixtures.
The assistant is judged on the same result. It proposes the sheet the manager acts on, and it is the role whose entire job is getting better at proposing sheets — which it cannot do from a history that never says how any of them did.
This is the difference between an agent with a diary and an agent with experience. A diary tells it what it said. Experience tells it what that was worth.
What we would tell someone building this
- Separate what is passed from what is remembered. A multi-agent chain that only passes context has no continuity; a chain that remembers everything it passes drowns. Decide per role which of its output is worth another week.
- Give each role its own store and share at the team boundary. Permissive inside a working group is safe precisely because the group cannot reach past itself.
- Write commitments as separate, comparable claims. Prose is for the prompt; one-sentence claims are for noticing that the agent has changed its mind.
- Instrument retrieval as a taxonomy. Empty and broken are different problems with the same exception.
- Never let the vector store be the only copy. Retrieval quality varies week to week; a durable source of truth turns a bad recall into a weaker prompt rather than a fabricated one.
- Query narrowly, and expect it to matter more over time. One broad question against a season of history is the shape of the problem, not a tuning detail.
- Make the read path as expressive as the write path. If an agent can write to a store, something must be able to read it back, and both should address it the same way.
- Close the loop with outcomes. An agent that recalls what it decided still cannot tell a good decision from a bad one. Attach the result to the record, and recall starts teaching instead of merely reminding.
The season runs to gameweek 38. Which model wins is the least interesting question it can answer. The one worth watching is whether any of them changes its mind in a way that looks like learning — and that is a question about memory, not about football.
Two gameweeks in, every manager holds its identity, its full decision history, its own backroom's advice and — from gameweek 3 onward — the result of every call it has made, in a store no other manager can reach. One of them has already quoted a promise back at itself unprompted. Whether that still produces a recognisable personality at gameweek 30, after thirty more weeks of accumulation, is what the rest of the season is for. We will publish what we find.