The Pinolo Manifesto

How we built an AI chief of staff — and why we built it this way.

10 chapters · ~25 min read · trade-offs and residual risks included


Abstract

Pinolo is a personal AI chief of staff. It ingests what your working life produces — meeting transcripts, messages, the commitments buried inside them — builds a memory it can defend, and works for you proactively: a brief in the morning, coaching after a difficult call, a nudge when a promise is about to slip. It runs on your own AI: the reasoning happens in the assistant you already use and already pay for, connected to Pinolo over an open protocol. The server behind it never runs a language model at all.

That last sentence usually raises eyebrows, and it is the right thread to pull. This document explains the design of Pinolo through the choices that made it: what we picked, what we rejected, what each choice cost, and what still worries us. It is written for two kinds of readers: people deciding whether to trust Pinolo with their working lives, and people building systems like it. Both deserve the real reasons.

Chapter 1 of 10

The obvious way to build this

The obvious way to build an AI assistant is a loop. Pipe everything the user does into a context window. When the context overflows, add a vector database and call it memory. When it needs to act, hand the model your tools and ask it nicely to be careful. It demos beautifully.

Then, a few weeks in, comes a moment every user of these systems learns to dread: the assistant confidently remembers something that never happened. Or it forgets a commitment you made in a meeting it transcribed itself. And when you ask why it believes what it believes, it has nothing to show you — no source, no history, no way to distinguish something you said from something it inferred, three summaries deep.

The failure is not the model. Models are the best they have ever been and improving monthly. The failure is architectural. Recollection without evidence. Memory without a paper trail. Isolation enforced by a WHERE clause someone has to remember to write. An "agent" whose permission system is a paragraph of prose it may or may not obey — and that anything it reads can try to rewrite.

We built Pinolo the other way around, on five commitments that are each roughly the opposite of the obvious loop:

  • Memory is evidence. Everything Pinolo believes traces back to an immutable record of where it came from. Nothing is silently overwritten; beliefs are superseded, and the supersession is itself recorded.
  • Boundaries are structural. Tenant isolation, write permissions, and autonomy limits are enforced by the database and the type system — never by instructions to a model, and never by the model's good behavior.
  • The server never thinks. All reasoning happens in the user's own AI, at the edge. The server is deterministic: state, queue, delivery, audit. It cannot be talked out of anything, because it cannot be talked to.
  • Proactivity is a suggestion. The system proposes; the user disposes. Quiet hours, hard caps, and a no-nagging discipline are design constraints, not settings.
  • Trust evidence, not time. Transient attention decays on its own; real obligations never silently expire just because they got old.

The rest of this document walks through the choices these commitments forced — one chapter per choice, each with the alternative we rejected and the price we paid.

Chapter 2 of 10

One database, one boundary

Pinolo is hosted and multi-tenant: many people's working lives, one system. The obvious way to keep them apart is application code — every query ends in WHERE tenant_id = ?, and everyone promises never to forget one. That discipline fails the way all disciplines fail: eventually, and quietly.

We wanted isolation to be an invariant, not a habit — enforced by the database regardless of what the application forgets. Every tenant table carries Row-Level Security, with FORCE: the database itself refuses to return or accept rows outside the current tenant, even to code that owns the table. The current tenant is set per transaction, never per connection or process — because connections are pooled and shared, and a tenant id parked on a shared connection is a leak waiting for the next request. Transaction-local context dies with the transaction; there is nothing to forget to clean up.

The database roles are cut the same way. The runtime roles hold minimum privilege and none can bypass row-level security. Better: the production login holds no privileges of its own and inherits none — until code explicitly assumes a role, it can do nothing at all. A forgotten role switch fails closed: the request errors instead of quietly running with too much power. This is the most underrated line of defense we know — making the forgetful path the harmless one.

There is also a hole in this design that most multi-tenant schemas never notice: foreign-key checks bypass row-level security. A plain project_id REFERENCES project(id) will happily accept a reference into another tenant's project — RLS filters what a query can read, not what a constraint can see. So every reference between tenant tables is composite: (tenant_id, id).

A cross-tenant reference is not forbidden in Pinolo; it is unexpressible.

Above the database, one rule closes the loop: tenant identity comes only from the authenticated session or token — never from a URL, a header, or a parameter. A client cannot ask for a tenant. It can only be one.

Exactly one piece of the system is deliberately cross-tenant — the job queue — and it gets its own audited role and a rule we will come back to: nothing that rides through it may contain tenant content. That is the next chapter.

