NAOMS Devlog

Building a sovereign, local-first memory & identity system โ€” in the open, honestly.

NAOMS engineering update

Week 2

A heavier week than the first: 1,727 commits against 1,014, with about twenty-two efforts finished. Everything the week touched is below, the largest threads first and every area at the end.

Commits
1,727
โ–ฒ +713
from 1,014
Busiest day
388
(Thursday 2026-03-26)
from 301
Efforts finished
~22
Efforts cancelled
2
on purpose, and named
Raw SQL in application code
0
(879 query calls migrated)
Process Dispatch free March 29, 2026ยท25 min readยทmeta

Commits per day โ€” Week 2

Four things changed this week

Four areas moved at once: encryption, consent, trust, and the network path underneath all three. The database beneath everything was rebuilt in the same seven days.

They are one layer rather than four features, which is the week's real shape. A consent rule, a trust edge and a sealed memory all refer to the same kind of self-owned identity, and none of them would mean much alone.

1

Your memories are encrypted on disk

Each field of a stored memory is sealed on its own, so only you can read it โ€” with a standing guarantee that your own access always survives.

2

Nothing is shared unless you say yes

Reads and shares are refused until a signed grant allows them, and every grant lands in an append-only trail that cannot be quietly edited afterwards.

3

You decide exactly who you trust, and for what

Relationships became explicit two-signature edges carrying a direction and a kind, capped at a human number of close ties, with no trust score stored anywhere.

4

Every message between machines arrives at one door

Four separate network listeners became one, and a message that is not a signed, sealed envelope is refused at that door rather than judged by a later layer.

Memories that only you can read โ€” and the one you can hand over

The sharing preferences screen. Each row is a separate decision with its own choices โ€” share my location (off, country, region, city, exact), let me appear in group chats, let friends see my friends list (off, a count, anonymous, names), send anonymised debug reports, share who I am โ€” and every default is the narrow one, marked DEF. A "Use safe defaults" button sits above a plain sentence saying what skipping means: location off, group-chat visibility off, friends visible off, identity sharing off. Dark theme, real capture.

The feature: the memories NAOMS keeps for you are encrypted where they sit on disk, and a single memory can be handed to a single person without opening any of the others.

Before: stored memories were readable to whatever could open the store, and letting someone in would have meant copying content out and sealing a second copy for them. Now: every field is sealed on its own with AES-256-GCM, and sharing seals a copy of that one field's key for one named recipient instead.

Encryption at rest answers one specific question: what someone learns by getting hold of the file. It says nothing about what a running program with the keys can see, and nothing about what you choose to share. Those are the next section's problem, and the three only work as a set.

Encrypting per field rather than per file is a deliberate trade, and it costs something. A single encrypted blob is simpler and hides more. It also has to be decrypted wholesale before anything inside it can be looked up, which is fatal for a system whose entire job is searching what it remembers.

What was built. Two independent layers. The whole database file is encrypted at rest, and inside it each memory field is encrypted again under a key derived from your own tree of keys. The outer layer locks the room; the inner one locks each box standing in it.

The unit of protection is what makes sharing possible at all. Once a room is open, every box in it is open too โ€” so a system that locks only the file can never hand over one memory and keep the rest sealed.

Drawing the boundary around the field is what buys that, and it is the reason the boundary sits there. (Memory that only you can read walks the envelope field by field.)

Each sealed field is stored as a small self-describing package: the sealed bytes, the random value used to seal them, a tag proving nothing was altered, the name of the algorithm, and the path to the key. Everything needed to open it except the secret itself.

A fresh random value is generated for every single write, and never reused. That requirement is written into the format rather than left to whoever calls it, because reusing one under this cipher is the mistake that undoes the protection entirely.

The mode matters as much as the cipher. AES-256-GCM is authenticated encryption, so a field that has been altered fails to decrypt rather than decrypting into something plausible and wrong. Silent corruption is the failure a memory system can least afford, because nothing downstream would know to doubt the result.

