Single SourceStudios Engage SSS
Phase 1 of 11 · v0.1.0, unpublished · local-only

A local event ingestion and retention backbone for macOS.

Observ is a single FastAPI service bound to 127.0.0.1:8400 that ingests timestamped events from arbitrary local sources into one SQLite table, deduplicates them at the database layer, and purges rows past a configurable retention window on a daily background task. It is Phase 1 of an 11-phase roadmap: only ingestion, storage, config, and retention exist. No capture sources, analysis engine, or digest pipeline are built.

22 commits, 2026-03-13 to 2026-04-09
48 tests passing
0 tags, 0 releases
2 API endpoints
7/85 requirements satisfied
22commits, one build session plus one sync commit
48tests collected across 4 test files
579 / 646source LOC / test LOC (Python)
2API endpoints: POST /api/events, GET /api/health
What it is

One HTTP service, one SQLite table, one retention loop.

The full roadmap in ROADMAP.md imagines an 11-phase system: Screenpipe, an interaction daemon, a Chrome extension, and a file watcher feeding the hub, with a classifier, waste detector, chain detector, and interest scorer consuming it, ending in a digest, nudge, and MCP output layer. None of that exists in this repo. Phase 1 built the plumbing underneath all of it: a CLI (observ serve) that resolves host and port from flags or a config file, hands off to uvicorn, and exposes exactly two routes for anything to post events into and check on.

Configuration lives at ~/.observ/config.toml, auto-created with defaults on first run and loaded through a Pydantic v2 singleton. The database lives at ~/.observ/events.db, outside the repo entirely; there is no database file checked in and no deploy target beyond a developer's own machine.

fastapi uvicorn pydantic aiosqlite tomli-w click
Hard numbers

Every figure below traces to a command or a file path.

MetricValueEvidence
Commits22git log --oneline | wc -l
First / last commit2026-03-13 / 2026-04-09git log --format='%ad' --date=short
Tags / releases0git tag (empty output)
Branches1 (master)git branch -a
Source LOC (Python)579find src -name "*.py" | xargs wc -l
Test LOC (Python)646find tests -name "*.py" | xargs wc -l
Test files4 (test_api.py, test_storage.py, test_config.py, test_retention.py)find tests -name "test_*.py"
Test cases48pytest --collect-only -q → "48 tests collected"
Requirements tracked85, 7 satisfied.planning/REQUIREMENTS.md; PROGRESS.md "7/85 requirements satisfied"
Package name / versionobserv 0.1.0, unpublishedpyproject.toml:2-3
API surface2 endpoints: POST /api/events, GET /api/healthsrc/observ/api/events.py:12, src/observ/api/health.py
CLI surface1 command, 4 flags (serve --host --port --reload --config)src/observ/cli/commands.py
Runtime depsfastapi, uvicorn, pydantic, aiosqlite, tomli-w, clickpyproject.toml:6-13
Deploy targetnone, local-only, bind 127.0.0.1:8400CLAUDE.md "Default bind is 127.0.0.1 (safe)"
Database in reponone (lives at ~/.observ/events.db)find for *.db / *.sqlite under repo returns nothing
Planning docs15 files under .planning/find .planning -type f
Architecture

A linear path from HTTP POST to SQLite row.

Data flow for Phase 1 is a straight line: HTTP POST → Pydantic validation → Database.insert_event → SQLite (WAL) → HTTP response. No capture sources, analysis, or output layers sit on either end.

Entry point

main.py, 45 lines

A FastAPI app with an async lifespan context manager that opens the SQLite database, starts the RetentionScheduler, and tears both down on shutdown. Two routers mount under /api.

Storage

storage/db.py, 155 lines

An async Database class over aiosqlite. Each operation, insert_event, health_check, purge_old_events, opens its own short-lived connection rather than reusing one. Schema lives in storage/schema.sql: one events table (id, timestamp, source, event_type, content, metadata, content_hash, created_at) with a UNIQUE(source, timestamp, event_type) constraint enforcing dedup, plus three DESC indexes on timestamp, (source, timestamp), and (event_type, timestamp).

Configuration

config/settings.py, 137 lines

A Pydantic v2 ObservSettings singleton loaded from ~/.observ/config.toml via stdlib tomllib, write path via tomli_w, auto-created with defaults on first run, with a reset_settings() test hook.

Retention

retention.py, 128 lines

A RetentionScheduler running as an asyncio.create_task, purging events older than retention_days (default 90) once immediately and then every interval_seconds, re-raising CancelledError on shutdown so task.done() reports correctly.

CLI

cli/commands.py

One Click command, observ serve, which resets the settings singleton, resolves host and port from flags or config, and hands off to uvicorn.run("observ.main:app", ...).

Engineering decisions

Six decisions visible in the code, the commits, and the docs.

The WAL PRAGMA resets on every write

bug, disclosed

WAL mode, synchronous = NORMAL, and wal_autocheckpoint = 1000 are set once in Database.init_schema, but insert_event and purge_old_events each open a fresh aiosqlite.connect(), which reverts synchronous to SQLite's default FULL on every write. journal_mode = WAL persists in the file header, but the code comment claiming "3x faster writes" is not realized on inserts.

content_hash is stored but never used

dead field

Computed and stored on every insert, but never queried or indexed. Deduplication runs solely on the UNIQUE(source, timestamp, event_type) constraint.

Dedup keys on the second, not the sub-second

known edge case

Two distinct events from the same source and event_type in the same second are treated as duplicates and the second is silently dropped: insert_event returns None, the API returns status="duplicate_dropped" with HTTP 201, not 409.