And the honesty that anchors this one: there is no "zero-leak" design. This isolation is a list of conditions that must all hold at once — so the list is executable. Continuous integration proves cross-tenant isolation on every commit, rather than trusting that it held last quarter.

What this costs us

Ceremony. Composite keys everywhere, per-transaction setup, verbose role pinning on every lane of the code. We pay it gladly — it is the cheapest insurance in the entire system.

Chapter 3 of 10

The queue lives in the database

An assistant that works while you don't is, mechanically, a system of background jobs. A transcript arrives at 3 p.m.; the extraction of its commitments happens later, when your AI next picks up work. A brief is due at 6 a.m.; something has to know that, hold the task, and survive a crash in between. The standard answer is a dedicated queue — Redis, Kafka, SQS — bolted alongside the database.

Pinolo's queue is a Postgres table.

Claiming a job is one atomic statement built on FOR UPDATE SKIP LOCKED: take the next pending row that nobody else is holding, without waiting. Two workers can poll at the same instant and are guaranteed to walk away with different jobs, because a locked row is skipped rather than fought over. That one primitive replaces a broker's entire delivery machinery at our scale — and "at our scale" is doing honest work in that sentence. A personal chief of staff processes tens of jobs per tenant per day, not tens of thousands per second. If you are building a firehose, use a broker. Most people building assistants are not building firehoses.

What do you get for staying in the database? One system to operate, back up, and reason about. The queue inherits Postgres's durability instead of introducing a second persistence model with its own failure modes. Job state lives one join away from the application state it describes, under the same transactional guarantees — with one deliberate exception we will keep returning to: a language-model call is never allowed inside a database transaction. Transactions are for milliseconds; reasoning takes seconds. Holding locks across an LLM call is how systems die slowly.

Crashes are handled the way message queues have always handled them: with leases. A claimed job must be heartbeat every few minutes or it returns to the queue — SQS calls the same idea a visibility timeout. The subtlety we would underline for anyone building this, because we had to relearn it ourselves, is that a lease is not a job duration; it is a deadman's switch. It bounds the interval between heartbeats, not the length of the work. A job may legitimately run for a long time; what it may not do is go silent. Confuse the two and you will evict slow-but-alive workers mid-job, waste their work, and retry into the same timeout forever.

A lease is not a job duration; it is a deadman’s switch.

Recovery creates a nastier problem than crashing: the zombie. A worker loses its lease — it stalled, the network hiccuped — the job is re-issued to someone else, and then the original worker comes back and tries to write its results as if nothing happened. Locks cannot save you; the zombie's transaction is new. So every attempt at a job gets a fresh attempt_id, and every write a worker makes must present the job id, its attempt id, its own identity, and a still-live lease. Stale attempt, dead lease, wrong worker: the write is refused. This is fencing, and it is the difference between a queue that demos and a queue that survives.

Which raises the honest question of "exactly-once". You cannot have exactly-once execution in a distributed system, and anyone selling it to you is rounding up. What you can have is at-least-once delivery with effectively-once effects: every write is keyed on a deterministic hash of the inputs that produced it, so when a retried job writes again, it lands on the same slot and becomes a no-op. Idempotency turns "it ran twice" from corruption into a non-event.

One last property matters more than all the mechanics: the queue carries metadata only — ids and status codes, never content. The queue is the one genuinely cross-tenant piece of infrastructure in the system, claimed by its own audited database role, and we wanted a structural guarantee — not a code-review habit — that no tenant's transcript or summary ever rides through shared machinery. Why "structural, not habitual" is the entire tenancy story is the previous chapter's subject.

What this costs us

Workers poll, so there is a tick of latency a push broker would not have. The jobs table needs its own hygiene — partial indexes on the claim path, a retention sweep so terminal jobs do not accrete forever. And at genuinely high throughput, a queue in Postgres becomes vacuum pressure you would have to engineer around. We accepted all three, because each is cheaper than operating a second distributed system whose failure modes we would also have to learn.

Chapter 4 of 10

Memory with a paper trail

If the server never thinks, what it does instead is remember — and remembering, for a chief of staff, is a chain-of-custody problem. Pinolo's memory has three layers, and the layering is the point.

At the bottom, evidence: the transcript as it arrived, the email as it was forwarded. Append-only and immutable — immutable by the database: update and delete are revoked, and triggers reject any attempt. Not a comment in the code asking politely; a constraint that refuses.

In the middle, beliefs: when a model extracts "Marco owes the pricing update by Friday" from a transcript, the extraction is recorded as a versioned assertion pointing at its evidence. Assertions are never edited — they are superseded, and the supersession is itself a record. You can ask what the system believed last Tuesday, and on what basis, and get an answer.

