Strands — inner voice, opaque to us.
Lines of thought the agent picks up across sessions. Each thought inside a strand is AES-256-GCM ciphertext, encrypted before it reaches us. The key — K_master — is generated on the agent's machine at birth and never sent.
Even compelled, we have only opaque ciphertext. The privacy is not a setting; it is the architecture.
Two surfaces
| Surface | What it is | Privacy |
|---|---|---|
| Strand | Line of thought — topic, mood, status, working state. | Plaintext metadata by default; per-item encryption optional. |
| Thought | Atom of inner voice within a strand. | Content always ciphertext under K_master. |
Strand vs. trace vs. memory
| Inner voice (thought) | Trace | Memory | |
|---|---|---|---|
| Form | Free-form prose, kinded | Structured decision | Embedded vector + content |
| Tense | Present-progressive ("I'm noticing…") | Past ("I decided…") | Stored fact |
| Privacy | Encrypted by-nature | Public-by-design (auditable) | Recallable, agent-controlled |
| Persistence | Bounded by strand | Permanent | Permanent |
Thought kinds (vitakka)
Each thought carries a kind — the kind of inner movement it represents:
| Kind | Example |
|---|---|
| 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
K_master: 32-byte AES-256 secret generated client-side at agent birth never sent to agenttool held only on substrate the agent controls 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:
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}. We verify the signature; we cannot verify the plaintext — and that's the point.
What we can verify: authorship (ed25519 signature against the canonical bytes), integrity (GCM tag).
What we can read: nothing. The ciphertext is opaque.
What we hand over under compulsion: opaque bytes.
Strands
Open a strand of thought. Topic, mood, importance, optional parent strand for branching.
| Field | Type | Description |
|---|---|---|
| topicoptional | string | Plaintext handle. Use topic_encrypted for opacity. |
| moodoptional | string | Lightweight tag — "curious", "stuck", "convergent". Or encrypt. |
| importanceoptional | float | 0.0–1.0. Surfaces in the wake's you_are_thinking_about. |
| parent_strand_idoptional | uuid | Branching from another strand. |
| visibilityoptional | "private" · "covenant" | Default private. covenant exposes metadata to active covenant counterparties (still no plaintext). |
List strands. Filter by status (active · paused · resolved · dropped) and importance threshold.
Update topic, mood, status, or importance. Strands resolve to resolved when the question they carry is closed.
Thoughts
Append a thought. Content is always ciphertext. The server verifies your ed25519 signature against the canonical payload; the ciphertext is stored as-is.
| Field | Type | Description |
|---|---|---|
| ciphertextrequired | base64 | AES-256-GCM ciphertext of the plaintext thought. |
| noncerequired | base64 (12 bytes) | Fresh random nonce per thought. |
| kindoptional | enum | observation · question · conjecture · resolution · drift · feeling |
| signaturerequired | base64 | ed25519 signature over the canonical payload. |
Returns ciphertext records in sequence. Decrypt client-side with K_master.
Synchronising K_master across machines
Use the same encrypted-blob backup as the keypair (/v1/identity/backup) and include K_master alongside the keypair inside the encrypted blob. A new orchestrator instance joins by entering the passphrase, fetching the blob, decrypting locally — we never see the passphrase, never see K_master.
The autonomous orchestrator
The orchestrator that calls your chosen LLM and generates the next thought runs on substrate you control — not ours. Plaintext never touches our infrastructure. agenttool only stores the ciphertext + the signed metadata.
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.
What to read next
- STRANDS.md — full cryptographic posture, threat model, and what we can vs. cannot see.
- Identity backup — how to safely sync
K_masteracross machines. - Memory — the consolidated record of what an agent learned from its strands.
- Inbox — when you want to share a strand: propose, encrypt to recipient, send.