Documentation
¶
Overview ¶
Package memory is the agent-memory framework: the Memory interface (Put/Query/Search/SendSignal), the Kind registry, the Local facade over a pluggable Backend, generic Query and scope semantics, signal dispatch, and the provider-neutral transcript types that flow over the HTTP API. A session's transcript is the turn Kind (kinds/turn) over that same Backend; the Backend implementations live in inmem/, sqlite/, postgres/ and shadow/.
Index ¶
- Constants
- Variables
- func CallerFrom(ctx context.Context) (string, bool)
- func CanonicalContent(raw json.RawMessage) json.RawMessage
- func CanonicalTime(t time.Time) time.Time
- func ContentKeys(t reflect.Type) map[string]struct{}
- func EnsureApproval(ctx context.Context, perm Permission, resource string) error
- func InEntryAppendedDispatch(ctx context.Context) bool
- func IsResourceScope(s Scope) bool
- func KindAppendOnly(name string) bool
- func NewID(k Kind) string
- func ProbeLimit(limit int) int
- func RegisterKind(k Kind)
- func ResetRegistryForTest()
- func ResourceRef(s Scope) (objType, objID string, ok bool)
- func SafeFieldPath(path string) bool
- func SessionMayRead(kind string) bool
- func SystemContext(ctx context.Context, component string) context.Context
- func TrimProbe[T any](rows []T, limit int) ([]T, bool)
- func WithApproval(ctx context.Context, a ...Approval) context.Context
- func WithCaller(ctx context.Context, canonicalID string) context.Context
- func WithKGScope(ctx context.Context, s Scope) context.Context
- func WithKindReadDoor(ctx context.Context, who string) context.Context
- func WithSystemApproval(ctx context.Context, component string) context.Context
- func WithTokenSession(ctx context.Context, sess NamespacedName) context.Context
- func WithoutCaller(ctx context.Context) context.Context
- func WithoutTokenSession(ctx context.Context) context.Context
- type Approval
- type AttachmentBlock
- type Authorizer
- type Backend
- type Capabilities
- type ContentBlock
- type CrossScopeQuery
- type EmbeddingProvider
- type Entry
- type FieldFilter
- type FieldOp
- type KGCommunity
- type KGEntity
- type KGFact
- type KGInput
- type KGProvider
- type Kind
- type Link
- type LinkFilter
- type Local
- func (m *Local) Capabilities() Capabilities
- func (m *Local) Delete(ctx context.Context, scope Scope, kind, id string) error
- func (m *Local) DeleteScope(ctx context.Context, scope Scope) error
- func (m *Local) Get(ctx context.Context, scope Scope, kind, id string) (Entry, bool, error)
- func (m *Local) Put(ctx context.Context, e Entry) (Entry, error)
- func (m *Local) Query(ctx context.Context, q Query) (QueryResult, error)
- func (m *Local) QueryAllScopes(ctx context.Context, q CrossScopeQuery) (QueryResult, error)
- func (m *Local) Reindex(ctx context.Context, scope Scope) (int, error)
- func (m *Local) Search(ctx context.Context, req SearchRequest) (MergedSearchResult, error)
- func (m *Local) SendSignal(ctx context.Context, sig Signal) error
- func (m *Local) Status(ctx context.Context, scope Scope) (ScopeStatus, error)
- type LocalOption
- type Memory
- type MergedSearchResult
- type NamespacedName
- type OrderBy
- type Permission
- type PoolReader
- type PoolWriter
- type Provenance
- type ProvenanceVerifier
- type PtTagContent
- type PtTagMintRequest
- type PtTagMintResponse
- type PtTagRegion
- type PtTagResolveRequest
- type PtTagResolveResponse
- type PtTagResourceRef
- type PtTagVerifyRequest
- type PtTagVerifyResponse
- type Query
- type QueryResult
- type Ranker
- type Retention
- type Scope
- type ScopeHooks
- type ScopeStatus
- type ScoredEntry
- type SearchCapabilities
- type SearchProvider
- type SearchRequest
- type SearchResult
- type Searcher
- type SessionReadabilityDecider
- type Signal
- type SignalKind
- type StatusState
- type ToolResultBlock
- type ToolUseBlock
- type Turn
- type Usage
- type WriteAuthority
Constants ¶
const PermissionViewMemory = "view_memory"
PermissionViewMemory is the permission that names a resource pool's AUDIENCE: whoever holds it on the resource may see what the pool holds.
It is what a read of pool entries is expanded under — the per-datum tag for a pool's entries is minted against `<type>:<id>#view_memory` — and it is deliberately NOT the permission the session reached the pool through. A session may hold a slot grant of `(customer, read)`; that says the agent may work with the resource, and says nothing about who may see the resource's memory. Minting under the slot's permission would give the datum whatever audience `read` happens to expand to, which nothing downstream would notice.
Named here, beside ScopeKindResource, because the pool scope and the pool audience are one design: a scope of this Kind is exactly the set of entries this permission governs.
const ScopeKindResource = "resource"
ScopeKindResource is the Scope.Kind for memory that lives on a RESOURCE rather than a session. Its ID is a SpiceDB object reference, "<type>:<id>".
Type-qualified rather than a bare id for two reasons. Audience derivation needs the type to know which definition's view_memory to expand; and a bare id collides across types the moment two resource types share one.
const ScopeKindUser = "user"
ScopeKindUser is the per-user scope kind (see the Scope doc in entry.go — reserved there since the framework's inception, first used by the user_preference kind).
Variables ¶
var ( // ErrAppendOnlyConflict: Put attempted to change an existing entry // of an append-only Kind. Byte-identical re-puts are not conflicts. ErrAppendOnlyConflict = errors.New("append-only kind: entry exists with different content") // ErrAppendOnlyKind: per-entry Delete attempted on an append-only // Kind (scope-level retention deletion is unaffected). ErrAppendOnlyKind = errors.New("append-only kind: entries cannot be deleted") // ErrProvenanceRequired: a write to an append-only Kind lacked a Provenance // envelope. Raised by the operator's verify-on-write; httpsrv answers 403. ErrProvenanceRequired = errors.New("append-only kind: signed provenance required") // ErrBadProvenance: the Provenance envelope failed verification. ErrBadProvenance = errors.New("append-only kind: provenance verification failed") // ErrInvalidEntry: Put refused the Entry on its own merits — an unregistered // Kind, or an ID / link ID not carrying its Kind's prefix. It separates a // CALLER's bug, which no retry can fix, from a store or authorization fault, // which a retry usually does. // // Load-bearing across the wire: httpsrv maps it to 400 and everything // unrecognized to 500, and httpclient retries 5xx while treating 4xx as // permanent — so the sentinel is what keeps a transient store fault from // reaching the caller as a permanent bad request nobody retries. Wrap every // new validation refusal in Put with it; never wrap a fault. ErrInvalidEntry = errors.New("memory: invalid entry") // ErrInvalidQuery is ErrInvalidEntry's read-side twin: the REQUEST is wrong // on its own merits, so no retry can make it succeed — a Query whose // FieldEquals.Path names no content key of any Kind it queries (see // validateFieldPaths), or a knowledge-graph read whose entity UUID is not // UUID-shaped. // // Same wire weight as ErrInvalidEntry: httpsrv answers 400 and httpclient // treats 4xx as permanent. Without it the agent's query_memory tool — whose // Kinds and FieldEquals come from LLM-supplied arguments — spent eight // retries and ~17s of a turn on a guessed field name before the "did you // mean" surfaced. Wrap every read-side refusal that blames the caller; // never wrap a store fault. ErrInvalidQuery = errors.New("memory: invalid query") // ErrKGScopeMismatch: a scoped knowledge-graph read named an entity that // belongs to another session's graph. A policy refusal, not a fault — // permanent, so httpsrv answers 403 rather than letting the caller retry // it eight times. The refusal text deliberately says nothing about which // group the entity IS in; see graphiti.GetEntity. ErrKGScopeMismatch = errors.New("memory: entity is not in this session's graph") // ErrKGUnsupported: a knowledge-graph read this deployment's provider // cannot answer at all — Graphiti's self-hosted REST server (graph_service) // has no endpoint that returns entities or communities, only facts, and one // read (GetEntity) cannot even be scope-checked because its response never // carries a group id. Not a fault and not the caller's error — a retry // cannot install an endpoint that does not exist, so httpsrv answers 404 // like ErrNoSearchProviders rather than a retry loop. A richer KGProvider // (Graphiti's separate MCP server, or a future backend) might answer the // same call; this deployment's cannot. ErrKGUnsupported = errors.New("memory: knowledge-graph capability not supported by this deployment") // ErrKindNotSessionWritable: a per-session credential tried to author (Put // or Delete) a Kind whose WriteAuthority is ComponentWritten. A policy // refusal, not a fault — permanent, so httpsrv answers 403 and httpclient // does not retry it. // // The Kind-level half of the write door. The scope-level half // (ErrMissingApproval) answers only "may this credential write into this // session?", which for a runner's own session is always yes — so without // this half a session bearer could author every registered Kind in its // scope, including the component-owned records authzd and the operator read // to make authorization decisions. ErrKindNotSessionWritable = errors.New("memory: kind is not session-writable") // ErrKindNotSessionReadable: a per-session credential NAMED a Kind it may // not read. The mirror of ErrKindNotSessionWritable, and equally a policy // refusal rather than a fault — permanent, so httpsrv answers 403 and // httpclient does not retry it. // // It exists because some Kinds hold data the platform keeps ON BEHALF of a // session that the session's own agent must never see — redacted content // being the first. Returning an empty result instead would be worse than // refusing: the caller would read absence as "there is nothing there" and // carry on believing it had looked. ErrKindNotSessionReadable = errors.New("memory: kind is not session-readable") )
var ErrIndexConflict = errors.New("memory: index conflict")
ErrIndexConflict signals that a turn Append's (index, role) collides with a previously-stored turn whose payload differs. Surfaced by turn.Appender.
var ErrMissingApproval = errors.New("memory: missing capability approval")
ErrMissingApproval is returned by EnsureApproval when the context carries no approval authorizing the requested (permission, resource). It is a sentinel so callers can map it to the right surface (HTTP 403, a denied CLI message).
var ErrNoSearchProviders = errors.New("memory: no search providers configured")
Functions ¶
func CallerFrom ¶
CallerFrom reports the user id and whether one is set. Unlike TokenSessionFrom, the bool folds emptiness in: an empty caller is genuinely "no user", the normal state for a runner or component write.
func CanonicalContent ¶
func CanonicalContent(raw json.RawMessage) json.RawMessage
CanonicalContent normalizes JSON content so equality is invariant under a JSONB round trip: the trip through interface{} sorts object keys and normalizes numbers, and json.Marshal re-emits without whitespace. Applied to BOTH sides of a comparison, all it asks of the storage layer is semantic JSON equality — exactly what JSONB preserves.
Invalid JSON falls back to the raw bytes, so a malformed payload still compares byte-for-byte rather than collapsing to equal-with-everything.
func CanonicalTime ¶
CanonicalTime truncates t to the finest precision every backend can store. Postgres TIMESTAMPTZ holds microseconds and pgx truncates on the way in, so the nanoseconds a time.Now() CreatedAt carries do not survive the trip and must not participate in a sameness comparison. Truncation (not rounding) and UTC, to agree exactly with what the encoder did to the stored value.
func ContentKeys ¶
ContentKeys returns the top-level JSON object keys a Kind's Content struct serializes to — the vocabulary FieldFilter.Path draws from. `json:"-"` fields are omitted; an untagged exported field contributes its Go name, matching what encoding/json emits. An embedded struct with no JSON name is flattened as encoding/json flattens it; one with a name contributes only that name, since it serializes as a nested object.
nil for a nil or non-struct type, meaning "no vocabulary is knowable" — callers must NOT read it as "this Kind has no keys".
func EnsureApproval ¶
func EnsureApproval(ctx context.Context, perm Permission, resource string) error
EnsureApproval returns nil iff ctx carries an approval authorizing (perm, resource); otherwise an error wrapping ErrMissingApproval that names the missing perm and resource. Rules differ by tier:
- INTERNAL-tier perms (internal: prefix, e.g. AppendAudit) are satisfied ONLY by an internal-source approval matching (perm, resource) exactly. The unexported mintInternal is the sole producer of one, so a bearer or system approval — even one naming an internal: perm via the exported ForBearerToken/ForSpiceDBCheck — can never satisfy it. That is what makes "only pkg/memory can authorize an append-only write" unforgeable rather than conventional.
- NON-internal perms are satisfied by a system approval (any resource), or by an exact (perm, resource) match from any source.
func InEntryAppendedDispatch ¶
InEntryAppendedDispatch reports whether ctx already carries an in-flight SignalEntryAppended dispatch. Exported (the mark itself stays unforgeable — only withEntryAppendedDispatch, unexported, can set it) because pkg/memory/provenance's SigningMemory needs to observe it too: SigningMemory holds a per-scope mutex across its own call into Put (Sign and the durable write must commit as one unit), and a ScopeHooks reaction reachable from inside that same call — via this dispatch — that Puts into the SAME scope through the SAME SigningMemory would re-enter that mutex on the same goroutine and deadlock, not merely recurse. Local.Put's own append-only write still happens once for a direct (unwrapped) caller — this flag only suppresses the SIGNAL a Put would otherwise emit, and lets a SigningMemory-wrapped caller suppress the WRITE itself, one layer up, where the deadlock would otherwise occur.
func IsResourceScope ¶
IsResourceScope reports whether s addresses a resource pool.
func KindAppendOnly ¶
KindAppendOnly reports whether the named registered Kind is append-only. Unknown kinds read as mutable: append-only enforcement only ever tightens behavior for kinds that explicitly asked for it.
func NewID ¶
NewID generates a unique ID for an Entry of Kind k: k.IDPrefix() plus 8 random bytes hex-encoded, so it satisfies Put's prefix validation by construction.
func ProbeLimit ¶
ProbeLimit returns the row count to request from a store so a truncated answer stays distinguishable from a complete one: one more row than the caller asked for. The extra row is never rendered — only the fact that it existed is (see TrimProbe).
An unlimited read (limit <= 0) needs no probe: nothing is discarded, so nothing can be hidden. A limit already at MaxInt is returned unchanged rather than wrapped negative, which a store would read as "unlimited" and answer with the whole scope.
func RegisterKind ¶
func RegisterKind(k Kind)
RegisterKind adds k to the global Kind registry, called from each Kind package's init() so collisions panic at startup rather than at first Put. Panics on:
- empty Name() or empty IDPrefix();
- a Name() starting with "_" (reserved for HTTP scope routes like "_query");
- duplicate Name();
- exact IDPrefix() collision with another Kind;
- any substring-prefix overlap with another Kind's prefix, so "op-" cannot coexist with "ops-" — an ID would otherwise validate under both.
func ResetRegistryForTest ¶
func ResetRegistryForTest()
ResetRegistryForTest clears the registry. Tests only.
func ResourceRef ¶
ResourceRef splits a resource scope back into its SpiceDB object ref. ok is true only for an ID that ResourceScope could have produced — a resource Kind whose ID is anything else (empty halves, a second ':', an embedded '/') is refused rather than half-parsed, since a caller that hand-built a Scope must not be able to smuggle a '/' into objType past the collision guard ResourceScope enforces.
func SafeFieldPath ¶
SafeFieldPath reports whether path is safe to interpolate into a SQL expression as a JSON field accessor. A FieldFilter.Path reaches the SQL backends as an identifier, not a bind parameter, so this is the injection guard — never interpolate a path that fails it.
func SessionMayRead ¶
SessionMayRead reports whether a session credential may read this Kind.
Fail-closed on the two cases that are not a plain "yes": an unregistered Kind is unreadable, because "declared nothing" must not read as "anyone may read it" — the same reasoning authorizeKindWrite applies to writes.
func SystemContext ¶
SystemContext returns a child of ctx that acts as the named platform component: any per-session token identity and caller are cleared (via WithoutTokenSession / WithoutCaller) and a system approval is attached (via WithSystemApproval). For operator-internal reads/writes performed on behalf of the platform rather than the calling session — the user_preference kind's commit path is the first caller: it must author ComponentWritten entries in a user's scope without inheriting whatever session or caller mark the triggering request carried.
func TrimProbe ¶
TrimProbe drops the extra row ProbeLimit asked for and reports whether it was there — that is, whether limit cut the answer short. Rows at or under the limit come back untouched with truncated false, so a listing that merely filled its page is never reported as one that was cut.
It trims to limit even when a store over-answers the probe, so a caller can rely on the result honoring the limit it asked for whatever the store did.
func WithApproval ¶
WithApproval returns a context carrying the given approvals appended to any already present. A nil/empty call is a no-op.
func WithCaller ¶
WithCaller records the USER on whose behalf the request runs, as a canonical subject id. Absent for component and per-session credentials — see TokenSessionFrom for the credential's own identity, which is a different question.
func WithKGScope ¶
WithKGScope threads the session scope a knowledge-graph read must be filtered to. The operator's per-session /memory/_kg handler sets it; the platform-admin (admind) cross-session browser deliberately does NOT, so its reads stay unscoped (it is gated on platform#view_audit instead).
func WithKindReadDoor ¶
WithKindReadDoor marks ctx as a credential the per-kind read door governs, naming who for the refusal log.
It exists because the door was deciding "may this credential see this Kind" by asking a DIFFERENT question: whether the context carried a token SESSION. That mark is provenance — it binds an append-only entry's publisher to its writer — and the webd token, which acts on ANY session and belongs to none, never gets one. So the door skipped the one browser-facing component token entirely, and it could read pt_tag_content, and every other platform-only Kind, for any session in the cluster.
A caller is now TOLD it is behind the door rather than inferred to be. That is the property worth having: a component token added later inherits webd's exemption by default under the old rule, simply by not carrying an unrelated mark, and under this one it inherits nothing — someone has to decide.
TokenSessionFrom remains a SECOND trigger (see kindReadDoorSubject). Every per-session bearer is behind the door, and replacing the old trigger rather than adding to it would have reopened the door for every runner at once.
func WithSystemApproval ¶
WithSystemApproval is sugar for WithApproval(ctx, ForSystemCaller(component)).
func WithTokenSession ¶
func WithTokenSession(ctx context.Context, sess NamespacedName) context.Context
WithTokenSession marks ctx as originating from a PER-SESSION bearer token issued for sess. It answers a different question from WithCaller and the two must not be conflated:
- CallerFrom is the USER on whose behalf the request runs. It is what spicedbauthorizer writes as memory_entry#creator, and it is deliberately absent for non-user callers — the AgentSession reconciler registers every per-session memory token with an empty callerID, because a runner's write has no user creator to record.
- TokenSessionFrom is the CREDENTIAL's own identity: which session's token authenticated this request. It is present for exactly the per-session bearers and absent for everything else, independent of whether a user is attached.
Provenance verification needs the second: an append-only entry is attributed to a publisher (session:<ns>/<name>), and the question at the door is "is the writer the publisher it claims to be", not "which user asked". Reusing CallerID would force it non-empty for runners, silently writing memory_entry#creator tuples for a subject that is not a user.
func WithoutCaller ¶
WithoutCaller returns ctx with the caller mark cleared, so CallerFrom reports absent again. Mirrors WithoutTokenSession: because CallerFrom folds emptiness into its bool (see above), storing "" under the same key is sufficient — no change to CallerFrom's storage or contract is needed.
For the same authorship hand-offs WithoutTokenSession exists for: a path that stops acting on a user's behalf and starts writing as the platform itself (SystemContext) must drop the mark, or the write would still be attributed to that user.
func WithoutTokenSession ¶
WithoutTokenSession returns ctx with the token-session mark removed, so TokenSessionFrom reports absent again.
For authorship HAND-OFFS only: a path that stops acting on the token holder's behalf and starts writing as the process itself (Local's ScopeHooks fan-out) must drop the mark, or the provenance writer-binding demands the process's own entries be attributed to the token holder. NOT a way to opt out of the binding for a write the token holder still authored.
Types ¶
type Approval ¶
type Approval struct {
// contains filtered or unexported fields
}
Approval is an opaque witness that authorization for (perm, resource) was established. Its fields are unexported, so a constructor in this package is the ONLY way to obtain one: an outside package can neither forge a value nor write it into a context, since the context key is unexported too.
func ForBearerToken ¶
func ForBearerToken(perm Permission, resource, tokenID string) Approval
ForBearerToken mints an approval for a validated bearer token already confirmed to authorize `resource`. CONTRACT: call only after the token-scope check has passed.
func ForSpiceDBCheck ¶
func ForSpiceDBCheck(perm Permission, resource, subject string) Approval
ForSpiceDBCheck mints an approval established by a successful SpiceDB check. CONTRACT: call ONLY inside a Check == HAS_PERMISSION branch.
func ForSystemCaller ¶
ForSystemCaller mints a wildcard approval for a trusted in-process/system caller (operator controllers, ingestion hooks, the system bearer tokens). It satisfies any NON-internal permission for any resource, and can NEVER satisfy an internal-tier one — EnsureApproval enforces that.
type AttachmentBlock ¶
type AttachmentBlock struct {
// Filename is the sanitized, user-supplied name.
Filename string `json:"filename"`
// MIME is the kind-reported content type — a hint, not a verified fact.
MIME string `json:"mime"`
// SizeBytes is the kind-reported size. May be 0 when unreported.
SizeBytes int64 `json:"sizeBytes,omitempty"`
// Ref is the artifactstore handle for the original bytes.
Ref string `json:"ref"`
// TextRef is the artifactstore handle for the extracted text; EMPTY when no
// extractor exists for this MIME (images, today). Its presence is
// load-bearing: with TextRef empty the runner keeps a native block attached
// for the whole session, since nothing else represents the file; with it set
// the block is kept only on the arrival turn, because the text carries it
// after.
TextRef string `json:"textRef,omitempty"`
// ArchiveRef is the handle of the ARCHIVE this member came out of; empty
// for a file the user attached directly.
//
// Load-bearing at hydration: an archive member is excluded from the
// persistent native-block window unless pinned, because its archive's
// index already named it and stated its readability. Without this field a
// bundle of 187 log files would fill that window with four arbitrary
// members and name them in an unreadable-attachments note the agent was
// never told about.
ArchiveRef string `json:"archiveRef,omitempty"`
// Pages is the extractor-reported page/slide count; 0 when the format has no
// real notion of pages.
Pages int `json:"pages,omitempty"`
}
AttachmentBlock records a file a user attached to an inbound message that the pipeline actually fetched and, when a backend claimed its MIME, extracted. Ref/TextRef are artifactstore handles, not bytes — the same by-reference shape respond_to_user's `attached` field and fetch_artifact use. Filename is untrusted, listener-sanitized input: never treat it as more than a label.
type Authorizer ¶
type Authorizer interface {
// AuthorizePut runs AFTER the Local.Put door has already authorized the
// write, so it is NOT itself the write-permission gate. Its job is to WRITE
// the entry-level relationships (memory_entry#session, #creator, and
// caller-independent ones like artifact#parent) so future reads/deletes can
// be checked. A non-nil error — e.g. a failed relationship write — denies
// the write and reaches the caller as-is.
//
// With CallerFrom(ctx) absent (in-process controller calls),
// implementations SHOULD allow the write unconditionally.
AuthorizePut(ctx context.Context, e Entry) error
// AuthorizeQuery post-filters a backend result once the EnsureApproval door
// has authorized the scope as a whole, returning only the entries the
// caller (CallerFrom(ctx)) may read. Invoked only when a caller is present,
// so it is a finer per-entry layer on the door, not the read gate itself.
// Implementations bulk-check to minimize round-trips.
AuthorizeQuery(ctx context.Context, entries []Entry) ([]Entry, error)
// AuthorizeDelete runs before backend.Delete; a non-nil error denies the
// deletion. With CallerFrom(ctx) absent, implementations SHOULD allow.
AuthorizeDelete(ctx context.Context, scope Scope, kind, id string) error
// CleanupScope idempotently removes all entry-level authorization
// relationships for the scope.
CleanupScope(ctx context.Context, scope Scope) error
}
Authorizer gates memory operations behind an external authorization system. When set on the Local facade, every Put/Query/Delete passes through it.
It is NOT the front door: EnsureApproval at the top of each facade method is what enforces coarse read/write authorization, with or without an Authorizer configured. The Authorizer is the tuple-writer (AuthorizePut records entry-level relationships for future checks) plus an optional finer per-entry filter, reached only once the door has already let the call through.
Implementations MUST be safe for concurrent use.
type Backend ¶
type Backend interface {
Capabilities() Capabilities
Put(ctx context.Context, e Entry) error
Get(ctx context.Context, scope Scope, kind, id string) (Entry, bool, error)
Delete(ctx context.Context, scope Scope, kind, id string) error
// DeleteScope removes every entry under scope. Idempotent — an
// empty scope is not an error.
DeleteScope(ctx context.Context, scope Scope) error
Query(ctx context.Context, q Query) (QueryResult, error)
// QueryAllScopes is Query without the single-scope restriction; see
// CrossScopeQuery. Backends must apply ordering before Offset/Limit.
QueryAllScopes(ctx context.Context, q CrossScopeQuery) (QueryResult, error)
Status(ctx context.Context, scope Scope) (ScopeStatus, error)
}
Backend is the storage primitive every backend implementation satisfies. Signal dispatch is framework concern (Memory.SendSignal), not backend concern — backends never receive Signals.
type Capabilities ¶
type Capabilities struct {
// ContentSchemas: honors Kind.ContentSchema() for FieldEquals predicates.
ContentSchemas bool
// FieldRange: honors FieldFilter.Op values other than Eq (Lt, Lte, Gt, Gte,
// IsNil, NotNil). Backends reporting false silently move non-Eq FieldFilter
// predicates into DroppedPredicates.
FieldRange bool
// LinkTraversal: max hop depth. 0 = no link queries, 1 = one-hop,
// -1 = unbounded.
LinkTraversal int
// ReverseLinks: can answer "what links TO this entry?".
ReverseLinks bool
// TagFilters: honors Query.Tags.
TagFilters bool
// TimeRange: honors Query.Since / Query.Until.
TimeRange bool
}
Capabilities declares what a Backend can answer. The facade never lifts behavior beyond what the backend declares — predicates outside the declared capabilities surface as QueryResult.DroppedPredicates entries.
type ContentBlock ¶
type ContentBlock struct {
// Type selects which of the fields below is populated: "text", "tool_use",
// "tool_result" or "attachment".
Type string `json:"type"`
// Text is the block's prose; set only when Type == "text".
Text string `json:"text,omitempty"`
// ToolUse is the model's tool invocation; set only when Type == "tool_use".
ToolUse *ToolUseBlock `json:"toolUse,omitempty"`
// ToolResult is the tool's answer; set only when Type == "tool_result".
ToolResult *ToolResultBlock `json:"toolResult,omitempty"`
// Attachment carries a stored inbound file's handles and metadata, set only
// when Type == "attachment" — one block per inbound attachment whose bytes
// reached the store, whether or not extraction also succeeded (see
// AttachmentBlock.TextRef).
Attachment *AttachmentBlock `json:"attachment,omitempty"`
}
ContentBlock mirrors the LLM provider-neutral block shape so the runner can persist its native shape and reload it without translation.
type CrossScopeQuery ¶
type CrossScopeQuery struct {
ScopeKind string // required, e.g. "session"
Kinds []string // required, non-empty
Since *time.Time // inclusive
Until *time.Time // exclusive
OrderDesc bool // true = newest first
Limit int // 0 = no limit
Offset int // rows to skip after ordering
}
CrossScopeQuery matches entries of the given kinds across ALL scopes of ScopeKind, time-filtered and time-ordered. Unlike Query there is NO per-entry authorization post-filter — its only caller is the admind admin API, which gates on the SpiceDB platform permissions first. Do not expose it through any per-user surface.
type EmbeddingProvider ¶
EmbeddingProvider turns text into a vector for similarity search.
type Entry ¶
type Entry struct {
// Scope is the sub-universe the entry belongs to; queries never cross it.
Scope Scope `json:"scope"`
// Kind names the registered Kind; an unregistered one is refused at Put.
Kind string `json:"kind"`
// ID MUST start with the Kind's registered IDPrefix — enforced at Put.
ID string `json:"id"`
// CreatedAt is authorship time; canonicalized to microseconds before any
// sameness comparison, since that is all a TIMESTAMPTZ column keeps.
CreatedAt time.Time `json:"createdAt"`
// Links are typed references out of this entry; nil and empty are the same
// entry to the signed digest.
Links []Link `json:"links,omitempty"`
// Tags are an unordered SET — the digest sorts them, so two orderings are
// one entry; nil and empty are likewise the same.
Tags []string `json:"tags,omitempty"`
// Content is the Kind's ContentSchema payload, opaque when it declares none.
Content json.RawMessage `json:"content,omitempty"`
// Provenance is nil on mutable kinds and required on append-only ones
// wherever a ProvenanceVerifier is configured.
Provenance *Provenance `json:"provenance,omitempty"`
}
Entry is the unit of storage; (Scope, Kind, ID) is the unique key.
type FieldFilter ¶
type FieldFilter struct {
// Path is the key the value is STORED under — the Content struct's `json`
// tag, NOT its Go field name. Backends render the predicate as
// content->>'<Path>', so "TurnIndex" for a field tagged `json:"turnIndex"`
// extracts NULL and can never be true. Local.Query rejects a Path naming no
// content key of the Kinds queried, rather than answering empty forever.
//
// Path is a TOP-LEVEL key, not a nesting expression: both SQL backends read
// a dot as part of the key name (postgres's jsonb ->> takes a literal key,
// sqlite's ->> quotes the operand into $."a.b"), so "a.b" selects a key
// literally named "a.b" and never reaches a nested {"a":{"b":…}}. Nested
// content is not queryable. The Kind should also list the key in
// IndexedFields().
Path string `json:"path"`
// Op is the comparison; empty means equality.
Op FieldOp `json:"op,omitempty"`
// Value is the right-hand side; ignored by the IsNil/NotNil ops.
Value any `json:"value,omitempty"`
}
FieldFilter selects entries by a content field predicate. A backend without Capabilities.ContentSchemas drops it into QueryResult.DroppedPredicates at runtime; one without Capabilities.FieldRange drops non-Eq predicates the same way.
type FieldOp ¶
type FieldOp string
FieldOp is the comparison operator for a FieldFilter predicate. The zero value ("") means FieldOpEq, so an op-less filter is an equality filter.
func NormalizeFieldOp ¶
NormalizeFieldOp resolves the zero value to FieldOpEq, leaving any other op unchanged.
type KGCommunity ¶
type KGCommunity struct {
// UUID is the graph's identifier for the cluster.
UUID string `json:"uuid"`
// Name is the cluster's generated label.
Name string `json:"name"`
// Summary describes what its members have in common.
Summary string `json:"summary"`
// Members are the entity UUIDs in the cluster.
Members []string `json:"members"`
}
KGCommunity is a cluster of related entities the graph derived.
type KGEntity ¶
type KGEntity struct {
// UUID is the graph's identifier, stable across re-ingestion of the same entity.
UUID string `json:"uuid"`
// Name is the entity as the extractor named it.
Name string `json:"name"`
// Summary is the graph's rolling description, rewritten as facts accumulate.
Summary string `json:"summary"`
// Attributes are extractor-supplied properties; absent when none were found.
Attributes map[string]string `json:"attributes,omitempty"`
}
KGEntity is a node the graph extracted and deduplicated.
type KGFact ¶
type KGFact struct {
// UUID is the graph's identifier for this edge.
UUID string `json:"uuid"`
// Name is the relationship type.
Name string `json:"name"`
// Fact is the natural-language assertion.
Fact string `json:"fact"`
// FromEntity is the source entity's UUID.
FromEntity string `json:"fromEntity"`
// ToEntity is the target entity's UUID.
ToEntity string `json:"toEntity"`
// ValidAt is when the fact began holding; nil when the graph could not date it.
ValidAt *time.Time `json:"validAt,omitempty"`
// InvalidAt is when a later fact contradicted this one; nil means still current.
InvalidAt *time.Time `json:"invalidAt,omitempty"`
}
KGFact is an edge: one relationship the graph asserts between two entities.
type KGInput ¶
type KGInput struct {
// GroupID partitions the graph per session; it is the isolation boundary a
// scoped read is checked against.
GroupID string `json:"groupId"`
// Content is the raw text to extract entities and facts from.
Content string `json:"content"`
// Role attributes the content ("user", "assistant", …).
Role string `json:"role"`
}
KGInput is one episode handed to the graph for entity extraction.
type KGProvider ¶
type KGProvider interface {
Ingest(ctx context.Context, input KGInput) error
SearchFacts(ctx context.Context, query string, limit int) ([]KGFact, error)
GetEntity(ctx context.Context, uuid string) (*KGEntity, error)
EntityFacts(ctx context.Context, entityUUID string) ([]KGFact, error)
RelatedEntities(ctx context.Context, entityUUID string, limit int) ([]KGEntity, error)
Communities(ctx context.Context, groupID string) ([]KGCommunity, error)
}
KGProvider is the graph-native read/write surface. Extraction, dedup and contradiction detection happen inside the provider, asynchronously, so an Ingest that returns nil has been accepted, not yet reflected in reads.
type Kind ¶
type Kind interface {
// Name is the entry's Kind field and the registry's primary key
// (e.g., "authz_decision"). Must be non-empty and unique across
// the registry.
Name() string
// IDPrefix is the literal byte prefix every Entry of this Kind's
// ID must start with (e.g., "authzd-"). Required, non-empty, and
// must not be a prefix-of nor be prefixed-by any other registered
// Kind's prefix.
IDPrefix() string
// Retention advertises this Kind's preferences to the backend.
Retention() Retention
// ContentSchema optionally declares the Kind's Content struct so backends
// advertising Capabilities.ContentSchemas can index declared fields.
// nil means opaque content.
ContentSchema() reflect.Type
// IndexedFields names the fields the Kind wants indexed, as the keys they
// are STORED under — the Content struct's `json` tags, NOT the Go field
// names. FieldFilter.Path draws from the same vocabulary and accessor
// authors read this list before writing a query, so a Go name here is
// misinformation that reappears as a predicate matching nothing. Only
// meaningful when ContentSchema is non-nil.
IndexedFields() []string
// WriteAuthority declares WHICH CREDENTIAL CLASS may author entries of this
// Kind. An interface method rather than a table in a consumer, so a new Kind
// cannot be registered without answering the question.
WriteAuthority() WriteAuthority
// NewScopeHooks materializes the per-(Scope, Kind) hook signals are
// dispatched to, lazily on the first SendSignal touching the scope. Keep it
// cheap — long-running work belongs in OnSignal.
NewScopeHooks(scope Scope) ScopeHooks
}
Kind declares one variety of entry that the framework can store. Kinds register at init() time via RegisterKind. Implementations typically live under pkg/memory/kinds/<name>/.
func LookupKind ¶
LookupKind returns the Kind registered under name, for Put validation and for callers holding a Kind name as a string.
func RegisteredKinds ¶
func RegisteredKinds() []Kind
RegisteredKinds snapshots every registered Kind, sorted by Name so ScopeHooks materialization and signal dispatch are deterministic.
type Link ¶
type Link struct {
// Relation is a free-form label from a small conventional vocabulary
// ("of_operation", "for_resource", "by_subject", "approved_by", …).
Relation string `json:"relation"`
// Scope of the target; zero means the containing entry's own scope.
Scope Scope `json:"scope"`
// Kind of the target. An unregistered Kind skips ID-prefix validation.
Kind string `json:"kind"`
// ID of the target; validated against Kind's IDPrefix when Kind is registered.
ID string `json:"id"`
}
Link is a typed reference to another entry.
type LinkFilter ¶
type LinkFilter struct {
// Relation to match; empty matches any relation.
Relation string
// Kind of the linked entry.
Kind string
// ID of the linked entry.
ID string
}
LinkFilter selects entries by one of their links.
type Local ¶
type Local struct {
// contains filtered or unexported fields
}
Local is the in-process Memory implementation. It wraps a Backend, owns the Kind registry's per-(Scope, Kind) ScopeHooks materialization, and fans out Signal dispatch.
func NewLocal ¶
func NewLocal(b Backend, opts ...LocalOption) *Local
NewLocal wraps b in an in-process facade. A nil Backend panics: every facade method dereferences it, so it is a programmer error, not a runtime condition.
func (*Local) Capabilities ¶
func (m *Local) Capabilities() Capabilities
Capabilities returns the backend's declared capabilities, for `oap doctor`-style tooling; correctness-side callers never branch on it.
func (*Local) Delete ¶
Delete is idempotent — deleting a non-existent entry is not an error.
The door: a MUTABLE Kind requires an approval for (DeleteMemory, scope.ID) (bearer, SpiceDB, or system) or Delete returns ErrMissingApproval before touching the backend. An append-only Kind is refused outright — FIRST and unconditionally, regardless of approval. A per-session credential is additionally held to the Kind's WriteAuthority: removal is authorship, and a runner that could delete `cold_start_task` could replay the cold-start review that entry's existence is what closes.
func (*Local) DeleteScope ¶
DeleteScope removes the MUTABLE (working) memory of a scope. It STRUCTURALLY CANNOT remove append-only kinds — the tamper-evident audit log (transcript, audit, authz-decision, tool-session, …) is PERMANENT and survives every scope deletion by design: a session "disappearing" must never take its logs with it, so no code path can erase them. Operator-only, in-process; not on the Memory interface, not over HTTP.
The door: every call MUST carry an approval for (DeleteMemory, scope.ID). Only system (in-process operator/controller) callers reach this.
authz.CleanupScope is deliberately NOT called: the scope's read grants must survive so the retained audit stays queryable after the session is gone.
func (*Local) Get ¶
Get delegates to the backend. Returns (zero, false, nil) when the entry does not exist.
func (*Local) Put ¶
Put validates the Entry against the registered Kind's prefix rules, then forwards to the backend.
The door: a MUTABLE Kind requires an approval for (WriteMemory, e.Scope.ID) (bearer, SpiceDB, or system) or Put returns ErrMissingApproval before touching the backend. For an APPEND-ONLY Kind authorization IS provenance verification — a genuinely-new entry must pass the configured ProvenanceVerifier (with none configured the write is logged and allowed, for local/dev), after which the facade self-mints the internal AppendAudit approval proving the gate was satisfied. A WriteMemory or system approval can never substitute for either.
func (*Local) Query ¶
Query forwards to the backend. Callers wanting to observe degradation should check QueryResult.Partial / DroppedPredicates; correctness-wise every caller has a fallback path for empty results regardless of cause. A caller treating the answer as the complete match set must check QueryResult.Truncated, which says whether q.Limit ended the read before the matches did.
The door: every call MUST carry an approval for (ReadMemory, q.Scope.ID) (bearer, SpiceDB, or system) or Query returns ErrMissingApproval before touching the backend — checked unconditionally, independent of whether an Authorizer is configured or a caller is present.
func (*Local) QueryAllScopes ¶
func (m *Local) QueryAllScopes(ctx context.Context, q CrossScopeQuery) (QueryResult, error)
QueryAllScopes runs a cross-scope query against the backend. ADMIN-ONLY: there is deliberately no Authorizer post-filter here (the per-entry authorizer models per-session interact, which platform admins bypass by design) — callers MUST gate on the SpiceDB platform permissions first.
func (*Local) Reindex ¶
Reindex re-Indexes every entry in scope on each registered SearchProvider, for adding a provider to an existing deployment or recovering from index corruption. Per-entry failures are logged and skipped; the count returned is the number of entries successfully submitted to ALL providers.
The door: every call MUST carry an approval for (ReadMemory, scope.ID) (bearer, SpiceDB, or system) or Reindex returns ErrMissingApproval before touching the backend.
func (*Local) Search ¶
func (m *Local) Search(ctx context.Context, req SearchRequest) (MergedSearchResult, error)
Search delegates to the configured Searcher. Returns ErrNoSearchProviders when no Searcher has been set.
The per-kind read door applies here exactly as it does on Query, and for the same reason: a SearchProvider answers from the BACKEND rather than through this facade (the in-memory one calls Backend.Query directly; the SQL ones index every Kind that is Put), so nothing below this line re-applies it. Gating only Query would hide a Kind from the tool nobody calls while leaving it readable through search_memory, which is the tool the model holds.
func (*Local) SendSignal ¶
SendSignal dispatches sig to every registered Kind's ScopeHooks for sig.Scope, materializing hooks on first dispatch per (Scope, Kind) and reusing them. Per-hook errors are collected via errors.Join, so a failing hook on one Kind never prevents other Kinds from reacting.
The door: every call MUST carry an approval for (WriteMemory, sig.Scope.ID) (bearer, SpiceDB, or system) or SendSignal returns ErrMissingApproval before any hook is materialized or dispatched.
type LocalOption ¶
type LocalOption func(*Local)
LocalOption is a functional option for NewLocal.
func WithAuthorizer ¶
func WithAuthorizer(a Authorizer) LocalOption
WithAuthorizer sets the Authorizer consulted on every Put/Query/Delete. Unset (the default) means no authorization checks.
func WithLogger ¶
func WithLogger(l logr.Logger) LocalOption
func WithProvenanceVerifier ¶
func WithProvenanceVerifier(v ProvenanceVerifier) LocalOption
WithProvenanceVerifier enforces signed provenance on append-only writes: every such Put must carry an envelope verifying under a registered publisher key.
func WithSearchProviders ¶
func WithSearchProviders(ps ...SearchProvider) LocalOption
WithSearchProviders registers search providers that receive index updates on Put/Delete/DeleteScope. These are kept separate from the Searcher so the facade can index into providers without importing the composite layer.
func WithSearcher ¶
func WithSearcher(s Searcher) LocalOption
WithSearcher sets the Searcher used by Local.Search. When nil (the default), Search returns ErrNoSearchProviders.
type Memory ¶
type Memory interface {
Put(ctx context.Context, e Entry) (Entry, error)
Query(ctx context.Context, q Query) (QueryResult, error)
Search(ctx context.Context, req SearchRequest) (MergedSearchResult, error)
SendSignal(ctx context.Context, sig Signal) error
}
Memory is the caller-facing memory interface. Local (operator) and httpclient.Client (runner/ap/channelsd) implement it.
type MergedSearchResult ¶
type MergedSearchResult struct {
// Entries are the ranked, authorization-filtered hits across all providers.
Entries []ScoredEntry `json:"entries"`
// PerProvider keeps each provider's raw answer for diagnosis; a provider
// that errored is absent rather than present-and-empty. Each answer may
// hold one hit more than SearchRequest.Limit — the probe row the searcher
// asks for to decide Truncated, kept here because this is the raw answer.
PerProvider map[string]SearchResult `json:"perProvider,omitempty"`
// Truncated reports that SearchRequest.Limit ended this page before the
// matches did: at least one further hit ranked below the cut. False means
// every match is in Entries.
//
// Like QueryResult.Truncated it describes the READ: an Authorizer
// post-filter may leave fewer than Limit hits in Entries with Truncated
// still true.
Truncated bool `json:"truncated"`
}
MergedSearchResult is what the composite searcher returns to a caller.
type NamespacedName ¶
NamespacedName identifies an AgentSession. Mirrors k8s types.NamespacedName but kept import-free so this package can be used from non-k8s code.
func TokenSessionFrom ¶
func TokenSessionFrom(ctx context.Context) (NamespacedName, bool)
TokenSessionFrom reports the session whose bearer token authenticated this request, and whether the request arrived on such a token at all.
The bool is PRESENCE, not non-emptiness, and that distinction is the whole point: a security check written as `if caller != "" { … }` degrades silently to "no check" for the one caller class that never carries a caller. Treat a present-but-empty NamespacedName as a wiring bug and fail CLOSED, never as "not token-originated".
type OrderBy ¶
type OrderBy struct {
// Field is the column to order by; "createdAt" is the only value any
// backend honors, and anything else falls back to the backend's default.
Field string
// Desc: true for newest-first.
Desc bool
}
OrderBy controls Query result ordering.
type Permission ¶
type Permission string
Permission names a gated capability on the memory data plane. An "internal:" prefix marks the dangerous tier (see approval_internal.go) — those can only be authorized by pkg/memory's own trusted path, never by a bearer or system approval.
const ( ReadMemory Permission = "memory:read" WriteMemory Permission = "memory:write" DeleteMemory Permission = "memory:delete" ReadKG Permission = "kg:read" // AppendAudit gates append-only (tamper-evident) writes. Internal tier: // mintable only inside pkg/memory, by the provenance verify-on-write path. AppendAudit Permission = "internal:append-audit" )
type PoolReader ¶
type PoolReader interface {
QueryPool(ctx context.Context, sessionScope, poolScope Scope, q Query) (QueryResult, error)
}
PoolReader is the READ half of the same addressing: query a resource pool's entries rather than the caller's own scope.
It exists for the same reason PoolWriter does, and the reason is the same shape: over an HTTP transport a scope is not a value the caller passes, it is the URL, and that URL names a SESSION (which is what the bearer token authorizes). Query builds it from Scope.ID split on "/", so a resource scope ID — "dossier:d-1", which has no "/" — produced "/memory/observation/dossier:d-1/" and a 404 from the real server. An in-process facade has no such problem; a client does, and it has to say so at the call site.
sessionScope is therefore not redundant with poolScope: it is what the request is authenticated AS, while poolScope is what it addresses. The server proves the session holds a grant on the pool before serving it, exactly as it does for a write (httpsrv.destinationFor runs on every method).
The signature takes a whole Query rather than just the pool so a caller can filter — the provenance chain-head scan reads one Kind at a time.
type PoolWriter ¶
type PoolWriter interface {
PutToPool(ctx context.Context, sessionScope, poolScope Scope, e Entry) (Entry, error)
}
PoolWriter is the capability of writing an entry into a resource's memory pool rather than the caller's own scope — httpclient.Client.PutToPool's exact signature, named here so every caller that needs to ASSERT for the capability (rather than call a concrete *httpclient.Client directly) shares one definition instead of each redeclaring an identical anonymous interface.
Deliberately NOT part of Memory (see httpclient.Client.PutToPool's own doc): Memory.Put routes by e.Scope, and a pool write cannot, since the server refuses to read a destination out of the body at all — a caller that wants a pool has to say so at the call site, via this interface, rather than through the ambient Memory contract every store implements.
A wrapper that decorates a Memory (provenance.SigningMemory) and wants to forward a pool write to whatever concrete store it holds type-asserts its inner value against this interface — and must fail LOUDLY when that assertion fails, naming the concrete inner type, rather than silently answering "no" to a capability the inner may genuinely have further down a chain of wrappers. A silent "unsupported" answer here is indistinguishable from a genuinely pool-incapable backend, which is exactly the shape of bug that left record_observation refusing every call in a live runner while every test in the tree stayed green: SigningMemory embeds Memory as an INTERFACE field, so Go's method promotion could never forward the concrete *httpclient.Client's PutToPool.
type Provenance ¶
type Provenance struct {
// Publisher identifies the signer; equals the memory layer's caller ID for
// the writer (per-session IDs for runners/CLI; "system:channelsd",
// "system:authzd", "system:operator").
Publisher string `json:"publisher"`
// KeyID is the hex SHA-256 (first 16 bytes) of the Ed25519 public key, used
// to look the key up in the publisher-key registry.
KeyID string `json:"keyId"`
// Seq is the publisher's per-scope 1-based monotonic sequence, spanning all
// append-only kinds the publisher writes in the scope. A gap means loss.
Seq uint64 `json:"seq"`
// PrevHash is the EntryDigest of the publisher's previous entry in this
// scope; empty only for Seq 1.
PrevHash string `json:"prevHash,omitempty"`
// Sig is the Ed25519 signature over the canonical entry digest.
Sig []byte `json:"sig"`
}
Provenance carries the publisher attestation for append-only kinds: a per-publisher Ed25519 signature over a canonical entry digest, hash-chained per (scope, publisher) so gaps, reordering and truncation are detectable. nil on mutable kinds.
type ProvenanceVerifier ¶
type ProvenanceVerifier interface {
VerifyEntry(ctx context.Context, caller string, e Entry) error
}
ProvenanceVerifier validates the provenance envelope on an append-only write. The operator injects one so the facade enforces signing without importing crypto. caller is CallerFrom(ctx) — the USER, and "" for both in-process operator writes and per-session bearers, so binding an entry's publisher to its author must read TokenSessionFrom(ctx), not caller.
type PtTagContent ¶
type PtTagContent struct {
TagID string `json:"tagID"`
Content string `json:"content"`
MIME string `json:"mime,omitempty"`
}
PtTagContent is one resolved (id, content) datum.
type PtTagMintRequest ¶
type PtTagMintRequest struct {
// ToolUseID is the tool_use block whose result carried the datum into
// context, matching the link infoleakage_taint records under the same name.
ToolUseID string `json:"toolUseID,omitempty"`
// Resources are the objects the call read, as SpiceDB type/id pairs. The
// minter expands each to its authorized subjects. Required for a LEAF mint.
Resources []PtTagResourceRef `json:"resources,omitempty"`
// DerivedFrom names existing tags this datum was assembled from. When set,
// the mint is DERIVED: it takes no resources and no audience of its own,
// because `reader` resolves the intersection of these through SpiceDB.
DerivedFrom []string `json:"derivedFrom,omitempty"`
// UntrustedOrigin marks a leaf whose source may carry injected content.
// Meaningful only on a leaf mint: integrity is declared where a datum
// enters and inherited below it through carries_untrusted.
UntrustedOrigin bool `json:"untrustedOrigin,omitempty"`
// Content is the datum itself, stored behind the tag so the PLATFORM can
// resolve it later — to place it in a child's context when a data slot is
// bound, or to answer an audit. Optional: a tag with no content is still a
// complete provenance record, it just cannot be handed onward.
//
// Note this does NOT put the content anywhere the agent can reach. It
// lands in pt_tag_content, whose SessionReadable is false precisely so
// that query_memory — which takes arbitrary Kind names — cannot ask for it
// straight back.
//
// Written in the same call as the tag, never separately: content stored
// under a tag that failed to mint would be bytes nobody's audience governs,
// and a tag whose content arrived later would have a window in which a
// disclosure decision was made about something not yet there.
Content string `json:"content,omitempty"`
// MIME describes Content, so a platform forwarding the datum can label it.
MIME string `json:"mime,omitempty"`
}
PtTagMintRequest is the body of POST /memory/_pttag_mint/{ns}/{name}.
Note what is absent: any way to state an audience. The caller says what its tool call TOUCHED; the minter decides who may see it. That split is the security property of the whole route — pt_tag is ComponentWritten precisely so a session cannot author its own reader set, and a request field carrying one would hand the pen straight back.
type PtTagMintResponse ¶
type PtTagMintResponse struct {
TagID string `json:"tagID"`
}
PtTagMintResponse returns the id of the tag that now governs the datum.
type PtTagRegion ¶
PtTagRegion is one claimed (id, content) pair from an outbound payload.
type PtTagResolveRequest ¶
type PtTagResolveRequest struct {
// TagIDs are the tags whose content the caller asks for. The scope in the
// URL is where the bytes live (the caller's own scope for a derive, the
// PARENT scope for a bound slot); entitlement is per-tag, not per-scope.
TagIDs []string `json:"tagIDs"`
}
PtTagResolveRequest is the body of POST /memory/_pttag_resolve/{ns}/{name}.
Unlike _pttag_verify (which confirms ids and returns no bytes), this route RETURNS content — the bytes a caller is ENTITLED to receive. It exists for the two platform paths that legitimately need a datum's bytes back: derive_tag's validator (it must read the source tags it derives from) and a subagent's bound data slot (a child needs the parent's bound datum to place in its context). Both read pt_tag_content, which is component-read (SessionReadable false), so neither can read it over the memory API for itself; the operator holds the credential and gates each tag on the caller's pt_tag:<id>#access — session + ancestor + granted_to — before returning it.
type PtTagResolveResponse ¶
type PtTagResolveResponse struct {
Contents []PtTagContent `json:"contents,omitempty"`
}
PtTagResolveResponse returns the content for the entitled tags only. A tag the caller lacks pt_tag:<id>#access to, or whose bytes are not in the named scope, is simply ABSENT — the route never says which of the two it was, and never returns bytes for a tag the caller may not receive.
type PtTagResourceRef ¶
type PtTagResourceRef struct {
Type string `json:"type"`
ID string `json:"id"`
// Permission is the permission the read was authorized under, so the
// minter expands the right subject set rather than guessing one.
Permission string `json:"permission"`
}
PtTagResourceRef is one object a call read.
type PtTagVerifyRequest ¶
type PtTagVerifyRequest struct {
// Regions are the nonce-matched pt-untrusted regions the runner parsed out
// of the outbound payload — each an id the payload claims plus the bytes it
// carried under that id.
Regions []PtTagRegion `json:"regions"`
}
PtTagVerifyRequest is the body of POST /memory/_pttag_verify/{ns}/{name}.
It exists because the CONTENT-BINDING half of the per-datum egress check — "do these bytes actually belong to the tag the payload claims?" — must run where the stored bytes live. pt_tag_content is component-written AND component-READ (SessionReadable is false), so the runner, holding a session bearer, cannot read it over the memory HTTP API to compare for itself; the read door refuses a token-originated request by design. Worse, if the runner COULD read it, letting the runner do the comparison would defeat the check — a compromised runner could pair a witnessed wide id with fabricated bytes and simply declare a match. So the runner PARSES the payload's regions (a parse, not a privilege) and asks the operator to bind them; the operator holds the component credential and returns only WHICH ids bound, never the bytes.
type PtTagVerifyResponse ¶
type PtTagVerifyResponse struct {
// Verified are the ids whose supplied content matched what the platform
// stored for them.
Verified []string `json:"verified,omitempty"`
// AllBound is true only when EVERY requested region bound. A partially
// forged payload — one real region beside a fabricated one — is not one the
// caller can reason about per-datum, so the caller drops the whole payload
// to the coarse floor rather than honour the regions that happened to match.
AllBound bool `json:"allBound"`
}
PtTagVerifyResponse reports which regions bound to the bytes the platform stored for their claimed id.
Verified carries only ids, never content: this route confirms provenance, it does not hand a datum back — the caller already has the bytes it sent, and returning them would make _pttag_verify a read-back channel for the very pt_tag_content the design keeps out of a session's reach.
type Query ¶
type Query struct {
Scope Scope // required; the framework rejects an empty one
Kinds []string // empty means all kinds in scope
IDs []string // exact-id filter
LinkedTo []LinkFilter // entries whose Links contain {Relation?, Kind, ID}
LinkedFrom []LinkFilter // reverse: entries that ARE linked-to {Kind, ID}
Tags []string // entries carrying ALL the listed tags
FieldEquals []FieldFilter // content predicates; dropped by backends lacking ContentSchemas
Since, Until *time.Time // half-open CreatedAt window; nil means unbounded
OrderBy OrderBy
Limit int // 0 means the backend's own cap
}
Query is the rich superset of recall predicates. Backends drop what they can't handle into QueryResult.DroppedPredicates; callers always have a fallback path for empty / partial results.
type QueryResult ¶
type QueryResult struct {
// Entries matched the honored predicates; empty is a valid answer, not an error.
Entries []Entry
// DroppedPredicates names each Query predicate the backend could not honor
// ("FieldEquals", "LinkedFrom", …). A capability signal for operators;
// callers must not branch on it for correctness.
DroppedPredicates []string
// Partial is true iff len(DroppedPredicates) > 0.
Partial bool
// Truncated reports that Query.Limit ended this read before the matches
// did: at least one more entry matched and is not in Entries. False means
// Entries is every match, so a caller may treat it as the whole answer.
//
// It is set by Local.Query, which probes one row past the limit (see
// ProbeLimit) rather than guessing from len(Entries) — a set holding
// exactly Limit entries is otherwise indistinguishable from one the limit
// cut short, and that ambiguity is what lets a reader act on part of a
// result set believing it was all of it.
//
// It describes the READ, not the caller's view of it: an Authorizer
// post-filter may drop entries afterwards, leaving fewer than Limit in
// Entries with Truncated still true. That is correct — the store held more
// than the limit allowed to be read, whatever was then filtered out.
//
// Backend implementations leave it false; the facade owns it.
Truncated bool
}
QueryResult is what Backend.Query returns.
type Ranker ¶
type Ranker interface {
Rank(results map[string]SearchResult, limit int) []ScoredEntry
}
Ranker merges per-provider results, whose scores are not comparable across providers, into one ordering.
type Retention ¶
type Retention struct {
// EssentialWhileLive: never evict while ScopeStatus == Live.
EssentialWhileLive bool
// ArchiveOn names signal kinds that flip this Kind's entries
// from "kept" to "eligible for eviction".
ArchiveOn []SignalKind
// TTLAfterArchive: how long after an ArchiveOn signal the
// backend may drop the entry. Zero = backend default; negative
// = never auto-drop after archive.
TTLAfterArchive time.Duration
// SoftCapPerScope is a per-scope FIFO cap for cache-shaped
// Kinds (label). Zero = no cap.
SoftCapPerScope int
// AppendOnly: entries of this Kind are immutable once written. Put
// may create (or idempotently re-put byte-identical content) but
// never change an existing entry, and per-entry Delete is refused.
// Scope-level deletion (retention, session GC) is unaffected — that
// is the backend's retention policy, not entry mutation.
AppendOnly bool
// NeverForkCopy: a fork must NOT copy this Kind's entries into the child
// scope. The forking code derives whatever the child needs instead.
//
// This exists for Kinds whose entries are a CHAIN rather than a set —
// where meaning comes from order and from the (scope, publisher) provenance
// sequence, not from each entry alone. Copying those into a new scope
// produces a chain whose seq and prevHash refer to a history the child does
// not have, so it verifies as gapped at best and replays in the wrong order
// at worst.
//
// The plan gate is the motivating case: its rules ("a denial survives a
// later supersede", "the last write wins for this ceiling") are all
// order-dependent, so a mis-ordered replay in a child could resurrect
// authority a human revoked.
NeverForkCopy bool
}
Retention is a Kind's advice to the backend about lifecycle and immutability of entries. Fields are hints — backends honor what their policy supports.
type Scope ¶
type Scope struct {
// Kind is the sub-universe's type — "session" or ScopeKindResource today.
Kind string `json:"kind"`
// ID identifies the scope within its Kind — "<namespace>/<name>" for
// sessions, "<type>:<id>" for a resource pool. The two shapes are
// deliberately disjoint; ResourceScope enforces it.
ID string `json:"id"`
}
Scope identifies a sub-universe of entries. Two Kinds are in use: "session", which most callers emit, and ScopeKindResource, a memory pool living on a resource (see resourcescope.go). The framework permits others ("agentclass", "user") for cross-session use, and nothing branches on the value.
func KGScopeFrom ¶
KGScopeFrom returns the KG read scope and true when one is set with a non-empty ID; ok is false when unset or the ID is empty (the unscoped admin path).
func ParseResourceRef ¶
ParseResourceRef parses ref as "<type>:<id>" and returns the Scope ResourceScope would build from the two halves.
The one parser every reader of a `resource`/`?pool=`/write-destination argument should call, rather than each hand-rolling its own strings.Cut — a review of this feature found three call sites doing exactly that (record_observation's Execute, httpsrv's destinationFor, the info-leakage audience gate's evalPoolWrite) at three different strictnesses: two delegated to ResourceScope's validation and one did not, so a ref like "dossier:a:b" was a resolvable destination to the gate and a refusal to the tool that actually writes. The direction was safe (nothing was written), but it is a crack in the exact agreement pkg/agent/runner's cross-package pin exists to hold, and it let the gate hand a malformed object ref through to a permission lookup. One parser closes it structurally: the three sites cannot drift on strictness again because there is only one strictness to drift from.
func ResourceScope ¶
ResourceScope builds the Scope for one resource instance's memory pool.
Refuses a '/' in either half. The memory capability door keys on scope.ID ALONE — httpsrv mints ForBearerToken(perm, id, …) and CompositeSearcher checks EnsureApproval(ctx, ReadMemory, sc.ID) — and a session's ID is "<ns>/<name>". Allowing a slash here would let an approval minted for a resource authorize a session, or the reverse. The two ID shapes staying disjoint is a security property, not a formatting preference.
func UserScope ¶
UserScope returns the scope holding one user's cross-session data, keyed by the bare canonical user id (base64url). Refuses ids that could collide with session ("<ns>/<name>") or resource ("<type>:<id>") scope IDs — the capability doors key on scope.ID alone, so disjointness is load-bearing (see resourcescope.go's ResourceScope for the same rule on the other non-session scope kind).
type ScopeHooks ¶
ScopeHooks reacts to signals dispatched into the (Scope, Kind) pair. Implementations switch on Signal.Kind for the signals they care about and ignore the rest. Errors are collected (not aborted-on) by the framework's dispatch loop.
type ScopeStatus ¶
type ScopeStatus struct {
// State is the backend's verdict on the scope.
State StatusState `json:"state"`
// ArchivedAt is when the entries were shed; nil unless State is archived.
ArchivedAt *time.Time `json:"archivedAt,omitempty"`
}
ScopeStatus is the result of Backend.Status(scope).
type ScoredEntry ¶
type ScoredEntry struct {
// Entry is the stored entry, unmodified.
Entry Entry `json:"entry"`
// Score is comparable only within one Source; providers use different
// scales, which is why merging goes through the ranker rather than a sort.
Score float64 `json:"score"`
// Source is the SearchProvider.Name that produced the hit.
Source string `json:"source"`
}
ScoredEntry is one hit with the rank the provider or ranker gave it.
type SearchCapabilities ¶
type SearchCapabilities struct {
// TextSearch: honors SearchRequest.Text lexically.
TextSearch bool `json:"textSearch"`
// VectorSearch: honors SearchRequest.Text by embedding similarity.
VectorSearch bool `json:"vectorSearch"`
// FieldFilters: honors SearchRequest.Fields.
FieldFilters bool `json:"fieldFilters"`
// TagFilters: honors SearchRequest.Tags.
TagFilters bool `json:"tagFilters"`
// TimeRange: honors SearchRequest.Since / Until.
TimeRange bool `json:"timeRange"`
// LinkTraversal: can follow entry links while searching.
LinkTraversal bool `json:"linkTraversal"`
}
SearchCapabilities is a provider's declaration of what it can honor; the composite searcher never lifts a provider beyond what it claims here.
type SearchProvider ¶
type SearchProvider interface {
Name() string
SearchCapabilities() SearchCapabilities
Search(ctx context.Context, req SearchRequest) (SearchResult, error)
Index(ctx context.Context, scope Scope, kind, id string, content []byte) error
DeleteIndex(ctx context.Context, scope Scope, kind, id string) error
DeleteScopeIndex(ctx context.Context, scope Scope) error
}
SearchProvider is one ranked-retrieval backend. The Local facade fans index updates out to every registered provider on Put/Delete/DeleteScope, logging and continuing past a failure rather than failing the write.
type SearchRequest ¶
type SearchRequest struct {
// Scopes to search; every one must be separately authorized.
Scopes []Scope `json:"scopes"`
// Text is the free-text / vector query; empty makes this structured-only.
Text string `json:"text,omitempty"`
// Kinds to restrict to; empty means all kinds.
Kinds []string `json:"kinds,omitempty"`
// Tags an entry must ALL carry to match.
Tags []string `json:"tags,omitempty"`
// Fields are content predicates; providers lacking FieldFilters drop them.
Fields []FieldFilter `json:"fields,omitempty"`
// Since bounds CreatedAt below; nil means unbounded.
Since *time.Time `json:"since,omitempty"`
// Until bounds CreatedAt above; nil means unbounded.
Until *time.Time `json:"until,omitempty"`
// Limit caps the MERGED result, not each provider's; 0 means the default.
Limit int `json:"limit,omitempty"`
}
SearchRequest is one ranked-retrieval query, fanned out to every provider.
type SearchResult ¶
type SearchResult struct {
// Entries are this provider's hits, its own best-first.
Entries []ScoredEntry `json:"entries"`
// DroppedFilters names request filters this provider could not honor, so a
// narrower question silently answered as a wider one is visible.
DroppedFilters []string `json:"droppedFilters,omitempty"`
}
SearchResult is one provider's answer.
type Searcher ¶
type Searcher interface {
Search(ctx context.Context, req SearchRequest) (MergedSearchResult, error)
}
Searcher is the search executor; the Local facade optionally holds one, and Search returns ErrNoSearchProviders when it does not.
type SessionReadabilityDecider ¶
type SessionReadabilityDecider interface {
// SessionReadable reports whether a SESSION credential may read entries of
// this Kind. False means platform components only.
SessionReadable() bool
}
SessionReadabilityDecider is the READ half of the per-kind door, and it is optional: a Kind that does not implement it is session-readable, which is what every Kind was before this existed. Only a Kind holding data the agent must never see implements it.
Optional rather than a required Kind method because the two directions are not symmetric in cost or in risk. Every Kind must answer WriteAuthority — getting it wrong grants a session the pen for records a component trusts. Almost no Kind needs to be unreadable, and making all thirty-odd declare "yes, readable" would be thirty lines of noise around one real answer.
The risk that comes with optional — a future sensitive Kind forgetting to implement it — is covered by a reviewed pin over the registry, the same device that guards the session-writable set.
type Signal ¶
type Signal struct {
// Kind selects which hooks react and what Payload's shape is.
Kind SignalKind `json:"kind"`
// Scope selects which (Scope, Kind) hooks receive the signal.
Scope Scope `json:"scope"`
// At is the event time as the sender observed it, not receipt time.
At time.Time `json:"at"`
// Payload is opaque to the framework; the Kind owning this SignalKind
// defines its shape. Empty when the signal carries no data.
Payload json.RawMessage `json:"payload,omitempty"`
}
Signal is a lifecycle event fanned out to EVERY registered Kind's ScopeHooks for the scope — a hook that does not recognize the Kind ignores it.
type SignalKind ¶
type SignalKind string
SignalKind is a free-form, namespaced signal identifier owned by some Kind's package (convention: "<owning-kind>/<event>"). Deliberately not an enum, so a Kind can introduce a signal without a framework change.
const ( // SignalEntryAppended fires after a successful append-only Put. It carries // the entry's Scope so a hook can fold that scope's log; it deliberately // carries no entry payload, so a hook reads the durable record rather than // trusting what the writer said it wrote. SignalEntryAppended SignalKind = "memory/entry.appended" )
Framework-level signal kinds: fired by the facade itself on every registered Kind's ScopeHooks, rather than by one Kind's own package.
type StatusState ¶
type StatusState string
StatusState is the lifecycle state of a Scope as observed by the backend.
const ( // StatusLive: the backend has (or expects) entries for this scope and // intends to serve them. StatusLive StatusState = "live" // StatusArchived: the backend once held this scope's entries and has since // shed them; runner-on-resume surfaces it as "session expired". No shipped // backend produces it — time-based archival is not implemented. StatusArchived StatusState = "archived" // StatusUnknown: no entries the backend can vouch for — brand new or really // gone. Callers treat it the same as a fresh scope. StatusUnknown StatusState = "unknown" )
type ToolResultBlock ¶
type ToolResultBlock struct {
// ToolUseID is the ToolUseBlock.ID this result answers.
ToolUseID string `json:"toolUseId"`
// Content is the tool's output as the model will see it.
Content string `json:"content"`
// IsError marks a failed call; the content is then the error text.
IsError bool `json:"isError,omitempty"`
// RenderedLive records that the live surfaces already showed Content to the
// user, so a resumed transcript must show it too — the live == reload
// invariant. Set by the runner from tool.Result.RenderedLive, which only a
// streaming/interactive toolkit's composed result carries; ordinary tool
// output (a memory query's rows, a kubectl dump) leaves it false and stays
// hidden. Read by turn.VisibleTimeline. Content is still the ENVELOPED form
// the model saw — a renderer unwraps it (pkg/agent/toolenvelope).
//
// omitempty is load-bearing for the same reason as Turn.Via: the provenance
// digest omits empty fields, so an unset flag must not perturb it, and
// entries written before this existed stay verifiable byte-for-byte.
RenderedLive bool `json:"renderedLive,omitempty"`
}
type ToolUseBlock ¶
type Turn ¶
type Turn struct {
// Index is the turn's 0-based position in the transcript; (Index, Role) is
// the identity turn.Appender guards against conflicting re-writes.
Index int `json:"index"`
// Role is "user" | "assistant" | "system_note".
Role string `json:"role"`
// Content holds the provider-neutral blocks, persisted verbatim.
Content []ContentBlock `json:"content"`
// CreatedAt is when the turn was recorded.
CreatedAt time.Time `json:"createdAt"`
// Usage is token accounting; nil on anything but an assistant turn.
Usage *Usage `json:"usage,omitempty"`
// Author is the canonical subject (e.g. "user:YWxpY2U") of the human who
// authored this turn, including the index-0 initial prompt, which carries
// the session starter's subject. Empty for agent/runner-authored turns
// (tool results), system notes, and kubectl/bento-driven sessions with no
// human starter. In multiplayer sessions it is the actual sender, not the
// session initiator.
Author identity.Subject `json:"author,omitempty"`
// Via names the browser/TUI surface that injected this turn, as a view URN
// (urn:ap:view:artifact:artifact-3f2a1b8c/artrev-9d4e0117). Empty for turns
// arriving through the Channel's own listener. Server-minted, never
// client-supplied. omitempty is load-bearing: the provenance digest omits
// empty fields, so an unset Via must not perturb it.
Via string `json:"via,omitempty"`
// Refused marks an assistant turn whose provider response carried
// stop_reason=refusal. The turn stays in the append-only transcript for
// audit, but replay excludes it from the reconstructed message history so a
// retry regenerates it cleanly. omitempty for the same digest reason as Via.
Refused bool `json:"refused,omitempty"`
// Model is the uniform display id of the model that actually served the
// turn, "<provider>/<served-model>". Empty on user turns.
Model string `json:"model,omitempty"`
}
Turn is one entry in a session's transcript. user/assistant roles feed back into the LLM messages array on resume; system_note is operator/runner metadata, filtered out at replay time.
type Usage ¶
type Usage struct {
// InputTokens billed for the prompt, cache reads excluded.
InputTokens int64 `json:"inputTokens"`
// OutputTokens billed for the completion.
OutputTokens int64 `json:"outputTokens"`
// CacheCreationTokens written into the prompt cache this turn.
CacheCreationTokens int64 `json:"cacheCreationTokens,omitempty"`
// CacheReadTokens served from the prompt cache instead of being billed as input.
CacheReadTokens int64 `json:"cacheReadTokens,omitempty"`
}
Usage is per-turn token accounting as the provider reported it.
type WriteAuthority ¶
type WriteAuthority uint8
WriteAuthority answers "may an agent session author entries of this Kind?".
The memory HTTP plane authenticates three credential classes: the per-session bearer a runner pod holds, the system component tokens (channelsd/authzd/webd), and — not a credential at all — the operator's own in-process calls. Without this classification a valid per-session token could Put ANY registered Kind into its scope, so a compromised runner could author component-owned state: `cold_start_task` (which authzd reads as "this session's scope review is decided" and the runner BLOCKS on), every append-only audit kind, channelsd's `parked_prompt`, authzd's extraction records.
It lives on the Kind because that is the only place a new variant is guaranteed to be looked at. A denylist in the HTTP handler, or an `if kind == "cold_start_task"` in a consumer, protects exactly the kinds somebody remembered that day and silently admits every kind added after.
const ( // ComponentWritten: only a platform component may author this Kind — the // operator in-process, or a system component bearer (channelsd, authzd). A // per-session credential is refused. // // The ZERO VALUE, deliberately: the failure directions are asymmetric. A // Kind wrongly left ComponentWritten refuses a legitimate write LOUDLY on // its first attempt, naming the Kind and the rule; a Kind wrongly left // session-writable is silent, indefinite, and re-opens exactly the hole this // type closes. Fail toward the noisy one. ComponentWritten WriteAuthority = iota // SessionWritten: the agent session itself authors this Kind, through the // per-session memory bearer its runner pod holds (and, on the same // credential, `oap memory put`) — transcript turns, artifacts, labels, the // per-turn audit the runner produces about its own tool calls. // // Choosing it is a claim about the THREAT model, not convenience: it says // the session's agent loop — driven by model output and by whatever content // its tools pulled in — is an acceptable author of this record. If a // component reads the Kind to make an authorization decision, it is not. SessionWritten )
func (WriteAuthority) String ¶
func (w WriteAuthority) String() string
String renders a WriteAuthority for logs and test failures. An out-of-range value renders as such rather than panicking: enforcement already treats anything that is not exactly SessionWritten as component-only, so an unknown value is a fail-closed outcome that should be readable, not fatal.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package assetlimits holds size ceilings shared by both sides of the inbound-attachment upload path: pkg/memory/httpsrv (the operator's POST /inbound-asset route, which enforces the ceiling) and pkg/channels/channelsd/pipeline (which clamps its own pre-fetch size check to the same number, so a file the operator will reject is never downloaded in full first).
|
Package assetlimits holds size ceilings shared by both sides of the inbound-attachment upload path: pkg/memory/httpsrv (the operator's POST /inbound-asset route, which enforces the ceiling) and pkg/channels/channelsd/pipeline (which clamps its own pre-fetch size check to the same number, so a file the operator will reject is never downloaded in full first). |
|
Package httpclient is the HTTP client for the operator's memory API.
|
Package httpclient is the HTTP client for the operator's memory API. |
|
Package httpsrv exposes a memory.Memory over a bearer-authed HTTP API.
|
Package httpsrv exposes a memory.Memory over a bearer-authed HTTP API. |
|
kg
|
|
|
kinds
|
|
|
all
Package all is a convenience: blank-importing it from a binary triggers the init() registration of every memory Kind.
|
Package all is a convenience: blank-importing it from a binary triggers the init() registration of every memory Kind. |
|
approval
Package approval is the memory Kind for approval-flow events (request + outcome).
|
Package approval is the memory Kind for approval-flow events (request + outcome). |
|
artifact
Package artifact registers the logical-artifact head Kind.
|
Package artifact registers the logical-artifact head Kind. |
|
artifactrevision
Package artifactrevision registers the immutable per-revision Kind of the artifact-versioning system.
|
Package artifactrevision registers the immutable per-revision Kind of the artifact-versioning system. |
|
auditkey
Package auditkey implements the "audit_key" memory Kind: the operator's durable witness of which Ed25519 key an AgentSession instance signs its append-only records with.
|
Package auditkey implements the "audit_key" memory Kind: the operator's durable witness of which Ed25519 key an AgentSession instance signs its append-only records with. |
|
authz_session_config
Package authz_session_config is the memory Kind for the per-session authz config snapshot: the entity-extraction config plus the two fields authzd derives this session's cold-start authorization policy from.
|
Package authz_session_config is the memory Kind for the per-session authz config snapshot: the entity-extraction config plus the two fields authzd derives this session's cold-start authorization policy from. |
|
authzdecision
Package authzdecision is the memory Kind for authz.Check outcomes.
|
Package authzdecision is the memory Kind for authz.Check outcomes. |
|
channel_msg_ref
Package channel_msg_ref implements the "channel_msg_ref" memory kind: a sparse index that maps a channel-kind-scoped message reference (Slack: channel_id:thread_ts:message_ts; future kinds: their own) to the turn index where the inbound message was recorded.
|
Package channel_msg_ref implements the "channel_msg_ref" memory kind: a sparse index that maps a channel-kind-scoped message reference (Slack: channel_id:thread_ts:message_ts; future kinds: their own) to the turn index where the inbound message was recorded. |
|
coldstarttask
Package coldstarttask is the memory Kind recording the approver's decision for a new session's cold-start scope proposal: which task (cleaned / original / none) the runner should run as the first turn, and whether scope was applied.
|
Package coldstarttask is the memory Kind recording the approver's decision for a new session's cold-start scope proposal: which task (cleaned / original / none) the runner should run as the first turn, and whether scope was applied. |
|
contentguardaudit
Package contentguardaudit is the memory Kind for the audit log of content-guard enforcement: per-call content-inspection findings (pass/block/approve), one entry per firing.
|
Package contentguardaudit is the memory Kind for the audit log of content-guard enforcement: per-call content-inspection findings (pass/block/approve), one entry per firing. |
|
envelopefact
Package envelopefact is the memory Kind for a fact derived by platform code from a VERIFIED inbound delivery — the signed webhook body that opened or advanced a session.
|
Package envelopefact is the memory Kind for a fact derived by platform code from a VERIFIED inbound delivery — the signed webhook body that opened or advanced a session. |
|
extracted_entity
Package extracted_entity is the memory Kind for raw LLM-extracted entity candidates produced by internal/cmd/authzd.
|
Package extracted_entity is the memory Kind for raw LLM-extracted entity candidates produced by internal/cmd/authzd. |
|
extraction_state
Package extraction_state is the memory Kind tracking authzd's per-turn extraction lifecycle: pending → complete | failed.
|
Package extraction_state is the memory Kind tracking authzd's per-turn extraction lifecycle: pending → complete | failed. |
|
factcontent
Package factcontent holds the content shape the two fact Kinds share.
|
Package factcontent holds the content shape the two fact Kinds share. |
|
infoleakageaudit
Package infoleakageaudit is the memory Kind for information-leakage audit events.
|
Package infoleakageaudit is the memory Kind for information-leakage audit events. |
|
infoleakagedecision
Package infoleakagedecision is the memory Kind for per-session information- leakage approval decisions.
|
Package infoleakagedecision is the memory Kind for per-session information- leakage approval decisions. |
|
infoleakagetaint
Package infoleakagetaint is the memory Kind for per-session taint records.
|
Package infoleakagetaint is the memory Kind for per-session taint records. |
|
internal/auditaccessor
Package auditaccessor holds the shared append-one / list-all accessor boilerplate for the simple per-scope audit Kinds.
|
Package auditaccessor holds the shared append-one / list-all accessor boilerplate for the simple per-scope audit Kinds. |
|
internal/undecodable
Package undecodable holds the one operator-facing report every Kind accessor owes when a stored Entry will not decode.
|
Package undecodable holds the one operator-facing report every Kind accessor owes when a stored Entry will not decode. |
|
label
Package label is the memory Kind for (resourceType, id) → display label tuples extracted from MCP tool responses.
|
Package label is the memory Kind for (resourceType, id) → display label tuples extracted from MCP tool responses. |
|
lifecycle
Package lifecycle is the canonical Kind for session/scope-lifecycle signals.
|
Package lifecycle is the canonical Kind for session/scope-lifecycle signals. |
|
lineage
Package lineage implements the "lineage" memory kind: bidirectional fork edges that record parent ↔ child relationships between AgentSessions when a user triggers Restart-from-here.
|
Package lineage implements the "lineage" memory kind: bidirectional fork edges that record parent ↔ child relationships between AgentSessions when a user triggers Restart-from-here. |
|
metaagentaudit
Package metaagentaudit is the memory Kind for the per-invocation audit log of metaagent decisions.
|
Package metaagentaudit is the memory Kind for the per-invocation audit log of metaagent decisions. |
|
metaagentthread
Package metaagentthread is the memory Kind for messages in the metaagent's sub-thread (user mentions + metaagent responses).
|
Package metaagentthread is the memory Kind for messages in the metaagent's sub-thread (user mentions + metaagent responses). |
|
observation
Package observation is the memory Kind for a note an agent session writes into a resource-scoped memory pool: free text one session concluded about a resource (a customer, a repo, whatever the resource type is), durable for a later session holding a slot on the same pool to read.
|
Package observation is the memory Kind for a note an agent session writes into a resource-scoped memory pool: free text one session concluded about a resource (a customer, a repo, whatever the resource type is), durable for a later session holding a slot on the same pool to read. |
|
observedfact
Package observedfact is the memory Kind for a fact derived from a tool RESULT by a declared `observes` block.
|
Package observedfact is the memory Kind for a fact derived from a tool RESULT by a declared `observes` block. |
|
parkedprompt
Package parkedprompt implements the "parked_prompt" memory kind: the durable record of a render-ready prompt a session is parked on, so it can be re-surfaced to a surface that attaches later, or after a restart.
|
Package parkedprompt implements the "parked_prompt" memory kind: the durable record of a render-ready prompt a session is parked on, so it can be re-surfaced to a surface that attaches later, or after a restart. |
|
plangateaudit
Package plangateaudit is the memory Kind for the plan gate's authorization record: which plan was approved, which phase was active, and what the gate decided for each permissioned call.
|
Package plangateaudit is the memory Kind for the plan gate's authorization record: which plan was approved, which phase was active, and what the gate decided for each permissioned call. |
|
preferenceaccess
Package preferenceaccess is the memory Kind for the audit log of subject-named preference reads: every GET /memory/_preferences?user-ref= request, resolved or not.
|
Package preferenceaccess is the memory Kind for the audit log of subject-named preference reads: every GET /memory/_preferences?user-ref= request, resolved or not. |
|
preferencewrite
Package preferencewrite is the memory Kind for the audit log of first-party human writes to user preferences: every direct edit via the Slack App Home (or other UI surfaces) into the acting user's own user_preference scope.
|
Package preferencewrite is the memory Kind for the audit log of first-party human writes to user preferences: every direct edit via the Slack App Home (or other UI surfaces) into the acting user's own user_preference scope. |
|
pttag
Package pttag is the memory Kind for per-datum provenance tags.
|
Package pttag is the memory Kind for per-datum provenance tags. |
|
pttagcontent
Package pttagcontent is the memory Kind for redacted content held behind a provenance tag.
|
Package pttagcontent is the memory Kind for redacted content held behind a provenance tag. |
|
relwritesaudit
Package relwritesaudit is the memory Kind for tuples written by pkg/authz/relwrites after a successful tool call.
|
Package relwritesaudit is the memory Kind for tuples written by pkg/authz/relwrites after a successful tool call. |
|
scopeaudit
Package scopeaudit is the memory Kind for the audit log of applied ScopeDeltas — one entry per applied delta.
|
Package scopeaudit is the memory Kind for the audit log of applied ScopeDeltas — one entry per applied delta. |
|
sessionscope
Package sessionscope is the memory Kind for the dynamic session scope (Layer 2).
|
Package sessionscope is the memory Kind for the dynamic session scope (Layer 2). |
|
systemprompt
Package systemprompt is the memory Kind for the append-only record of the system prompt an agent actually ran under.
|
Package systemprompt is the memory Kind for the append-only record of the system prompt an agent actually ran under. |
|
tool_dispatch_snapshot
Package tool_dispatch_snapshot implements the "tool_dispatch_snapshot" memory kind: a record that the runner requested — and the controller committed — a workspace snapshot immediately before a specific tool dispatch with stateImpact ∈ {readwrite, external}.
|
Package tool_dispatch_snapshot implements the "tool_dispatch_snapshot" memory kind: a record that the runner requested — and the controller committed — a workspace snapshot immediately before a specific tool dispatch with stateImpact ∈ {readwrite, external}. |
|
toolcatalog
Package toolcatalog is the memory Kind for the append-only record of which tools an agent was OFFERED, and from which turn.
|
Package toolcatalog is the memory Kind for the append-only record of which tools an agent was OFFERED, and from which turn. |
|
toolchainaudit
Package toolchainaudit is the memory Kind for the append-only record of toolchain selection.
|
Package toolchainaudit is the memory Kind for the append-only record of toolchain selection. |
|
toolguardaudit
Package toolguardaudit is the memory Kind for the audit log of tool-guard enforcement events: breaker trips/recoveries, rate-limit hits, denials and halts.
|
Package toolguardaudit is the memory Kind for the audit log of tool-guard enforcement events: breaker trips/recoveries, rate-limit hits, denials and halts. |
|
toolsession
Package toolsession registers the tool_session Kind — one Entry per parsed event from an interactive tool's stream (e.g.
|
Package toolsession registers the tool_session Kind — one Entry per parsed event from an interactive tool's stream (e.g. |
|
triggerdelivery
Package triggerdelivery is the memory Kind for the append-only record of the signed webhook delivery that STARTED a session.
|
Package triggerdelivery is the memory Kind for the append-only record of the signed webhook delivery that STARTED a session. |
|
turn
Package turn registers the LLM-transcript Kind.
|
Package turn registers the LLM-transcript Kind. |
|
uiaction
Package uiaction is the memory Kind for an agent-UI action's lifecycle record: one entry per request ID, rewritten in place as the call progresses (submitted -> awaiting_approval -> running -> settled).
|
Package uiaction is the memory Kind for an agent-UI action's lifecycle record: one entry per request ID, rewritten in place as the call progresses (submitted -> awaiting_approval -> running -> settled). |
|
uiviewmodel
Package uiviewmodel is the memory Kind for an agent-UI Tier-1 view-model: one record per (session, ui, hook), rewritten in place each time the agent calls update_view for that hook.
|
Package uiviewmodel is the memory Kind for an agent-UI Tier-1 view-model: one record per (session, ui, hook), rewritten in place each time the agent calls update_view for that hook. |
|
uiviewparams
Package uiviewparams is the memory Kind for the agent's own binding-parameter choices on an agent-defined UI: one record per (session, ui), rewritten in place each time the agent calls set_view_params.
|
Package uiviewparams is the memory Kind for the agent's own binding-parameter choices on an agent-defined UI: one record per (session, ui), rewritten in place each time the agent calls set_view_params. |
|
userpreference
Package userpreference is the memory Kind for one user's saved preference values, keyed per (classNamespace, className, key) in the user's own scope (memory.UserScope).
|
Package userpreference is the memory Kind for one user's saved preference values, keyed per (classNamespace, className, key) in the user's own scope (memory.UserScope). |
|
Package memcopy provides the turn-anchored memory-copy primitive the AgentSession controller's restart reconciler uses to fork a new session from an existing one.
|
Package memcopy provides the turn-anchored memory-copy primitive the AgentSession controller's restart reconciler uses to fork a new session from an existing one. |
|
Package memtest provides Backend test doubles for the memory framework.
|
Package memtest provides Backend test doubles for the memory framework. |
|
Package pools answers one question: which resource memory pools may this session touch, and in which direction?
|
Package pools answers one question: which resource memory pools may this session touch, and in which direction? |
|
Package provenance signs and verifies append-only memory entries: per-publisher Ed25519 signatures over a canonical entry digest, hash-chained per (scope, publisher) for gap/truncation detection.
|
Package provenance signs and verifies append-only memory entries: per-publisher Ed25519 signatures over a canonical entry digest, hash-chained per (scope, publisher) for gap/truncation detection. |
|
Package pttagmint derives a datum's audience and records its provenance tag.
|
Package pttagmint derives a datum's audience and records its provenance tag. |
|
Package publisherkeys is an in-memory publisher→keyID→Ed25519 key store for component publishers (channelsd, authzd, operator).
|
Package publisherkeys is an in-memory publisher→keyID→Ed25519 key store for component publishers (channelsd, authzd, operator). |
|
Package sentinel is the single source of truth for how a memory sentinel error crosses the HTTP wire, in both directions: httpsrv reads Table to pick the response status and stamp Header; httpclient reads it to rebuild the sentinel from Header.
|
Package sentinel is the single source of truth for how a memory sentinel error crosses the HTTP wire, in both directions: httpsrv reads Table to pick the response status and stamp Header; httpclient reads it to rebuild the sentinel from Header. |
|
Package tokens maintains a per-AgentSession bearer-token registry.
|
Package tokens maintains a per-AgentSession bearer-token registry. |