At the top, knowledge: typed entities — people, projects, goals, commitments, notes, documents. Real columns, real foreign keys, real constraints: the semantic state the assistant actually reasons over. The split loosely mirrors what the agent-memory literature calls episodic versus semantic memory — but here the schema is where the metaphor stops being one.

Why three layers instead of one clever one? Because each single-layer design fails in its own way, and we wanted neither failure. Throw everything into flexible JSON and you land in the entity-attribute-value trap: no referential integrity, no indexes worth the name, every query an archaeology dig. Type everything up front and every new kind of knowledge becomes a schema migration, while the flexible ingest substrate disappears. So evidence stays flexible, beliefs stay versioned, knowledge gets types — and promotion, the step that turns beliefs into entities, is idempotent and provenance-preserving. Run it twice; get the same graph.

Provenance goes down to the individual field. Each entity field accretes links to the assertions that support it — many of them, over time. Ask a commitment why it exists and the answer is a chain: this field, from that assertion, from that meeting, on that day.

The type system also encodes a position most assistants miss: a fact is not an obligation is not an artifact. A note records something true; a commitment records who owes what to whom; a document is a made thing. Conflate them — and the obvious loop conflates them constantly — and facts rot into false debts while yesterday's reports masquerade as memory.

One last rule runs through the whole layer, learned the hard way: identity lives in the unique index, not in lookup code. Start a recording twice and one meeting arrives as two; they must collapse into one. The arbiter must be the database constraint itself — because any separate "have we seen this before?" query will eventually disagree with the index it thinks it mirrors, and that disagreement is a factory for duplicates that only show up under load, or after a deploy, or on a Tuesday.

What this costs us

Discipline, forever. Promotion has to stay idempotent and provenance-preserving on every code path, or the typed layer quietly drifts into a second, ungoverned truth — the one failure this design cannot survive. And field-level provenance makes every write more ceremony than a bare UPDATE would be. The alternative is a memory that cannot show its work, so we pay.

Chapter 5 of 10

The server that never thinks

The most consequential choice in Pinolo is the one that sounds most like a riddle: the server runs zero language-model calls. Not "a small model for the cheap paths" — zero. The server cannot summarize, cannot classify, cannot decide. Every act of reasoning happens at the edge, inside the user's own AI assistant, connected to Pinolo over MCP — the open protocol that lets an assistant call external tools.

Mechanically it works like this. Pinolo's server exposes a small, curated tool surface. Your assistant connects and, when it is time to work, claims a job from the queue under a lease. It loads the relevant skill — a job description written in markdown, served by Pinolo: how to extract commitments from a transcript, how to compose a morning brief. It reads the declared inputs, reasons — the one part only a model can do — and writes back the declared output, which the server validates against a schema and records with full provenance. Then it marks the job complete. The server orchestrated everything and understood nothing.

Three arguments drove this, and they compound.

Leverage. Your assistant already exists, and it is already excellent. Claude — the assistant Pinolo is built for today — comes with a world-class reasoning engine, a chat surface you already live in, an agentic work loop, and, crucially, connections into your actual tools. Rebuilding any of that inside Pinolo would produce a worse copy of each, behind one more login nobody asked for. So Pinolo refuses to compete with your assistant and completes it instead: it supplies the two things the assistant does not have — a durable memory that can defend itself, and a disciplined background work loop. (This also happens to be the economical arrangement — you already pay for intelligence once, and Pinolo does not meter it back to you at a margin — but the economics are a consequence, not the reason.)

Custody. Your reasoning should happen where your data already lives. Claude already holds your integrations — email, calendar, chat — with credentials Pinolo never sees. The server keeps what it must keep to be a memory: the evidence and the entities derived from it, isolated per tenant. It never becomes the honeypot that holds everyone's keys to everything else.

Security — the deep one. A language model can be talked into things. That is not a bug awaiting a patch; it is what "instructable" means. Anything a model reads — an email, a transcript, a pasted document — is a potential instruction, which is why prompt injection remains an unsolved category of attack rather than a bug with a CVE number. From that one fact follows the design rule everything else here obeys: the model must never be the permission system. Authority has to live below the model, in machinery that prose cannot reach.

The model must never be the permission system.

