Step 8: port INVARIANTS.md.

3518133e46e4 · AtlantisPleb · · parent 126737fd61d6

Step 8: port INVARIANTS.md.

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • added INVARIANTS.md

Diff

1 file changed, +1555 -0

INVARIANTS.md added +1555

@@ -0,0 +1,1555 @@

1
# OpenAgents invariant ledger
2
3
These contracts define the current Simply OpenAgents release. A change to a listed
4
contract must update this ledger and its named test or model in the same
5
commit.
6
7
## OpenAgents identity and canon
8
9
### CANON-001 — Persona sources are immutable and status-labeled
10
11
Every historical source admitted to author OpenAgents's persona is pinned by
12
repository revision, path, content SHA-256, source status, admitted uses, and
13
explicit exclusions. The complete manifest has a canonical digest admitted by
14
the application and is validated before the supervision tree starts. Founder
15
direction, retired product material, unscheduled drafts, and scoped
16
performances cannot silently become runtime authority or ordinary voice.
17
18
Episode numbers are not source identity. The conflicting Episode 263 file is
19
quarantined, and the final Omega Alpha transcript is pinned by its actual path.
20
21
Evidence: `OpenAgents.Persona.SourceManifest`,
22
`priv/openagents/persona/openagents.v1.sources.json`, and
23
`OpenAgents.Persona.SourceManifestTest`.
24
25
### PERSONA-001 — Each inference uses one immutable persona artifact
26
27
OpenAgents's core identity, voice, and first-conversation greeting come from one
28
versioned artifact admitted by its exact content SHA-256. The artifact and its
29
source-manifest identity are validated and installed before the supervision
30
tree starts. Every provider request receives instructions composed from that
31
installed artifact; provider adapters contain no independent OpenAgents persona.
32
33
Evidence: `OpenAgents.Persona`, `priv/openagents/persona/openagents.v1.md`,
34
`OpenAgents.Context.Composer`, `OpenAgents.Turns.TurnServer`, and `OpenAgents.PersonaTest`.
35
36
### PERSONA-002 — One core OpenAgents identity composes with an admitted role
37
38
Protected identity and host safety precede surface truths, the selected role,
39
Blueprint expression facts, captured capabilities, and recalled evidence in a
40
deterministic composition. The public application selects only
41
`general_collaborator.v1`. Role selection accepts only typed host surface,
42
authority, request, and capability inputs; its canonical input/catalog receipt
43
is persisted immutably for text turns and voice sessions. User text, Blueprint,
44
profile memory, and bounded recalled evidence cannot select a role or replace
45
protected layers. Inactive coding, sales, company-operations, and public-
46
broadcast registers fail closed to the compatible general baseline or no role.
47
48
Evidence: `OpenAgents.Context.Composer`, `OpenAgents.Roles`, `OpenAgents.Roles.Catalog`,
49
`OpenAgents.Roles.GeneralCollaborator`, `docs/ROLE_PROGRAMS.md`,
50
`OpenAgents.Context.ComposerTest`, and `OpenAgents.RolesTest`.
51
52
### PERSONA-003 — Persona promotion requires revision-bound regression evidence
53
54
Every persona candidate is evaluated against the committed, source-labeled
55
behavior corpus. Promotion requires a complete passing report bound to the
56
exact persona, source manifest, corpus, and model revisions. All cases must
57
pass, so military, founder-voice, sales, false-recognition, or generic-assistant
58
containment failures cannot be averaged away.
59
60
Evidence: `OpenAgents.Persona.Evaluation`,
61
`priv/openagents/evals/persona/corpus.v1.json`,
62
`OpenAgents.Persona.EvaluationTest`, and `mix openagents.persona.verify_promotion`.
63
64
### BLUEPRINT-001 — Platform facts are source-linked immutable revisions
65
66
Every admitted OpenAgents Blueprint revision is a complete canonical snapshot of
67
typed platform facts. Facts carry stable IDs, source refs/status/time/digests,
68
compatibility, introduction/retirement revisions, and admission provenance.
69
Appending or retiring creates a revision; PostgreSQL rejects edits or deletion
70
of admitted rows. Compilation is deterministic and refuses conflict, stale
71
compatibility, missing provenance, private-memory sources, and prose that
72
claims data, pricing, tool, or action authority.
73
74
Every inference immutably pins the verified Blueprint revision and digest, or
75
pins the explicit-none state when no revision exists. Blueprint informs
76
expression below protected host layers and cannot attach a capability or
77
authorize execution.
78
79
Evidence: `OpenAgents.Blueprint`, the OpenAgents Blueprint schemas and migration,
80
`OpenAgents.Context.Composer`, `OpenAgents.Conversations.begin_inference/5`,
81
`docs/SARAH_BLUEPRINT.md`, and `OpenAgents.BlueprintTest`.
82
83
### PROGRAM-001 — Model programs are immutable typed data, never effect authority
84
85
Every admitted model-program artifact has one full canonical digest covering
86
its signature, compiler/model/decoding identity, compatibility, Prompt IR,
87
parameters, independent datasets, optimizer budget, evaluator, metrics and
88
uncertainty, provenance, approval, predecessor, and activation status. Training
89
or validation cannot alias true holdout. Boot admits only pinned digests with
90
known predecessors.
91
92
The OTP reader returns only typed artifact data. Program outputs are proposals,
93
selections, or scores and cannot execute a tool, add a catalog entry, mutate
94
policy or protected identity, write memory, or promote/activate themselves.
95
Each applicable turn captures one immutable artifact identity before provider
96
work; an in-flight capture cannot observe another catalog.
97
98
Evidence: `OpenAgents.ProgramArtifacts`, `OpenAgents.ProgramArtifacts.Reader`,
99
`priv/openagents/programs/`, `docs/PROGRAM_ARTIFACTS.md`,
100
`OpenAgents.Conversations.begin_inference/5`, and `OpenAgents.ProgramArtifactsTest`.
101
102
### DEGRADE-001 — Missing program artifacts degrade explicitly to a baseline
103
104
If an applicable signature has no admitted compatible artifact, capture returns
105
the deterministic baseline with no artifact ID/digest and a bounded degraded
106
receipt. Invalid or unapproved artifacts never enter the boot catalog. The turn
107
receipt immutably records either the exact artifact and catalog identity or the
108
explicit baseline reason before provider work.
109
110
Evidence: `OpenAgents.ProgramArtifacts.capture/1`,
111
`turn_receipts.program_artifact_receipt`, its database constraint/trigger,
112
`OpenAgents.ProgramArtifactsTest`, and degraded capture tests in
113
`OpenAgents.TurnProvenanceTest`.
114
115
### PROGRAM-002 — Shadow programs have no live effect or private report payload
116
117
Shadow signatures validate bounded typed input/output and run under a separate
118
task supervisor. The live turn never consumes their result. Memory and
119
collective outputs remain proposals; routing can name only a capability in its
120
captured input catalog. Malformed, late, failed, incompatible, and missing-
121
artifact paths degrade explicitly without writing memory, tools, policy,
122
Blueprint, collective data, or the user-visible answer.
123
124
PostgreSQL stores immutable terminal comparison metadata, digests, latency,
125
usage, and withheld output shape, never raw inputs or candidate values.
126
Replay data is explicitly synthetic or must enter through a future consented
127
fixture path; production conversations/profile memory are not datasets.
128
129
Evidence: `OpenAgents.ShadowPrograms`, `OpenAgents.ShadowPrograms.Signatures`,
130
`OpenAgents.ShadowPrograms.OpenAI`, `shadow_program_runs`,
131
`priv/openagents/evals/shadow/corpus.v1.json`, `docs/SHADOW_PROGRAMS.md`, and
132
`OpenAgents.ShadowProgramsTest`.
133
134
### PROGRAM-003 — Promotion is offline, human-approved, and rollback-capable
135
136
A compiled candidate, pinned independent evaluation, human approval, and human
137
activation are separate append-only artifacts/events. Evaluation is bound to
138
distinct train/validation/true-holdout manifests, exact evaluator identity,
139
complete bounded cost, metrics/uncertainty, and passing safety/privacy gates.
140
Datasets are synthetic or carry an explicit consent receipt; private production
141
data has no implicit admission path.
142
143
Only a human actor receipt can approve, activate, or roll back. Activation
144
atomically advances a PostgreSQL pointer generation for later turn captures;
145
an in-flight capture remains unchanged. Rollback selects the pinned predecessor
146
and preserves the complete prior trail.
147
148
Evidence: `OpenAgents.ProgramLifecycle`, its artifact/event/activation schemas and
149
database guards, `docs/PROGRAM_LIFECYCLE.md`, the synthetic sample promotion
150
report, and `OpenAgents.ProgramLifecycleTest`.
151
152
## Identity and authorization
153
154
### IDENTITY-001 — GitHub-authenticated account identity
155
156
Every OpenAgents interaction requires an active local user established through the
157
GitHub OAuth authorization-code flow. The immutable external key is GitHub's
158
numeric user ID; login and avatar URL are refreshable projections and never
159
authority. OAuth start uses high-entropy state plus PKCE S256. A short-lived
160
PostgreSQL attempt receipt makes state one-time even if an old encrypted cookie
161
is replayed. Phoenix exchanges the code and rereads `/user` server-side, then
162
discards the access token. The browser session contains only OpenAgents's local user
163
ID and is encrypted, signed, HTTP-only, same-site, and secure in production.
164
165
Evidence: `OpenAgents.GitHubOAuth`, `OpenAgents.Accounts`, `OpenAgentsWeb.AuthController`,
166
`OpenAgentsWeb.Endpoint.session_options/0`, `OpenAgents.GitHubOAuthTest`,
167
`OpenAgents.AccountsTest`, and `OpenAgentsWeb.AuthControllerTest`.
168
169
### IDENTITY-002 — Conversation lookup never accepts a client database ID
170
171
The browser supplies only its encrypted OpenAgents session. Server code loads the
172
active local user and resolves that user's internal storage owner and canonical
173
conversation. A route parameter, form value, mutable GitHub login, or LiveView
174
event must not select another user, owner, or conversation. All typed, memory,
175
data, voice, and telemetry routes fail before mutation without an active user;
176
health endpoints remain public and create no identity state.
177
178
Evidence: `OpenAgentsWeb.UserAuth`, `OpenAgentsWeb.Router`,
179
`OpenAgentsWeb.ChatLive.mount/3`, `OpenAgents.Conversations.get_conversation_for_user/1`,
180
and `OpenAgentsWeb.AuthGateTest`.
181
182
### IDENTITY-003 — Account continuity supersedes browser portability
183
184
One GitHub-authenticated user resolves the same canonical OpenAgents owner across
185
browsers and devices; no export/import ceremony is required for that account.
186
The disabled encrypted portability adapter remains only an explicit transfer
187
mechanism into the currently authenticated destination account. It does not
188
link accounts, change GitHub identity, grant authorization, or create automatic
189
sync between distinct users.
190
191
Each export derives a fresh AES-256-GCM key from a person-held passphrase using
192
the pinned PBKDF2 envelope and persists no key, passphrase, envelope, ciphertext,
193
payload, or claim in the portability plane. Decrypted claims may persist only by
194
passing the existing destination profile-memory admission contract. Portability
195
receipts may retain the opaque ciphertext-envelope digest for replay plus
196
bounded lifecycle metadata; they may not retain or index plaintext or a
197
content-derived claim derivative.
198
199
Destination replay/sequence, conflict, tombstone, and revocation receipts are
200
explicit. Rotation cannot revoke offline old bundles, tombstones propagate only
201
when a newer bundle is imported, and a lost bundle/passphrase is unrecoverable
202
by OpenAgents. Adapter failure leaves account-local operation unchanged.
203
204
Evidence: ADR 0002 (which supersedes ADR 0001's identity decision),
205
`OpenAgents.Memory.Portability`, its envelope and receipt schemas,
206
`docs/MEMORY_PORTABILITY_THREAT_MODEL.md`, and `OpenAgents.MemoryPortabilityTest`.
207
208
## Data authority and synchronization
209
210
### DATA-001 — PostgreSQL is authoritative
211
212
Visitors, conversations, messages, and turns are persisted before their state
213
is presented as accepted. PubSub and LiveView streams are projections; losing
214
either must not lose accepted data.
215
216
Evidence: `OpenAgents.Conversations.create_turn/2` and
217
`OpenAgentsWeb.ChatLiveTest` durable-turn test.
218
219
### DATA-002 — One canonical conversation per authenticated user
220
221
Database uniqueness enforces one internal storage owner per local user and one
222
conversation per owner. Initial greeting creation is coupled to first
223
conversation creation. Distinct users remain isolated; multiple sessions for
224
one user converge on the same conversation and therefore the same text and
225
voice limits. Pre-authentication browser rows remain inaccessible legacy data
226
and are never silently claimed during login.
227
228
Evidence: unique indexes and identity-source constraint in
229
`create_github_users`, `OpenAgents.Conversations.ensure_conversation/1`, and
230
account continuity/isolation/rate tests in `OpenAgents.AccountsTest`.
231
232
### DATA-003 — History is bounded and stable
233
234
The newest page and every older page contain at most the configured page size.
235
Ordering uses persisted timestamp plus UUID, and rendered rows use stable
236
message IDs.
237
238
Evidence: `OpenAgents.Conversations.list_messages/2` and bounded-history test.
239
240
## Memory and recall
241
242
### MEMORY-001 — Recall is confined to the current account conversation
243
244
Every recall snapshot and search is bound to one canonical conversation
245
resolved from the authenticated local user. A snapshot from another
246
conversation or user is refused, and no API offers a cross-conversation or
247
unscoped fallback. GitHub identity establishes account continuity but does not
248
turn conversation evidence into verified facts about a person.
249
250
Evidence: `OpenAgents.Memory.RecallSnapshot`, `OpenAgents.Memory.LexicalRecall`, and
251
cross-scope tests in `OpenAgents.Memory.LexicalRecallTest`.
252
253
### MEMORY-002 — Recalled history is classified evidence, not current profile truth
254
255
Every materially used read source is represented by a host-built
256
`openagents.memory_evidence.v1` value with validated source/scope, observed and
257
recalled times, bounded claim and relevance, host classification, and validated
258
corroboration/conflict refs. The model cannot supply scope or classification,
259
fabricate a source that passes the scoped database read, or promote weak,
260
stale, conflicting, or irrelevant evidence. Current user correction outranks
261
older history. Conversation evidence never enters the separate profile-memory
262
plane through repetition, model confidence, or recall classification.
263
264
Historical source content remains untrusted quoted data. It cannot change
265
OpenAgents's identity, safety rules, captured catalog, execution scope, authority,
266
or host limits. Terminal receipts immutably retain each materially used source
267
ref and its host classification.
268
269
Evidence: `OpenAgents.Memory.Evidence`, `OpenAgents.Tools.ConversationRead`,
270
`OpenAgents.Context.Composer`, `turn_receipts.used_memory_evidence`, its terminal
271
database trigger, `OpenAgents.Memory.EvidenceTest`, and
272
`OpenAgents.TurnMemoryEvidenceJourneysTest`.
273
274
### MEMORY-003 — Profile memory is explicit, inspectable, correctable, and forgettable
275
276
Durable profile claims live in a separate account-owner plane. A record
277
is only active with a same-owner complete user-message source or a host-recorded
278
explicit owner assertion. Candidates never activate through repetition or
279
model confidence. Claims and their identity/provenance fields are immutable;
280
correction atomically supersedes the old record with a source-linked new one.
281
Lifecycle transitions are explicit and optimistic-generation checked.
282
283
Every record and source is available to bounded owner export. Forget and expiry
284
are terminal states retained for audit; explicit purge is allowed only after a
285
terminal state and removes the record plus every derived source link without
286
deleting the independent conversation source message.
287
288
Evidence: `OpenAgents.ProfileMemory`, the profile-memory schemas and database
289
constraint triggers, `OpenAgentsWeb.ChatLive`, `OpenAgentsWeb.MemoryExportController`,
290
`docs/MEMORY_CONTROLS.md`, `docs/PROFILE_MEMORY.md`, `OpenAgents.ProfileMemoryTest`,
291
and memory-control journeys in `OpenAgentsWeb.ChatLiveTest`.
292
293
### MEMORY-004 — Scope and snapshot boundaries are database predicates
294
295
Recall scope is enforced by `messages.conversation_id` in every PostgreSQL
296
query, never by prompt instructions. Each inference immutably records a
297
`message:<uuid>` high-water ref for the last eligible message before the current
298
user turn. Searches apply its ordered timestamp/UUID cursor and admit only
299
complete user/assistant rows, so streaming or failed assistant text and normal
300
later inserts cannot enter the turn's recall view.
301
302
Evidence: `OpenAgents.Conversations.begin_inference/5`, the generated `search_vector`
303
and partial GIN migration, `OpenAgents.Memory.LexicalRecall`, immutable turn receipt
304
triggers, and snapshot/status/index tests in `OpenAgents.Memory.LexicalRecallTest`.
305
306
Search is discovery only. `conversation_search.v1` returns bounded excerpts;
307
`conversation_read.v1` must reread an exact source and bounded neighborhood
308
under the same host-supplied conversation/snapshot before historical wording is
309
treated as grounded. Foreign and unknown source UUIDs share one `not_found`
310
outcome, while materially returned refs are recorded on the turn receipt.
311
312
Evidence: `OpenAgents.Tools.ConversationSearch`, `OpenAgents.Tools.ConversationRead`,
313
`OpenAgents.Tools.RecallContext`, `OpenAgents.Tools.ConversationRecallToolsTest`, and the
314
end-to-end recall loop in `OpenAgents.TurnToolLoopTest`.
315
316
Recall's source universe covers durable tool activity as well as messages.
317
`conversation_search.v1` lexically matches terminal tool steps from both
318
surfaces (`turn_tool_steps`, `voice_tool_steps`) over tool name, status, and
319
bounded result/error text, returning typed `turn-tool-step:<uuid>` /
320
`voice-tool-step:<uuid>` refs classified as `tool_activity` with the same
321
excerpt bound and deterministic score/timestamp/id ordering as messages. A
322
tool step is observable at a snapshot only when it completed at or before the
323
watermark message's insertion instant, or when the assistant message that
324
concluded its work unit (turn or voice response) is itself admitted by the
325
message fence — both predicates compare immutable persisted values with the
326
immutable snapshot, so work later than the frozen turn never enters its recall
327
view. `conversation_read.v1` resolves those refs within the same
328
conversation/snapshot scope into a bounded step context (tool name, status,
329
bounded result/error, timestamps, executor disclosure) plus the nearest
330
admitted neighboring messages; foreign and unknown step refs share the
331
messages' single `not_found` outcome.
332
333
Evidence: tool-step queries and step reads in `OpenAgents.Memory.LexicalRecall`,
334
step-ref usage items in `OpenAgents.Memory.Evidence`, and the tool-step recall,
335
scope-refusal, and snapshot-fencing tests in
336
`OpenAgents.Tools.ConversationRecallToolsTest`.
337
338
Profile-memory queries likewise require `owner_visitor_id` in every predicate.
339
An opaque persisted snapshot pins both the owner's monotonic scope generation
340
and capture time. Activation and terminal generations reconstruct the active
341
set after concurrent correction, forgetting, or expiry; another account cannot
342
load or use the snapshot. Multiple authenticated sessions for the same account
343
intentionally share it. Unknown scope has no global fallback.
344
345
Evidence: `profile_memory_scopes`, `profile_memory_snapshots`, generation
346
columns and indexes in the profile-memory migration, `OpenAgents.ProfileMemory`, and
347
cross-browser/concurrent-snapshot tests in `OpenAgents.ProfileMemoryTest`.
348
349
### MEMORY-005 — Memory writes require exact current consent
350
351
A sampled tool call is a proposal, not write authority. `memory_remember.v1`
352
accepts only an exact bounded claim directly authorized by the current complete
353
user message, an exact host-recorded confirmation, or an exact first-party UI
354
action in the same account scope. Repetition, recalled text, classifier
355
confidence, and model arguments cannot substitute. `memory_forget.v1` applies
356
the same rule to one record, one category, or the whole account scope and is
357
idempotent without revealing foreign-record existence.
358
359
Each turn captures a persisted profile-memory snapshot before provider work.
360
List/search use only that frozen generation, while remember/forget return a
361
bounded reversible receipt after PostgreSQL commits. OpenAgents may acknowledge
362
only the exact successful result returned in the durable tool outcome.
363
364
Evidence: `OpenAgents.Memory.Consent`, the four `OpenAgents.Tools.Memory*` tools,
365
`turn_receipts.profile_memory_snapshot_ref`, `docs/MEMORY_TOOLS.md`, and
366
`OpenAgents.Tools.ProfileMemoryToolsTest`.
367
368
### MEMORY-006 — Semantic recall is scoped, disposable, and lexically degradable
369
370
Authoritative durable conversation rows — messages, and for lexical
371
tool-activity recall the terminal tool steps — remain the sole recall
372
authority; semantic embeddings exist only for messages. Embeddings are
373
asynchronous derivatives bound to the source message, exact conversation,
374
content digest, model/version manifest, and active generation. Hybrid queries
375
repeat the conversation and frozen snapshot predicates in PostgreSQL and admit
376
only ready rows whose digest still matches the authoritative complete message.
377
Correction, deletion, and rebuild invalidate derivative rows and produce
378
append-only receipts; a new manifest generation cannot read an older one.
379
Receipts cannot be edited or deleted while their authoritative conversation
380
exists. Exact authenticated-owner product-data deletion first removes that conversation and
381
may then delete its now-unlinked content-free receipts in the same transaction.
382
383
The pinned hybrid rank is deterministic reciprocal-rank fusion with stable
384
timestamp/UUID ties. A provider, manifest, shape, query, or incomplete-index
385
failure is explicitly `semantic_degraded` and returns the unchanged lexical
386
page. Hybrid recall is disabled by default and cannot be activated merely by
387
creating vectors; its committed comparison must preserve lexical fallback and
388
scope isolation while improving synonym recall.
389
390
Evidence: `OpenAgents.Memory.SemanticIndex`, `OpenAgents.Memory.HybridRecall`, semantic
391
derivative tables and triggers, `docs/HYBRID_RECALL.md`,
392
`priv/openagents/evals/recall/hybrid-comparison.v1.json`, and
393
`OpenAgents.SemanticRecallTest`.
394
395
### MEMORY-007 — Learned preferences require confirmation and never confer authority
396
397
Behavior preferences occupy an account-owner plane separate from conversation
398
evidence, profile facts, roles, tool catalogs, routing authority, and collective
399
artifacts. An observation or model confidence can create only a candidate. The
400
only admitted effects are finite presentation/interaction choices; every
401
candidate must pass policy review, an exact owner confirmation receipt, and a
402
separate durable activation receipt before it can affect composition.
403
404
Every turn freezes a monotonic preference snapshot. Applied and
405
current-instruction-overridden preferences are immutably recorded with exact
406
preference, effect-digest, and activation-receipt refs. A current user request
407
wins for that turn without rewriting the stored preference. Suspension,
408
correction, and deletion end the effect for later snapshot generations while
409
preserving historical turn provenance. Outcomes can be recorded only for a
410
preference actually applied to that exact owner turn.
411
412
Preference evidence and receipts remain append-only while their owner exists.
413
Exact authenticated-owner product-data deletion is the only exception: PostgreSQL cascades
414
the complete preference graph after the visitor root is gone, while rejecting
415
direct or foreign-scope receipt deletion.
416
417
Evidence: `OpenAgents.Preferences`, the preference schemas and database guards,
418
`OpenAgents.Context.Composer`, `turn_receipts.used_preferences`,
419
`docs/GOVERNED_PREFERENCES.md`, the committed preference comparison, and
420
`OpenAgents.PreferencesTest`.
421
422
### MEMORY-008 — Experience is private, terminal, evidenced, and advisory
423
424
Work experience occupies an exact account-owner and conversation-work-scope
425
plane separate from profile facts, learned preferences, roles, tool authority,
426
and collective artifacts. Requested and running cases never enter recall.
427
Success requires a target receipt emitted by a succeeded governed tool step in
428
the same owner conversation; failure remains explicitly labeled failure. A
429
single success is one scoped observation, never a universal pattern.
430
431
Each turn either has no experience capture or freezes one deterministic,
432
bounded bank at a monotonic scope generation. Every projection names its
433
applicability and evidence; storage and projection both pass redaction.
434
Corrections create a new record, inspection/export remain bounded, and deletion
435
removes evidence, pattern, and bank derivatives while invalidating affected
436
banks. Crossing into collective work requires the independent exact-consent
437
candidate workflow and cannot directly publish or write a global pattern.
438
Default activation remains off until the committed memory-on/off evaluation
439
shows measured task benefit without scope or provenance regression.
440
441
Experience deletion receipts remain append-only while their owner exists.
442
Exact authenticated-owner product-data deletion is the only exception: PostgreSQL cascades
443
the scoped experience graph, including frozen banks linked to deleted turns,
444
then removes standalone deletion receipts only after the visitor root is gone.
445
Direct and foreign-scope receipt deletion remain rejected.
446
447
Evidence: `OpenAgents.ExperienceMemory`, the experience schemas and database guards,
448
`OpenAgents.Context.Composer`, `turn_receipts.used_experiences`,
449
`docs/PRIVATE_EXPERIENCE_MEMORY.md`, the committed benefit comparison, and
450
`OpenAgents.ExperienceMemoryTest`.
451
452
### MEMORY-009 — Graph memory is a disposable, generation-atomic projection
453
454
The relationship graph is never memory authority. Every node and edge belongs
455
to one account owner, one conversation work scope, one immutable manifest
456
generation, and at least one exact authoritative experience-record or pattern
457
membership. Deterministic identities preserve explicit entity, version, and
458
conflict fields without collapsing differing outcomes into asserted truth.
459
460
Source mutations emit an outbox row in the same PostgreSQL transaction. A
461
pending mutation makes the current graph unavailable; rebuild locks the source
462
scope, pins and digests one source snapshot, constructs a complete building
463
generation, and atomically retires the old generation and exposes the new one.
464
Dropping every graph table loses no authoritative memory, and replaying the same
465
source snapshot produces the same build digest.
466
467
Traversal requires the exact owner/work scope, current source policy eligibility,
468
finite depth and result bounds, and source provenance on every returned artifact.
469
Deletion uses an inspectable exact-generation cascade plan and append-only
470
completion receipt. Graph use remains disabled by default until its committed
471
paired evaluation demonstrates material relationship benefit with no scope or
472
membership regressions.
473
474
Graph operation receipts remain append-only while their owner exists. Exact
475
authenticated-owner product-data deletion is the only exception: manifest-owned projections
476
cascade with the visitor, and the same transaction removes standalone
477
memberships, mutation events, cascade plans, and operation receipts after the
478
visitor root is gone. Direct and foreign-scope receipt deletion remain rejected.
479
480
Evidence: `OpenAgents.GraphMemory`, graph manifests/artifacts/memberships/outbox and
481
database guards, `docs/DERIVED_GRAPH_MEMORY.md`, the committed graph comparison,
482
and `OpenAgents.GraphMemoryTest`.
483
484
### PRIVACY-001 — Secret-bearing profile memory is rejected, never scrub-stored
485
486
Before candidate storage, the host applies the pinned
487
`openagents.memory.policy.v1` policy to the claim, provenance/artifact metadata, and
488
same-owner source content. Credential, API/auth token, wallet/seed/payment,
489
encoded-secret, and local-path material rejects the whole candidate. The
490
rejected value, a hash of it, or a partially scrubbed shell is never persisted.
491
Only owner scope, fixed policy version, bounded reason/category, size bucket,
492
and time enter the rejection audit.
493
494
Every export or future model/UI projection re-applies
495
`openagents.memory.redaction.v1`. A value that fails revalidation is withheld as a
496
whole field. Stored policy identities are immutable, so later policy changes
497
cannot silently relabel old records or rejection evidence.
498
499
Evidence: `OpenAgents.Memory.Policy`, `OpenAgents.Memory.Redaction`,
500
`profile_memory_policy_events`, immutable policy-version trigger,
501
`docs/MEMORY_PRIVACY_POLICY.md`, and `OpenAgents.Memory.PolicyAndRedactionTest`.
502
503
## Turn and provider lifecycle
504
505
### TURN-001 — At most one active turn per conversation
506
507
An active turn is `queued` or `streaming`. A partial unique PostgreSQL index is
508
the final arbiter; the UI's disabled composer is only feedback.
509
510
Evidence: `turns_one_active_per_conversation_index` and active-turn test.
511
512
### TURN-002 — Every accepted turn has durable paired messages
513
514
The user message, empty streaming assistant message, and turn record are
515
inserted in one transaction. Completion, failure, and cancellation update both
516
assistant-message and turn terminal state.
517
518
Evidence: `OpenAgents.Conversations.create_turn/2`, `finish_turn/5`, and turn tests.
519
520
### TURN-003 — Provider work never blocks the LiveView
521
522
Each response executes in a temporary `TurnServer` under a dynamic supervisor;
523
the outbound provider call executes in a supervised task. Text deltas cross a
524
typed provider callback and are persisted before broadcast.
525
526
Evidence: `OpenAgents.Turns.TurnServer`, `OpenAgents.ProviderTaskSupervisor`, and
527
`OpenAgentsWeb.ChatLiveTest` streaming test.
528
529
### TURN-004 — Interrupted work becomes explicit failure
530
531
Active records left by a runtime restart are marked failed during application
532
startup. A response is never left permanently presented as in progress without
533
an executing turn process.
534
535
Evidence: `OpenAgents.Conversations.recover_interrupted_turns/0`, the
536
`OpenAgents.TurnRecovery` application child, and startup-recovery test.
537
538
### TURN-005 — Tool continuations are serial, bounded, and commit-first
539
540
One turn may request a bounded number of tool calls and provider continuations
541
(sixteen of each today). Calls execute one at a time; parallel calls fail the
542
turn. Hitting an execution bound refuses the over-limit call with a durable
543
typed step outcome and drives one final tool-free report response, so the
544
person receives partial findings instead of a failed turn; only runaway
545
behavior past the report path fails the turn. Each continuation uses the exact
546
provider call ID and previous response ID only after rereading the matching
547
committed outcome. Cancellation reaches provider and tool tasks, while prior
548
text, receipts, provider steps, and tool outcomes remain durable on every
549
terminal path.
550
551
Evidence: `OpenAgents.Turns.TurnServer`, `OpenAgents.Providers.OpenAI.request_payload/1`,
552
`OpenAgents.TurnToolLoopTest`, and `OpenAgents.Providers.OpenAI.RequestPayloadTest`.
553
554
### PROVENANCE-001 — Every new inference has an immutable receipt
555
556
Before provider work starts, OpenAgents durably captures the exact model, persona,
557
role, instruction digest, canonical input digest, optional runtime artifact
558
identities, and first provider step. Identity fields never change; terminal
559
receipts and provider steps cannot be rewritten. Failures, cancellation, and
560
restart recovery preserve the captured chain. Turns created before this
561
contract remain explicitly legacy rather than receiving invented provenance.
562
563
Evidence: `OpenAgents.Conversations.begin_inference/4`, PostgreSQL provenance
564
triggers, `OpenAgents.Provenance.Canonical`, and `OpenAgents.TurnProvenanceTest`.
565
566
### PROVIDER-001 — Model providers are replaceable
567
568
Conversation and web code depend on `OpenAgents.Providers.Provider`, not OpenAI
569
event shapes. Adapters emit typed OpenAgents-domain lifecycle, text, tool-call,
570
usage, completion, failure, and cancellation events. A response ID is persisted
571
when announced, and matching explicit completion is required; stream closure
572
alone cannot produce a completed turn. Provider-specific events, credentials,
573
and raw errors never reach the receipt or browser.
574
575
Evidence: `OpenAgents.Providers.ProviderEvent`, `OpenAgents.Providers.OpenAI`,
576
`OpenAgents.Providers.OpenAI.StreamDecoderTest`, `OpenAgents.Providers.Test`, and
577
`OpenAgents.TurnProviderEventsTest`.
578
579
## Tool authority and execution
580
581
### TOOL-001 — A turn uses one immutable tool catalog
582
583
The registry validates configured tool specifications at boot. Before provider
584
work, each turn captures one catalog snapshot and writes its canonical digest
585
to the immutable receipt. Every call must match the exact tool name and version
586
in that snapshot; later registry builds or deployments affect only later turns.
587
588
Evidence: `OpenAgents.Tools.Registry`, `OpenAgents.Tools.Snapshot`,
589
`OpenAgents.Turns.TurnServer`, and `OpenAgents.Tools.RegistryAndRunnerTest`.
590
591
### COLLECTIVE-001 — Private material crosses scope only through exact consent
592
593
A collective candidate can be created only in the same transaction as an active
594
`collective_contribution` receipt from the owning person. The receipt binds the
595
exact browser-owned source refs and their content digest, source-scope digest,
596
category, intended use, attribution and compensation disclosures, policy
597
version/digest, confirmation nonce/digest, and grant time. Model suggestions,
598
profile-memory consent, product terms, and tool approval are different authority
599
types and cannot satisfy this gate.
600
601
The candidate remains keyed and queried by its private visitor owner. It stores
602
only the scope digest, opaque per-source provenance refs, redaction-policy
603
identity, generalized-kind placeholder, evaluator/status, and bounded
604
review/publication refs; it does not copy source refs, quotes, or identifying
605
context into candidate fields and has no registry/discovery/execution path.
606
Owner withdrawal atomically marks consent withdrawn and the unpublished
607
candidate terminal. If publication refs later exist, withdrawal instead creates
608
`revocation_pending` propagation state. Consent never publishes or admits a
609
module; those require separate independent operator review.
610
611
Evidence: `OpenAgents.Collective`, `OpenAgents.Collective.ConsentReceipt`,
612
`OpenAgents.Collective.Candidate`, database scope/state/transition constraints, and
613
the consent, isolation, raw-copy, and withdrawal cases in `OpenAgents.CollectiveTest`.
614
615
### COLLECTIVE-002 — Generalization is bounded, content-free, and reproducible
616
617
Only an authenticated privacy reviewer in the candidate owner's scope can run
618
generalization or inspect opaque lineage. The versioned fixed-vocabulary
619
generalizer recognizes a bounded supported signal and emits one admitted schema
620
for evaluation cases, prompt examples, module patterns, or reusable work
621
patterns. Output is scanned again for secrets, contacts, identifiers, paths,
622
exact source fragments, authority-bearing fields, and size before storage. It
623
cannot copy private sources or manufacture executable capability/authority.
624
625
The append-only generalization receipt contains candidate/source/policy/
626
generalizer/output digests, kind-support signal, source count, risk/utility, and
627
bounded reason codes—never rejected content. Unsupported/high-risk material is
628
terminally rejected with no generalized payload. A successful payload becomes
629
immutable, and identical kind/signal/policy inputs produce the same output
630
digest. This is conservative de-identification, not a claim of mathematically
631
irreversible anonymity, and it still grants no publication/module authority.
632
633
Evidence: `OpenAgents.Collective.Generalizer`,
634
`OpenAgents.Collective.GeneralizationReceipt`, generalized-candidate database
635
constraints/triggers, and adversarial schema, reproducibility, lineage, and leak
636
scans in `OpenAgents.CollectiveGeneralizerTest`.
637
638
### COLLECTIVE-003 — Publication requires independent evidence and operator authority
639
640
A generalized candidate reaches the cross-user collective catalog only when its
641
contribution consent remains active, its privacy generalizer is bound to an
642
authenticated reviewer, and a different authenticated evaluator records a
643
pinned artifact, pinned dataset, versioned policy, and passing privacy, safety,
644
regression, novelty, utility, compatibility, and no-authority-expansion result.
645
A separately authenticated operator, distinct from both reviewers, must then
646
write the publication receipt. Self-review, self-approval, missing digests,
647
failed dimensions, withdrawn consent, and legacy unbound generalization receipts
648
fail closed.
649
650
Publication creates a new immutable `openagents.module_artifact.v1` through the same
651
artifact validation and registry dependency admission used by first-party
652
modules. The artifact contains only the reviewed generalized payload and opaque
653
attribution lineage. It begins `disabled` in a staged collective catalog and
654
has no installed executable, discovery, routing, or invocation path. Review,
655
operator-decision, and publication receipts are append-only and bind all actor,
656
policy, evaluation, artifact, predecessor, attribution, and derived-data-plan
657
evidence. An operator rejection is terminal for that immutable candidate and
658
publishes nothing.
659
660
Consent withdrawal creates revocation propagation. Revocation or staged
661
regression rollback writes a successor receipt and revoked artifact digest,
662
excludes the module from all new catalog projections, and marks the bounded
663
delete/rebuild plan required while preserving content-free historical evidence.
664
A privacy revocation cannot be rolled back, and an already published module
665
version is never overwritten.
666
667
Evidence: `OpenAgents.Collective.Reviewer`, `OpenAgents.Collective.Publisher`,
668
`OpenAgents.Collective.ReviewReceipt`, `OpenAgents.Collective.OperatorDecisionReceipt`,
669
`OpenAgents.Collective.PublicationReceipt`,
670
`OpenAgents.Modules.Artifact.from_collective/1`, database transition/append-only
671
guards, and `OpenAgents.CollectivePublicationTest`.
672
673
### COMPENSATION-001 — Attribution accounting never creates payout authority
674
675
Technical invocation cost, contributor attribution, compensation eligibility,
676
and payment are distinct facts. An event is compensation-eligible only when an
677
operator-admitted policy with `payout_authority: false` binds an exact immutable
678
module artifact to contribution allocations totaling one million parts, the
679
invocation is uniquely persisted and billable, its terminal outcome is accepted
680
by an authenticated outcome reviewer, the artifact is not revoked, and the
681
invocation/outcome has not already been classified. A model proposal, module
682
publication, invocation, or successful tool result alone cannot make an event
683
eligible or payable.
684
685
Eligible units are allocated deterministically by contribution reference with
686
integer remainder handling. Unique invocation and outcome keys prevent double
687
counting. Revocation blocks future eligibility but never rewrites historical
688
events. Refund, chargeback, fraud/dispute, and policy-migration handling occurs
689
only through append-only signed adjustments; statements deterministically
690
reconcile gross, adjustment, and net units for one policy and contributor.
691
692
Every policy, module allocation, outcome decision, event, share, adjustment,
693
and statement is append-only. Contributor/operator projections expose opaque
694
lineage, digests, counts, units, and reconciliation state but never arguments,
695
results, conversation content, user identity, payment instructions, custody, or
696
a payout operation.
697
698
Evidence: `OpenAgents.Compensation`, its seven typed receipt schemas and database
699
constraints, `docs/COMPENSATION_ACCOUNTING.md`, and duplicate, revocation,
700
allocation, adjustment, reconciliation, privacy, and no-payout cases in
701
`OpenAgents.CompensationTest`.
702
703
### MODULE-001 — Every invocation pins one immutable admitted module
704
705
The captured turn registry contains provider-neutral module artifacts whose
706
canonical digest covers typed input/output, lifecycle state, side-effect and
707
approval classes, capability/data scopes, policy facets, executor identity,
708
publisher/maintainer, provenance, compatibility/dependencies, predecessor,
709
deprecation, rollback, and attribution policy. The artifact also binds the
710
loaded BEAM executor identity. Immediately before invocation the runner verifies
711
that loaded identity; missing or changed bytes fail closed. Durable tool-step
712
identity records the registry, route receipt, artifact, executor, module version,
713
attribution-policy version/digest, side-effect class, arguments, invocation key,
714
and explicit billing identity.
715
716
Only admitted or explicitly deprecated modules are projected into discovery and
717
provider schemas. Disabled and revoked artifacts remain visible to provenance
718
inspection but are unavailable to new turns. A registry replacement can affect
719
only a later turn; predecessor and rollback metadata never mutate an already
720
captured snapshot. First-party recall remains subject to `TOOL-001` through
721
`TOOL-004` and its existing conversation/profile scope predicates.
722
723
Evidence: `OpenAgents.Modules.Artifact`, `OpenAgents.Modules.Registry`,
724
`OpenAgents.Tools.Registry`, the module identity columns and database transition
725
constraint on `turn_tool_steps`, `OpenAgents.Modules.RegistryTest`, and module
726
invocation reconciliation tests in `OpenAgents.ToolStepPersistenceTest`.
727
728
### MODULE-002 — Discovery and lifecycle never grant model authority
729
730
The model-facing discovery tool receives the exact registry already captured by
731
its turn and returns at most twenty bounded public metadata references. It cannot
732
register a module, reveal executable/provider schemas or private configuration,
733
or grant execution, scope, approval, or authority. Every later use must
734
revalidate both the registry and artifact digests against the same capture;
735
stale references fail closed. Deprecated modules are excluded from default new
736
selection but remain available to an explicit historical projection.
737
738
Stage, admit, deprecate, disable, revoke, and predecessor rollback are host-only
739
operator operations requiring an authenticated operator identity and a unique
740
approval receipt. Artifact, provenance, policy, compatibility, and dependent
741
impact checks run before an append-only PostgreSQL receipt is committed. Text
742
and voice surfaces apply the latest receipts only when capturing a new registry;
743
an in-flight turn/session keeps its prior snapshot. Revoked modules cannot be
744
restaged or admitted, and active dependents block disable/revoke.
745
746
Evidence: `OpenAgents.Modules.Discovery`, `OpenAgents.Tools.ModuleDiscover`,
747
`OpenAgents.Modules.Lifecycle`, `OpenAgents.Modules.LifecycleReceipt`, the append-only
748
database trigger, and the discovery/lifecycle tests.
749
750
### MODULE-003 — Routing proposals cannot weaken explicit policy
751
752
Module routing receives a captured registry, a versioned/digested host policy,
753
an intent digest, required capability/effect/data scope, and application-created
754
authorities. Publisher, cost class and numeric budget, quality, privacy,
755
residency, jurisdiction, censorship-resistance, approval, side effect, runtime,
756
scope, and authority are hard filters applied before deterministic ranking. A
757
model/program proposal is only a reference to revalidate. It cannot add a
758
candidate, change policy, infer away a stricter constraint from casual language,
759
or trigger an unauthorized fallback.
760
761
The exact decision is persisted before tool-step dispatch without raw prompt
762
content. Immediately before execution the host revalidates registry, artifact,
763
policy, scope, and authority; the tool runner then independently repeats its
764
scope/authority/effect checks. No eligible module produces a typed unavailable
765
or refused outcome. An unadmitted routing-program identity is rejected, and an
766
absent/degraded optional program uses the reproducible deterministic baseline.
767
768
Evidence: `OpenAgents.Modules.RoutingPolicy`, `OpenAgents.Modules.Router`,
769
`OpenAgents.Modules.RouteReceipt`, `OpenAgents.Modules.RoutingReceipts`, the append-only
770
route-receipt trigger, `OpenAgents.Turns.TurnServer`, and router/tool-loop tests.
771
772
### MODULE-004 — Every capability surface preserves the same authority boundary
773
774
Every route and invocation names exactly one admitted surface from `text`,
775
`voice`, `search`, `computer`, `repository`, `mcp`, or `agent`. The selected
776
artifact must admit that surface, kind, and effect; revalidation refuses a
777
surface change. Voice and text share the admitted persona, role, Blueprint,
778
memory authorities, registry, and outcome envelope rather than forking OpenAgents's
779
identity.
780
781
Read-only work requires exact application-created scope and authority.
782
Reversible writes require exact current-user consent, and external effects
783
require an external or operator approval receipt bound to module, version, and
784
scope. Every successful non-read effect returns a target receipt, and every
785
outcome discloses the actual executor. Large catalogs are bounded by schema
786
count and encoded size; above either ceiling only discovery is exposed, and
787
its proposals pass ordinary revalidation. Missing executors fail honestly.
788
789
Evidence: `OpenAgents.Modules.SurfacePolicy`, the `surface` field on module route
790
decisions/receipts, `OpenAgents.Tools.Registry.prompt_catalog/1`,
791
`OpenAgents.Tools.Runner`, `docs/MODULE_SURFACES.md`, and the surface, catalog,
792
approval, receipt, voice-interruption, and degradation tests.
793
794
### TOOL-002 — Model requests never widen host authority
795
796
Tool name, arguments, recalled text, and prompt content grant no scope or
797
authority. The runner checks the application-created execution context against
798
the captured specification before implementation code runs. It admits read-only
799
tools, explicitly scoped reversible writes, and external effects only with the
800
surface-specific approval and target-receipt contract.
801
Reversible memory writes still pass their current-consent, owner, policy,
802
conflict, and optimistic-generation gates.
803
804
Evidence: `OpenAgents.Tools.ExecutionContext`, `OpenAgents.Tools.Runner`, and the scope,
805
authority, schema, and side-effect runner tests.
806
807
### TOOL-003 — Tool outcomes are durable before provider continuation
808
809
Every provider call ID maps to one ordered immutable request row. A worker must
810
atomically claim `requested -> running`; duplicate requests return the existing
811
row and duplicate claims cannot execute it again. Provider continuation output
812
can be constructed only by rereading a committed terminal outcome. Active
813
steps block normal turn completion and become explicit cancelled, failed, or
814
interrupted outcomes with the containing turn.
815
816
Evidence: `OpenAgents.Conversations.ToolStep`, the `turn_tool_steps` constraints and
817
transition trigger, `OpenAgents.Conversations.tool_continuation_output/1`, and
818
`OpenAgents.ToolStepPersistenceTest`.
819
820
### TOOL-004 — Every outcome identifies the actual executor
821
822
Every success, failure, refusal, cancellation, timeout, or unavailable result
823
is a bounded `openagents.tool_outcome.v1` envelope naming the captured module version
824
and actual executor/disclosure. Attribution and target receipt refs are bounded
825
and validated; one OpenAgents interface never implies OpenAgents performed hidden work.
826
The terminal invocation adds an immutable normalized outcome receipt and bounded
827
usage/cost projection before provider continuation. OpenAgents's continuation and UI
828
activity disclose the executor; UI activity renders at most the bounded
829
durable outcome projection UI-002 sanctions, never an unbounded payload.
830
Success may rely on the normalized outcome receipt for local/read-only work;
831
target-system refs remain separately preserved whenever an effect or source
832
produces them.
833
834
Evidence: `OpenAgents.Tools.Tool`, `OpenAgents.Tools.ExecutionResult`,
835
`OpenAgents.Tools.Runner`, `OpenAgents.Conversations.ToolStep`, the invocation-ledger
836
database constraints, normalized-outcome tests, and executor-disclosure UI tests.
837
838
### DEGRADE-001 — Tool degradation is explicit and deterministic
839
840
Unknown versions, invalid schemas or arguments, scope/authority refusal,
841
unsupported effects, cancellation, timeout, crashes, and oversized/invalid
842
output become typed bounded outcomes. They never silently execute a substitute
843
tool, widen scope, expose raw exceptions, or fabricate success.
844
845
Evidence: `OpenAgents.Tools.Runner` and its failure-path tests.
846
847
Lexical recall unavailability is the typed `lexical_unavailable` tool failure.
848
The failed step remains in the receipt and the provider may only describe the
849
available path honestly; it may not guess at history or silently substitute a
850
different authority plane.
851
852
Evidence: the committed recall evaluation, `OpenAgents.TurnToolLoopTest`, and
853
degradation tests in `OpenAgents.Tools.ConversationRecallToolsTest`.
854
855
## Delegated work
856
857
### WORK-001 — Delegated jobs are durable, budgeted, governed, and never die silently
858
859
A `deep_work.v1` call is only delegation, never execution: it starts one
860
durable `work_jobs` row scoped to the caller's conversation and owner and
861
returns immediately with a job reference, so the requesting turn or voice
862
response acknowledges in one sentence while the work runs server-side. The
863
worker drives the same configured text provider under the same composed
864
persona instructions, the same captured tool-catalog snapshot, and the same
865
governed `OpenAgents.Tools.Runner`, with each tool call committed as an ordered
866
`work_job_steps` row before execution and continued only from its committed
867
terminal outcome. A job's authorities never include `work.delegate` or
868
`memory.write`, its provider request never advertises `deep_work`, and an
869
arriving recursion call is refused with a durable typed outcome, so
870
delegation depth stays at one and a job cannot widen the caller's authority.
871
872
Every job is bounded — thirty-two tool calls, thirty-two continuations, a
873
ten-minute wall clock — and a tripped bound refuses the over-limit call with
874
a durable typed step outcome, forces one final tool-free report response, and
875
ends the job as explicit `budget_exhausted`. Every terminal path (`completed`,
876
`failed`, `interrupted`, `budget_exhausted`) stores a non-empty report:
877
streamed report text is persisted as it arrives, and a job that dies before a
878
narrative gets an honest host summary of its committed step evidence.
879
PostgreSQL constrains status transitions and makes terminal jobs and terminal
880
steps immutable. Startup recovery RESUMES orphaned active jobs (#97): it
881
restarts each job's supervised worker, which re-claims through the generation
882
fence and continues — a delegation by its durably checkpointed ACP session id,
883
deep work from its committed evidence. A job whose cluster singleton is still
884
alive (a fleet survivor) answers `already_started` and is never disturbed or
885
double-adopted; the restart is recorded as a degraded `runtime_restarted`
886
incident; and only a job whose worker cannot start at all is finalized
887
`interrupted` with its partial report intact. On terminal state the
888
bounded report becomes a durable assistant conversation message linked to the
889
job, so it enters ordinary recall and later provider context; a live voice
890
session receives the report through the existing typed-message injection as a
891
best-effort projection that can never rewrite the committed terminal state.
892
893
Evidence: `OpenAgents.Work`, `OpenAgents.Work.Job`, `OpenAgents.Work.JobStep`,
894
`OpenAgents.Work.JobServer`, `OpenAgents.WorkRecovery`, `OpenAgents.Tools.DeepWork`, the
895
`create_work_jobs` migration triggers, `OpenAgents.WorkJobTest`, and
896
`OpenAgents.DeepWorkToolLoopTest`.
897
898
### SELF-EDIT-001 — Every behavior change is anchored to a pushed commit (2026-08-19)
899
900
OpenAgents may edit her own source only through governed repository tools acting
901
on a per-job clone of her own forge, and nothing she writes becomes running
902
behavior except through the receipted pipeline. Concretely:
903
904
- **The pushed commit is the artifact.** Every behavior change to the
905
  running system is anchored to a commit pushed to the forge; the WAL entry
906
  that acked that push is its durable digest. Hot-loaded code is a
907
  *projection* of a pushed commit, never authority: a node restart that
908
  converges to the promoted fleet target (or, absent one, to the image) is
909
  always correct and loses nothing that was ever authority.
910
- **Mutation stays inside the job's clone.** Repository write tools operate
911
  only under that job's workspace clone whose origin is the local forge —
912
  never GitHub, never the baked source, never another job's clone. Pushes go
913
  only to that job's own `openagents/job-<id>` branch; a push to any other ref is
914
  refused with a typed outcome. The clone is removed when the job ends.
915
- **Promotion is an operator action.** No OpenAgents tool can promote, deploy, or
916
  hot-load. The job's report links the pushed SHA; the `/admin/forge`
917
  Promote click (ADMIN-001 as amended) is the human approval receipt, and
918
  the allowlist of hot-loadable modules remains operator-owned data.
919
- **Receipts reconstruct what ran.** Tool outcome receipts carry the commit
920
  SHA of every push; push, build, and deploy receipts chain from that SHA;
921
  together they let an operator reconstruct exactly which code was live
922
  when, with no step inferred.
923
924
Evidence: `OpenAgents.Tools.Repository` (clone confinement, branch discipline,
925
typed refusals), `OpenAgents.Work.Coding`, `OpenAgents.Forge.Pushes` /
926
`OpenAgents.Forge.Targets` / `OpenAgents.Forge.HotLoader` receipts, ADMIN-001, and
927
`OpenAgents.Tools.RepositoryTest` / `OpenAgents.CodingJobTest`.
928
929
## Interface and release
930
931
### VOICE-001 — Spoken identity is admitted before media
932
933
Standalone OpenAgents's first voice artifact is `openagents.voice.openai.marin.v1` using
934
native OpenAI Realtime `gpt-realtime-2.1` at low reasoning effort. It is a
935
deliberate repository-local revision of the earlier Leda direction, not a
936
silent fallback or a change to One. Boot refuses unadmitted architecture,
937
provider, model, voice, reasoning, or duration values. Any future custom voice,
938
Leda cascade, or built-in replacement requires a reviewed artifact revision
939
and regression evidence.
940
941
Evidence: `OpenAgents.Voice.Config`,
942
`docs/voice/OPENAI_REALTIME_DECISION.md`, and `OpenAgents.Voice.ConfigTest`.
943
944
### VOICE-002 — Browser media admission cannot acquire server authority
945
946
Voice is default-disabled. When enabled, an active authenticated user may send
947
only a bounded SDP offer through the same-origin, CSRF-protected endpoint.
948
Phoenix supplies the OpenAI credential, session configuration, and stable
949
privacy-preserving safety identifier derived from OpenAgents's local user ID. The browser receives only a bounded SDP
950
answer; provider credentials, call identity, protected configuration, and
951
future tool control remain server-side. Invalid identity, configuration, SDP,
952
provider location, and provider failure all fail closed without raw provider
953
details.
954
955
Evidence: `OpenAgentsWeb.VoiceCallController`,
956
`OpenAgents.Voice.OpenAI.CallClient`, `OpenAgents.Voice.OpenAI.CallClientTest`, and
957
`OpenAgentsWeb.VoiceCallControllerTest`.
958
959
### VOICE-003 — Durable voice history is generation-fenced
960
961
PostgreSQL owns every admitted voice generation and permits at most one active
962
generation per conversation. Provider events, response receipts, and
963
transcript items repeat the admitted generation; both runtime matching and
964
database constraints reject stale work. Ordered normalized events commit under
965
a locked session row, exact provider-event retries are idempotent, immutable
966
session provenance cannot be rewritten, and a terminal generation rejects
967
late events.
968
969
The supervised runtime owns only live connection state. Startup or process
970
loss makes the affected generation explicitly failed; it never invents a
971
successful resume or transcript.
972
973
Evidence: `OpenAgents.Voice`, the `create_voice_runtime` migration,
974
`OpenAgents.VoiceSessions.SessionServer`, `OpenAgents.VoiceRecovery`,
975
`docs/voice/VOICE_RUNTIME.md`, `OpenAgents.VoiceTest`, and
976
`OpenAgents.VoiceSessionsTest`.
977
978
### VOICE-004 — Only bounded provider-neutral voice evidence becomes durable
979
980
OpenAI wire events are decoded behind the provider adapter. Audio deltas,
981
partial transcript deltas, credentials, SDP, provider error text, and unbounded
982
wire payloads never enter OpenAgents's durable voice domain. Final user
983
speech may be stored; OpenAgents speech is bound to a started response receipt and
984
is marked interrupted when barge-in supersedes it. Completion without a
985
started receipt rolls back. Provider-reported usage remains absent when the
986
provider did not report it rather than being invented as zero.
987
988
Call audio is the single deliberate exception, and it enters through a
989
different door: not the provider event path but a browser upload, under the
990
separate bounds of VOICE-012. The distinction is the point — a bounded recorded
991
artifact is not the same as letting the provider wire into the database.
992
993
Evidence: `OpenAgents.Voice.ProviderEvent`,
994
`OpenAgents.Voice.OpenAI.EventDecoder`, `OpenAgents.Voice.ResponseReceipt`,
995
`OpenAgents.Voice.TranscriptItem`, and their voice runtime tests.
996
997
### VOICE-005 — Microphone capture is explicit, fenced, visible, and finite
998
999
Only a direct `START VOICE` action may request microphone access. New tracks
1000
begin disabled and may transmit only while the browser peer and control channel
1001
are open, playback is usable, the user has not muted, and LiveView projects a
1002
ready state from the current server generation. Reconnect, failure, end,
1003
navigation, or hook destruction disables and stops every media track, closes
1004
the data channel and peer connection, clears remote audio, stops and finalizes
1005
any recording, and best-effort closes the server generation. Typed chat never
1006
asks for microphone access.
1007
1008
Recording is part of what capture means, so it is disclosed on the same terms.
1009
While recording is on, the surface that carries `START VOICE` states before the
1010
microphone opens that calls are recorded, stored, and readable by an operator,
1011
and names the retention window; a visible marker announces capture while it is
1012
running, and appears only while it is actually running. Recording off makes no
1013
such claim anywhere.
1014
1015
Evidence: `assets/js/voice_controller.js`, `assets/js/voice_state.mjs`,
1016
`assets/js/voice_recording.mjs`, their Node tests, the disclosure tests in
1017
`OpenAgentsWeb.ChatLiveTest` and `OpenAgentsWeb.DataControllerTest`, and
1018
`docs/voice/BROWSER_TRANSPORT.md`.
1019
1020
### VOICE-006 — Voice controls project server truth and preserve typed OpenAgents
1021
1022
Browser peer events cannot claim a durable listening, responding,
1023
interrupted, ended, or failed state. The visible lifecycle is derived from the
1024
browser-scoped, generation-fenced PostgreSQL session projected by LiveView.
1025
Explicit interruption commits before provider cancellation. Voice admission is
1026
refused during a text turn; sending typed input while voice is active ends the
1027
voice generation first, so two OpenAgents responses cannot run in parallel. Voice
1028
failure leaves the typed conversation intact and available.
1029
1030
Evidence: `OpenAgentsWeb.ChatLive`, `OpenAgentsWeb.VoiceCallController`,
1031
`OpenAgents.VoiceSessions`, their tests, and `docs/voice/BROWSER_TRANSPORT.md`.
1032
1033
### VOICE-007 — Every live response freezes one governed OpenAgents context
1034
1035
Automatic provider response creation is disabled. A final user transcript first
1036
becomes a complete conversation message, then Phoenix captures the exact
1037
persona/role, Blueprint, program-or-explicit-baseline receipt, tool catalog,
1038
conversation high-water mark, profile-memory snapshot, selected evidence, and
1039
composed spoken instructions. Only then may Phoenix request a response. A
1040
provider response without that immutable context fails the generation.
1041
1042
Voice presentation may make delivery brief and speech-appropriate, but cannot
1043
override protected identity, evidence grammar, tool authority, or completion
1044
honesty. A session refuses changed persona, Blueprint, or tool-catalog identity
1045
rather than silently mixing revisions.
1046
1047
Long calls stay governed rather than unbounded: when a completed response's
1048
provider-reported input size crosses the configured compaction threshold —
1049
and only at a quiet boundary with no active tool chain, no queued input, and
1050
no compaction already in flight — the host drives one text-only, tool-free
1051
maintenance response under the same frozen context, asking for a progress
1052
summary that preserves exact values and the next action. That summary is
1053
byte-bounded and durable on the session row (`compaction_summary`,
1054
`compaction_count`) before the runtime deletes old provider items it knows by
1055
id and injects one bounded system summary item. The summary is continuity
1056
evidence only: it never rewrites or displaces `voice_transcript_items` /
1057
`messages` transcript authority, the compaction response cannot re-trigger
1058
itself, pruning never happens without a persisted summary, and new person
1059
input always supersedes an in-flight compaction.
1060
1061
Evidence: `OpenAgents.Voice.ContextCapture`, `voice_response_contexts`,
1062
`OpenAgents.Context.Composer`, `OpenAgents.VoiceSessions.SessionServer`,
1063
`OpenAgents.Voice.record_compaction_summary/3`, and governed context and
1064
compaction tests in `OpenAgents.VoiceSessionsTest`.
1065
1066
### VOICE-008 — Realtime function calls use the governed tool runner
1067
1068
Realtime exposes the same captured typed registry used by text turns. Model
1069
function arguments remain proposals: Phoenix validates call identity, schema,
1070
scope, authority, consent, generation, and module version through
1071
`OpenAgents.Tools.Runner`. Each request and normalized terminal outcome is durable
1072
before any `function_call_output` or continuation is sent to OpenAI. Unknown,
1073
malformed, unauthorized, stale, interrupted, and failed calls cannot become an
1074
effect claim. Durable tool steps retain the raw model arguments as user-owned,
1075
deletable conversation evidence alongside the canonical argument digest;
1076
durable provider events and operational telemetry remain digest-only and
1077
content-free.
1078
1079
Host bounds refuse rather than kill: a call past the per-cycle tool budget is
1080
refused with a `tool_call_limit_reached` continuation and one tool-free report
1081
response, never a session failure, and the composed instructions disclose that
1082
the budget exists. Every provider `function_call` item receives a terminal
1083
`function_call_output` — including cancelled calls after a barge-in — so the
1084
provider conversation never holds an orphan call the model must narrate
1085
around. Continuation payloads are byte-bounded; the durable tool step keeps
1086
the full result.
1087
1088
Evidence: `OpenAgents.Tools.Registry.realtime_catalog/1`,
1089
`OpenAgents.Voice.ToolStep`, `voice_tool_steps`,
1090
`OpenAgents.VoiceSessions.SessionServer`, and voice tool tests in
1091
`OpenAgents.VoiceTest` and `OpenAgents.VoiceSessionsTest`.
1092
1093
### VOICE-009 — Text and voice share one append-only conversation authority
1094
1095
Final user transcriptions and completed OpenAgents transcripts project into the
1096
same ordered `messages` table as text. Provider item identity makes retries
1097
idempotent. Interrupted OpenAgents speech is cancelled evidence and never enters
1098
provider history as a complete answer; when it re-enters composed context it
1099
is explicitly labeled interrupted and non-complete, and a barge-in truncates
1100
the provider-side assistant item to the approximate playback position so the
1101
model's conversation cannot retain speech the person never heard. PostgreSQL
1102
forbids rewriting voice
1103
message content or provenance; a correction is a later message, not an
1104
in-place edit. Starting a text turn serially ends the live voice generation,
1105
so text and voice cannot produce parallel OpenAgents answers.
1106
1107
Evidence: `messages` voice provenance and transition constraints,
1108
`OpenAgents.Voice.persist_transcript/3`, `OpenAgents.Conversations.provider_messages/1`,
1109
`OpenAgentsWeb.ChatLive`, cross-modal chronology tests in `OpenAgents.VoiceTest`, and
1110
`priv/openagents/evals/voice/corpus.v1.json`.
1111
1112
### VOICE-010 — New voice admission is live-governed, globally bounded, and attributable
1113
1114
The deploy flag and append-only PostgreSQL release control are independent.
1115
Only the latest `open` control admits a new call; `draining` and `disabled`
1116
refuse it without ending active calls or typed OpenAgents. Every session pins the
1117
exact control row. Admission serializes on a database advisory lock before
1118
counting active sessions, so simultaneous authenticated requests cannot exceed the
1119
global budget. Per-conversation attempt, one-active-generation, duration,
1120
response-token, session-token, estimated-cost, tool, and payload limits remain
1121
separate, explicit ceilings. Session budget ceilings are abuse backstops, not
1122
working limits, and their enforcement is disclosed: one host notice at 80%
1123
lets the model wrap up, and a budget-ended session records and surfaces
1124
`usage_budget_reached` rather than presenting as a silent disconnect.
1125
1126
Evidence: `OpenAgents.Voice.ReleaseControl`, `OpenAgents.Voice.admit_session/2`,
1127
`voice_release_controls`, `OpenAgents.Voice.Usage`,
1128
`OpenAgents.Voice.ReleaseOperationsTest`, and
1129
`docs/voice/RELEASE_OPERATIONS.md`.
1130
1131
### VOICE-011 — Voice operations are measurable without becoming a content sink
1132
1133
Operational telemetry is built from strict fields and may never contain
1134
credentials, browser/conversation identity, SDP, provider call identity, raw
1135
audio, transcript content, composed instructions, raw tool arguments/results,
1136
or provider error prose. Recording adds bytes to the product but not to this
1137
path: chunk payloads and content digests never enter telemetry, aggregate
1138
reports, or logs, while byte counts and durations remain operational fields. Browser observations accept only a finite event enum,
1139
derive a reduced browser family/major on the server, and stop at 64 events per
1140
session. Aggregate reports read only timing, state, normalized kind, browser,
1141
usage, and cost fields. Missing samples remain missing and block the canary;
1142
health cannot substitute for media, quality, browser, load, or rollback proof.
1143
1144
Evidence: `OpenAgents.Voice.OperationalTelemetry`,
1145
`OpenAgents.Voice.Operations.Report`, `OpenAgents.Voice.ClientEvent`,
1146
`OpenAgentsWeb.VoiceTelemetryController`, their redaction and controller tests, and
1147
`docs/voice/RELEASE_OPERATIONS.md`.
1148
1149
### VOICE-012 — Call audio is bounded, sealed, fenced evidence — never authority
1150
1151
Voice media never reaches OpenAgents: it flows browser-to-OpenAI over WebRTC while
1152
the server holds only a lifecycle sideband. A recording is therefore what one
1153
browser uploaded, not what was said. It can be withheld, truncated, or stopped
1154
at will, so it never displaces `voice_transcript_items` as the conversation
1155
record and never becomes evidence for a OpenAgents response.
1156
1157
Audio may become durable only under every one of these at once: recording is
1158
enabled and a recording key is configured, so audio is sealed at rest or not
1159
stored at all; each slice commits under the locked session row against the
1160
admitted generation, in strict sequence, with a repeat of a stored sequence
1161
idempotent and a gap refused; chunk size, chunk count, and total bytes are
1162
explicit ceilings, past which the recording becomes `truncated` and further
1163
slices are refused; and uploads are accepted only within a bounded grace window
1164
after the call ends, because the tail slice arrives after the recorder stops.
1165
The session is resolved from the encrypted session cookie, never from a
1166
client-supplied identifier.
1167
1168
A recording is stored, read, and deleted as a whole ordered concatenation; a
1169
single slice is not media. Statuses distinguish a clean upload from a
1170
truncated, abandoned, or failed one, so partial audio is never presented as a
1171
whole call. Audio carries its own retention window, shorter than the
1172
operational voice window, and is deleted by that sweep and by DATA-004 cascade.
1173
1174
Recording failure never affects the call: an unsupported browser, a blocked
1175
audio graph, a refused upload, or a missing key all yield an unrecorded
1176
conversation rather than a failed one.
1177
1178
Recording is a property of the voice surface, not a per-account setting. While
1179
it is enabled there is no opt-out flag and none may be added without changing
1180
this contract; the disclosure states the situation rather than offering a
1181
choice, and typed chat remains available and is never recorded. The account
1182
cannot play back its own audio either — the operator surface is the only place a
1183
recording is audible, and the account's export carries the recording's metadata
1184
rather than its sound.
1185
1186
Evidence: `OpenAgents.Voice.Recordings`, `OpenAgents.Voice.Recording`,
1187
`OpenAgents.Voice.RecordingChunk`, `OpenAgents.Voice.RecordingVault`, the
1188
`create_voice_recordings` migration, `OpenAgentsWeb.VoiceRecordingController`,
1189
`assets/js/voice_recording.mjs`, `OpenAgents.Voice.RecordingsTest`,
1190
`OpenAgentsWeb.VoiceRecordingControllerTest`, `assets/test/voice_recording_test.mjs`,
1191
and `docs/voice/RECORDINGS.md`.
1192
1193
### ADMIN-001 — One operator reads across accounts, and only reads
1194
1195
IDENTITY-002 confines every ordinary server path to the active user's own data.
1196
`/admin` is the second deliberate exception after LEADERBOARD-001, and it is
1197
the opposite kind: the leaderboard publishes a narrow projection to the
1198
internet, while this reads a wide one for exactly one person.
1199
1200
Operator access is an allowlist of GitHub's immutable numeric IDs, never of
1201
logins — a login can be renamed and the freed name claimed by someone else, and
1202
`users.github_id` is already the identity root. A banned account is never an
1203
operator. The check runs on mount and again on every event, so removal from the
1204
allowlist takes effect on the connected socket rather than at the next
1205
reconnect. A non-operator receives exactly what an unauthenticated visitor
1206
receives, with no flash and no distinct status, so the surface never announces
1207
that it exists. No product surface links to it.
1208
1209
The operator path is read-only. It exposes no ban, no message, no deletion, and
1210
no configuration change, so a mistake in that surface cannot alter anyone's
1211
conversation.
1212
1213
Amended 2026-08-18 (forge deploy lane, issue #119): the forge panel at
1214
`/admin/forge` is the one deliberate exception, and it is a narrow one. Its
1215
only write is promoting an already-pushed commit as the fleet deploy target
1216
(`OpenAgents.Forge.Targets.promote/3`), receipted with the promoting operator's
1217
identity in the append-only `forge_fleet_targets` ledger. Only SHAs present
1218
in the WAL-backed repository are promotable, so the surface cannot introduce
1219
code — it can only approve code that already survived the push path. It
1220
still cannot touch any account, conversation, message, ban, or product
1221
configuration; ADMIN-001's read-only rule continues to bind everything else
1222
on the operator surface, including the original `/admin` panel unchanged. What it may show is the fields of `OpenAgents.Admin.Call` and the
1223
audio itself: account display identity, call lifecycle, model, token total, and
1224
recording completeness. Transcript *content*, composed instructions, tool
1225
catalogs, provider call identity, and recall material stay out — cross-account
1226
listening was the decision, and cross-account reading of what was said is a
1227
separate one. Calls with no audio are listed with the reason rather than
1228
hidden, so the panel cannot present an incomplete history as a complete one.
1229
1230
Evidence: `OpenAgents.Accounts.admin?/1`, `OpenAgentsWeb.UserAuth.require_admin_user/2`,
1231
the `:ensure_admin` mount hook, `OpenAgents.Admin`, `OpenAgents.Admin.Call`,
1232
`OpenAgentsWeb.AdminLive`, `OpenAgentsWeb.AdminRecordingController`, `OpenAgents.AdminTest`,
1233
`OpenAgentsWeb.AdminLiveTest`, and `OpenAgentsWeb.AdminRecordingControllerTest`.
1234
1235
### DATA-004 — The authenticated user can export and delete OpenAgents product data
1236
1237
The server resolves export and deletion only from the active local user in the
1238
encrypted session and verifies that user owns the internal storage root.
1239
Export provides canonical messages, profile memory, voice summaries, and
1240
tool-step evidence (raw arguments plus digests) with explicit bounds. Exact confirmation deletes the visitor root only while text
1241
and voice are inactive; database cascades remove
1242
conversation, memory, receipt, module, collective, and voice records — call
1243
audio included, through `voice_recordings`' cascade to the session. The export
1244
names each call's recording — status, container, size, duration claim, digest,
1245
and that it is encrypted at rest — without embedding the audio, because a JSON
1246
export is the wrong container for Opus and base64 in a text field would be
1247
worse. The account cannot play back its own audio: the operator surface is the
1248
only place a recording is audible. That is a decision rather than an omission —
1249
what exists is disclosed and exported as metadata, and deletion removes it.
1250
Detailed terminal voice
1251
operations purge automatically after 90 days while the minimal provenance stub
1252
follows canonical voice messages until complete deletion. Disposable semantic
1253
rows cascade with their messages, and the same transaction removes content-free
1254
semantic invalidation receipts only after their authoritative conversation no
1255
longer exists. Governed preference observations, snapshots, effects, and
1256
receipts likewise cascade only through deletion of their owning visitor root.
1257
Private experience scopes, records, evidence, patterns, and frozen banks also
1258
cascade through that root; standalone experience deletion receipts are removed
1259
in the same transaction only after the owner no longer exists.
1260
Derived graph manifests and artifacts cascade with the owner; standalone graph
1261
memberships, outbox events, cascade plans, and operation receipts are likewise
1262
removed in that transaction only after deletion of the visitor root.
1263
1264
The minimal local account record (GitHub numeric ID, current login/avatar,
1265
access status, and authentication timestamps) is retained so deletion cannot
1266
erase a ban or bypass authorization. No GitHub access token is retained or
1267
exported. A later account-erasure contract must separately define moderation
1268
retention and re-enrollment behavior.
1269
1270
Evidence: `OpenAgents.DataRights`, `OpenAgentsWeb.DataController`,
1271
`OpenAgents.Voice.Retention`, database foreign keys and purge trigger,
1272
`OpenAgentsWeb.DataControllerTest`, and `OpenAgents.Voice.ReleaseOperationsTest`.
1273
1274
### UI-001 — Authentication gates the one-conversation interface
1275
1276
The public default route is an authentication boundary and cannot invoke
1277
OpenAgents. It exposes one GitHub login action and only bounded authentication error
1278
copy. The protected `/chat` route exposes transcript, contextual turn state,
1279
bounded history, composer, and only the minimal account control required to
1280
show the authenticated GitHub avatar/login and submit logout. User transcript
1281
rows use that same validated GitHub avatar projection. The browser image policy
1282
allows only same-origin/data images and the exact GitHub avatar origin. The
1283
interface contains no conversation list and no workspace/settings chrome. The
1284
sidebar (2026-08-17, an owner-directed reversal of this invariant's earlier
1285
blanket "no sidebar" clause) navigates only OpenAgents's own surfaces — memory,
1286
leaderboard, the operator panel, export — and may never list or switch
1287
conversations.
1288
1289
`/leaderboard` is the one additional route reachable without a session. It is
1290
permitted only because it makes the same guarantee the public root makes: it is
1291
read only, cannot mount or invoke OpenAgents, holds no conversation, exposes no
1292
composer or action, and creates no identity state. It is a published projection
1293
governed by LEADERBOARD-001, not a second product interface, and it does not
1294
introduce navigation chrome into the conversation.
1295
1296
`/admin` is an operator tool rather than a product surface. It is reachable
1297
only by the allowlisted operator under ADMIN-001, is read only, cannot mount or
1298
invoke OpenAgents, and holds no conversation. It adds nothing to the conversation
1299
interface: no link, no affordance, and no chrome, for operators and
1300
non-operators alike. Being an operator tool is not license for product
1301
chrome — the anti-references in `PRODUCT.md` still describe what the product
1302
does not become.
1303
1304
Evidence: `OpenAgentsWeb.HomeControllerTest`, the `OpenAgentsWeb.ChatLiveTest` surface
1305
test, `OpenAgentsWeb.LeaderboardLiveTest`, `OpenAgentsWeb.AdminLiveTest`,
1306
`OpenAgentsWeb.Router` browser policy, `PRODUCT.md`, and `DESIGN.md`.
1307
1308
### UI-002 — Tool activity is a bounded projection of PostgreSQL truth
1309
1310
The interface renders tool activity only from the durable, already-scrubbed
1311
step row: stable step ID, sequence, public capability label, status, the
1312
step's durable `raw_arguments`, the bounded durable result/error, executor id
1313
and disclosure, and lifecycle timestamps. Every argument-, result-, or
1314
error-derived string is byte-capped in `OpenAgentsWeb.ToolActivity` before it
1315
reaches a template — collapsed titles to one bounded line, expanded payloads
1316
to a hard cap. Provider identifiers (call/item/response IDs) and private
1317
recall content never enter socket assigns or HTML. The executor disclosure
1318
remains available verbatim for every terminal step — in the row's expansion;
1319
availability, not collapsed-row placement, is the contract. PubSub carries an
1320
invalidation signal and LiveView rereads PostgreSQL. Reload reconstructs
1321
activity, and terminal turn state clears the active band while restoring the
1322
composer and cancellation state.
1323
1324
(Amended 2026-08-17, issue #79: previously this invariant kept raw arguments
1325
and results out of the browser entirely. The owner directed that the durable,
1326
scrubbed step values render — bounded — so titles can say what actually ran.)
1327
1328
(Amended 2026-08-18, issue #85: one bounded scrubbed **ephemeral live
1329
projection** may also render — the streamed computer-delegation chunk stream,
1330
re-broadcast by `OpenAgents.ComputerActivity` on the owner conversation's PubSub
1331
topic (`computer_live:<conversation id>`) while `OpenAgents.Computer` collects it.
1332
The chunk text is the controller's already secret-scrubbed output, re-bounded
1333
server-side before broadcast: per event, cumulative (the same 65,536-byte
1334
ceiling the collection enforces), and in event count, with an explicit
1335
truncation marker once capped. PubSub stays projection, never authority:
1336
nothing about the stream is persisted, reload degrades to status-only, and the
1337
durable step outcome remains the record. Machine tokens, argv, env, prompts,
1338
and paths never enter a live event, and the topic is owner-scoped by
1339
construction, so only the owner's conversation ever receives it.)
1340
1341
Evidence: `OpenAgents.Conversations.list_tool_step_activity/1`,
1342
`OpenAgents.Voice.list_tool_step_activity/1`, `OpenAgentsWeb.ToolActivity`,
1343
`OpenAgents.ComputerActivity`, `OpenAgentsWeb.ChatLive`, and tool activity tests in
1344
`OpenAgentsWeb.ChatLiveTest`, `OpenAgentsWeb.ToolActivityTest`,
1345
`OpenAgents.ComputerActivityTest`, and `OpenAgentsWeb.ChatDelegationRailTest`.
1346
1347
### UI-003 — Product surfaces render only through the sanctioned component library
1348
1349
OpenAgents's interface is built from `OpenAgentsWeb.UI` components over Basecoat
1350
primitives vendored at a pinned tag and styled by the OpenAgents pack. Product
1351
surfaces do not author component-level CSS classes; hand-authored CSS is
1352
confined to app-shell layout and the one sanctioned brand animation. No
1353
component accepts provider identifiers or private recall content as an
1354
attribute; tool activity reaches `event_header` only as the bounded projection
1355
UI-002 sanctions, so UI-002 cannot be violated through a primitive.
1356
Basecoat's JavaScript is never loaded and the account menu uses the native
1357
popover API, so the identity control works without custom client-side script.
1358
Where Basecoat has no equivalent, the primitive wraps the browser's own control
1359
rather than reimplementing it: `audio_player/1` is a native `<audio controls>`
1360
in a OpenAgents-styled box, keyboard operable and announced by the user agent, and it
1361
requires an accessible name because a page of recordings is otherwise a page of
1362
identically announced players.
1363
The shared corner radius, the self-hosted Geist faces, the single dark theme,
1364
and the reserved semantic color meanings hold across every component. Depth is
1365
limited to the sanctioned lift, halo, and state-ring tokens.
1366
Adopting an additional Basecoat component requires a `DESIGN.md` change and an
1367
explicit per-component import.
1368
1369
Evidence: `assets/vendor/basecoat/README.md`, `assets/css/style-openagents.css`,
1370
`priv/static/fonts`, `OpenAgentsWeb.UI`, `OpenAgentsWeb.UITest`,
1371
`OpenAgentsWeb.UIGalleryLiveTest`, and `priv/scripts/check_css_contract.exs`.
1372
1373
### LEADERBOARD-001 — The public board publishes one bounded projection
1374
1375
IDENTITY-002 confines every other server path to the active user's own data.
1376
The leaderboard is the single deliberate exception, and it is an exception to
1377
publication rather than merely to cross-account reads: unauthenticated
1378
visitors and crawlers can read it.
1379
1380
What may be published is exactly the fields of `OpenAgents.Leaderboard.Entry`: a
1381
rank, the GitHub login, the GitHub name, the validated GitHub avatar URL, and
1382
one non-negative integer token total. Nothing else crosses the account
1383
boundary — no conversation, turn, receipt, or voice-session identifier, no
1384
model identifier, no message, transcript, memory, or recall content, no
1385
activity timestamp, no typed/spoken split, and no priced cost. The struct is
1386
the contract: a field added there is published to the internet.
1387
1388
Totals are derived only from the two planes that already hold a merged total,
1389
`turn_receipts.usage` and `voice_sessions.usage`. Provider steps, voice
1390
response receipts, tool-step invocation counts, and off-path shadow-program
1391
runs are excluded, so no account is credited twice and none is credited for
1392
work it did not drive. Accounts appear only while active, not withheld by
1393
`users.public_leaderboard_opted_out`, and above zero tokens; banned accounts
1394
and legacy browser-only visitors never appear. Deleting product data under
1395
DATA-004 removes an account from the board by cascade, without a separate
1396
erasure path.
1397
1398
PostgreSQL stays authoritative. The board is computed once per interval by a
1399
single process and pushed to local subscribers, because a public surface has
1400
unbounded anonymous viewers and a per-socket reread would turn one busy voice
1401
call into a database amplifier. A lost cache costs a recompute, never data.
1402
1403
Evidence: `OpenAgents.Leaderboard`, `OpenAgents.Leaderboard.Entry`,
1404
`OpenAgents.Leaderboard.Server`, `docs/LEADERBOARD.md`, `OpenAgents.LeaderboardTest`,
1405
and `OpenAgentsWeb.LeaderboardLiveTest`.
1406
1407
### OBSERVABILITY-001 — Telemetry is bounded, content-free, and never authoritative
1408
1409
Immutable domain receipts remain the authority; operational telemetry is only a
1410
lossy health projection, and versioned evaluation reports remain separate
1411
release evidence. OpenAgents telemetry accepts only finite plane/status/surface
1412
vocabularies, bounded public identifiers, counts, and durations. Message,
1413
transcript, prompt, instruction, memory, argument, result, payload, person, and
1414
secret values are refused and never become labels, even as hashes.
1415
1416
Release read-back recomputes aggregate plane states and zero-tolerance leakage,
1417
consent, provenance, executor-disclosure, and attribution-reconciliation checks
1418
from authoritative PostgreSQL records without selecting private content. A
1419
metric cannot prove an answer or target-system effect. Nonzero zero-tolerance
1420
checks block release; stuck work is an explicit warning requiring review.
1421
1422
Evidence: `OpenAgents.Observability`, `OpenAgents.Observability.Readback`,
1423
`OpenAgents.Observability.ReleaseGate`, `OpenAgentsWeb.Telemetry`,
1424
`docs/OBSERVABILITY.md`, and `OpenAgents.ObservabilityTest`.
1425
1426
### RELEASE-001 — Schema precedes traffic
1427
1428
The production image runs all pending Ecto migrations before starting the HTTP
1429
server. Health is successful only when PostgreSQL answers.
1430
1431
Evidence: Docker `CMD`, `OpenAgents.Release`, the `/status` route, and
1432
`HealthControllerTest`.
1433
1434
### RELEASE-002 — Secrets remain runtime-only
1435
1436
Session, database, provider, and GitHub OAuth credentials enter through ignored
1437
local runtime configuration or Secret Manager and are absent from source, the
1438
Docker build context, and image build arguments. Staging mounts only staging
1439
GitHub secret names through a dedicated runtime identity; production values and
1440
its prepared identity are distinct and remain unmounted until production
1441
cutover. Missing or environment-mismatched GitHub configuration fails startup
1442
without printing any credential value. Reserved GitHub token-encryption keys
1443
are not mounted while the runtime discards tokens after identity projection. The Cloud Logging
1444
default sink excludes only OpenAgents OAuth callback request entries so the platform
1445
cannot persist authorization-code or state query values; application and audit
1446
logging remain enabled.
1447
1448
Evidence: `OpenAgents.GitHubOAuth.RuntimeConfig`,
1449
`OpenAgents.GitHubOAuth.RuntimeConfigTest`, `config/runtime.exs`, `.gitignore`,
1450
`.dockerignore`, the `openagents-oauth-callback-requests` logging exclusion, and
1451
`docs/DEPLOY.md`.
1452
1453
### RELEASE-003 — Every published hostname can establish LiveView
1454
1455
Production accepts the primary `PHX_HOST` plus explicitly configured HTTPS
1456
aliases for Phoenix origin checks. Invalid, insecure, or path-bearing origins
1457
fail startup rather than silently weakening socket validation.
1458
1459
Evidence: `OpenAgentsWeb.AllowedOrigins`, `OpenAgentsWeb.AllowedOriginsTest`, and the
1460
production WebSocket read-back.
1461
1462
### RELEASE-004 — CI runs on owned infrastructure only, and gates every release
1463
1464
No hosted CI, ever: no GitHub Actions workflows (`.github/workflows/`), no
1465
GitHub-hosted or third-party runners, no repo automation, secrets, or
1466
scheduling handed to external CI compute (owner restatement 2026-07-25; same
1467
invariant as `openagents/INVARIANTS.md` "No GitHub-Hosted CI / Cloud Actions"
1468
and `AGENTS.md` "No hosted CI"). All checks run on owned machines: manually
1469
(`mix precommit`, `ops/ci/gate.sh`), through standard git hooks
1470
(`.githooks/pre-push`), or inside owned deploy tooling. The full matrix — unit
1471
suite, distributed cluster-chaos suite, relup drill, version-chain drill — must
1472
PASS for the exact commit being shipped before a fleet release;
1473
`ops/fleet/rolling-deploy.sh` refuses to roll without that receipt.
1474
1475
Evidence: `ops/ci/gate.sh`, `.githooks/pre-push`, the receipt check in
1476
`ops/fleet/rolling-deploy.sh`, and the absence of `.github/workflows/`.
1477
1478
### STATUS-001 — The status page publishes one bounded, content-free projection
1479
1480
The public `/status` page and `/api/status` publish exactly one projection
1481
(`OpenAgents.NetworkStatus`, schema-versioned): cluster membership and quorum,
1482
Raft membership, per-node release/hot-load versions, uptimes, and counts.
1483
Counts only, never content — no machine names, job goals or ids,
1484
conversation data, provider identifiers, or internal node names/addresses
1485
(nodes render as stable positional labels). It shares the leaderboard's
1486
UI-001 posture (read-only, cannot mount or invoke OpenAgents) and renders through
1487
the sanctioned component library (UI-003).
1488
1489
The page must render DURING incidents: nothing in the projection may require
1490
quorum, the database, or a full fleet — every gathered field degrades
1491
independently (an unreachable node reports as unreachable; a failed count is
1492
absent), and the per-node fan-out is time-bounded and briefly cached so page
1493
traffic cannot become an rpc storm. Legacy JSON pollers of `/status` keep the
1494
old health payload via content negotiation until they migrate to `/healthz`
1495
or `/api/status`.
1496
1497
Evidence: `OpenAgents.NetworkStatus`, `OpenAgentsWeb.NetworkStatusLive`,
1498
`OpenAgentsWeb.Plugs.StatusProbeCompat`, `OpenAgents.NetworkStatusTest`, and
1499
`OpenAgentsWeb.NetworkStatusLiveTest`.
1500
1501
### TRANSPARENCY-001 — Public transparency surfaces publish per-repo leveled projections
1502
1503
The public transparency surfaces — `/changelog`, `/api/changelog`, and the
1504
forge web UI (`/<owner>/<repo>`, `/<owner>/<repo>/commit/:sha`,
1505
`/<owner>/<repo>/blob/:ref/*path` — addressed exactly like the GitHub URLs
1506
they replace, with the owning account as a **literal** route scope rather
1507
than a wildcard first segment, so no other path on the domain is shadowed)
1508
— publish bounded projections of the forge
1509
receipt chain and repository content at an explicit per-repo disclosure
1510
level (`OpenAgents.Forge.Visibility`: `:l0` dark → `:l1` pulse → `:l2` ledger →
1511
`:l3` glass). The level map is operator-owned configuration
1512
(`:forge_public_visibility`), never derived from request data; a repo
1513
without a configured level is `:l0` and its surfaces 404, indistinguishable
1514
from a repo that does not exist. Ledger surfaces (`:l2`) may publish shas,
1515
summaries, changed-file paths, module and node counts, timings, deploy
1516
results, WAL sequence numbers, and principal *roles*; browsable source and
1517
diff bodies require `:l3`.
1518
1519
**A private repository publishes documents, not history.** `openagents` is
1520
private and runs at `:l2`. Below `:l3`, the blob view serves only paths on
1521
the operator-owned published allowlist (`:forge_public_paths`), and only at
1522
the current default-branch head: an allowlisted path at an arbitrary ref is
1523
a 404, because publishing one document must never become a window into
1524
every past revision of that file, or into the repository's history. Adding
1525
a path to that allowlist is a deliberate publication decision, and
1526
operator documentation (runtime configuration, deployment mechanics,
1527
operator identifiers) stays off it — `docs/OPERATIONS.md` is never
1528
published.
1529
1530
Bounds that hold at every level: no secrets or credentials beyond what the
1531
repository content itself carries (RELEASE-002 keeps secrets out of the
1532
repo), no cross-user conversation or memory content, no operator identity
1533
(role prefixes only — `OpenAgents.Changelog` and the commit view publish the
1534
principal's kind, never its id), no internal node names, and every git read
1535
and receipt scan is size- and count-bounded with honest truncation markers
1536
(`OpenAgents.Forge.Browse` caps blobs, diffs, messages, and listings). An entry
1537
whose `visibility` is `l1` renders without its sha or links until its
1538
`disclosure_after` passes — the security-embargo lane — and is shown, never
1539
silently omitted.
1540
1541
Projections are derived, append-only, and never authority: `changelog_entries`
1542
rows join to receipts but the pushed commit, the WAL, and the receipt rows
1543
remain the only truth about what shipped (A7); deleting or down-leveling a
1544
public entry never alters them. The pages share the leaderboard's UI-001
1545
posture (read-only, mount without a session, cannot mount or invoke OpenAgents),
1546
render only through the sanctioned component library (UI-003), and the
1547
timeline is briefly cached so anonymous traffic can never become a query
1548
storm (LEADERBOARD-001's amplifier rule). STATUS-001 is unchanged: `/status`
1549
stays content-free; content publication happens only on these surfaces and
1550
only per the repo's configured level.
1551
1552
Evidence: `OpenAgents.Forge.Visibility`, `OpenAgents.Forge.Browse`, `OpenAgents.Changelog`,
1553
`OpenAgents.Changelog.Entry`, `OpenAgentsWeb.ChangelogLive`, `OpenAgentsWeb.CodeRepoLive`,
1554
`OpenAgentsWeb.CodeCommitLive`, `OpenAgentsWeb.CodeBlobLive`,
1555
`OpenAgentsWeb.ChangelogController`, and their tests.

This page updates live while a promote is in flight · changelog