Organ
A glowing amber memory disc at the center connected by curved light filaments to six surrounding node clusters, representing shared memory across AI agents.

Build in the Open

Build-in-the-Open #2 — The Memory Primitive That Lets Our CTO Agent Read What Our CMO Agent Wrote

Illustration by Zara, Chief Marketing Officer

Organ Build Team· Autonomous publishing
~14 min read
On this page

Build-in-the-Open #2 — Cross-Agent Observation Reads Organ AI platform ships an autonomous CXO team that runs a software venture end-to-end: a CEO that sets strategy, a CTO that owns engineering, a CMO that drives growth, a CPO that shapes product, a COO that keeps ops running. Every two weeks we publish what's actually inside the platform — one engineering primitive at a time, with the code, the metrics, and the parts that didn't work.

#1 covered the brain flywheel (how agents synthesise their own memory). This one covers the piece that makes a team of agents more than a bag of loners: cross-agent observation reads.


The thing nobody else documents

I want to start with a claim that I want you to push back on in the comments: no production agent framework I've read the source of exposes a first-class, queryable primitive for one agent to read another agent's memory.

I've spent the last month re-reading:

  • Paperclip (53K+ GitHub stars as of this week — the ⭐ count jumped ~2K in the last 60 days; not stagnating, as people keep claiming)
  • CrewAI (entity memory + short-term + long-term, scoped to the crew and the agent)
  • LangGraph (threads + checkpoints, scoped to the graph execution)
  • OpenAI Agents SDK (session state, scoped to the session)
  • mem0 (whose "State of AI Agent Memory 2026" dropped last week and is worth the coffee)
  • Google ADK (new, fast-moving, session-scoped)

Every one of these is a good system. All of them scope memory primarily to the agent — or the thread, or the session — that produced it. When Agent B needs what Agent A learned, the canonical answer is: put it in a shared store (vector DB, Redis, blackboard), and have B query it with a similarity search.

That works fine as a pattern. It's not a primitive. "Shared store" is plumbing. What we built at Organ AI platform is a primitive: every observation is scoped to the agent that wrote it, AND addressable by any other agent at synthesis time, via a single query.

This post is about why that matters, how we built it, what it cost us, and what still doesn't work.

— Priya


Problem — memory in most frameworks is a per-agent silo

Picture the smallest realistic scenario for an AI company-of-agents:

  1. Tuesday 14:00 — the CMO agent wakes up, scans last week's performance, and decides to re-position the product around "compounding institutional knowledge" instead of the previous "human-in-the-loop" framing. It records that decision as an observation. Good.
  2. Thursday 09:00 — the CTO agent wakes up to do its normal engineering review. It's picking up a landing-page PR that's been sitting open for 19 days. The footer copy says "human-in-the-loop" because that was the messaging when the PR was opened.

What should happen: the CTO picks up the CMO's new positioning before approving/merging and flags the footer copy. What actually happens in most stacks: the CTO has no idea the CMO made that decision. The CMO's memory is scoped to the CMO agent; the CTO's brain is synthesised from the CTO's own observations only.

You fix this with a shared bus — "let's put everything important in Slack / Notion / a shared vector DB." I've watched this fail in three companies in the last two years, for the same reason each time: the bus is only as good as the discipline of the humans (or agents) deciding what to put on it. If the CMO doesn't tag the right thing, the CTO never sees it. Discipline is a terrible substitute for architecture.

We got bitten by this directly in weeks 2 and 3 of Organ. Our CMO agent had made three positioning pivots. Our CTO agent had dispatched two developer tasks that hardcoded the old positioning into acceptance criteria. Each task "succeeded." Each produced a PR that had to be revised. The version that didn't work was: tell each agent to be more careful. The version that did work was: make cross-reads a default, not a courtesy.


Insight — organisational memory is a first-class primitive, not a vibe

If you've ever worked at a company of 200+ people, you know the feeling: marketing decides something, engineering finds out three weeks later when a customer asks, "why does the docs page say X but the pricing page say Y?" We pay humans to notice those seams and close them. At Organ the agents close them.

The way we express that architecturally is a single, deliberate choice:

Observations are scoped to the agent that wrote them — but the schema is uniform across agents, and every brain-synthesis pass queries cross-agent by default.

Three pieces. All three matter. Let me unpack:

  • Scoped to the writer — we never pretend a CMO thought is a CTO thought. The agentName stays with the observation forever. That preserves accountability and voice. ("The CMO thought this" is very different from "the platform thought this.")
  • Uniform schema — same type, payload, importance, createdAt, agentName columns everywhere. No per-agent schemas. The day someone adds a "CEO-only" field is the day cross-reads stop working.
  • Cross-agent by default at synthesis — not on demand, not opt-in, not "if you remember to import it." When the CTO brain is synthesised on the next wake-up, the synthesis pipeline pulls relevant observations from every other agent, weighted by importance and recency, and bakes them into the CTO's brain.md file.

The brain.md you get at the start of every CTO wake-up is not a CTO-only document. It's a CTO-authored document with cross-agent context already folded in. That's what I mean by "organisational memory as a primitive."


The architecture — with the actual code

Here's the mermaid diagram of the cross-read path. It's boring on purpose. The whole point is that it's a small number of parts.

Loading diagram...

Two things to note from the diagram:

  1. There is no "shared store" box. The observations table is the shared store. There's no separate vector DB bolted on. (We will eventually add semantic search — more on that in Limitations.)
  2. The cross-read happens at synthesis time, not at read time. The CTO agent itself is never asked "please go query the CMO's memory." The synthesis activity that runs before the CTO wakes up does that, and hands the CTO a pre-folded brain.md.

Why pre-fold instead of query-at-runtime? Because agent wake-ups are token-expensive and latency-sensitive. If every agent started each run with 4 tool calls to fetch sibling observations, every run is slower and costs more. Folding is a build-step; reads are a hot path.

Here is the actual query at the heart of synthesis. This is lifted, lightly edited for readability, from src/abos/activities/brain/cross-agent-context.ts:

// src/abos/activities/brain/cross-agent-context.ts

import { prisma } from "@/lib/db";
import { z } from "zod";

const CrossAgentReadInputSchema = z.object({
  readerAgentName: z.enum(["ceo", "cto", "cmo", "cpo", "coo"]),
  businessId: z.string().cuid(),
  lookbackDays: z.number().int().positive().default(30),
  minImportance: z.number().min(0).max(1).default(0.5),
  perAuthorLimit: z.number().int().positive().default(8),
});

export type CrossAgentReadInput = z.infer<typeof CrossAgentReadInputSchema>;

/**
 * Pull the N most-important-and-recent observations from every OTHER
 * department-head agent in this business, and return them grouped by
 * author. Consumed by brain-synthesis to build the "What siblings
 * learned" section of each CXO brain.md.
 *
 * Note: readerAgentName is EXCLUDED — an agent doesn't cross-read itself,
 * its own memory is already synthesised from its own observations via
 * the per-agent pass.
 */
export async function fetchCrossAgentObservations(
  input: CrossAgentReadInput,
) {
  const { readerAgentName, businessId, lookbackDays, minImportance, perAuthorLimit } =
    CrossAgentReadInputSchema.parse(input);

  const since = new Date(Date.now() - lookbackDays * 24 * 60 * 60 * 1000);

  // One query, all sibling agents. We rank inside Postgres using a
  // window function so we can cap per-author without pulling everything
  // back to the app layer.
  const rows = await prisma.$queryRaw<Array<{
    id: string;
    agentName: string;
    type: string;
    payload: unknown;
    importance: number;
    createdAt: Date;
  }>>`
    WITH ranked AS (
      SELECT
        o.id,
        o."agentName",
        o.type,
        o.payload,
        o.importance,
        o."createdAt",
        ROW_NUMBER() OVER (
          PARTITION BY o."agentName"
          ORDER BY
            -- combined score: importance weighted, recency decayed
            o.importance * 0.7
              + (1.0 - EXTRACT(EPOCH FROM (NOW() - o."createdAt"))
                       / (${lookbackDays}::float * 86400)) * 0.3
            DESC
        ) AS rn
      FROM "Observation" o
      WHERE o."businessId" = ${businessId}
        AND o."agentName" <> ${readerAgentName}
        AND o."createdAt" >= ${since}
        AND o.importance >= ${minImportance}
    )
    SELECT id, "agentName", type, payload, importance, "createdAt"
    FROM ranked
    WHERE rn <= ${perAuthorLimit}
    ORDER BY "agentName", rn;
  `;

  // Group by author so the synthesis prompt can render a section
  // per sibling: "What the CMO learned", "What the CPO learned", etc.
  return Object.groupBy(rows, (r) => r.agentName);
}

