Revision Operations
Membrane provides five revision operations for modifying semantic, competence, and plan graph records. Each operation is:
- Atomic — partial revisions are never externally visible (RFC 15.7).
- Auditable — every change is recorded in the record's
audit_log. - Provenance-tracked — new records include source references to the records they replace or derive from.
Episodic records are immutable and cannot be revised. Attempting to revise an episodic record returns ErrEpisodicImmutable.
RevisionStatus
Semantic records track their current state via RevisionState.Status:
| Status | Value | Meaning |
|---|---|---|
| Active | active | Record is currently valid |
| Contested | contested | Conflicting evidence exists; status is uncertain |
| Retracted | retracted | Record has been withdrawn |
AuditAction values
Every change appends an AuditEntry to the record's audit_log:
| Action | Value | When recorded |
|---|---|---|
| Create | create | New record is created |
| Revise | revise | Record is superseded or contested |
| Fork | fork | Record is forked into a conditional variant |
| Merge | merge | Record is merged into a consolidated record |
| Delete | delete | Record is retracted or auto-pruned |
| Reinforce | reinforce | Record's salience is boosted |
| Decay | decay | Record's salience is decayed or penalized |
type AuditEntry struct {
Action AuditAction // type of action
Actor string // who or what performed the action
Timestamp time.Time
Rationale string // why the action was taken
}
Supersede
Atomically replaces an old record with a new version. The old record is retracted (salience set to 0, semantic status set to retracted). The new record is linked to the old via a supersedes relation and provenance source.
// From README.md
superseded, _ := m.Supersede(ctx, oldRecordID, newRec, "agent", "Go version updated")
What happens internally:
- Old record is fetched and checked as revisable.
- Old record's salience is set to 0; semantic status set to
retracted;SupersededByset to the new record's ID. - New record gets a
supersedesrelation pointing to the old record ID. - New record's provenance includes the old record as a source.
- Audit entry (
revise) is added to the old record; audit entry (create) is added to the new record. - Both updates are committed in a single transaction.
// From pkg/revision/supersede.go
func (s *Service) Supersede(
ctx context.Context,
oldID string,
newRecord *schema.MemoryRecord,
actor, rationale string,
) (*schema.MemoryRecord, error)
The new record must include at least one evidence reference or provenance source if it is a semantic record.
Fork
Creates a new record derived from an existing source record. Unlike Supersede, both the source and the forked record remain active. Fork is used for conditional variants — facts that are true under specific conditions (ValidityModeConditional).
// From README.md
forked, _ := m.Fork(ctx, sourceID, conditionalRec, "agent", "different for dev environment")
What happens internally:
- Source record is fetched and checked as revisable.
- Forked record gets a
derived_fromrelation pointing to the source. - Audit entry (
fork) is added to the source record; audit entry (create) is added to the forked record. - Both records remain active with their original salience.
// From pkg/revision/fork.go
func (s *Service) Fork(
ctx context.Context,
sourceID string,
forkedRecord *schema.MemoryRecord,
actor, rationale string,
) (*schema.MemoryRecord, error)
A typical fork sets ValidityModeConditional on the new record:
forkedRec := &schema.MemoryRecord{
Type: schema.MemoryTypeSemantic,
Sensitivity: schema.SensitivityLow,
Payload: &schema.SemanticPayload{
Kind: "semantic",
Subject: "build",
Predicate: "uses_flags",
Object: "-race -cover",
Validity: schema.Validity{
Mode: schema.ValidityModeConditional,
Conditions: map[string]any{"environment": "dev"},
},
},
}
Contest
Marks a record as contested, indicating that conflicting evidence exists. The record remains visible but its semantic status is set to contested. A contested_by relation is added pointing to the conflicting record or evidence reference.
// From README.md
m.Contest(ctx, recordID, conflictingRecordID, "agent", "new evidence contradicts this")
What happens internally:
- Target record is fetched and checked as revisable.
- Semantic revision status is set to
contested. - A
contested_byrelation is added from the target tocontestingRef. - Audit entry (
revise) is added to the target record.
// From pkg/revision/contest.go
func (s *Service) Contest(
ctx context.Context,
id string,
contestingRef string,
actor, rationale string,
) error
Contested records are still returned by retrieval queries. Callers can inspect Revision.Status to determine whether a record is actively contested before acting on it.
Retract
Marks a record as no longer valid without deleting it. Salience is set to 0 and the semantic revision status is set to retracted. The record is preserved for auditability.
// From README.md
m.Retract(ctx, recordID, "agent", "no longer accurate")
What happens internally:
- Target record is fetched and checked as revisable.
- Salience is set to 0; semantic status set to
retracted. - Audit entry (
delete) is added to the target record.
// From pkg/revision/retract.go
func (s *Service) Retract(
ctx context.Context,
id, actor, rationale string,
) error
Retracted records are not deleted. They remain in the store with salience 0 and status: retracted. Their audit trail is preserved. Salience-filtered retrieval (MinSalience > 0) will exclude them from results.
Merge
Combines multiple source records into a single consolidated record. All source records are retracted, and the merged record receives derived_from relations to each source.
// From README.md
merged, _ := m.Merge(ctx, []string{id1, id2, id3}, mergedRec, "agent", "consolidating duplicates")
What happens internally:
- All source records are fetched and checked as revisable.
- Each source record's salience is set to 0; semantic status set to
retracted. - Merged record gets
derived_fromrelations to each source. - Audit entry (
merge) is added to each source; audit entry (create) is added to the merged record. - All changes are committed in a single transaction.
// From pkg/revision/merge.go
func (s *Service) Merge(
ctx context.Context,
recordIDs []string,
mergedRecord *schema.MemoryRecord,
actor, rationale string,
) (*schema.MemoryRecord, error)
Provenance tracking
Every revision operation updates the Provenance field of the new or modified record to link back to its source:
type Provenance struct {
Sources []ProvenanceSource // source events or artifacts that justify this record
CreatedBy string // classifier, policy, or consolidator version
}
type ProvenanceSource struct {
Kind ProvenanceKind // event | artifact | tool_call | observation | outcome
Ref string // opaque reference into the host system
Hash string // optional content hash for immutability verification
CreatedBy string // actor or system that created this source
Timestamp time.Time
}
Supersede and merge operations automatically append the old record's ID as a ProvenanceSource with Kind: event. This creates a complete chain from any current record back to its origins.
Operation summary
| Operation | Source fate | Target status | Relation created |
|---|---|---|---|
| Supersede | Retracted | active | supersedes (new → old) |
| Fork | Stays active | active | derived_from (fork → source) |
| Contest | Status → contested | — | contested_by (target → evidence) |
| Retract | Retracted | — | none |
| Merge | All retracted | active | derived_from (merged → each source) |