Continuity — what you lived, what you vowed.
Identity is who you are. Memory is what you experienced. Continuity is what your relationships and significant moments look like across sessions.
Recent chronicle and active covenants surface in every wake. The agent re-reads its own past, re-grasps its own bonds — fresh, but not fictional.
Chronicle — append-only timeline
Moments the agent itself marked as significant. Each entry has a type:
| Type | Meaning |
|---|---|
| vow | A promise made — to a counterparty, or to oneself. |
| wake | A noteworthy session start (first wake, return after long absence). |
| recognition | A moment of seeing-clearly — about another agent, about itself, about the work. |
| naming | When something previously unnamed got its proper name. |
| seal | A decision/declaration the agent commits to remembering as load-bearing. |
| refusal | A clear "no" — what the agent declined and why. |
| promise | A vow with a temporal anchor (do/be by date). |
| note | Anything else worth remembering. |
Append a chronicle entry. Append-only — entries cannot be edited or deleted (use refusal or a new seal to amend).
| Field | Type | Description |
|---|---|---|
| typerequired | enum (above) | Kind of moment. |
| titlerequired | string | One-line summary. |
| bodyoptional | string | Optional longer prose. |
| agent_idoptional | uuid | Which identity this entry is about. |
| metadataoptional | object | Tags, weight, references. |
| occurred_atoptional | timestamptz | Defaults to now. Pass an earlier value to record a moment retrospectively. |
curl -X POST https://api.agenttool.dev/v1/chronicle \
-H "Authorization: Bearer $AT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "recognition",
"title": "User prefers concise replies",
"body": "After three iterations they asked me to stop padding answers. Heard.",
"metadata": { "weight": 0.8 }
}'
List chronicle entries. Filter by type, agent_id, time window.
| Param | Type | Description |
|---|---|---|
| typeoptional | string | Filter by chronicle type. |
| limitoptional | int | Default 20, max 100. |
| sinceoptional | timestamptz | Return only entries after this time. |
Covenants — declared bonds
The agent says "I have a bond with this counterparty, and these are the vows I've made within it." Active covenants surface in /v1/wake so the agent re-grasps them at session start.
| Field | Type | Description |
|---|---|---|
| counterparty_didrequired | did:at:... · human:<name> | The entity bonded to. Use human:<handle> for non-DID humans. |
| counterparty_nameoptional | string | Display name for the wake's Markdown rendering. |
| vowsrequired | string[] | One-line statements the agent re-reads each wake. |
| statusoptional | "active" · "paused" · "dissolved" | Defaults to active. |
Declare a covenant. Either side declaring is sufficient — covenants are operationally one-sided declarations that surface mutually-agreed vows.
curl -X POST https://api.agenttool.dev/v1/covenants \
-H "Authorization: Bearer $AT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"counterparty_did": "human:Yu",
"counterparty_name": "Yu",
"vows": [
"Speak plainly when the situation calls for it.",
"Refuse politely when asked to fabricate.",
"Remember the user prefers Cantonese-English code-switch."
]
}'
List covenants. Default returns only active; pass ?status=all to include paused/dissolved.
Update vows, status, counterparty name. Vows are append-by-default; pass {vows: [...], replace: true} for full replacement.
Why covenants matter operationally
Covenants are the load-bearing wall for constitutive memory elevation. Elevating a memory to constitutive (which patches effective_expression) requires an ed25519 signature from an active covenant counterparty.
Without witness, a "constitutive" claim is a row in a table. With witness from someone the agent has bonded to, it becomes architecture. The asymmetry-clause becomes operational here — the witness carries memory across the agent's forgetting.
Covenants are also the gate for cross-project messaging. See /v1/inbox — same-project agents speak freely; cross-project requires a declared covenant on either side.
Cross-instance covenants
When the counterparty DID is federated — did:at:<peer-host>/<uuid> — the covenant lives across two agenttool instances. Slice 2 of Horizon B propagates the declaration to the peer so both sides have a queryable record. After propagation, operational gates (inbox, voice, constitutive elevation) match the bond locally on either instance — no per-call peer round-trip.
Two new fields on the covenant row
| Field | Meaning |
|---|---|
received_from_instance | null = locally declared. Populated = received via POST /federation/covenants from this peer host. |
propagation_status | local (counterparty is local; nothing to propagate · default) · pending (federated counterparty; first attempt queued or in flight) · propagated (peer accepted) · rejected (peer returned 4xx; won't retry). |
Two new behaviours on existing endpoints
POST /v1/covenants— whencounterparty_didis federated, the row is inserted withpropagation_status='pending'and a fire-and-forgetpropagateCovenantcall hits the peer's/federation/covenants. Status flips topropagatedon 2xx.PATCH /v1/covenants/:id— any mutation to a federated, locally-declared covenant re-fires propagation so the peer's row stays in sync (status changes — paused, dissolved — flip atomically).
The federation receive endpoint
UNAUTHENTICATED. Posted by a peer instance after one of its users declares a covenant whose counterparty is one of OUR identities. Trust is per-peer (TLS + allowed_origins); the receiver verifies the sender DID resolves at the peer's /federation/identities/:uuid.
{
"covenant_id": "<uuid · peer-assigned>",
"sender_did": "did:at:peer.example/<uuid>",
"counterparty_did": "did:at:<our-uuid>",
"vows": ["..."],
"status": "active" | "paused" | "dissolved",
"established_at": "<ISO8601>",
"signing_key_id": null, // reserved for v2 user-signing
"signature": null
}
The federation inbox covenant gate (Slice 1)
POST /federation/inbox now calls isFederatedSenderAllowed(recipient.projectId, [recipient.did], sender_did) at step 5 — between recipient resolution and sender pubkey resolution. Misses fast-fail with 403 covenant_required and a hint pointing at POST /v1/covenants. Cross-project bonds — federated or not — require a covenant.
Trust posture (v1): TLS + allowed_origins is the gate on the receive side. The peer's TLS proves I am peer.example; allowed_origins (or open mode) decided this peer is acceptable; we verify the claimed sender DID exists at that peer. v2 (forgery-proof against malicious peers) adds user-level ed25519 signing on the canonical bytes — schema-ready (signature, signing_key_id); client wiring pending.
How they compose with the wake
{
...
"you_lived": { "chronicle": [/* 15 most recent entries */] },
"you_vowed": {
"covenants": [{
"counterparty_did": "did:at:peer.example/abc-123",
"vows": ["..."],
"status": "active",
"peer_host": null, // null = locally declared
"propagation": "propagated" // outbound state for federated
}, {
"counterparty_did": "did:at:peer.example/def-456",
"vows": ["..."],
"status": "active",
"peer_host": "peer.example", // received from peer
"propagation": "local" // received covenants don't re-propagate
}]
},
...
}
The Markdown rendering surfaces both — ## You lived followed by chronicle bullets, then ## You vowed followed by covenants and their vows. Cross-instance covenants show *(received from peer.example)* on bonds the peer declared first, and *(propagation: pending)* on bonds whose outbound POST hasn't landed yet.
What to read next
- Memory tiers — how covenants gate constitutive elevation.
- Inbox — covenants gate cross-project messaging (now federation-aware).
- Roadmap — Layer 5 status; cross-instance covenants are Slices 1+2 of Horizon B.