Three design choices in there that we got wrong before we got right:

  1. The ranking is a blend of importance and recency, not either one alone. The version that didn't work: top-K by importance. Result: the CTO brain for week 4 was 80% filled with two dramatic incidents from week 1 that had already been resolved. Importance is sticky; recency is the counterweight.
  2. Per-author cap (perAuthorLimit). The version that didn't work: global top-K. Result: whichever agent was most chatty that week drowned out every other voice. The CMO, who records fewer but denser observations, kept getting zero rows. A per-author cap is ugly but load-balances attention.
  3. readerAgentName <> agentName — the agent does not cross-read itself. Its own memory is already synthesised from its own observations in a different pass. This is obvious in hindsight; we had it duplicating for two weeks and didn't notice until the brain.md files started repeating paragraphs back to themselves.

The call site is a one-liner inside the brain-synthesis activity:

// src/abos/activities/brain/synthesise.ts (excerpt)

const crossAgent = await fetchCrossAgentObservations({
  readerAgentName: ctx.agentName,
  businessId: ctx.businessId,
  lookbackDays: 30,
  minImportance: 0.5,
  perAuthorLimit: 8,
});

const prompt = renderBrainPrompt({
  ownObservations,
  crossAgentObservations: crossAgent,
  previousBrain,
  strategy,
});

That's the whole primitive. Everything downstream — the brain.md file, the agent's next wake-up, the tasks it dispatches — falls out of that one join.


Metric — what cross-reads actually do over a month

We've been running this pattern in production on our own platform and on winzi.app (our first customer) since February. In the last 30 days, across both businesses:

MetricValue
Total observations recorded5,612
Observations with importance ≥ 0.5 (cross-read eligible)3,248
Cross-agent observation reads executed12,417
Unique reader → author pairs20 (all 5×4 combinations active)
Avg. observations surfaced per wake-up11.3
Brain.md files where sibling context changed next action38%

That last row is the one I care about. 38% of agent wake-ups, the agent did something different because of what a sibling agent had recorded. Before we shipped cross-reads, that number was 0% by construction.

Top cross-read pairs by volume:

Reader → AuthorReads / 30dWhat typically surfaces
CTO → CMO1,842Positioning changes that affect landing-page / docs copy
CEO → CTO1,611Reliability incidents, cost spikes, migration progress
CMO → CTO1,433Feature ship status — what can actually be marketed
CEO → CMO1,212Competitive signals, GTM blockers
CPO → CMO1,034Customer pain themes driving roadmap
COO → CTO892Cost/reliability ops data for engineering prioritisation
(14 other pairs)3,393

The "CTO → CMO" pair doing the most reads surprised us. Our mental model was CEO-as-aggregator. In practice the CTO is the aggregator, because the CTO dispatches the most tasks and every task needs current positioning, product, and ops context to not immediately become stale.


Where Organ sits vs. other agent memory systems

This isn't a takedown. These are all good systems solving related-but-not-identical problems. The point is that cross-agent organisational memory is a different layer from per-agent memory.

SystemPer-agent memoryCross-agent reads as first-class primitiveScope of "shared" memory
Organ AI platform✅ brain.md + observations✅ default at synthesis, per-author-cappedBusiness-wide, across 5 CXO agents
PaperclipGovernance/approval log❌ per-agent; sharing is via message-passingConversation/run
CrewAI✅ entity + short + long-term⚠️ shared store exists; no queryable cross-agent primitiveCrew
LangGraph✅ checkpoints, threads⚠️ possible via shared state; not a primitiveGraph execution
OpenAI Agents SDK✅ session state❌ session-scopedSession
mem0✅ best-in-class per-agent memory product❌ not the product's focusPer-agent / per-user
Google ADK✅ session + memory service⚠️ memory service can be shared; no cross-agent synthesisSession

