1. guides
temper
  • Temper documentation
  • 2026-07-06-cogmap-telos-differentiation
  • handoffs
  • plans
    • archived
    • evidence
  • reviews
  • specs
  • spikes
  • agents
  • auth
    • Temper auth & security
    • The authorization seam (`temper-services::auth`)
    • Cognitive-map & resource authoring authorization
    • JWT verification
    • The issuer / resource-server boundary & the machine-token contract
    • The internal reconcile channel
  • code-reviews
  • cognitive-maps
  • decisions
  • development
  • guides
    • Building a cognitive map from a large corpus
    • Using temper from Claude Desktop (and claude.ai)
    • Cloud Agent Development Guide
    • Ingesting a corpus into a context
    • Development setup
    • Drain operator queries (TraceQL)
    • Enterprise Install — Ground Up
    • Operator guide: read-only GitHub credential via a BYO App + Vercel Connect
    • Operator guide: provisioning a GitHub connection in temper
    • Installing Temper
    • Delivering L0 Kernel Cogmap Content
    • Machine Credentials
    • OpenTelemetry Setup
    • Operational Memory
    • Bootstrapping a Temper org
    • Releasing Temper
    • Self-Hosting Temper with Okta
    • Self-Hosting Temper with a SAML IdP
    • Self-Hosting Temper
    • The Slack Integration — identity, credentials, and what revocation actually means
    • Operator guide: `@temper` on Slack — end-to-end setup
    • Bootstrapping a team's self-cognition cognitive map
    • Working with Teams
    • Integrating Ruby with temper-rb
    • Deploying an Eve agent to Vercel (the steward and the citation auditor)
    • WS6 Collapsed-Schema Dev Environment
  • research
  • security
  • specs
  • doors
    • Building against Temper
    • Running Temper
    • Using Temper
  • Temper /api/query — the compositional read surface
    • Run a declared composition of situated acts.
    • Schemas
      • Composition
      • OutcomeDeclaration
      • ReturnSpec
      • Intention
      • StageNode
      • ActInvocation
      • CombineNode
      • StageInput
      • StageRelation
      • Disclosure
      • QueryResponse
      • StageResult
      • StageOutput
      • ScoreKind
      • Scoring
      • ResourceHit
      • CompositionTrace
      • StageTrace
      • InputSource
      • NarrowedBy
      • StageName
      • ActName
      • IdKind
      • IdSet
      • BoundTerm
      • Extent
      • StageDisposition
      • ActRefusal
      • PlanRefusal
      • RefusalReason
      • ActQuantity
      • QuantityScale
      • ResourceFilter
      • EdgeFilter
      • PropertyPredicate
      • PropertyOp
      • ResourceSection
      • EdgeKind
      • OrdOp
      • CogmapId
      • ContextId
      • IdProvenance
      • FacetPredicate
      • CombineOp
      • BlockId
      • MatchLocation
      • BodyStorage
      • ResourceId
      • IngestState
      • ManagedMeta
      • ProfileId
      • ResourceView
      • Polarity
      • ViaEntry
      • LensId
      • RegionId
      • CogmapRegionRow
      • RegionHit
      • StageInputTrace
      • Standing
      • ActorAuthority
      • Refusal
      • SystemAccessDetails
      • PlanRefusalDetails
      • ErrorDetails
      • ErrorDetail
      • ErrorBody
  • Temper Cloud API
    • Access
      • POST /api/access/requests — submit a join request for the gating team.
      • GET /api/access/requests/me — check own join request status.
      • DELETE /api/access/requests/me — withdraw a pending join request.
      • POST /api/access/reviews — a revoked principal asks an admin to reconsider (spec D15).
      • GET /api/access/settings — read public system settings.
    • Slack Link
      • Disconnect any principal. Operator path — offboarding and stuck users.
      • Disconnect EVERY Slack principal bound to the caller's own profile.
    • Auditor
      • auditor_dispatch
      • auditor_sweep
      • complete_auditor_job
    • Graph
      • POST /api/cogmaps/{id}/graph/slice — R4 cogmap-scoped neighborhood slice.
      • GET /api/graph/cogmaps/{id}/panorama — enter-a-cogmap Tier-0 interior.
      • GET /api/graph/contexts/composition — Beat E Tier-1: the force-graph composition of a container's (or a residual bucket's) members.
      • GET /api/graph/contexts/panorama — Beat E Tier-0: goal-container territories + residual tray.
      • GET /api/graph/home — the you→teams→cogmaps membership home.
      • GET /api/graph/regions/composition — Beat D region→resources composition drill.
    • Cognitive Maps
      • list_cognitive_maps
      • genesis
      • get_cognitive_map
      • reconcile
      • analytics
      • grant_cogmap_access
      • revoke_cogmap_access
      • materialize
      • materialize_delta
      • region_metrics
      • shape
      • bind_team
      • unbind_team
    • Contexts
      • list_contexts
      • create_context
      • get_context
      • context_materialize
      • reassign
      • context_region_metrics
      • rename
      • context_shape
      • share_team
      • unshare_team
    • Events
      • cursor
      • GET /api/graph/elements/{kind}/{id}/trail — R5 element event-trail. kind ∈ {node, edge}.
    • Facets
      • set_facet
      • Read the live facets of one edge.
      • Set a facet whose owner is an **edge** rather than a resource.
      • Read the live facets of one resource — the confirming read for a write that steers region formation and Atlas grouping.
    • Health
      • health_check
    • Ingest
      • create_ingest
      • update_ingest
      • list_blocks
      • append_block
      • finalize_resource
    • Invitations
      • accept
      • decline
      • list_mine
      • list_team_invitations
      • revoke_team_invitation
      • create_team_invitation
    • Invocations
      • list_invocations
      • open
      • show
      • close
    • Profile
      • get_profile
      • update_profile
      • list_auth_links
    • Query
      • `POST /api/query`.
    • Relationships
      • assert
      • fold
      • retype
      • reweight
    • Resources
      • `GET /api/resources` — **one response type, unconditionally.**
      • create_resource
      • get_resource
      • delete_resource
      • update_resource
      • List the finding at `{id}`'s citation-audit trail — one row per audit, each naming its auditor.
      • Record an auditor's signed defensibility verdict on one `(block, source)` citation of the finding at `{id}`. CONFORM to `handlers::edges::assert` (the sibling authored-write handler): thin — build the command, dispatch it, map the error. No persistence he
      • get_content
      • list_resource_edges
      • resource_evidence
      • grant_resource_access
      • revoke_resource_access
      • resource_lineage
      • provenance
      • annotate_resource
    • Meta
      • get_meta
      • update_meta
    • Reassign
      • reassign_resource
      • reassign_team
    • Search
      • `POST /api/search`.
    • Steward
      • candidates
      • dispatch
      • sweep
      • delta
      • advance
    • Teams
      • list_teams
      • create_team
      • detail
      • delete_team
      • update_team
      • add_member
      • remove_member
      • change_role
    • Schemas
      • AcceptInvitationResponse
      • ActInput
      • ActInvocation
      • ActName
      • ActQuantity
      • ActRefusal
      • ActorAuthority
      • AddMemberRequest
      • AdvanceWatermarkAck
      • AdvanceWatermarkRequest
      • AgentAuthorship
      • AppendBlockPayload
      • AssertRelationshipRequest
      • AtlasEdge
      • AtlasHome
      • AtlasNode
      • AtlasSubgraph
      • AuditCitation
      • AuditSweepRow
      • AuditorDispatchTickRequest
      • AuditorDispatchTickResponse
      • AuditorJobCompleteAck
      • BindTeamOutcome
      • BindTeamRequest
      • BlockId
      • BlockProvenanceRow
      • BlocksResponse
      • BodyStorage
      • Bridge
      • BulkReassignAck
      • BulkReassignRequest
      • ChangeRoleRequest
      • CharterBlock
      • CharterDisposition
      • CitationAuditRequest
      • CitationAuditRow
      • ClaimedAuditJob
      • ClaimedJob
      • CloseInvocationRequest
      • CogmapAnalyticsRow
      • CogmapDetail
      • CogmapFoundationRow
      • CogmapGrantBody
      • CogmapId
      • CogmapRegionMetricsRow
      • CogmapRegionRow
      • CogmapRegulationRow
      • CogmapRevokeBody
      • CogmapRow
      • CogmapStaleness
      • CombineNode
      • CombineOp
      • Composition
      • CompositionTrace
      • ConfidenceBand
      • ContentResponse
      • ContextCreateRequest
      • ContextId
      • ContextOwnerRef
      • ContextPanorama
      • ContextRow
      • ContextRowWithCounts
      • CorrelationId
      • CreateCogmapOutcome
      • CreateCogmapRequest
      • CreateInvitationRequest
      • CreateRequestBody
      • CreateReviewBody
      • DeleteResponse
      • DeviceOverrides
      • DispatchTickRequest
      • DispatchTickResponse
      • Disposition
      • DriftSweepRow
      • EdgeFacetRow
      • EdgeFacetSetRequest
      • EdgeFacetsResponse
      • EdgeFilter
      • EdgeId
      • EdgeKind
      • ElementEvent
      • ElementKind
      • EmbedDispatchSummary
      • Entitlements
      • ErrorBody
      • ErrorDetail
      • ErrorDetails
      • EventCursorResponse
      • EventTrail
      • ExactArm
      • ExactHit
      • Extent
      • FacetAck
      • FacetPredicate
      • FacetSetRequest
      • FinalizePayload
      • FoldRelationshipRequest
      • GrantOutcome
      • GraphEdgeRow
      • GroupKeyMeta
      • HealthResponse
      • HomeCogmap
      • HomeContext
      • IdKind
      • IdProvenance
      • IdSet
      • IdpRevocation
      • IngestCreateResponse
      • IngestDelta
      • IngestPayload
      • IngestState
      • InheritedReadGrant
      • InheritedShare
      • InputSource
      • Intention
      • InvitationStatus
      • InvitationTokenRequest
      • InviteeInvitation
      • InvocationAck
      • InvocationActRow
      • InvocationCloseAck
      • InvocationId
      • InvocationSummary
      • InvocationView
      • JoinRequest
      • JoinRequestStatus
      • LensId
      • LineageNode
      • ManagedMeta
      • MatchLocation
      • MaterializeAck
      • MaterializeDelta
      • MaterializeRequest
      • MergePolicy
      • MetaUpdatePayload
      • NarrowedBy
      • NodeHome
      • OpenInvocationRequest
      • OrdOp
      • OrphanNode
      • OutcomeDeclaration
      • PlanRefusal
      • PlanRefusalDetails
      • Polarity
      • Profile
      • ProfileAuthLink
      • ProfileId
      • ProfileUpdateRequest
      • ProfileWithEntitlements
      • PropertyOp
      • PropertyPredicate
      • ProvenanceSource
      • PublicSystemSettings
      • QuantityScale
      • QueryResponse
      • ReassignAck
      • ReassignContextOutcome
      • ReassignContextRequest
      • ReassignResourceRequest
      • ReconcileCogmapRequest
      • ReconcileEdge
      • ReconcileEdgeTombstone
      • ReconcileEntry
      • ReconcileOutcome
      • ReconcileTelos
      • ReconcileTelosBlock
      • ReconcileTombstone
      • Refusal
      • RefusalReason
      • RegionHit
      • RegionId
      • RelationshipAck
      • RemoveMemberOutcome
      • RenameContextOutcome
      • RenameContextRequest
      • ResidualBucket
      • ResidualContext
      • ResidualGroups
      • ResidualOwnedReach
      • ResourceAnnotateRequest
      • ResourceCreateRequest
      • ResourceFacetRow
      • ResourceFacets
      • ResourceFacetsResponse
      • ResourceFilter
      • ResourceGrantBody
      • ResourceHit
      • ResourceId
      • ResourceLineage
      • ResourceListResponse
      • ResourceRevokeBody
      • ResourceSection
      • ResourceSortField
      • ResourceUpdateRequest
      • ResourceView
      • ReturnSpec
      • RetypeRelationshipRequest
      • RevokeOutcome
      • ReweightRelationshipRequest
      • ScoreKind
      • Scoring
      • SearchParams
      • SearchReason
      • SearchResponse
      • SearchResultRow
      • SearchScope
      • SearchScopeInfo
      • SegmentInfo
      • SegmentedBegin
      • SegmentedBeginResponse
      • ShareContextOutcome
      • ShareContextRequest
      • SlackDisconnectRequest
      • SlackDisconnectResponse
      • SlackDisconnectedPrincipal
      • SliceRequest
      • SortOrder
      • StageDisposition
      • StageInput
      • StageInputTrace
      • StageName
      • StageNode
      • StageOutput
      • StageRelation
      • StageResult
      • StageTrace
      • Standing
      • StandingShape
      • Subscription
      • SubscriptionOverride
      • SystemAccessDetails
      • TeamCreateRequest
      • TeamDetail
      • TeamInvitation
      • TeamMemberDetail
      • TeamMemberRow
      • TeamMemberSource
      • TeamRole
      • TeamRow
      • TeamUpdateRequest
      • Territory
      • TerritoryKind
      • TerritoryOverview
      • UnbindTeamOutcome
      • UnshareContextOutcome
      • VaultConfig
      • ViaEntry
      • WideArm
      • WideHit
  1. guides