The guarantee that matters more than the cipher

An encryption scheme has two failure modes and only one of them gets attention. The famous one is somebody else reading your data. The quiet one is you being unable to, and in a personal system it is the more damaging of the two, because no support desk is holding a spare copy.

Each sealed field carries the path to its own key rather than the key itself โ€” a coordinate into a tree of keys grown from a twenty-four-word recovery phrase.

Given the phrase, the path says exactly which key to derive. Given a stranger's field, the same path says nothing useful at all. (What your recovery phrase really buys you follows one phrase down to the keys.)

That is the self-access guarantee, stated plainly: a person holding their recovery phrase can decrypt all of their own memories with no service running, no vault process and no network. Naming that beside the algorithm is the difference between a security feature and a data-loss bug wearing a security feature's clothes.

The tree also separates the jobs. The key that signs your public statements is a different branch from the key that seals your private memories, and every agent you create gets its own. One exposure therefore does not cost you the others.

There is a second thing the guarantee buys, less obvious than recovery. It makes the encryption safe to turn on by default, because a scheme that can strand its owner has to be optional, and an optional protection protects roughly nobody.

Restoring is that same walk in reverse. Feed the phrase back on a new machine and the identical tree grows again, key for key, because every key is a function of the phrase and a path rather than a stored secret.

The words are shown once and written down by hand. There is no path that resets them, which is the same coin as nobody else being able to reset them either.

Sharing moves a key, not the memory

Sharing could work by decrypting a memory, copying the contents out, and sealing a fresh copy under a key the recipient holds.

That works, and it is a trap: plaintext moves on every share, large content is re-encrypted per person, and the same memory now exists in two forms that can drift apart.

What was built instead. Content is sealed once, for its whole life, under a key of its own. Letting a person in seals a copy of that key for them and nothing else โ€” a fixed cost of thirty-two bytes per recipient, rather than a cost that grows with the size of the memory. Remove the person, remove that one wrapped copy. (Share a memory by wrapping a key has the mechanism.)

Every wrap uses a throwaway key pair created for that one operation, agreed against the recipient's public key, so two wraps of the same memory for the same person do not produce the same bytes and the owner's long-term key never takes part. Only the recipient's private key opens the result.

Access also becomes something you can list. Answering "what may this person read?" is a lookup over the wrapped keys addressed to them, rather than a scan of everything you own and a guess about each item.

One consequence deserves stating in a reader's terms: a completely stolen database reveals nothing. It holds sealed content and sealed keys, and nothing inside it can open either, because the secret those keys grow from was never stored there in the first place.

Two limits, both real. Taking access back marks a wrapped key dead so the system refuses it, but the underlying key is not rotated โ€” the ceremony that would make an old key useless forever was designed and then cancelled.

And nobody can be made to un-see what they already read, which is a boundary every access-control system lives with.

The store also keeps its shape in the open. Sealing field by field protects each field's contents; it does not conceal how many memories exist or how they are arranged.

Within one person's store the keys are not yet partitioned by category either. Both are costs, named here rather than left to be discovered.

Trust starts at zero, and four gates stand between it and your data

The feature: deciding who can see what, with every one of those decisions explicit, revocable and recorded.

Before: access was ambient โ€” being on a list was the permission โ€” and trust was a number stamped on a person. Now: a stranger arrives with nothing, trust is a signed edge between two named beings, and a read has to clear four independent gates before it happens.

Ambient permission is the normal way software does this, and it fails in a predictable direction. Names accumulate on lists, lists outlive the reason they were made, and nobody is ever asked to reconfirm. The failure is not that the wrong person was added; it is that the right person was never removed.

Default-deny is a sentence anyone can say and a property very few systems have. The test is not whether a request without permission is refused. The test is what happens to a request nobody thought about, and the only structure that answers it well is one where the absence of a rule means no.

