The writer agent pulls from two layers: static markdown files (system prompt) and the Supabase knowledge_base table (user prompt). Here is how each works.

Supabase knowledge_base table

Loaded in loadAgentContext() and merged in the writer prompt under "Knowledge base entries you may cite":

  const kb = ctx.relevantKb.concat(ctx.brandVoiceKb).concat(ctx.caseStudyKb);
  const uniq = new Map(kb.map((e) => [e.id, e]));
  const kbBlock = [...uniq.values()].map(formatKbEntry).join('\\n---\\n');
  // ...
    '## Knowledge base entries you may cite',
    kbBlock || '_(no matching knowledge base entries — write conservatively and avoid named entities)_',

Three queries run in parallel:

Source Query Limit
relevantKb Any row whose tags overlap the content idea’s tags 40
brandVoiceKb kind = 'brand_voice' 20
caseStudyKb kind = 'case_study' 20
  const [relevantRes, brandVoiceRes, caseStudyRes, postsRes] = await Promise.all([
    tagFilter.length
      ? supabase
          .from('knowledge_base')
          .select('*')
          .overlaps('tags', tagFilter)
          .limit(40)
      : Promise.resolve({ data: [] as KnowledgeEntry[] }),
    supabase.from('knowledge_base').select('*').eq('kind', 'brand_voice').limit(20),
    supabase.from('knowledge_base').select('*').eq('kind', 'case_study').limit(20),

Kinds in the schema (managed at /admin/knowledge-base):

If the content idea has no tags, relevantKb is empty and the writer only sees brand voice + case studies.

Static “brain” files (system prompt)

These are not in knowledge_base, but the writer always loads them into the system prompt:

const FILES = {
  persona: 'agents/content-strategist/PERSONA.md',
  strategy: 'agents/content-strategist/STRATEGY.md',
  important: 'agents/content-strategist/IMPORTANT.md',
  skill: 'skills/write-blog-post/SKILL.md',
  humanize: 'docs/humanization-guide.md',
  geo: 'docs/geo-guidelines.md',
};

Other context the writer uses