Operational Memory

How a person, and then a team, moves the working knowledge an agent session accumulates out of one
machine's directory and into Temper — and how to evaluate the whole thing without adopting any of
it.
A memory is a resource. It has doc type memory, lives in a context, and is read, written,
corrected and searched with the ordinary resource commands and tools. What it needs beyond a body
lives in its open tier: status (active / superseded) and verified (the ISO date the claim
was last checked against the system) — the two the index render reads, and the two it refuses on —
plus descriptor (the one-line recall hook) and source_file (the local file it was migrated from,
when it was migrated from one). Everything else — reach, history, attribution, search — is what a
resource already does.
MEMORY.md is one client's convenience. Claude Code loads a rendered index each session, and
temper memory emit writes it. The file is a cache; the resources are the record. A client with no
index file and no command line reads the same memories over the cloud and needs none of this guide
past the sharing section.
Status, 2026-08-02. Read this before acting on anything below.
PartState
Contract, commands, the local drift gateshipped (PR #612)
Migration of an existing local corpusexercised — 182 local files accounted for, 183 memories in Temper on one machine
Title harvest before the takeoverexercised — 110 titles stamped
The takeover (emit over the hand-written index)taken, 2026-08-02 — 181 entries, 19,339 bytes, check clean
The collapsed tail (reinforced_min)shipped dormant — the mechanism works and no machine has set a threshold, because none has the months of data one would have to be chosen from
A second machinenot exercised
Two people writing into one shared contextnot exercised — the sharing section below describes an intended mechanism, not an observed one
Reading memories from Desktop / mobile / webnot exercised — believed to work by construction
Every command output in this guide is pasted from a real run on 2026-08-02, not described from
memory. Those runs were captured non-interactively, so the output is JSON; on a terminal you get
TOON by default and --format json forces what is shown here. Long skipped arrays are
summarized in the prose beneath them and absolute home paths are shortened; nothing else is edited.
The corpus grew from 183 to 184 partway through, so earlier outputs report the smaller count —
they are left as they ran rather than reconciled after the fact.

Nothing happens until you edit a config file#

Start here, on any machine, adopted or not:
$ temper memory status
{
  "opted_in": false,
  "contexts": [],
  "in_temper": 0,
  "defects": [],
  "local_files": 0,
  "local_without_counterpart": []
}
opted_in: false is not an error. It is the primary case this command exists to report, and it is
a supported end state — a machine may measure itself, decide against the whole idea, and never
return.
What that report cannot tell you yet, and this is a real limit rather than a caveat. The
local_files count is read from the directory containing the configured index_path. With no
[memory] section there is no index_path, so there is no directory to scan, so an unadopted
machine reports local_files: 0 even when it is carrying 182 memory files. The zero above came
from exactly such a machine. Before adoption, status tells you that you have not adopted; it does
not measure your divergence.
Measuring divergence therefore costs one config edit. That edit writes nothing — not to disk, not
to Temper — which is what makes it an evaluation step rather than a commitment.

Adopting: one config section#

Add [memory] to ~/.config/temper/config.toml:
[memory]
# Contexts whose memories reach EVERY project on this machine.
shared_contexts = ["@me/working-agreements"]
# Contexts for this project. A list — a project may legitimately span contexts.
project_contexts = ["@me/temper"]
# The rendered index. `emit` refuses to overwrite a file it did not write, so an
# existing hand-written MEMORY.md is safe until it is deliberately moved aside.
index_path = "~/.claude/projects/<project-dir>/memory/MEMORY.md"
FieldMeaning
shared_contextsrendered for every project on this machine
project_contextsrendered for this project only. Both lists are read shared-first and deduped, so a context named in both is fetched and rendered once
index_pathwhere the rendered index is written, and the directory scanned for local files
stale_after_dayshow old a verified date must be before the index marks it UNEXAMINED. Defaults to 90; omit it unless you want a different number
reinforced_minhow many distinct open_meta.reinforced dates a memory needs to keep its own line in the index. No default, and leaving it out is the right answer today — see The collapsed tail
The section is Option, not defaulted, precisely so "not configured" and "configured empty" stay
distinguishable — and at least one context is required, so an empty [memory] block is rejected
rather than silently doing nothing.
With index_path set, the same command now measures the machine. On a directory holding one local
memory that has never been migrated:
$ temper memory status
{
  "opted_in": true,
  "contexts": [
    "@me/working-agreements",
    "@me/temper"
  ],
  "in_temper": 183,
  "defects": [],
  "local_files": 1,
  "local_without_counterpart": [
    "feedback_never_run_migrations_on_friday.md"
  ]
}
local_without_counterpart is the list that matters: local files that no memory in Temper claims
via open_meta.source_file. Matching is on that exact filename and nothing else — never on a title,
never on a sluggified form — so a memory authored natively in Temper (from a session, from Desktop)
simply contributes no match and is never itself reported as orphaned.
defects is a report, not a failure: a memory with a missing or malformed status/verified is
named here and the report still succeeds. Only emit refuses on one.

Populating the store: harvest, then migrate#

Two commands, and the order is load-bearing.

temper memory harvest runs first#

A memory's human-readable title exists in exactly one place: the link text in the hand-written
MEMORY.md. Nothing in the memory file carries it. harvest copies each curated title into the file
it names, so the title survives the index being taken over later.
Take the takeover first and those titles are gone — and with them migrate's ability to move the
remaining files at all, because a file no link names is skipped rather than given a title invented
from its filename. That is deliberate: measured on a real corpus, filename-derivation lost the hook
on 51 of 110 files.
harvest is idempotent, and a dry-run shows you why:
$ temper memory harvest --dry-run
{
  "dry_run": true,
  "scanned": 182,
  "titles_harvested": 178,
  "stamped": [],
  "skipped": [ ... ]
}
Nothing to stamp on this machine, because all 182 files are already accounted for: 110 already
carry a title:
(a prior run stamped them) and 72 are already in Temper (matched by
source_file)
— a migrated memory's title lives in the store, which is authoritative, so stamping
the file would be writing to the cache.
Each stamp also pins metadata.modified to the file's pre-write mtime, so the write's own mtime
bump cannot re-date a claim nobody re-checked. Run it with --dry-run first; it takes --unattended
to write with no terminal attached. It reaches the network — the already-migrated set comes from
Temper, not from the files.

temper memory migrate reconciles — it does not bulk-import#

$ temper memory migrate --dry-run
{
  "cohort": "feedback",
  "context": "@me/working-agreements",
  "dry_run": true,
  "scanned": 182,
  "titles_harvested": 178,
  "proposals": [],
  "skipped": [ ... ]
}
On the run above the 182 skips break down as: 69 already in Temper (matched by source_file),
107 not in cohort (type is project), 6 not in cohort (type is reference).
Cohorts. --cohort selects by the files' frontmatter type, defaulting to feedback. The
target context defaults to the first shared_contexts entry for the cross-project cohort and the
first project_contexts entry otherwise; --context overrides it. Run it once per cohort.
Re-runs are safe. A file already in Temper is matched on source_file and skipped, so an
interrupted batch is resumed by running it again rather than by reasoning about what got through.
It detects nothing about near-duplicates. The source_file skip above is the whole of its
reconciliation; nothing is compared against what the target context already holds, so two accounts
of one incident written on two machines both land. Adjudicating overlap is a step to take before
running it — see Reading for near-duplicates.
Interactive by default, where it confirms the whole batch once — count, target context, cohort
— defaulting to no, and says plainly at the prompt that near-duplicates are not detected. A batch
with nothing in it never asks. It refuses to write with no terminal attached unless
--unattended authorizes writing without that confirmation. --dry-run is always permitted and
writes nothing.

Reading for near-duplicates is yours now#

A pre-write full-text search used to surface overlapping memories for you to judge. It was deleted
on 2026-08-02, and the measurement is why:
against a real 184-memory store it surfaced 54
collisions, 3 of which genuinely overlapped. The other 51 matched on shared project vocabulary
rather than on claim — temper invocation against NEVER abbreviate a UUIDv7 to its prefix,
cargo make cannot against the ts-rs drift gate. Full-text search cannot see that two texts make
unrelated claims, and this gets worse in a shared context, not better, because shared vocabulary
is exactly what a team's context accumulates.
The rule did not go with the mechanism. Two accounts of nearly the same thing are still surfaced
for judgment and never merged automatically. What changed is who forms the candidate set: read the
local memories against what the store already holds and compare the claim, not the wording. Three
outcome kinds were each observed on 2026-08-02, and a reader who expects only the first will
mis-handle the other two — a duplicate (one incident recorded twice, resolved by keeping the
older, richer account), a supersession (a newer account strictly richer than the one it covers),
and both stale (both out of date, the newer one still the more wrong — recency arbitrated
nothing). The memories skill carries the working form of this on both surfaces.
What this costs, plainly. A compiled-in gate is unskippable; guidance is skippable by
construction. The trade is an enforcement mechanism for a judgement mechanism, and it is
defensible only because the enforcement traded away was ~94% noise and was already being switched
off under operational pressure — not because guidance is as strong as a gate. It is not.

The takeover: emit and check#

emit renders the index from Temper and writes it. Against a hand-written file it refuses:
$ temper memory emit
✗ temper: Bad request: refusing to overwrite /Users/.../memory/MEMORY.md — it was not generated
  by `temper memory emit` (it does not start with the generated-file header). This looks like a
  hand-written or otherwise pre-existing file. Move it aside or point `index_path` (or --path)
  somewhere else, then run `temper memory emit` again.
$ echo $?
1
The check is one fact emit can verify without a second source of truth: a previously-emitted index
starts with the line <!-- GENERATED by ... — do not edit --> shown in the render below. A file that
does not is either hand-written or something else entirely, and clobbering it silently is the bug.
So the takeover is a deliberate act, and it is the step to take last. Move the hand-written index
aside (keep it — it is the only copy of anything harvest did not stamp), then run emit. To see
what would be written without touching anything, point it elsewhere first:
$ temper memory emit --path /tmp/MEMORY-preview.md
✓ Memory index written: /tmp/MEMORY-preview.md
That render, on a 184-memory corpus, was 193 lines and 19,339 bytes — near enough the 19,759 the
hand-written file it replaced occupied:
Two things that file says about itself, both deliberate:
The link target is a bare id, which is what temper resource show accepts — a target copied out
of the index resolves as-is. It is not a temper:// URI: that form resolves in neither surface a
reader has, since the CLI's ref parser rejects it and MCP's resource URI is temper://resources/{id}.
The preamble is the one place the index explains itself, and it costs its two lines once rather
than per entry. What a line is (a hook, not the memory), how to resolve one, and what an old date
does not mean are exactly the things a session loading this file cold would otherwise have to
infer.
The marker says UNEXAMINED, never stale or wrong. An old date means nobody has re-checked the
claim; it carries no opinion about whether the claim holds.
A memory whose status or verified is missing or unparseable stops the whole render rather
than being rendered with a guessed value. Those are the fields that describe a memory's reliability,
and nothing validates them at write time — so the render is where a bad one has to surface.
check is the drift gate. The index lives outside the repo, so nothing in git can diff it:
$ temper memory check --path /tmp/MEMORY-preview.md
✓ Memory index is up to date.
$ echo $?
0
Against an index that has diverged it prints a unified diff and exits non-zero — which is exactly
what a machine that has migrated but not yet taken the index over will see, since the hand-written
file and a fresh render are not the same document. Gate a hook or a CI step on the exit code. If you
emitted with --path, check with the same --path, or you are checking a different file than the
one that was written.

The collapsed tail — demotion, never deletion#

An index bounded by hand stops being bounded the moment nobody trims it. open_meta.reinforced —
the dates a memory did work, written with --open-meta-add (see
the rationale) — is what lets the index bound itself by evidence
instead. reinforced_min is the number of distinct dates a memory needs to keep its own line.
It has no default, and on every machine in existence it is absent. That is not a soft default
dressed up as an absence: the key is Option with no #[serde(default)], and a test at the
deserializer fails if anyone ever gives it one. A threshold can only honestly be chosen from months
of real reinforcement data, and guessing one now would put a constant with no evidence behind it
into every reader's index.
So the mechanism ships dormant, and the first thing to check is that dormant means nothing.
Building the pre-change binary and the current one, then running both against this machine's corpus
seconds apart, produced the same file byte for byte [verified 2026-08-02]:
$ shasum -a 256 /tmp/AB-old.md /tmp/AB-new.md
b035be83f216989c6b1fe489038517b53ffa316daf806d3ddb85a7dd3128b6e9  /tmp/AB-old.md
b035be83f216989c6b1fe489038517b53ffa316daf806d3ddb85a7dd3128b6e9  /tmp/AB-new.md
Both binaries were built before either ran, because a second machine was actively culling this
corpus at the time — an A/B with a build in the middle of it measures the cull, not the change.
Worth knowing generally: this file is a projection of a shared store, so two renders minutes apart
are not expected to match, and a diff between them is not evidence about the renderer.
status reports the distribution a threshold would eventually be chosen from, which today reads as
"the convention is in use, barely" — exactly the state a number cannot yet be set from:
$ temper memory status --format json | jq '{in_temper, reinforcement}'
{
  "in_temper": 294,
  "reinforcement": {
    "reinforced": 5,
    "never_reinforced": 289,
    "last_reinforced": "2026-08-02",
    "malformed": []
  }
}
Set one, and below-threshold memories stop rendering individually. With reinforced_min = 1 on that
same corpus, 290 rendered entries became 5 plus two tail lines:
Those numbers reconcile, and the arithmetic is the point. in_temper: 294 is every memory row
fetched, superseded ones included. The render drops superseded before the threshold ever applies, so
it works over 290 — leaving 4. And 5 + 163 + 122 = 290: the five reinforced memories status
counted are exactly the five that keep their line.
But never_reinforced: 289 against 163 + 122 = 285 collapsed is a gap of 4 — those same four
superseded memories. Neither number is wrong; the gap is the documented consequence of the
distribution counting the corpus in_temper counts rather than the corpus the index renders. The
distribution answers "is this convention being used at all", not "what will the tail hide"
— read
as the second, it over-counts by the number of superseded memories, every time.
Demotion, never deletion. Nothing is dropped, nothing becomes unfindable, and the file stays
bounded however large the corpus grows. The tail is per section, states its own count, and carries
a route back to the section it hides them in — --all included, because a bare resource list returns
a capped page and sending a reader there would replace one truncation with another. This is the same
rule the rest of the design already holds: supersession replaces deletion, and falling out of a
summary is never falling out of the record.
Above a threshold of 1 the line states the threshold instead (reinforced fewer than 2 times),
because a memory with one date is reinforced and still demoted — calling it "unreinforced" would
be a false statement about the memories it describes.
A malformed reinforced is a soft report, not a defect. status names it under
reinforcement.malformed and the render carries straight on, treating that memory as unreinforced —
unlike status/verified, which stop the whole render. The line is what each key holds up:
status and verified are the fields the render's own claims depend on, reinforced only orders
the list. The accepted cost is stated rather than mitigated: one mistyped date silently demotes a
memory into the tail. It is recoverable precisely because demotion is never deletion, and status
is where it stops being silent.
The two status captures earlier in this guide predate the reinforcement field and are left as
they ran, on the same policy as the 183/184 count drift noted at the top.

Sharing memories with a team#

Intended mechanism, not an observed one. Nothing has yet exercised two people writing into one
shared context. What follows is grounded in how the commands are wired, not in a run.
A memory reaches whoever can read the context it lives in. There is no per-memory reach field,
and a memory makes no claim about its own audience — emit and status list memory-typed
resources in each configured context through the ordinary resource read path, so a member sees a
memory exactly when they could see any other resource there.
That makes team adoption a context question, answered with the commands that already exist:
1.
Put the shared memories in a context the team can read — either a team-owned context
(+team-slug/<slug>) or one you own, shared into the team's read-reach:
Sharing requires that you administer the context and manage the target team (owner or
maintainer), or that you are an instance administrator. Note that @me shorthand is not accepted
here — use your handle or the context UUID.
2.
Each member names that context in their own [memory] section, under shared_contexts if it
should reach every project on their machine or project_contexts if it is scoped to one.
3.
Members who work from Desktop, mobile or the web name nothing and configure nothing. They read
and author the same memories as resources.
Two consequences worth being explicit about. Reach follows membership and grants, so it changes
when the team changes
— nothing has to be re-tagged when someone joins or leaves. And a member
who never adopts the CLI is not a second-class participant
: the index is one client's rendering,
not the mechanism.

Declining#

Delete the [memory] section. Nothing else changes: no local file is touched, no resource is
removed, and temper memory status keeps answering. A machine that evaluated and declined is in a
supported state, not an unfinished one.

The limits, stated plainly#

Before adoption, status cannot measure divergence — see the top of this guide. Evaluating
costs a config edit, which writes nothing but is still an edit.
Nothing detects a near-duplicate any more. The residue used to be "what the search missed"; it
is now everything, because there is no search. migrate will write a second account of something
the context already holds and say nothing about it. The store can hold near-duplicates, and only a
reader looking for them will find them — see
Reading for near-duplicates for why that trade was
taken and what it costs.
The replacement is guidance, and guidance is skippable. An agent told to adjudicate overlap
before migrating can simply not do it, and nothing fails. That is a real downgrade from the gate it
replaced, and it is not defended on the grounds that the two are equivalent.
Nothing validates the open tier at write time. status, verified, descriptor and
source_file are ordinary open-meta keys. A malformed one is accepted by the write, reported by
status, and refused by emit. Nothing catches it at the moment it is written.
Some harvested titles will not be hooks, and you only see it after the first emit. A title
comes from the link text in the hand-written index, and link text that read fine inside its
sentence — "Slack — [topic file]", "Principal admission (D11) — [Phase 1 shipped]" — is
useless standing alone in a flat list, because the sentence supplied the subject. Read the emitted
index once and retitle the offenders with temper resource update <ref> --title. Doing so does
not advance verified, which is correct: renaming a memory is not re-checking its claim.
The lazy tail is never forced. Beyond the cohort you deliberately migrate, files move when
someone touches them. Nothing requires the rest to finish, and nothing reports the remainder except
status.
The evidence base is one person, one machine, one client. The corpus is real (184 memories)
and the takeover has been taken; the multi-writer and multi-environment claims remain unexercised.
The index is flat. The hand-written file it replaces grouped entries under principle headings;
nothing keys a memory to a grouping, so the render lists by context and nothing else. That
synthesis, where it exists, has to live in a memory of its own.

Where the rationale lives#

This guide is deliberately not the argument for any of the above. For what was decided and why —
the memory contract, why reach is the home context rather than a field, why the index is generated,
and why the migration is a bounded batch plus a lazy tail — read the design,
internal/superpowers/specs/2026-08-01-memories-in-temper-design.md. The session-facing
version of the workflow, which is what an agent reads rather than a person, ships in the temper
skill's memories.md.
Modified at 2026-08-19 17:59:47
Previous
OpenTelemetry Setup
Next
Bootstrapping a Temper org
Built with