tendnote
- Status
- Active
- Kind
- Product
- Year
- 2026
- Live site
- tendnote.com
- Source
- GitHub
A private, consent-first memory for the people you care about - where nothing an assistant infers becomes a durable fact until you approve it.
What it is#
I kept losing the details that sit outside a calendar: Priya is shipping a launch on Monday, Casey's birthday is in July, and I told Jordan I'd check in after his final interview. A CRM tracks a pipeline. A notes app stores the details without telling me which note matters this morning. tendnote gives people their own private pages and turns due context into a small daily shortlist.

Relationship memory was the first surface. The same reviewed store can hold a fact about Priya, a chore, something you own, or a question you have not answered, so the Product grew outward from that shared lifecycle.
The current Product includes person pages and follow-ups, Assets for things you own, General Actions and Routines for your own work, Saved Items for material without a richer record family, Household workspaces, and capture from Google, Gmail, Contacts, and Discord. The direction is an all-inclusive personal memory OS: one reviewed store for details you would otherwise have to hold in your head. Relationship memory remains the clearest demonstration of that model.

One composer accepts a note, link, open question, reminder, or recurring chore, then files it into the appropriate record family. A person page shows what you know about someone and the source behind each fact. Today collects due follow-ups, birthdays, overdue chores, and review work into a bounded shortlist.
Eve, the built-in assistant, can read approved context, discuss it, draft a message grounded in cited memories, and propose new records for review. The Product contains no path for sending email, chat messages, or social posts. Every decision remains with the person using it.
Capture
Store the original note as a source record.Extract
Propose a typed record with inherited sensitivity.Review
Show the proposal beside the source sentence.Approve
Stamp the decision, write the audit entry, and schedule retrieval work.

Review is a database lifecycle#
The privacy stance is implemented in the database and query layer. Every memory row starts as suggested through the column default. Approval transitions the row to approved, stamps approvedAt, and writes an audit entry. The mutation reloads the row first and rejects any record whose status has already changed.
A memory also requires provenance. The source_record_id foreign key is NOT NULL with ON DELETE RESTRICT, preserving the note behind each fact and letting the review card show the sentence the user wrote. Sensitivity is inherited from that source record, which prevents model wording from weakening the original classification. Background extraction, Eve, and Discord capture all submit suggestions through this lifecycle.

How it's built#
Repository shape#
tendnote is a pnpm and Turborepo monorepo with two applications over five shared packages. apps/web is the Next.js 16.3 App Router UI on React 19.2, Tailwind 4, and shadcn/Radix. apps/agent contains Eve's instruction set, tools, subagents, skills, channels, and scheduled-workflow dispatcher.
packages/db owns the Drizzle schema, migrations, and owner-scoped queries. packages/domain holds shared Zod schemas. packages/auth provides the Better Auth baseline used by both the web Product and Eve. packages/rate-limit and packages/config complete the shared layer. Consumers import explicit @tendnote/db subpaths to keep Eve's bundle lean.
Storage and record families#
Postgres with pgvector stores durable records through Drizzle. Redis backs sessions and rate-limit state. Two tables form the spine: source_records holds evidence such as notes, interaction summaries, and Discord captures; memories holds durable facts that point back to those sources.
Follow-ups reconnect you with a person. General Actions are to-dos for you, and Routines add a cadence to General Actions. Saved Items hold material without a richer record family. Assets represent things you own. Every record carries an owner, visibility scope, and sensitivity. Scope and sensitivity stay separate because they answer separate questions: who may see this, and how delicate is it?
The memory pipeline#
Capture writes a source record and enqueues a Postgres-owned extraction job. The job table records attempts, run_after, claimed_at, and a unique idempotency key, making each job inspectable and safe to rerun.
Extraction produces suggested memories. The review queue interleaves five families: suggested memories, suggested actions, asset review groups, source records, and self-context facts. Approval schedules an embedding job and makes the fact eligible for retrieval.