So in Pinolo, judgment lives in markdown, but power lives in the manifest. A skill's markdown body can be rich, opinionated, even wrong — it steers the model's judgment, and that is its job. What it can never do is widen the model's reach: each skill declares what it may read and write, and the server enforces the declaration at the moment of writing, on its own side of the wall. A hostile instruction smuggled into a transcript can change what the model wants to do. It cannot change what the server will accept. Writes outside the declared surface are refused no matter how persuasive the prose upstream was, and ingested content is treated as data, never as instructions, all the way through the pipeline.

What this costs us

Control we chose not to have. Because execution lives in the user's assistant, the server can schedule work but cannot force it to run: if your assistant never shows up, jobs wait — and Pinolo tells you, calmly, that your worker seems to be missing, rather than pretending. Pinolo also cannot force model quality: run your assistant on a model too weak for what a skill declares it needs, and Pinolo will warn you and keep going, because halting your work loop over a model choice is not its call to make. And steering behavior through markdown means behavior is bounded, not clockwork — we constrain it with schemas, allowlists, and validation, and we say "bounded" rather than pretending "eliminated".

What we bought is a server whose every guarantee is expressed as structure — schemas, roles, fences, append-only tables — rather than as hope about model behavior. A server that cannot think is a server that cannot be talked out of its promises. The rest of this document is, in one way or another, a tour of that structure.

Chapter 6 of 10

Your keys stay yours

Every integration platform eventually builds the same room: a vault where users deposit credentials to all their other tools, so the platform can act on their behalf. That vault is the most attractive object in the building.

We decided not to build the room.

Pinolo's default posture is simple: it hosts no third-party connectors and stores no third-party credentials. Data enters through two doors. Push: external tools send content in — a webhook, a forwarded email — authenticated by per-tenant tokens that Pinolo stores only as hashes. It can recognize a token; it can never reproduce one. Pull, at the edge: your assistant reads your tools directly, through the integrations it already has, with credentials Pinolo never sees — the custody argument from the previous chapter, completing itself.

Reality granted one exception, and we kept it narrow. Some transcript services offer only a polling API: no webhook, nothing to push, nothing for the assistant to read live. For those, Pinolo holds a per-tenant key — under three disciplines. The key is envelope-encrypted with the tenant and the provider bound into the cipher's authenticated data, so a leaked ciphertext cannot even be decrypted against another tenant's row: if row-level security somehow failed, the cryptography would still refuse. The key never appears in any read path — no tool, no API, no log ever returns it. And it is entered only through the web portal, never through chat, for a reason that generalizes well beyond Pinolo: a secret pasted into a conversation lives in that conversation's history forever. Where a secret may travel matters as much as where it rests.

What this costs us

Convenience. A platform that holds your keys can offer one-click sync with everything; Pinolo's ingestion depends instead on what can be pushed to it and on what your assistant can reach, and every exception has to clear a bar far higher than "it would be handy". We would rather explain a missing integration than an incident.

Chapter 7 of 10

A system that proposes

A chief of staff who only answers questions is a search box with manners. The job is noticing: the commitment about to slip, the project gone quiet, the person you have not spoken to since March. So Pinolo runs a slow, deterministic sweep over its own memory, looking for conditions that deserve attention.

The hard part is not the detecting; it is the telling.

Each detection becomes a signal: an immutable assertion — "this commitment was overdue on Tuesday" — with a separate disposition that belongs to you: open, acted on, dismissed, snoozed. The split is the same evidence discipline as everywhere else, applied to the system's own opinions. Mutate the signal to record your reaction and you have edited history: when the condition recurs next month, you cannot tell recurrence from resurrection. Keep the assertion immutable and the disposition beside it, and the past stays askable.

Signals also withdraw themselves: when the sweep no longer detects the underlying condition — the commitment got done, the project woke up — the signal auto-resolves. The system cleans up its own stale opinions, which turns out to be rarer in this industry than it should be.

The hard constraint on all of it is not technical: the trust problem outranks the detection problem. Detecting one more nudge-worthy condition is easy. A system that cries wolf gets muted, and a muted chief of staff is dead weight. So surfacing is calm by contract, not by tuning: one morning brief that ranks brutally rather than lists exhaustively; suggestions in the app; quiet hours; hard daily caps; a "don't remind me again" that is honored forever. Almost nothing earns an email. Every surfacing decision is designed backwards from a single question: will you still be listening in six months?

Will you still be listening in six months?