What was built. Four gates, each able to refuse on its own: a signed identity, an explicit trust edge from the owner toward the asker, a consent rule granting a specific scope, and a capability token carrying that grant to whatever does the work. All four to allow; any single one to refuse. (No by default walks the four in order.)

The first gate is a signature rather than a session. Whoever is asking has to present a self-owned identifier and prove control of it by signing the request, so an unauthenticated ask never reaches the gates behind it.

The gates also fail in distinguishable ways. You can hold a relationship with no rule behind it, a rule with no token, or a token whose authority was narrowed below what this particular operation needs.

The fourth gate does the unglamorous work. A token is scoped to one grant and travels with the request, so the component doing the reading carries its own permission instead of asking a central authority whether it is allowed. That authority can be narrowed as it is passed onward, and never widened.

Default-deny has a cost and it lands on the person, not the machine. Every share becomes an act rather than an assumption, which is more friction than a list-based system asks for. The trade is that nothing is ever shared because somebody forgot to remove a name.

Four gates also means four places to look when access is refused. That is a real cost in confusion, paid to remove a larger cost in surprise.

What a relationship is, once it stops being a score

Trust here is a property of a relationship rather than of a person. An edge runs from one being toward another, carries a kind โ€” vouching for someone, attesting to a skill, delegating authority, recommending them โ€” and a weight.

It also records whether trust may travel onward through it or stops where it lands. (Trust is directional follows that one decision through the design.)

Alongside the structural kind sits a softer note saying why the edge exists at all โ€” a shared piece of work, a thank-you, a piece of feedback. The kind drives the arithmetic; the note keeps the human story attached, so an answer can be explained and not only scored.

An edge's direction is the load-bearing part. What you think of someone and what they think of you are two separate records, and a single number per person cannot hold both without averaging them into something neither of you said.

Both ends have to sign before an edge exists, and either end can tear it down alone. Granting takes two; walking away takes one.

A graph you could leave only with your counterparty's approval would be a trap rather than a graph. (Trust lives on the edge has the edge schema and the traversal.)

There is a ceiling on how many edges you may draw: 150 for a person, 50 for an agent, 1,500 for a collective.

A reputation score has no natural ceiling, which is exactly why scores get farmed. Vouching for more people than you can hold in mind is not a decision, it is an accumulation.

No number is stored anywhere. A verdict is computed from the signed edges when some decision needs one, and then let go.

A bad month therefore cannot sit in a column, being queried and exported long after everyone involved has moved on. (The temperature is not the patient is the argument for keeping nothing.)

Refusing to store a number also lets the answer depend on the question. Vouching for someone as a gardener says nothing about whether they should approve a financial decision, and a single stored figure is forced to answer both with the same value.

Requiring both signatures does one more thing worth naming. It puts the asymmetry in the open: there is no central rater handing down a verdict, only two beings who each said, in writing, what they were willing to say.

Standing does not leak, either. Trust earned in one community begins again at zero in the next, and crossing that boundary takes an explicit, scoped grant.

The friction is the point: a relationship that could be conjured in a single click would be worth exactly what a click costs. (Trust starts at zero.)

Why every grant is written where it cannot be changed

Every grant and every revocation is appended to a hash-chained trail, so the history of who could see what, and when, is itself tamper-evident. Append-only is the first half; hash-chained is the second. One says entries are never removed, the other says earlier entries cannot be quietly rewritten.

Grants, denials and withdrawals all accrete in the same trail, and nothing is ever edited in place. Access granted on a Tuesday and withdrawn on a Thursday leaves both facts standing, in order, rather than one quietly replacing the other in the record.

That is a stronger property than logging. A log answers what happened according to whoever kept the log. A hash chain answers what happened according to mathematics, because altering an earlier entry breaks every entry that came after it.

The reason to want the stronger property is uncomfortable and worth stating. The party most able to quietly rewrite an access record is the party running the system, which in this design is you โ€” and a record you could edit is a record nobody else has reason to accept.