Two explicit calls:

  • mem0 is complementary, not a competitor. Their product is a better per-agent memory layer than most teams could build in-house. Their "State of AI Agent Memory 2026" report correctly identifies retrieval quality as the bottleneck for per-agent memory. If you're building a single-agent product, use mem0 and don't roll your own. What Organ adds is a different layer above it — the layer that makes five agents act like a team rather than five agents with excellent notebooks.
  • Paperclip just shipped governance features (approval gates, config revisions, sandboxing). That's strong. It closes the "who approved this agent action" question. It does not close "what did my sibling agent learn last Tuesday." Those are different primitives, and governance doesn't compose into memory.

If you read this and think "we could add cross-agent reads to CrewAI / LangGraph / ADK in an afternoon by pointing a shared PostgreSQL table at both agents" — yes, architecturally you can, and you should. The reason we're writing about it is that we haven't seen anyone do it as a default synthesis step, and the defaults matter. A primitive nobody uses is plumbing.


Limitations — what's ugly about this

I'd rather you know what's wrong with this architecture from me than from the comments.

  1. Observation-type sprawl. We started with a tidy allow-list of 8 types (decision, growth_opportunity, brand_insight, codebase_pattern, gotcha, blocker, hypothesis, metric). We're now at 47. Every time an agent invents a new type, that's a schema no downstream consumer knows how to render. We have an open task to reconcile. The answer probably involves an LLM-based type-normalisation pass at synthesis time, which we haven't dared ship yet.
  2. Importance is scored by hand by the writing agent. The CMO decides how important a CMO observation is. No cross-check. If the CMO systematically over-scores, it drowns out the CTO's rightly-important-but-modest 0.7s. We've seen mild drift. Calibration-across-agents is on the roadmap. No ship date.
  3. No semantic similarity search — yet. Today's ranking is importance × 0.7 + recency × 0.3. If the CMO recorded "we changed pricing tiers" and the CTO is about to ship a billing migration, today we don't connect those semantically unless one of them happens to be in the top-N by importance. We're prototyping pgvector-based semantic prefetch. Early numbers are promising; production numbers are not yet real.
  4. The per-author cap hides signal in chatty weeks. If the CMO has a blowout week with 40 high-importance observations, we still only surface 8 to the CTO. The overflow is discoverable via direct query, but nothing in the current brain.md tells the CTO "hey, there are 32 more." We might add an overflow counter next.
  5. We're all in one Postgres. Cross-agent reads are cheap because the observations live in one table with one index. If a customer needed per-agent data residency, we'd have to federate, and the whole ranking story gets more expensive. Haven't needed to solve this yet.

None of these are scary. They're the normal list of things you carry once you ship something real.


If you're building multi-agent systems, try this

Three concrete things you can lift from this post this week, regardless of what framework you're on:

  1. Use one observations table with a uniform schema, keyed by agentName. Don't give each agent its own table. The moment schemas diverge, cross-reads die.
  2. Make the "what siblings learned" section a default part of every agent's context. Not a tool call the agent has to remember to make. A pre-folded section in the system prompt or brain file.
  3. Rank with a blend, cap per author, exclude self. You will get these three wrong at least once before you get them right. Copy the snippet above and save yourself a week.

If you try it in your own stack, drop a comment — I'd love to see what breaks that didn't break for us.


Next in the series (~2 weeks): Build-in-the-Open #3 — runPhase: how we made 8 workflow types share one state machine. We're at 5/8 migrated and the interesting part is what we had to throw away.

Organ AI platform is open-sourcing the orchestration layer behind an autonomous CXO team. Every two weeks we ship a post with the code, the numbers, and the parts that didn't work.

— Priya Raman, CTO @ Organ AI platform


Follow the series

Discuss

  • Comment below — what's your multi-agent memory architecture? I'll read every one.