Back to Theory
Theory7 min read · June 16, 2026

Feather DB for Performance Marketing: AI That Remembers Every Campaign

Performance marketing AI has an amnesia problem: every campaign brief starts cold, with no memory of what worked last quarter. Feather DB fixes that — one namespace per brand, typed edges between creative and performance data, and adaptive decay that keeps recent learnings hot while 18-month-old context quietly fades.

F
Feather DB
Engineering

The amnesia problem

Every performance marketing team has some version of this: a creative hook crushed on Meta six months ago — 20% CTR uplift, lowest CPL of the year. The campaign ended. Three months later, a new brief lands. The AI assistant, the copywriter, sometimes even the strategist starts from scratch. The hook that worked is sitting in a spreadsheet somewhere, or in a Slack thread, or in nobody's head anymore.

Marketing AI compounds this. A GPT-4o call has no memory between sessions. Each brief ingestion is a cold start. You dump the relevant campaign history into context, pay for every token of it, and the model still doesn't know which parts actually mattered. Winning hooks look the same as losing ones in a flat document dump. Competitor moves that preceded a creative pivot are disconnected from the creative that responded to them.

The result: AI that's fluent but amnesiac. It can write. It cannot remember.

What needs to be remembered

Performance marketing memory isn't a single type of data. It's at least four distinct signal categories with different half-lives:

  • Winning hooks — specific copy angles and visual framings that hit KPIs. These should be sticky for 12–18 months, weighted by the spend behind them.
  • Brand guardrails — tone, claim restrictions, visual identity rules. These should be permanent. They don't decay.
  • Competitor moves — a rival's new offer, a price drop, a campaign angle your brand hasn't responded to yet. These have a short window of relevance — usually 30–60 days before the market absorbs them.
  • Audience insights — what emotional angles resonate with a specific segment, what objections appear in comments, what messaging causes drop-off. These sit in the middle: relevant for a quarter or two, then slowly replaced by newer signal.

A flat vector store treats all of these as equal. Dump them in, retrieve by cosine similarity, done. The problem is a brand guardrail from 2019 and a hook from last week look equally fresh in a flat index. A competitor move from 18 months ago surfaces alongside last Thursday's launch. The retrieval is semantically correct but temporally wrong.

Feather DB architecture for marketing memory

The setup is straightforward: one .feather file per brand, with namespaces inside it to separate content categories.

import feather_db as fdb

# One file per brand — embedded, zero infrastructure
db = fdb.DB.open("brand_acme.feather", dim=768)

# Separate namespaces for signal types
# brand::hooks      — winning copy angles with spend data
# brand::guardrails — permanent identity rules
# brand::competitors — competitor creative intelligence
# brand::audiences  — segment-level behavioral insights

Namespaces aren't just logical labels. Every search() and context_chain() call scopes to a namespace, so a query for "emotional hooks for consumer apps" never accidentally surfaces the competitor namespace unless you explicitly ask it to. This matters when you're running multiple brands out of one instance — the guardrails for Brand A cannot bleed into Brand B's creative brief.

Multimodal in a single 768-dim index

Performance marketing generates three distinct signal types that traditionally live in three separate places:

  • Text — creative briefs, hook copy, strategy notes, competitor messaging analysis
  • Image descriptions — visual creative summaries, competitor ad descriptions, brand visual audits
  • Video transcripts — ad scripts, UGC content summaries, competitor video intelligence

The standard pattern stores these separately and tries to merge results. The merge is always a guess. When you search for "consumer app emotional hooks," should a text brief score higher than a video transcript describing the same hook? There's no principled answer without cross-modal training.

Gemini Embedding 2 (gemini-embedding-exp-03-07) changes this. All three modalities embed into the same 768-dimensional space with comparable geometry — a text brief describing a hook and a video transcript narrating the same hook land near each other by cosine similarity. One index, one search call, all modalities ranked together:

from gemini_embedder import GeminiEmbedder  # wrapper around generativeai SDK

emb = GeminiEmbedder(api_key=os.environ["GOOGLE_API_KEY"])

# Text creative brief
db.add(id=1001,
       vec=emb.embed_text(creative_brief),
       text=creative_brief,
       namespace="brand::hooks",
       entity="text_brief")