What the trail does not yet do is present itself. The history is recorded and tamper-evident; nothing this week puts it in front of a person as an ordinary thing to read. A record nobody looks at is still worth keeping, but it is not yet doing the job its properties promise.

One defence the week only sketched: stopping a single person from minting a thousand identities.

The layered answer written down โ€” the shape a fake cluster makes in a graph, people vouching for people in the same room, and the time a real relationship takes to build โ€” is a blueprint on paper rather than running code, and it says so. (One being, one vote is that blueprint, labelled as one.)

What this means, in plain terms

Most of what lands in a week belongs to the thing it landed in. A few things generalise past it. These five are the ones this week actually paid for.

A lock you can be locked out of is a data-loss bug in a costume

Memories are now sealed field by field, and the work ships with an explicit guarantee that the owner can always reach their own data from their recovery phrase alone. Without that clause the same cipher would be a way to destroy a personal archive permanently, by accident. Ship the recovery property in the same change as the lock.

Independently revocable layers, or you only have one layer

Access needs an identity, a trust edge, a signed consent rule and a scoped token โ€” and pulling any single one of them stops it, without disturbing the other three. Layers that can only be removed together are one layer wearing four names. A defence you cannot partially withdraw is a defence you will never withdraw.

A record its own keeper can edit is not evidence

Every grant and revocation lands in a hash-chained trail, so changing an old entry breaks all the entries after it. The point is not distrust of the operator; it is that a record only persuades when its keeper could not have altered it unnoticed. Tamper-evidence is what turns a log into evidence.

Publish the scorecard while it is still red

Mid-way through removing every raw query, an audit was published saying 31% of the exit conditions passed, with the gaps listed. Waiting for green would have made the instrument worthless, because a scorecard that only ever appears flattering is a press release. An instrument allowed to say only good news has stopped being an instrument.

A test that goes quiet is not a test

Tests for the language-model path used to skip themselves when the model was not built, so a build regression that removed it read as all green. A missing dependency is now a loud failure in the suite that claims to cover it. A check that goes silent when its subject disappears reports the absence of its subject as health.

How much healthier is it than a week ago?

CommitsEvery commit in the window's log, counted by line
1,014โ†’1,727
โ–ฒ +713
Busiest dayCommits on the heaviest single day; Thursday 2026-03-26 here
301โ†’388
โ–ฒ +87
Efforts finishedDistinct pieces of work that reached their finished state in the window
~22
Efforts cancelledWork explicitly stopped, with the decision recorded
2
Raw SQL in application codeQuery call sites written as raw SQL, across the whole codebase
879โ†’0
โ–ผ โˆ’879

Group spaces going from a local network to the open internet is the change with the widest reach and the least visible surface.

Gossip spreads what a member knows to the members it can reach, lazy sync fetches the rest on demand rather than up front, and backup and compaction keep the accumulated history from growing without bound.

The unified transport is the other change with reach beyond its own area. Every peer-to-peer path โ€” shared-history sync, chat, call setup, tool access โ€” now enters at one authenticated door, so authentication stops being something each path has to remember.

The counting rule for commits is the same as last week's: a count of the window's own log. Both figures are reproducible from the record, which is why they lead here even though a commit count says nothing about difficulty.

The busiest day moved from 301 to 388 on that same rule, so the two weeks can be compared directly.

Net lines are not quoted, and the reason is specific. This window's diff is dominated by removals โ€” the second database went, and the wiring that had grown around it went with it โ€” so a net figure would report the busiest week of the series so far as a small one. What replaced them is the figure worth reading: zero raw queries left in application code.

Two efforts stopping in the same week as twenty-two finishing is the healthier signal of the two. A plan where nothing is ever cancelled is a plan nobody is reading.

In one line

memories became encrypted on disk with a guarantee you can still read them, sharing became default-deny across four independently revocable gates written to a tamper-evident trail, every message between machines moved to one authenticated door, and the data layer finished its migration with zero raw SQL left standing.

Four honest notes.

