How I solved Claude's "amnesia problem" with a simple microservice
Every conversation with an AI assistant starts the same way: from scratch.
If you're using Claude, ChatGPT, or any LLM for serious work – coding, product development, writing – you know the pain. You have project briefs, architecture docs, PRDs, and user stories. And every single chat, you're either:
- Copy-pasting walls of text
- Re-explaining context you've explained 100 times
- Watching the AI confidently hallucinate details it should know
I got tired of this. So I built a Context API that gives Claude persistent, structured access to my project documentation. Here's how – and why you might want something similar.
The Problem: GitHub Raw URLs Suck for AI Context
My first attempt was simple: store all project docs in a GitHub repo and have Claude fetch them via raw URLs.
https://raw.githubusercontent.com/myorg/context/master/projects/chainsights/prd.md
Elegant, right? Version controlled, easy to update, free hosting.
Except it doesn't work.
GitHub's CDN caches raw files aggressively. Even with ?nocache=1 appended, I'd often get stale content. Claude would reference outdated requirements. Bugs would slip through because the AI was working with last week's architecture doc.
Other problems:
- No structured queries – you get raw markdown, nothing more
- No delta updates – fetch everything or nothing
- No write-back – Claude can't persist insights or tasks
- Two-repo sync hell – docs live in project repos, but Claude needs them centralized
I needed something better.
The Solution: A Dedicated Context Service
I built a simple API service that:
-
Receives docs from GitHub Actions – when I push to
_bmad-output/in any project, a webhook syncs to the API - Serves structured JSON to Claude – not raw markdown, but typed documents with metadata
- Supports delta queries – "give me only what changed since yesterday"
- Keeps version history – every update is preserved
- Detects orphans – renamed or deleted docs don't become zombies
The architecture looks like this:
┌─────────────────┐ GitHub Action ┌─────────────────┐
│ Project Repo │ ───────────────────▶ │ Context API │
│ (chainsights) │ POST /sync │ (api.masem.at) │
└─────────────────┘ └────────┬────────┘
│
┌─────────────────┐ │
│ Project Repo │ ───────────────────────────────▶│
│ (tellingcube) │ │
└─────────────────┘ │
▼
┌─────────────────┐
│ Claude │
│ GET /context │
└─────────────────┘
The Data Model
Three tables. That's it.
Projects
CREATE TABLE ctx_projects (
id UUID PRIMARY KEY,
slug VARCHAR(50) UNIQUE NOT NULL, -- "chainsights"
name VARCHAR(100) NOT NULL, -- "ChainSights"
github_repo VARCHAR(200), -- "myorg/chainsights"
is_active BOOLEAN DEFAULT true
);
Documents
CREATE TABLE ctx_documents (
id UUID PRIMARY KEY,
project_id UUID REFERENCES ctx_projects(id),
doc_type VARCHAR(50) NOT NULL, -- "brief", "prd", "epic", "story"
doc_key VARCHAR(100) NOT NULL, -- "prd", "epic-auth-flow"
title VARCHAR(200),
content TEXT NOT NULL,
file_sha VARCHAR(40), -- Git SHA for change detection
version INTEGER DEFAULT 1,
updated_at TIMESTAMPTZ,
UNIQUE(project_id, doc_type, doc_key)
);
Document History
CREATE TABLE ctx_document_history (
id UUID PRIMARY KEY,
document_id UUID REFERENCES ctx_documents(id),
content TEXT NOT NULL,
file_sha VARCHAR(40),
version INTEGER NOT NULL,
created_at TIMESTAMPTZ
);
The file_sha is key – when the GitHub Action syncs, we compare SHAs. Same SHA? Skip the update. Different SHA? Archive the old version, update the current.
Idempotent syncs. No duplicate writes. Clean history.
The API
Reading (for Claude)
# Get all docs for a project
GET /v1/context/chainsights
# Get only briefs and PRDs
GET /v1/context/chainsights?types=brief,prd
# Get changes since a timestamp
GET /v1/context/chainsights?since=2024-02-20T00:00:00Z
# Get a specific document
GET /v1/context/chainsights/epic/auth-flow
Response:
{
"project": {
"slug": "chainsights",
"name": "ChainSights"
},
"documents": [
{
"doc_type": "prd",
"doc_key": "prd",
"title": "Product Requirements Document",
"content": "# ChainSights PRD\n\n## Overview...",
"version": 7,
"updated_at": "2024-02-21T09:15:00Z"
}
],
"updated_at": "2024-02-21T09:15:00Z"
}
Writing (from GitHub Actions)
POST /v1/context/chainsights/sync
{
"documents": [
{
"doc_type": "prd",
"doc_key": "prd",
"title": "Product Requirements Document",
"content": "...",
"file_path": "_bmad-output/prd.md",
"file_sha": "a1b2c3d4..."
}
],
"known_paths": [
"_bmad-output/prd.md",
"_bmad-output/epics/auth-flow.md"
]
}
Response:
{
"synced": 2,
"created": 0,
"updated": 1,
"unchanged": 1,
"orphaned": [
{
"doc_type": "epic",
"doc_key": "old-feature",
"file_path": "_bmad-output/epics/old-feature.md"
}
]
}
The known_paths array is clever – it tells the API "these are all the docs that currently exist." Anything in the database but not in this list? That's an orphan. Maybe it was renamed, maybe deleted. The API flags it so I can clean up.
The GitHub Action
name: Sync BMAD Output to Context API
on:
push:
paths:
- '_bmad-output/**'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Sync to Context Service
env:
MMS_API_KEY: ${{ secrets.MMS_CONTEXT_API_KEY }}
run: |
# Collect all docs, build JSON payload
# POST to api.masem.at/v1/context/chainsights/sync
Configuration per project (.github/mms-context.yml):
project: chainsights
documents:
- path: _bmad-output/brief.md
type: brief
key: brief
- path: _bmad-output/prd.md
type: prd
key: prd
- path: _bmad-output/epics/*.md
type: epic
# key derived from filename
Every push to _bmad-output/ triggers a sync. Docs flow from my repo to the API automatically.
Integrating with Claude
Here's the magic part. In Claude's system prompt (or custom instructions), I add:
You have access to the MMS Context Service for project documentation.
API-Key: mms_context_claude_xxxxx
Base-URL: https://api.masem.at
Endpoints:
- GET /v1/context/{project}?types=brief,prd&format=summary
- GET /v1/context/{project}/{doc_type}/{doc_key}
When the user mentions "Context: {project}", fetch the relevant documents.
Load summaries first, then fetch details as needed.
Now when I start a chat with "Context: chainsights", Claude:
- Fetches the project summary (titles + first 500 chars)
- Understands what docs are available
- Loads specific docs when needed for the task
No more copy-pasting. No more "as I mentioned in our last conversation." Claude just knows.
Caching Done Right
Since we're solving a caching problem, we better cache properly:
Server-side (Redis):
context:chainsights:full → 5 min TTL
context:chainsights:prd → 5 min TTL
context:chainsights:epic:auth-flow → 5 min TTL
When a sync happens, we invalidate only the affected keys. Granular, not nuclear.
Client-side (ETag):
Response: ETag: "sha256-of-content"
Request: If-None-Match: "sha256-of-content"
Response: 304 Not Modified
Claude's requests include the ETag from the last fetch. If nothing changed, we return 304 – fast and bandwidth-friendly.
Delta updates:
GET /v1/context/chainsights?since=2024-02-20T00:00:00Z
Claude can ask "what changed since my last sync?" and only get the deltas. Perfect for long-running conversations.
Results
After two weeks of using this:
- Zero stale context issues – Claude always has the latest docs
- Faster conversations – no preamble, no re-explaining
- Better AI output – consistent terminology, accurate references
-
Cleaner repos – no more
masemit-contextsync repo
The service handles 7 projects, ~50 documents, and costs me exactly $0/month extra (it runs on existing infrastructure).
Should You Build This?
Yes, if:
- You use AI assistants for ongoing project work (not just one-off questions)
- You have structured documentation (PRDs, specs, architecture docs)
- You're frustrated by context limits and repetition
- You already have some API infrastructure
Maybe not, if:
- You're doing ad-hoc AI tasks with no recurring context
- Your docs change rarely (just upload to Claude Projects)
- You don't want to maintain another service
What's Next
I'm considering open-sourcing this as a standalone service. If there's interest, I'll clean up the code and publish it.
Features I'm still thinking about:
- Full-text search across all documents
- Task write-back – Claude can create tasks that sync back to GitHub Issues
- Webhooks – notify Slack when syncs fail
- Multi-tenant – let other teams use it
TL;DR
- GitHub raw URLs have caching problems that break AI context
- A simple Context API solves this: sync via GitHub Actions, serve via REST
- SHA-based idempotency prevents duplicate writes
- Delta queries and ETags keep things fast
- Claude gets reliable, structured, up-to-date project context
If you're building something similar or want to discuss the approach, drop a comment or find me on LinkedIn, Farcaster or X.
Mario Semper is the founder of masemIT, building developer tools and Web3 analytics. He's currently shipping ChainSights (DAO governance analytics), tellingCube (synthetic business data), PayWatcher (stablecoin payment verification), and occasionally yelling at GitHub's CDN.
Top comments (0)