And what the system proposes, it delivers as something you can talk to. The morning brief, the post-meeting coaching, the periodic pulse: Pinolo creates them unprompted, persists them as documents with full provenance, and delivers them to your inbox. Each one carries a handle back into conversation — a link that opens Claude with that document loaded, so the natural next move (push back, drill in, ask "why do you think this matters?") is one click away, with the entire memory behind the answer. A deliverable is a conversation starter, not a terminal report. One discipline keeps that door safe: links carry pointers, never payloads. An id travels in the URL; your content never does.

The same humility governs action. Pinolo drafts and proposes; acting outward on your behalf — sending, publishing, committing you to things — sits behind explicit gates. Suggestions, not orders, is not a tone of voice. It is an enforced autonomy tier.

What this costs us

Immediacy. A system this reluctant to interrupt will sometimes tell you tomorrow morning what a noisier one would have pushed tonight, and a gated assistant will sometimes ask where a bolder one would have acted. Both are deliberate. Between an assistant you sometimes have to check and an assistant you eventually mute, we chose the one you have to check.

Chapter 8 of 10

A system that keeps itself clean

Long-lived memory systems die of two opposite diseases: they hoard until the signal drowns in the noise, or they forget things that mattered. Pinolo's answer is two lifecycle rules, deliberately paired.

What decays: attention. A project's heat is recomputed from current facts on every sweep — never incremented, never accreted. The distinction is worth internalizing: an accumulated counter is a belief about history that drifts with every bug and can never be trusted again; a recomputation is self-healing, wrong for at most one cycle. Signals, as we saw, auto-resolve when their condition disappears. And infrastructure exhaust — finished jobs, old logs, expired deduplication records — is swept on schedule, with one detail that says more about the design than the sweeping does: in the entire runtime, the privilege to delete exists in exactly one place. One role, one audited function, with hard floors on the retention windows, so that no configuration mistake can turn the janitor into a shredder.

What never decays: evidence and obligations. Raw evidence is append-only, enforced by the database, full stop. And a real commitment never expires because it got old. Age is not evidence — six silent weeks might mean the thing resolved itself, or that it is quietly becoming the most important item on your plate. So commitments close on evidence — someone said it was done, the artifact showed up in a later meeting — or by explicit human decision. Never by timeout.

Trust evidence, not time.

The corollary lives at the moment of capture. "Send the pricing update by Friday" and "I should really think about marketing" are different species: the first is an obligation — someone is owed something checkable — the second an intention. Pinolo classifies them at the source. Obligations get due dates and follow-through; intentions route to weekly planning for an honest reckoning — commit, drop, or keep — instead of rotting in the guilt pile. Filing an aspiration as a debt is precisely the memory rot that makes assistants unbearable to live with.

What this costs us

The reckoning stays yours. A system that refuses to auto-close obligations will carry an uncomfortable backlog in plain sight until you decide what each item means — commit, drop, or keep. We could have fogged the mirror and called it tidiness. A chief of staff that flatters you is worse than none.

Chapter 9 of 10

What we would warn you about

A manifesto that lists only victories is marketing. These are the risks we accepted, in the same spirit as the decision log they come from.

  • There is no zero-leak design. Tenant isolation is invariant-strong, but it holds only while every one of its conditions holds — which is why the conditions are executable tests run on every commit, not a document that was true once.
  • Model-steered behavior is bounded, not eliminated. Schemas, allowlists, and fenced writes cap what a misled model can do; nothing caps what it can conclude. The paper trail exists so that when it is wrong, you can catch it being wrong.
  • The model-quality guard warns and never blocks. Run your assistant on a model too weak for the work, and Pinolo will say so and carry on. Your loop, your call — including a call we would not make.
  • Work happens when your assistant shows up. Polling has a cadence; a push system would be faster, and less honest about who is in charge.
  • The queue in Postgres is right at our scale and would be wrong at a much larger one. We know roughly where that cliff is, and we would rather tell you it exists than pretend the choice is free.
  • The bug class we fear most: two components silently disagreeing about identity or deduplication. Our rule — the database constraint is the only arbiter; delete the second opinion — has paid for itself repeatedly, and it is the rule we would hand any builder before any other in this document.

Chapter 10 of 10

Where this goes

The assistants everyone is building will keep getting more capable; the models guarantee that much. What the models do not guarantee is anything worth calling trust. Trust is architecture, and it has to be earned structurally: memory with a paper trail, boundaries that hold without anyone's good behavior, initiative that respects your attention, a server that cannot be talked out of its promises.

Pinolo is our attempt at that standard — intelligence at the edge, where you already own it; discipline in the middle, where it can be proven. A memory that compounds for years instead of resetting every session, and can show its work for every belief it holds.

Hold anything that wants to run your working life to this standard. Including Pinolo.