A real-time memory and operating system for companies — it remembers everything that happens across your tools, knows when that information goes stale, and can act on your behalf.
Most AI tools that companies plug into Slack, Notion, or their docs have a quiet flaw: they read your company's knowledge once, then keep answering from that snapshot — even after the real answer changed. A policy updates in Slack. A process changes in a GitHub PR. The AI tool never finds out. It keeps giving the old answer, confidently, to whoever asks.
MemOS fixes this and goes further. It is two things:
1. A memory layer that reads Slack and GitHub in near real time, stores what it learns with versioned timestamps, detects when new information contradicts something already stored, and — instead of silently overwriting — puts both side by side for a human to approve or reject. Nothing is ever deleted; old information is archived with a timestamp so you can ask what was true on any past date.
2. An action layer sitting on top of that memory. Because MemOS knows what is actually true in your company right now, it can act on that knowledge — send a Slack message, open a GitHub issue, draft an email — using plain English commands, with a confirmation step before anything executes.
For the company: every employee gets the current, correct answer with a citation. When information changes, a human confirms before it takes effect. Auditors can ask what the company knew on any specific date and get an exact sourced answer.
For each individual: the support rep stops giving out-of-date answers. The new hire asks MemOS instead of interrupting five coworkers. The engineer stops scrolling through six-month-old Slack threads. The PM stops posting the same update to three channels manually.
Slack / GitHub / manual text
│
▼
Decision filter ──── regex scan, skips noise before any API call
│
▼
Chunk + embed (Gemini text-embedding-004)
│
▼
Store in ChromaDB
timestamp → ISO string (display)
timestamp_epoch → Unix int (ChromaDB filtering)
deprecated_at → ISO string (display)
deprecated_at_epoch → Unix int (ChromaDB filtering, sentinel: 253402300799)
is_deprecated → bool
│
▼
Conflict check ──── cosine similarity against active chunks → pending queue
│
▼
Human review ──── approve retires old chunk, reject keeps both
Why epoch integers? ChromaDB's $lte/$gt operators only work on numbers, not strings. Timestamps are stored as both ISO strings (for display) and Unix epoch integers (for filtering). The sentinel for "never deprecated" is 253402300799 (year 9999 as epoch), which lets historical queries use a clean deprecated_at_epoch > as_of_epoch filter without nullable fields.
Historical queries: ask what was true on any past date using timestamp_epoch ≤ as_of_epoch AND deprecated_at_epoch > as_of_epoch. Active chunks always pass the second condition because their sentinel is far in the future.
- Real-time Slack ingestion (polls every 30 seconds, including thread replies)
- Real-time GitHub ingestion via webhooks (pushes, PR merges, issues)
- Manual ingestion of any pasted text
- Decision-keyword filtering — 22 regex patterns skip non-decision Slack noise before any API call
- Per-user isolated ChromaDB collections (multi-tenant ready)
- Automatic conflict detection on every ingest
- Human-in-the-loop deprecation queue — nothing auto-deleted or auto-overwritten
- Side-by-side conflict view with similarity score and source info
- Full version history — old chunks archived with timestamp, never erased
- Historical queries — reconstructs what was known on any past date
- Natural-language Q&A grounded only in stored knowledge
- Inline citations linking back to original source
- Source name, URL, and timestamp shown per citation
- Plain-English command parsing via Gemini function calling
- Confirm-before-execute on every action
- Send Slack message to any channel
- Create GitHub issue with title, body, and labels
- Draft email (subject + full body)
- Full action history log with status
/healthendpoint — DB status, scheduler status, last poll time, pending conflict count- Live sidebar health indicator (poller running/stopped, conflicts awaiting review, chunk count)
- Memory browser with source filter, deprecated toggle, and pagination
- Webhook URL override field for ngrok / production URLs
| Component | Technology |
|---|---|
| API framework | FastAPI (async throughout) |
| Vector store | ChromaDB 0.5.3 (persistent, cosine similarity) |
| Relational store | SQLite via aiosqlite |
| Embeddings | Gemini text-embedding-004 (768 dims, retrieval_document / retrieval_query task types) |
| Q&A + action parsing | Gemini Flash 1.5 (function calling for actions) |
| Auth | JWT (python-jose) + bcrypt 4.0.1 (pinned for passlib compatibility) |
| Background jobs | APScheduler async (Slack polling loop) |
| Slack | slack_sdk AsyncWebClient |
| GitHub | PyGithub + HMAC webhook verification |
| Component | Technology |
|---|---|
| Structure | Single HTML file, no build step |
| Styling | Tailwind CSS (CDN) |
| Reactivity | Alpine.js 3.14 |
| Fonts | Inter + JetBrains Mono (Google Fonts) |
ChromaDB chunk metadata:
source str # 'slack', 'github', 'manual'
source_name str # '#general', 'PR #42', 'Policy doc'
source_url str # link back to original
author str
timestamp str # ISO 8601 (display)
timestamp_epoch int # Unix epoch (ChromaDB filtering)
deprecated_at str # ISO 8601 or '9999-12-31T23:59:59Z'
deprecated_at_epoch int # Unix epoch or 253402300799
is_deprecated bool
deprecated_by str
decision_match bool # matched a decision keyword
memos/
├── backend/
│ ├── main.py # FastAPI app, lifespan, /health endpoint
│ ├── config.py # Pydantic settings from .env
│ ├── database.py # SQLite schema, 5 tables
│ ├── auth.py # JWT, bcrypt, user CRUD
│ ├── vector_store.py # ChromaDB wrapper with epoch timestamp handling
│ ├── scheduler.py # APScheduler — Slack background polling
│ ├── services/
│ │ ├── embedder.py # Gemini text-embedding-004
│ │ ├── llm.py # Gemini Flash — Q&A + function calling
│ │ ├── ingestion.py # Decision filter → chunk → embed → store → conflict check
│ │ ├── conflict.py # Epoch-based similarity search → deprecation queue
│ │ └── slack_poller.py # Incremental channel + thread reply polling
│ └── routers/
│ ├── auth_router.py
│ ├── memory_router.py # /stats and /list before /{chunk_id}
│ ├── deprecation_router.py
│ ├── actions_router.py
│ └── integrations_router.py # HMAC webhook, Slack token validation
├── frontend/
│ └── index.html # Full SPA — login + 5 sections
├── data/ # Auto-created at boot
├── requirements.txt # bcrypt==4.0.1 pinned
├── .env.example
├── run.py
└── test_memos.py
# 1. Unzip and enter
cd memos
# 2. Create venv
python3 -m venv venv && source venv/bin/activate # Windows: venv\Scripts\activate
# 3. Install
pip install -r requirements.txt
# 4. Configure
cp .env.example .env
# Edit .env — add your GEMINI_API_KEY (free at https://aistudio.google.com/app/apikey)
# 5. Run
python run.py
# → http://localhost:8000- Create app at https://api.slack.com/apps → From scratch
- OAuth & Permissions → Bot Token Scopes:
channels:history,channels:read,chat:write - Install to workspace → copy
xoxb-…token - In Slack:
/invite @YourBotNamein each channel to monitor - In MemOS Settings → paste token + channel IDs → Connect
- https://github.com/settings/tokens → scopes:
repo,issues - In MemOS Settings → paste token + default repo → Connect
- For webhooks: paste your public URL in the override field (use ngrok for local dev:
ngrok http 8000) - In GitHub repo → Settings → Webhooks → Add: events push, pull_request, issues
pip install httpx
python test_memos.py
# Against deployed: python test_memos.py --url https://your-app.fly.dev| Variable | Required | Default | Description |
|---|---|---|---|
GEMINI_API_KEY |
✅ | — | Google AI Studio (free) |
JWT_SECRET |
— | insecure default | Change before deploying |
SLACK_BOT_TOKEN |
— | — | xoxb-… |
SLACK_CHANNELS |
— | — | Comma-separated channel IDs |
SLACK_POLL_INTERVAL_SECONDS |
— | 30 | Polling frequency |
GITHUB_TOKEN |
— | — | Personal access token |
GITHUB_WEBHOOK_SECRET |
— | — | HMAC secret for webhook verification |
SIMILARITY_THRESHOLD |
— | 0.85 | Conflict detection sensitivity |
fly auth login
fly launch # accept defaults, skip database
fly secrets set GEMINI_API_KEY=your_key JWT_SECRET=your_secret
fly volumes create memos_data --size 1
# Add to fly.toml: [mounts] source="memos_data" destination="/app/data"
fly deploy| Bug | Impact | Fix |
|---|---|---|
bcrypt version conflict with passlib |
Auth completely broken | Pinned bcrypt==4.0.1 in requirements |
ChromaDB $lte/$gt rejects string timestamps |
Historical queries silently returned no results | Added timestamp_epoch and deprecated_at_epoch as Unix int fields |
| Conflict detection used string timestamp comparison | Wrong ordering, missed conflicts | Switched to timestamp_epoch comparison |
| Slack thread replies not fetched | Decisions in threads completely missed | Added conversations_replies call for all threaded messages |
hmac.new() deprecated form |
Minor warning | Updated to hmac.HMAC() |
GET /memory/stats registered after GET /memory/{chunk_id} |
Route could shadow stats endpoint | Moved specific routes before parameterised routes |
GitHub webhook URL showed localhost in Settings |
Webhook useless for local dev | Added public URL override input |
- Email SMTP sending (draft is ready, send not wired)
- Notion / Confluence connectors
- Google Calendar scheduling actions
- Automated standups and weekly digest
- Proactive alerts (unanswered questions, stale deprecation queue)
- Cross-tool rule automation ("when X, do Y")
- Slack slash command (
/ask what is our refund policy?)