Two efforts were cancelled and both are named. Trust decay with demurrage and standalone revocable access did not ship and are not quietly pending; the decision to stop was deliberate and is recorded here rather than left to be inferred from silence.

The finished-effort count is approximate and marked as such. About twenty-two pieces of work reached their finished state, and the figure carries a tilde because the boundary of what counts as one effort is a judgement rather than a measurement.

Where a number is exact it is quoted exactly, and where it is not, it is not quoted at all. The count of migrated query call sites is exact, so it appears; the net line movement for the window is an estimate, so it does not, and the two should not be mixed in one sentence.

Group spaces reached the open internet, and reach is not the same as scale. Nothing here measures how the gossip and sync layer behaves at a size larger than the three-machine proof it grew out of.

Nothing in this window has a contemporaneous picture. The image below is a later capture of the same surface, captioned as a stand-in, because no image of the March build exists to show instead.

What changed, area by area

Everything that moved this week, in rough order of how much of it moved. Per-area file counts are not available for this window, so each area is named without one rather than with a figure that cannot be reproduced.

Trust.

Relationships became explicit typed edges between two signing beings rather than membership in an ambient list.

An edge carries a direction, a kind, a weight, and a flag saying whether trust may travel onward through it; outbound edges are capped at 150 for a person, 50 for an agent and 1,500 for a collective.

Queries fan out only a few hops, so nobody needs the whole graph to reason about their neighbours. No trust number is stored anywhere: a verdict is computed when a decision needs one and then discarded, and it is computed per domain, so vouching for someone as a gardener says nothing about a financial decision.

( The temperature is not the patient .)

Consent.

Reads and shares are refused by default until a signed grant allows them, across four gates that can each refuse alone and each be pulled back alone.

Every grant and revocation is appended to a hash-chained trail that makes a later edit detectable. Authority carried in a token can be narrowed as it is delegated onward, never widened.

Memory encryption.

Stored memories are sealed field by field with AES-256-GCM inside an already-encrypted database file, each field carrying the path to its own key rather than the key.

The owner can decrypt everything from their recovery phrase with nothing running. Per-category key separation and key rotation within one person's store are later phases, not present here.

Key wrapping and sharing.

Sharing a memory seals a copy of that memory's key for the recipient instead of re-encrypting any content, so access is a small object beside the data rather than a second copy of it.

Listing what someone can read becomes a lookup. Withdrawing access marks that wrapped key dead; rotating the underlying key was designed and cancelled.

The data layer.

Every query in the application now goes through one interface, the database went fully graph-native, and the migration finished rather than stalling: 879 query call sites across 97 files converted, the second database retired, and zero raw SQL left in application code.

Only a reducer replaying a signed event may write a graph node, so the queryable view cannot disagree with the record it is derived from. Seventy-eight registered materializers do that projection work, with 72 tests behind the graph-native store. ( Killing raw SQL .)

Peer-to-peer transport.

Four separate listeners became a single authenticated door, and a message arriving without a signed, sealed envelope is refused there rather than adjudicated later.

The work began as a startup hang and ended as an architecture; the server now starts with peer-to-peer networking enabled in under a second. It shipped at 31 of 32 end-to-end cases and the record says so. ( One endpoint to rule them all .)

Group spaces.

A group became a being in its own right โ€” its own chain with governance, metadata and content branches, its own signing identity, and signed revocable membership credentials it issues about its own members, with joining gated by a vouch.

Hives also moved from a three-machine local-network proof to the open internet. A group's emergent culture and its record of who contributed what remain designs. ( A crowd that is also a person .)

Identity.

Names in NAOMS resolve to nobody's server: an identifier carries a fingerprint of its own first record, so anyone can check it is genuine without asking a registry, online or off.