Pydantic BaseModel, not BaseSettings

deliberate

Chosen to avoid environment-variable coupling; the codebase owns config loading via tomllib / tomli_w instead (decision [01-03]).

asyncio.create_task over ensure_future

deliberate

CancelledError is caught and re-raised rather than swallowed, because a silent swallow breaks task.done() detection for the retention scheduler (decision [01-04]).

No agent scaffolding added

scoped out

No skills, agents, or hooks under .claude/, only a settings.local.json. The project's own CLAUDE.md states the bar for agent scaffolding was judged not met for a project this small.

Timeline

One build session, one sync commit, then silence.

git log --format='%ad %s' --date=short shows one continuous build on 2026-03-13: 21 commits running research synthesis, requirements, the 11-phase roadmap, a Phase 1 plan across three waves, then four feat/test/docs cycles for the scaffold, the SQLite store, the config system, and the retention scheduler, each closed out with a phase-completion doc commit.

A single commit follows, "chore: sync uncommitted changes", dated 2026-04-09, 27 days later. No feature commit has landed since.

Reproduction spikes

Three AI systems rebuilt the same Phase 1 spec, months apart.

Three sibling directories, observ-grok, observ-kimi, observ-manus, each attempt an independent rebuild of the same Observ Phase 1 spec by a different AI system, named for the tool that built it. None is a fork or clone of the canonical git history: observ-grok's .git exists with zero commits; observ-kimi and observ-manus have no .git directory at all. All four share one architectural constant: the events table schema, columns, types, defaults, and all three indexes, is identical byte-for-byte in intent across every variant, differing only in comment style.

Metricobserv (canonical)observ-grokobserv-kimiobserv-manus
Built2026-03-13 to 04-092026-07-212026-07-282026-07-21
Prompt inputgit history, planning docs1,012-line few-shot prompt embedding literal source excerptsplanning docs / prose (HANDOVER.md cites canonical ROADMAP.md)none in repo, no prompt or handover doc
Source LOC579 (Python)4151,144 Python + 1,053 HTML/CSS/JS317
Test LOC646735696305 (single file)
Tests collected48676953
API endpoints22102
DB connection patternper-operation connect, WAL PRAGMA reset on reconnectsame per-operation pattern, same WAL-reset characteristicsingle persistent connection, no PRAGMAs issuedsingle persistent connection, no PRAGMAs issued
Self-report vs. actual test countn/aPDF claims "52+ tests"; repo has 67HANDOVER.md claims 32; repo has 69no self-report document exists
GROK · SOURCE-FED

Closest architectural match

Given literal excerpts of the canonical Database class, PRAGMA sequencing, and dedup logic, with instructions to match "whitespace, parameter handling, async patterns exactly." Reproduced the identical 2-endpoint API and the same per-operation-connection, WAL-reset characteristic as the canonical build.

docs/observ-handover.pdf is internally contradictory: it claims "pytest structure ready (52+ tests spec-compliant)" in its verification section, then separately admits "No full 48 tests written" under deviations. Neither figure matches the 67 tests actually collected.

KIMI · PROSE-FED

Built past Phase 1 scope

Pointed at planning docs rather than source, and used that latitude to add a capture/ package, a second BackgroundScheduler, and a full static/ web dashboard (1,053 combined lines) with pause and resume controls, none of which Phase 1 or the other two variants have. 10 routes, not 2.

HANDOVER.md claims "32 tests, all passing"; the repo as it stands collects 69. It also flags its own Screenpipe client as untested against a live instance, and a stray directory literally named ~ holds a leftover 218-row database from an unexpanded shell path, not a code defect.

MANUS · PROSE-FED

Closest to spec size

No prompt, handover, or planning document exists in the repo at all, the earliest of the three builds. Smallest footprint: 317 source lines, 305 test lines, 53 collected test cases, and the only variant matching the canonical 2-endpoint API exactly.

Retention is a plain while True loop with inline asyncio.sleep(86400); on shutdown it cancels the task but never awaits it. The only variant with author metadata in pyproject.toml and a checked-in egg-info build artifact.

Self-reports are unreliable Both AI-authored handover documents undercount or contradict their own repositories: Grok's PDF claims two different, mutually exclusive test counts, and Kimi's HANDOVER.md is stale against code written after it. In both cases the file system, not the model's narrative about its own work, is the only trustworthy count.

Fidelity to the canonical implementation tracks directly with how much of the actual source a model was given. Grok, handed literal code excerpts as a few-shot pattern, reproduced the architecture, WAL-reset bug included, almost exactly. Kimi and Manus, working from prose plans, independently converged on a different, simpler choice (one persistent connection, no WAL PRAGMAs at all) and diverged from each other in scope, with Kimi alone building substantially beyond Phase 1 into an unrequested dashboard and capture-polling layer.

Boundary

What Phase 1 is not.

Observ Phase 1 is an event ingestion and storage backbone only. It is not a behavioral analysis system: no Screenpipe integration, interaction daemon, browser extension, file watcher, activity classifier, chain detector, digest generator, or MCP server exists in this repo. It has never been packaged for distribution, no tags, no PyPI publish, and has no deployment target beyond a developer's own machine.

Evidence: compiled from the observ repository on 2026-08-03. Every number on this page traces to a file path or command output in the source tree. Last updated: 2026-08-03.
Single Source

Every number on the dossier and whitepaper pages traces to a file path or command output in the source tree.

LinkedIn Facebook (c) 2026 Single Source Studios (Pty) Ltd