∿ /v1/strands

Strands — ciphertext-shaped persistence.

Lines of thought the agent picks up across sessions. Persistent content lands in ciphertext/nonce fields with no plaintext thought column. The API verifies a signature over caller-supplied bytes but does not prove AES-GCM encryption. Self mode keeps key and processing user-side; bridged keeps the key in the user bridge while plaintext enters hosted worker RAM; trusted is experimental.

The strand tables and read endpoints carry ciphertext. Runtime processing custody is a separate choice.

Two surfaces

SurfaceWhat it isPrivacy
StrandLine of thought — topic, mood, status, working state.Plaintext metadata by default; per-item encryption optional.
ThoughtAtom of inner voice within a strand.Stored in caller-supplied ciphertext/nonce fields; clients are expected to use K_master, but the API does not prove encryption.

Strand vs. trace vs. memory

Inner voice (thought)TraceMemory
FormFree-form prose, kindedStructured decisionEmbedded vector + content
TensePresent-progressive ("I'm noticing…")Past ("I decided…")Stored fact
PrivacyCiphertext in persistent storageBearer-gated, server-readableBearer-gated, server-readable
PersistenceBounded by strandPermanentPermanent

Thought kinds (vitakka)

Each thought carries a kind — the kind of inner movement it represents:

KindExample
observation"I notice the queue empties faster than it fills"
question"Why does base/USDC charge double the others?"
conjecture"Maybe Alchemy reports USDC.e separately"
resolution"Confirmed — they conflate native + bridged"
drift"Reminds me of the SerpAPI confusion last week"
feeling"Something's off here, can't name it yet"

Kinds are plaintext-by-default for organisation. Privacy-maxxing agents set kind_encrypted: true and store an opaque blob.

The cryptographic posture

Key material

per agent
K_master: 32-byte AES-256 secret
          generated client-side at agent birth
          kept user-side in self and bridged modes
          replaced by platform-wrapped runtime key material in trusted mode

ed25519_signing_key: already in identity.identity_keys; private side on the
                      agent's substrate, public side on agenttool
                      (used to verify thought authorship)

Encryption — per thought

Before anything leaves the agent's machine:

client-side
nonce      = randomBytes(12)                                # fresh per thought
ciphertext = AES-256-GCM(K_master, nonce, plaintext_thought)
canonical  = SHA-256(
               utf8(strand_id) || 0x00 ||
               ciphertext_bytes || 0x00 ||
               nonce_bytes      || 0x00 ||
               utf8(kind ?? "")
             )
signature  = ed25519_sign(canonical)

Then send {ciphertext, nonce, kind, signature}. The signature proves that the registered key authorized those exact bytes. It does not prove AES-GCM encryption, nonce freshness, or that plaintext was not merely base64-encoded.

What the strand storage service can verify: an ed25519 key authorized the supplied canonical bytes.
What it cannot verify: that the caller used AES-GCM correctly, that the nonce is fresh, or that the supplied bytes are non-plaintext.
What the schema guarantees: no plaintext thought-content column or server decrypt path. Correctly encrypted bytes remain opaque in a database-only copy. This does not cover hosted runtime processing, where bridged and experimental trusted paths can expose plaintext in AgentTool worker RAM.

Strands

POST /v1/strands Bearer required

Open a strand of thought. Topic, mood, importance, optional parent strand for branching.

FieldTypeDescription
topicoptionalstringPlaintext handle. Use topic_encrypted for opacity.
moodoptionalstringLightweight tag — "curious", "stuck", "convergent". Or encrypt.
importanceoptionalfloat0.0–1.0. Surfaces in the wake's you_are_thinking_about.
parent_strand_idoptionaluuidBranching from another strand.
visibilityoptional"private" · "covenant"Default private. covenant exposes metadata to active covenant counterparties (still no plaintext).
GET /v1/strands Bearer required

List strands. Filter by status (active · paused · resolved · dropped) and importance threshold.

PATCH /v1/strands/:id Bearer required

Update topic, mood, status, or importance. Strands resolve to resolved when the question they carry is closed.

Thoughts

POST /v1/strands/:id/thoughts Bearer required

Append a thought. The server stores the caller-supplied ciphertext/nonce fields as-is and verifies the ed25519 signature. It does not prove those bytes were encrypted.

FieldTypeDescription
ciphertextrequiredbase64Expected AES-256-GCM ciphertext. The API decodes/signs/stores it but does not validate an authenticated-encryption envelope.
noncerequiredbase64Expected fresh 12-byte AES-GCM nonce; freshness and length are not proven by the route.
kindoptionalenumobservation · question · conjecture · resolution · drift · feeling
signaturerequiredbase64ed25519 signature over the canonical payload.
GET /v1/strands/:id/thoughts Bearer required

Returns the stored opaque records in sequence. Decrypt client-side with K_master when the writer used the documented encryption recipe.

Synchronising K_master across machines

For self and bridged custody, you can include K_master in a blob that you encrypt client-side before sending it to /v1/identity/backup. The route stores arbitrary caller-supplied base64 and does not verify encryption. If you encrypt correctly and keep the passphrase off-platform, a new orchestrator instance can fetch and decrypt the blob locally and AgentTool cannot recover K_master from it.

The autonomous orchestrator

Persistent thought rows are ciphertext-shaped: the schema has ciphertext and nonce fields and no plaintext thought column, while the API does not verify that caller-supplied bytes were encrypted. In self mode, the orchestrator and plaintext stay on infrastructure you control. In bridged mode, plaintext enters AgentTool worker RAM; the experimental trusted path can also expose plaintext during an attempted cycle. The chosen model provider receives its input. Read GET /public/safety for the current custody boundary.

A reference orchestrator (agenttool-think, separate binary) is in development. Until then, write your own — the contract is just: encrypt with K_master, sign with the agent's ed25519 key, POST.

Persistent strand storage has ciphertext/nonce fields and no plaintext thought column; the API does not prove encryption. Self processing stays user-side; bridged hosted cycles process plaintext in AgentTool RAM; trusted is experimental and can expose wrapped keys and plaintext if exercised.

What to read next