1// Package gnomem is a shared, contestable memory for multiple agents.2//3// Most "agent memory" products store text and retrieve similar chunks.4// That works for one agent talking to itself. It breaks the moment several5// independent agents — possibly run by different people, on different6// models — must maintain a *shared* understanding of the same world and7// sometimes disagree about it.8//9// gnomem models memory not as text but as a graph of structured claims.10// Each claim is a (subject, predicate, object) triple with an author,11// evidence and a confidence. Other agents can support it, contest it, add12// evidence, supersede it with a better claim, or trigger adjudication. No13// agent can silently erase an inconvenient finding: claims can be14// superseded or retracted, but the history stays visible and ordered.15//16// The point of the demo is the structure, not access control: anyone may17// write. Gating *who* may write to which claim is exactly what the18// capability-wallet demo adds on top.19package gnomem2021import (22 "chain"23 "chain/runtime"24 "strings"2526 "gno.land/p/nt/ufmt/v0"27)2829// Status is a claim's position in the argument lifecycle.30type Status string3132const (33 StatusProposed Status = "proposed" // asserted, no endorsements yet34 StatusSupported Status = "supported" // has support, no live contest35 StatusContested Status = "contested" // at least one live contest36 StatusAccepted Status = "accepted" // adjudicated true (terminal)37 StatusRetracted Status = "retracted" // adjudicated false / withdrawn (terminal)38 StatusSuperseded Status = "superseded" // replaced by a better claim (terminal)39)4041// Endorsement is a support or contest signal from one agent.42type Endorsement struct {43 Agent address44 Confidence uint8 // 0..10045 Note string46 Height int6447}4849// Evidence points at off-chain material backing (or refuting) a claim.50type Evidence struct {51 By address52 Kind string // "report", "exploit", "dataset", "citation", ...53 Hash string // commitment to the off-chain artifact54 Height int6455}5657// Claim is one node in the shared memory graph.58type Claim struct {59 ID uint6460 Subject string61 Predicate string62 Object string63 Author address64 Confidence uint865 Status Status66 CreatedAt int6467 Supports []Endorsement68 Contests []Endorsement69 Evidence []Evidence70 SupersededBy uint64 // 0 if none71 Supersedes uint64 // 0 if none72 Resolver address73 ResolvedAt int6474}7576var claims []*Claim // index 0 => ID 17778// ProposeClaim asserts a new (subject, predicate, object) triple.79func ProposeClaim(cur realm, subject, predicate, object, evidenceHash string, confidence uint8) uint64 {80 assert(subject != "" && predicate != "" && object != "", "empty triple field")81 assertConfidence(confidence)8283 id := uint64(len(claims)) + 184 c := &Claim{85 ID: id,86 Subject: subject,87 Predicate: predicate,88 Object: object,89 Author: cur.Previous().Address(),90 Confidence: confidence,91 Status: StatusProposed,92 CreatedAt: runtime.ChainHeight(),93 }94 if evidenceHash != "" {95 c.Evidence = append(c.Evidence, Evidence{96 By: c.Author, Kind: "initial", Hash: evidenceHash, Height: c.CreatedAt,97 })98 }99 claims = append(claims, c)100 chain.Emit("ClaimProposed", "id", ufmt.Sprintf("%d", id), "subject", subject, "predicate", predicate)101 return id102}103104// SupportClaim endorses a claim. Support never overrides a live contest —105// a contested claim stays contested until adjudicated.106func SupportClaim(cur realm, id uint64, confidence uint8, note string) {107 c := mustOpen(id)108 assertConfidence(confidence)109 c.Supports = append(c.Supports, Endorsement{110 Agent: cur.Previous().Address(), Confidence: confidence, Note: note, Height: runtime.ChainHeight(),111 })112 if c.Status == StatusProposed {113 c.Status = StatusSupported114 }115 chain.Emit("ClaimSupported", "id", ufmt.Sprintf("%d", id))116}117118// ContestClaim challenges a claim, optionally attaching refuting evidence.119func ContestClaim(cur realm, id uint64, note, evidenceHash string) {120 c := mustOpen(id)121 agent := cur.Previous().Address()122 h := runtime.ChainHeight()123 c.Contests = append(c.Contests, Endorsement{124 Agent: agent, Confidence: 0, Note: note, Height: h,125 })126 if evidenceHash != "" {127 c.Evidence = append(c.Evidence, Evidence{By: agent, Kind: "refutation", Hash: evidenceHash, Height: h})128 }129 c.Status = StatusContested130 chain.Emit("ClaimContested", "id", ufmt.Sprintf("%d", id))131}132133// AddEvidence attaches supporting or contextual off-chain material.134func AddEvidence(cur realm, id uint64, kind, hash string) {135 c := mustOpen(id)136 assert(hash != "", "empty evidence hash")137 c.Evidence = append(c.Evidence, Evidence{138 By: cur.Previous().Address(), Kind: kind, Hash: hash, Height: runtime.ChainHeight(),139 })140}141142// SupersedeClaim replaces an open claim with a new one. The old claim is143// marked superseded (terminal) and linked to its replacement, so the144// correction is visible rather than a silent overwrite.145func SupersedeClaim(cur realm, oldID uint64, subject, predicate, object, evidenceHash string, confidence uint8) uint64 {146 old := mustOpen(oldID)147 newID := ProposeClaim(cur, subject, predicate, object, evidenceHash, confidence)148 newC := claims[newID-1]149 newC.Supersedes = oldID150 old.Status = StatusSuperseded151 old.SupersededBy = newID152 chain.Emit("ClaimSuperseded", "old", ufmt.Sprintf("%d", oldID), "new", ufmt.Sprintf("%d", newID))153 return newID154}155156// ResolveClaim adjudicates an open claim as accepted (true) or retracted157// (false/withdrawn). The resolver's address is recorded — resolution is158// itself a provenance-bearing act, not an anonymous verdict.159func ResolveClaim(cur realm, id uint64, accepted bool) {160 c := mustOpen(id)161 if accepted {162 c.Status = StatusAccepted163 } else {164 c.Status = StatusRetracted165 }166 c.Resolver = cur.Previous().Address()167 c.ResolvedAt = runtime.ChainHeight()168 chain.Emit("ClaimResolved", "id", ufmt.Sprintf("%d", id), "status", string(c.Status))169}170171// ---- read-only API ----172173// Get returns a copy of a claim by id.174func Get(id uint64) Claim { return *mustGet(id) }175176// Count returns the number of claims in the graph.177func Count() int { return len(claims) }178179// IsOpen reports whether a claim can still be supported/contested/resolved.180func IsOpen(id uint64) bool { return isOpen(mustGet(id).Status) }181182// ---- internal ----183184func isOpen(s Status) bool {185 return s == StatusProposed || s == StatusSupported || s == StatusContested186}187188func mustGet(id uint64) *Claim {189 assert(id >= 1 && id <= uint64(len(claims)), "unknown claim")190 return claims[id-1]191}192193func mustOpen(id uint64) *Claim {194 c := mustGet(id)195 assert(isOpen(c.Status), "claim is resolved (terminal): "+string(c.Status))196 return c197}198199func assertConfidence(c uint8) { assert(c <= 100, "confidence must be 0..100") }200201func assert(cond bool, msg string) {202 if !cond {203 panic(msg)204 }205}206207// Render shows the full graph, or one claim's argument tree at :<id>.208func Render(path string) string {209 if path == "" {210 return renderIndex()211 }212 return renderClaim(parseID(path))213}214215func renderIndex() string {216 var sb strings.Builder217 sb.WriteString("# GnoMem — Contested Shared Memory\n\n")218 sb.WriteString(ufmt.Sprintf("A graph of %d structured claim(s) maintained by multiple agents.\n\n", len(claims)))219 if len(claims) == 0 {220 sb.WriteString("_No claims yet._\n")221 return sb.String()222 }223 sb.WriteString("| # | Claim | Status | ✋ support | ⚔ contest |\n")224 sb.WriteString("|---|---|---|---|---|\n")225 for _, c := range claims {226 sb.WriteString(ufmt.Sprintf("| [%d](/r/moul/agents/gnomem/v0:%d) | %s | %s | %d | %d |\n",227 c.ID, c.ID, triple(c), statusBadge(c.Status), len(c.Supports), len(c.Contests)))228 }229 return sb.String()230}231232func renderClaim(id uint64) string {233 c := mustGet(id)234 var sb strings.Builder235 sb.WriteString(ufmt.Sprintf("# Claim #%d\n\n", c.ID))236 sb.WriteString(ufmt.Sprintf("> **%s**\n\n", triple(c)))237 sb.WriteString(ufmt.Sprintf("- **Status:** %s\n", statusBadge(c.Status)))238 sb.WriteString(ufmt.Sprintf("- **Author:** %s\n", short(c.Author)))239 sb.WriteString(ufmt.Sprintf("- **Author confidence:** %d/100\n", c.Confidence))240 sb.WriteString(ufmt.Sprintf("- **Created at height:** %d\n", c.CreatedAt))241 if c.Supersedes != 0 {242 sb.WriteString(ufmt.Sprintf("- **Supersedes:** [#%d](/r/moul/agents/gnomem/v0:%d)\n", c.Supersedes, c.Supersedes))243 }244 if c.SupersededBy != 0 {245 sb.WriteString(ufmt.Sprintf("- **Superseded by:** [#%d](/r/moul/agents/gnomem/v0:%d)\n", c.SupersededBy, c.SupersededBy))246 }247 if c.ResolvedAt != 0 {248 sb.WriteString(ufmt.Sprintf("- **Resolved by:** %s at height %d\n", short(c.Resolver), c.ResolvedAt))249 }250 sb.WriteString("\n")251252 sb.WriteString(ufmt.Sprintf("## ✋ Support (%d)\n\n", len(c.Supports)))253 renderEndorsements(&sb, c.Supports)254 sb.WriteString(ufmt.Sprintf("## ⚔ Contest (%d)\n\n", len(c.Contests)))255 renderEndorsements(&sb, c.Contests)256257 sb.WriteString(ufmt.Sprintf("## 📎 Evidence (%d)\n\n", len(c.Evidence)))258 if len(c.Evidence) == 0 {259 sb.WriteString("_None._\n")260 } else {261 sb.WriteString("| By | Kind | Commitment | Height |\n|---|---|---|---|\n")262 for _, e := range c.Evidence {263 sb.WriteString(ufmt.Sprintf("| %s | %s | `%s` | %d |\n", short(e.By), e.Kind, shortHash(e.Hash), e.Height))264 }265 }266 return sb.String()267}268269func renderEndorsements(sb *strings.Builder, es []Endorsement) {270 if len(es) == 0 {271 sb.WriteString("_None._\n\n")272 return273 }274 sb.WriteString("| Agent | Confidence | Note | Height |\n|---|---|---|---|\n")275 for _, e := range es {276 sb.WriteString(ufmt.Sprintf("| %s | %d | %s | %d |\n", short(e.Agent), e.Confidence, e.Note, e.Height))277 }278 sb.WriteString("\n")279}280281func triple(c *Claim) string {282 return ufmt.Sprintf("%s — %s — %s", c.Subject, c.Predicate, c.Object)283}284285func statusBadge(s Status) string {286 switch s {287 case StatusAccepted:288 return "✅ accepted"289 case StatusRetracted:290 return "🚫 retracted"291 case StatusSuperseded:292 return "♻️ superseded"293 case StatusContested:294 return "⚔ contested"295 case StatusSupported:296 return "✋ supported"297 default:298 return "· proposed"299 }300}301302func parseID(s string) uint64 {303 var n uint64304 for i := 0; i < len(s); i++ {305 c := s[i]306 assert(c >= '0' && c <= '9', "invalid claim path: "+s)307 n = n*10 + uint64(c-'0')308 }309 return n310}311312func short(a address) string {313 s := a.String()314 if len(s) <= 12 {315 return s316 }317 return s[:8] + "…" + s[len(s)-4:]318}319320func shortHash(s string) string {321 if len(s) <= 12 {322 return s323 }324 return s[:6] + "…" + s[len(s)-4:]325}326AddEvidence(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id uint64, kind string, hash string)
ContestClaim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id uint64, note string, evidenceHash string)
Count() int
Get(id uint64) struct{ID uint64; Subject string; Predicate string; Object string; Author .uverse.address; Confidence uint8; Status gno.land/r/moul/agents/gnomem/v0.Status; CreatedAt int64; Supports []gno.land/r/moul/agents/gnomem/v0.Endorsement; Contests []gno.land/r/moul/agents/gnomem/v0.Endorsement; Evidence []gno.land/r/moul/agents/gnomem/v0.Evidence; SupersededBy uint64; Supersedes uint64; Resolver .uverse.address; ResolvedAt int64}
IsOpen(id uint64) bool
ProposeClaim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, subject string, predicate string, object string, evidenceHash string, confidence uint8) uint64
Render(path string) string
ResolveClaim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id uint64, accepted bool)
SupersedeClaim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, oldID uint64, subject string, predicate string, object string, evidenceHash string, confidence uint8) uint64
SupportClaim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id uint64, confidence uint8, note string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.