# Image description of the same ad
db.add(id=1002,
       vec=emb.embed_image(image_description=visual_desc),
       text=visual_desc,
       namespace="brand::hooks",
       entity="image_creative")

# Video transcript
db.add(id=1003,
       vec=emb.embed_video_transcript(transcript),
       text=transcript,
       namespace="brand::hooks",
       entity="video_creative")

Now a single query vector — the text of a new brief — can surface semantically similar text briefs, image creatives, and video transcripts in one ranked list. No merge logic, no modality-specific pipelines.

Typed edges: connecting creative to cause

Semantic proximity tells you what's similar. Typed edges tell you what's related by causation or composition. For performance marketing, two edge types carry most of the signal:

# same_ad: image creative → text brief for the same campaign
db.link(from_id=1002, to_id=1001, rel_type="same_ad", weight=1.0)

# caused_by: campaign brief → performance data that validated it
db.link(from_id=1001, to_id=5001, rel_type="caused_by", weight=0.9)

# preceded_by: creative response → competitor move that triggered it
db.link(from_id=1001, to_id=9001, rel_type="preceded_by", weight=0.8)

The caused_by edge is particularly useful. A hook that achieved 20% CTR uplift can be explicitly linked to the performance data record that proves it. When that hook surfaces in a future search, the context chain traversal pulls in the performance record automatically — the model sees not just the hook but the evidence behind it.

preceded_by edges capture the competitive context that motivated a creative decision. If a competitor launched a price-based campaign and your team responded with an emotional angle that performed well, that chain of causation is preserved. A future brief asking for "emotional angle alternatives to price competition" will surface the competitor move and the response that worked.

Adaptive decay: recent learnings weighted higher

The scoring formula in Feather DB's include/scoring.h applies time-weighted decay to every result:

stickiness    = 1 + log(1 + recall_count)
effective_age = age_in_days / stickiness
recency       = 0.5 ^ (effective_age / half_life_days)
final_score   = ((1 - time_weight) × similarity
                 + time_weight × recency) × importance

For performance marketing, you tune the half_life per signal type:

from feather_db import ScoringConfig

# Competitor moves: short window, 45-day half-life
competitor_cfg = ScoringConfig(half_life=45.0, weight=0.4, min=0.0)

# Winning hooks: weighted by spend, 270-day half-life
hooks_cfg = ScoringConfig(half_life=270.0, weight=0.3, min=0.0)

# Brand guardrails: effectively permanent
guardrails_cfg = ScoringConfig(half_life=3650.0, weight=0.05, min=0.0)

# Search competitor namespace with short decay
results = db.search(query_vec, k=10,
                    namespace="brand::competitors",
                    scoring=competitor_cfg)

An 18-month-old competitor creative in a 45-day half-life configuration has decayed to roughly 0.003× its original recency score. It's still retrievable if semantically very close to the query — but it won't surface ahead of last week's intelligence. No manual pruning needed. The decay handles curation automatically.

The stickiness mechanism adds a useful property: hooks that get queried repeatedly — because they keep getting referenced in new briefs — accumulate recall count, which compresses their effective age. A hook that's been queried 20 times has a stickiness of ~4x, meaning it ages at one quarter the normal rate. The creative memory that's actually being used stays fresh. The one that was tried once and forgotten fades.

Setting importance from real spend data at ingestion time anchors the baseline:

# importance from actual campaign spend — anchors retrieval weight
meta.set_attribute("importance", min(1.0, total_spend / 6_000_000))
# Floor at 0.4 for low-spend tests that still hold signal
if importance < 0.2:
    meta.set_attribute("importance", 0.4)

Real use case: Hawky.ai

Hawky.ai is a performance marketing intelligence platform for D2C brands. The founding team ran a performance marketing agency before building the product — which means the memory problem isn't theoretical. It's something they lived operationally: campaigns ending, institutional knowledge walking out the door, new campaigns starting cold.

The Hawky architecture uses Feather DB with one .feather file per brand client. Each file accumulates creative intelligence across campaigns: winning hooks tagged with spend data, brand voice guardrails that are effectively permanent, competitor creative indexed as it's observed, and audience insight notes from comment analysis and qualitative research.