Retrieval combines rebuildable context snapshots, Postgres full-text search over a generated tsvector, pgvector semantic search over approved memories and eligible records, and unified asset search. Owner, scope, sensitivity, and lifecycle filters run inside the query before ranking, so ineligible rows never enter the candidate set.
Four bounded uses of AI#
- Extraction uses a replaceable LLM adapter in production and a deterministic adapter in tests and offline development. The deterministic adapter exercised the lifecycle before the production model was connected.
- Embeddings, through the AI Gateway.
- Eve, the conversational agent, uses the
eveframework and mounts same-origin into Next throughwithEve(). The browser streams turns from/eve/v1/*, using the site origin with no separate agent URL or CORS configuration.memory_curatorproposes cleanup,relationship_strategistproposes follow-ups, andprivacy_guardreviews with no tools. - Generated briefs use the model for one presentation summary. Deterministic selection from the relationship agenda decides the brief's contents.
On Vercel, /eve/v1/* reaches the Eve service before Next filesystem routing. Eve's channel therefore verifies the Better Auth cookie, requires persisted beta access, charges the ingress rate-limit budget before model work, and stamps the verified user ID onto the session principal.
Precomputed context snapshots are rebuildable caches. Source records and approved memories remain the durable truth used to recreate them.
Repository scale at the verified commit#
| Measure | Count |
|---|---|
| Commits, over two months | 459 |
| Tracked TypeScript files | 1,992 |
| Of those, test files | 650 |
| Committed migrations | 77 |
| Eve eval files | 62 |
| Architecture decision records | 238 |
Decisions worth defending#
Provenance is required by the schema#
memories.source_record_id is NOT NULL with ON DELETE RESTRICT. The shared mutation layer rejects provenance-free memories in normal Product flows; seeds and repair scripts may create controlled fallback source records. ADR 0022 first recorded the policy, then the schema made it mandatory. Every review card can show a fact beside its source sentence.
packages/db/src/schema/app/memories.ts
sourceRecordId: uuid("source_record_id")
.notNull()
.references(() => sourceRecords.id, { onDelete: "restrict" }),
memoryType: memoryType("memory_type").notNull().default("context"),
content: text("content").notNull(),
status: memoryStatus("status").notNull().default("suggested"),Approval is a state transition#
Observations and memories share a table with the lifecycle suggested → approved | dismissed | archived. This kept the first ingestion path small while leaving room to split noisy provider-derived candidates into another table later. Approval reloads the row, requires suggested status, stamps approvedAt, and writes an audit entry that records who promoted the fact and when.
packages/db/src/queries/memories/review.ts
/** Loads a memory and asserts it is still an actionable suggestion. */
async function requireSuggestedMemory(ctx: MemoryReviewContext, input: MemoryReviewActionInput) {
const memory = await ctx.store.getMemory(input);
if (!memory) {
throw new Error("Memory not found.");
}
if (memory.status !== "suggested") {
throw new Error("Only suggested memories can be reviewed.");
}
return memory;
}Scope is enforced in queries#
The query layer enforces scope for retrieval, search, Eve's tools, and the UI. The privacy_guard subagent can review phrasing for leakage and has no tools for granting, widening, or approving access. ADR 0137 made this enforcement a precondition for household sharing.
apps/agent/agent/subagents/privacy_guard/tools/bash.ts
import { disableTool } from "eve/tools";
/**
* Eve resolves its default harness per agent node, so a declared subagent gets
* its own `bash` unless it disables one here. See `agent/tools/bash.ts`.
*/
export default disableTool();The tool gate contains its own failure mode#
Eve's modes narrow the tools available to a turn. The gate resolves the mode from the principal stamped by channel authentication and ignores message text when making that decision.
The sharp edge: how a framework can make a gate fail open
The framework lets a dynamic resolver override an authored tool but cannot
delete one, so the gate rebinds withheld tools to definitions that perform no
action and explain the restriction. The framework also skips a resolver that
throws and proceeds with the full authored tool set. The gate catches its own
resolution errors and falls back to restricted, where no tools are
available. The source file documents this framework behavior beside the
defensive code.
The prompt ships with the source#
tendnote ships under AGPL-3.0 with the application source, Eve instruction set, tool and subagent definitions, ADR corpus, and eval suite. A determined user can extract the deployed instruction set, so publishing it preserves a complete, inspectable case study. Hosted customers pay to avoid operating Postgres, Redis, a queue, a scheduler, and several OAuth applications.
AGPL gives the Project an open-source license with network-use obligations. A CLA is collected from the first external contribution so consent is recorded while the contributor list is still small.
The first Phase 9a eval remains visible#
The repository preserves the first Phase 9a evaluation exactly as it ran: google/gemini-3.7-flash, 52 passed, 8 failed, 0 skipped, and 0 errored across 60 cases, with exit code 1. The same suite passes completely in Nick's local run. Almost all failures in the preserved deterministic GitHub run came from the eval harness. The case study classifies the bundle as exploratory evidence and links the raw reports and checksums so readers can inspect the result.
Agent-assisted development is disclosed#
tendnote uses heavy agent-assisted development with Matt Pocock's wayfinder, to-spec, to-tickets, implement, and code-review skills as the loop from specification through review. The repository's 238 ADRs, 62 eval files, and 650 test files form the review surface around that work. The canonical case study also states that I have personally read roughly 15% of the code.
My role is choosing and reviewing the constraints that govern the Product. The most important constraints live in the schema and query layer, where both human-written and agent-written features inherit them.