Its history is an append-only log with key rotation committed in advance. Three internal ways of answering "who am I?" collapsed into one, ending a class of bug where you were refused access to something you had just created. ( An identity that resolves to nobody's server .)

Recovery phrases and keys.

Twenty-four words became the trunk of a whole tree of keys, one branch per job โ€” signing, encryption, authentication, and a branch per agent you create.

Nothing is stored; every key is re-grown from the phrase on demand. The seed is wiped from memory once it is used, the phrase is shown once, and no reset path exists. ( What your recovery phrase really buys you .)

The foundation as a whole.

Ten work units across identity, encryption, storage and trust closed together on a single day, with 168 tests behind them, which is what makes the week's four headline items one layer rather than four features.

The closeout published its own gaps beside it โ€” twelve of fifteen constraints reviewed, three open โ€” and re-sequenced the consent engine into a later wave. ( The foundation speaks .)

Relationship cards.

A relationship became an object you can hold: a small card that is the connection between two beings, carrying the keys that let each side verify the other's later updates, plus content-addressed attachments.

Each side authors its own, so the two are not copies. The card that keeps itself current โ€” pushing a changed field to the people who hold it โ€” is a design, and a screen for browsing your cards was cancelled. ( A relationship you can hold .)

Cross-instance trust.

The first phase of the handshake that lets two separate installations reach across a network landed: an exchange that leaves both sides holding a shared secret neither transmitted, over which the two-signature edge ceremony can then run.

Every later phase, including revocation across instances, is unfinished โ€” started, not done.

Plugin wiring.

Features stopped carrying their own hand-written wiring and moved onto one shared path, which is what lets a feature be added without touching the system that loads it.

The unified plugin system reached 80 of 80 on its pipeline in a Friday push and closed the day with nine work items still open, so it is progress rather than a finish.

Test discipline.

Tests that quietly skipped themselves when an optional language-model build was absent now fail loudly instead, and a suite that wants to run without that dependency has to say so by being a separate, named suite.

A three-agent audit of the in-process model path filed 3 critical, 3 high and 4 medium risks โ€” findings a silently skipping suite would have sailed past. ( When tests aren't allowed to lie .)

The published scorecard.

An audit went out mid-migration reporting that 31% of the exit conditions passed, with the gaps enumerated rather than smoothed over.

That map is what made the remaining work plannable: 559 calls left, split into six waves, of which the first four cleared 121 and brought the count to 438 before the finish. ( The honest audit .)

Proof of unique being.

Keeping one person from minting a thousand identities was researched and written down in full this week โ€” the shape a fake cluster leaves in a social graph, in-person ceremonies where several already-verified people jointly attest to a newcomer, a weaker video tier marked as weaker, and no biometrics anywhere.

None of it is running code, and below roughly ten participants the graph maths does not work at all.

Fellow travelers.

The identity layer takes three ideas from KERI and says so: committing in advance to your next key so a stolen one cannot become a takeover, treating an identity as its own signed history rather than a mutable record, and deriving a name that proves its own origin.

The always-online witness infrastructure was left behind, because a being here may be a sleeping laptop or a phone with no signal. ( KERI and the identity that is its own history .)

Mobile.

Research for a local daemon on mobile is complete; the first Android milestone is the next step and has not been taken.

Nothing runs on a phone yet.

The two efforts that stopped

Both stopped deliberately, and both are recorded as stopped. Neither is sitting in a backlog reading as work that might still happen, which is the state a quietly abandoned feature normally occupies for a year or more. They stopped for opposite reasons, and the difference is the lesson.

Trust decay and demurrage โ€” cancelled.

The idea that a relationship left untended should quietly lose weight was explored all the way to a no.

The rate could not be defended, the prototype had never been exercised, and a decaying edge silently changes what everyone downstream of it can reach. ( What we didn't build: trust decay and saying no .)

Standalone revocable access โ€” cancelled.

Revocation did not vanish with the item; it dissolved into the things around it.

Pulling a signed grant ends future access through the consent gates, and revoking a lost or compromised device shipped this week as a real command with recovery-phrase confirmation and remote uninstall. Revocation therefore has a home in the shipped product rather than in a cancelled item.

โ† more in Process   home โœฆ   all โ†’