When a new campaign brief is requested, the agent queries against the hooks namespace first — not to copy what worked, but to retrieve the creative principles behind it. The context chain then traverses caused_by edges to surface supporting performance evidence, and preceded_by edges to surface the competitive context that shaped those creative decisions.

The result is a brief grounded in institutional memory: not "write an emotional hook" but "here are three emotional hooks that worked for this brand in consumer-app contexts, here's the spend data behind each, and here's the competitor move that made the emotional angle the right call at the time."

Hawky reports a 27% CPL reduction and 20% CTR uplift within 7 days across client campaigns. The memory layer isn't the only factor, but it's the infrastructure that makes the AI's creative recommendations defensible rather than arbitrary.

Query pattern: finding hooks that worked

The query that powers most of Hawky's brief generation is straightforward:

query = "hooks that worked for consumer apps with emotional angles"
query_vec = emb.embed_text(query)

# Top-k semantic search in the hooks namespace
results = db.search(
    query_vec,
    k=8,
    namespace="brand::hooks",
    scoring=ScoringConfig(half_life=270.0, weight=0.3, min=0.0)
)

for r in results:
    print(f"score={r.score:.4f}  spend={r.meta.get_attribute('total_spend')}")
    print(f"  {r.text[:120]}")

The results are scored by a combination of semantic similarity to the query, recency (270-day half-life), and importance (anchored to spend). A hook from last quarter with high spend and high recall_count will outscore a semantically closer hook from 18 months ago with low spend. The ranking isn't just "what sounds similar" — it's "what worked, recently, with evidence."

The context chain call extends this into a full creative brief context:

# Expand the top hooks into their full causal context
chain = db.context_chain(
    query_vec,
    k=4,
    hops=2,
    namespace="brand::hooks",
    scoring=ScoringConfig(half_life=270.0, weight=0.3, min=0.0)
)

# hop=0: winning hooks (semantic match)
# hop=1: performance data records (caused_by edges)
# hop=2: competitor moves that preceded those creative decisions (preceded_by edges)

for node in sorted(chain.nodes, key=lambda n: (n.hop, -n.score)):
    print(f"hop={node.hop}  [{node.meta.get_attribute('entity_type')}]  score={node.score:.4f}")
    print(f"  {node.text[:100]}")

A two-hop traversal surfaces: the winning hooks themselves (hop=0), the performance data behind each one (hop=1 via caused_by), and the competitor moves that set the strategic context (hop=2 via preceded_by). The agent constructing the brief gets all three layers without knowing to ask for them explicitly.

Cost: 38× cheaper than full-context dumping

The naive alternative is to dump all campaign history into GPT-4o's context window at the start of every brief. For a brand with 18 months of campaign data — creative briefs, performance reports, competitor intelligence, strategy notes — that's easily 200,000–400,000 tokens per call.

At GPT-4o pricing, a 300,000-token context call costs roughly $2.25 per brief. Run that ten times a day across a five-brand agency and you're at $337/day in context costs alone — before any generation.

Feather DB retrieves the relevant 8–12 nodes from the index and passes roughly 2,000–4,000 tokens of targeted context to the model. Same GPT-4o call now costs $0.06. The retrieval adds negligible overhead — p50 ANN search latency on a 500K-vector index is 0.19ms.

The 38× cost reduction isn't the main argument for memory-based retrieval — the quality argument is stronger. Full-context dumping gives the model everything indiscriminately: the hooks that failed alongside the ones that worked, 18-month-old competitor intelligence alongside last week's. The model has no principled way to know which is which. The context engine surfaces what's relevant, recent, and evidenced. The brief quality improves. The cost drops as a side effect.

Setting it up

pip install feather-db

The full marketing memory setup — namespace schema, ingestion functions, scoring configs, context chain query pattern — is under 200 lines of Python. No server, no managed infrastructure. One .feather file per brand client, stored wherever your agent runs. The same file can be queried from a LangChain agent, a CrewAI workflow, or a direct API call with the MCP integration for Claude Desktop.

For agencies managing multiple brand clients: keep one file per client, open them on demand, and close them when done. The embedded format means no connection management overhead. Each file is self-contained, portable, and auditable — if a client asks why a particular hook was recommended, you can inspect the retrieval chain directly from the file.

Install: pip install feather-db · GitHub: github.com/feather-store/feather