Documentation
¶
Index ¶
- Constants
- Variables
- func IsTraceIDExists(err error) bool
- func KeyFingerprint(key string) string
- func ParseSince(s string) (time.Duration, error)
- func ResolveTLSPaths(cortexDir string, cfg *AccessConfig) (certPath, keyPath string)
- func SanitizeFTS5Query(q string) string
- func WriteManifest(dir string, m Manifest) error
- type AccessConfig
- type AccessKey
- type BackfillResult
- type ConsolidationActivity
- type ConsolidationConfig
- type ConsolidationDay
- type ConsolidationTotals
- type Cortex
- func (c *Cortex) Add(t *trace.Trace) error
- func (c *Cortex) AdminPurge(id, reason, expectedTier string, hard bool, actor ReadActor) error
- func (c *Cortex) Append(id, content string) error
- func (c *Cortex) ApplyExternalPurge(id string) error
- func (c *Cortex) Archive(id string) error
- func (c *Cortex) ArchiveDir() string
- func (c *Cortex) BackfillCreateEvents(dryRun bool) (BackfillResult, error)
- func (c *Cortex) CheckSourceLock(id string) error
- func (c *Cortex) Close() error
- func (c *Cortex) ConsolidationActivity(since time.Duration) (ConsolidationActivity, error)
- func (c *Cortex) CreateDistilledTrace(spec DistilledTraceSpec) (string, error)
- func (c *Cortex) Demote(id, newTier string) error
- func (c *Cortex) DerivedBy(id string) ([]string, error)
- func (c *Cortex) DivergenceCount() (int, error)
- func (c *Cortex) EmbedBackfill(ctx context.Context, e Embedder, model string, opts EmbedBackfillOpts) (EmbedBackfillResult, error)
- func (c *Cortex) EmbeddingStatus(model string) (EmbeddingStatus, error)
- func (c *Cortex) EmitCoordinationEvent(action event.Action, windowID string, data any) error
- func (c *Cortex) EngagementStats() (EngagementStats, error)
- func (c *Cortex) Events(traceID string) ([]event.Event, error)
- func (c *Cortex) EventsSince(afterID string, limit int) ([]event.Event, error)
- func (c *Cortex) FindSimilar(traceID string, opts SimilarOpts) ([]SimilarMatch, error)
- func (c *Cortex) FindSimilarAs(traceID string, opts SimilarOpts, actor ReadActor, topN int) ([]SimilarMatch, error)
- func (c *Cortex) Get(id string) (*Row, error)
- func (c *Cortex) GetAs(id string, actor ReadActor) (*Row, error)
- func (c *Cortex) GetClock() (federation.VClock, error)
- func (c *Cortex) GraduationCandidates(minAge time.Duration) ([]PromotionCandidate, error)
- func (c *Cortex) HasConsolidationSuccessAfter(cutoff time.Time) (bool, error)
- func (c *Cortex) HybridSearch(ctx context.Context, e Embedder, query string, opts SemanticOpts, ...) ([]ScoredRow, error)
- func (c *Cortex) HybridSearchAs(ctx context.Context, e Embedder, query string, opts SemanticOpts, ...) ([]ScoredRow, error)
- func (c *Cortex) HybridSimilar(traceID string, opts SemanticOpts, weight float64) ([]ScoredRow, error)
- func (c *Cortex) HybridSimilarAs(traceID string, opts SemanticOpts, weight float64, actor ReadActor, topN int) ([]ScoredRow, error)
- func (c *Cortex) IngestExternalDelete(id string) error
- func (c *Cortex) LLMCandidates(window time.Duration) ([]PromotionCandidate, error)
- func (c *Cortex) LastMutationTime() (time.Time, error)
- func (c *Cortex) List(opts ListOptions) ([]Row, error)
- func (c *Cortex) LocalUsageSince(since string, limit int) ([]federation.TraceUsage, error)
- func (c *Cortex) MarkArchivedNoMove(id string) error
- func (c *Cortex) MarkRecoveredNoMove(id string) error
- func (c *Cortex) MarkTrashedNoMove(id string) error
- func (c *Cortex) MarkUnarchivedNoMove(id string) error
- func (c *Cortex) MergeClock(remote federation.VClock) error
- func (c *Cortex) MergeRemoteUsage(rows []federation.TraceUsage) error
- func (c *Cortex) MidEngagementSnapshot(olderThan time.Duration) (MidEngagementSnapshot, error)
- func (c *Cortex) MidLineageBreakdown() (MidLineageBreakdown, error)
- func (c *Cortex) OneSourceMidCount() (OneSourceMidCount, error)
- func (c *Cortex) Promote(id, newTier string) error
- func (c *Cortex) PromotionCandidates(tier string, window time.Duration) ([]PromotionCandidate, error)
- func (c *Cortex) PromotionLatency() (PromotionLatency, error)
- func (c *Cortex) PublicKey() string
- func (c *Cortex) Purge(days int) error
- func (c *Cortex) RebuildFTSIfStale() error
- func (c *Cortex) Recover(id string) error
- func (c *Cortex) Remove(id string) error
- func (c *Cortex) ReplayEvent(e event.Event) error
- func (c *Cortex) ResolveDivergence(divergenceID, acceptOrigin, customBody string) error
- func (c *Cortex) Search(query string, opts ListOptions) ([]Row, error)
- func (c *Cortex) SearchAs(query string, opts ListOptions, actor ReadActor, topN int) ([]Row, error)
- func (c *Cortex) SemanticSearch(ctx context.Context, e Embedder, query string, opts SemanticOpts) ([]ScoredRow, error)
- func (c *Cortex) SemanticSearchAs(ctx context.Context, e Embedder, query string, opts SemanticOpts, ...) ([]ScoredRow, error)
- func (c *Cortex) SemanticSimilar(traceID string, opts SemanticOpts) ([]ScoredRow, error)
- func (c *Cortex) SemanticSimilarAs(traceID string, opts SemanticOpts, actor ReadActor, topN int) ([]ScoredRow, error)
- func (c *Cortex) SetForceSourceLock(v bool)
- func (c *Cortex) SetTraceTags(id string, tags []string) error
- func (c *Cortex) SetTraceTagsAs(id string, tags []string, actor ReadActor) error
- func (c *Cortex) ShortTierCount() (int, error)
- func (c *Cortex) Sync() (SyncResult, error)
- func (c *Cortex) SyncWithOptions(opts SyncOptions) (SyncResult, error)
- func (c *Cortex) TagActivity(n int) ([]TagSummary, error)
- func (c *Cortex) TierStats() (TierStats, error)
- func (c *Cortex) TierVotes(id string) (int, error)
- func (c *Cortex) TopSearchedTraces(n int) ([]PopularTrace, error)
- func (c *Cortex) TraceFile(id string, archived bool) string
- func (c *Cortex) TracesDir() string
- func (c *Cortex) Trash(id string) error
- func (c *Cortex) TrashDir() string
- func (c *Cortex) TrashFile(id string) string
- func (c *Cortex) Unarchive(id string) error
- func (c *Cortex) Update(id string) error
- func (c *Cortex) UpdateAs(id string, actor ReadActor) error
- func (c *Cortex) UpdateFromFile(id string) error
- func (c *Cortex) Vote(id string, delta int, actor ReadActor) error
- type DistilledTraceSpec
- type EmbedBackfillOpts
- type EmbedBackfillResult
- type Embedder
- type EmbeddingStatus
- type EngagementStats
- type ErrTraceIDExists
- type FederationConfig
- type GraduationConfig
- type ListOptions
- type Manifest
- func (m Manifest) PeerLabelCollidesWithSelf(peerLabel string) bool
- func (m Manifest) ResolvedEmbeddingAPIKeyEnv() string
- func (m Manifest) ResolvedEmbeddingEndpoint() string
- func (m Manifest) ValidateConsolidation() error
- func (m Manifest) ValidateFederation() error
- func (m Manifest) ValidateSearch() error
- type MidEngagementSnapshot
- type MidLineageBreakdown
- type OneSourceMidCount
- type PeerEntry
- type PopularTrace
- type PromotionCandidate
- type PromotionLatency
- type PromotionStats
- type ReadActor
- type Row
- type ScoredRow
- type SearchConfig
- type SemanticOpts
- type SigningConfig
- type SigningKey
- type SimilarMatch
- type SimilarOpts
- type SyncOptions
- type SyncResult
- type TagSummary
- type TierStats
- type WatchConfig
Constants ¶
const ( FederationModeSync = "sync" // bidirectional: pull from peers + serve events FederationModePublish = "publish" // outbound only: serve events, never pull FederationModeSubscribe = "subscribe" // inbound only: pull from peers, refuse to serve )
Federation mode constants.
const ( PeerModeSync = "sync" // actively pull from this peer PeerModePaused = "paused" // configured but skipped by the syncer )
Peer mode constants.
const ( VerifyOff = "off" // do not verify event signatures VerifyWarn = "warn" // verify and log problems, but accept the event VerifyEnforce = "enforce" // reject events that fail verification )
Federation signature-verification mode constants. They control how replayed events are checked against their signatures.
const ( SearchModeLexical = "lexical" SearchModeSemantic = "semantic" SearchModeHybrid = "hybrid" )
Search-mode identifiers. These string values are part of the frozen v1.0 surface (the MCP `mode` param and cortex.md `default_mode`); don't rename.
const AccessKeyEnvVar = "NOEMA_MCP_KEY"
AccessKeyEnvVar is the environment variable that overrides cortex.md's access.shared_key_file. When set to a non-empty, non- whitespace value it is used as the MCP shared key, and any configured file path is recorded so the caller can warn about the override.
const DefaultSearchHitTopN = 3
DefaultSearchHitTopN is how many of a search's top-ranked results bump search_hit_count when an agent runs the query. Auto-injection providers (Hermes-style) typically fit 1–3 trace summaries into their memory budget, so we count the top three as "consumed" and ignore the rest of the result list. Tunable via SearchAs/FindSimilarAs callers if a different surface uses a wider window.
const ManifestVersion = 2
ManifestVersion is the current cortex.md schema version. Cortexes written at this version carry an `id` field; cortexes at any earlier version must be migrated via `noema migrate cortex-id` before federation will accept them.
const MaxSearchQueryLen = 1000
MaxSearchQueryLen caps the length of FTS5 search queries to prevent denial of service via expensive wildcard or deeply nested expressions.
Variables ¶
var ErrDistillSourceMissing = errors.New("source trace not found")
ErrDistillSourceMissing is returned when one of the declared source trace IDs does not exist in the cortex. Keeps the derived_from lineage honest — a consolidation event that points at a ghost trace breaks retrospective source-recall queries downstream.
var ErrDistillSourcesInsufficient = errors.New("distilled trace requires >= 2 source IDs")
ErrDistillSourcesInsufficient is returned when a caller tries to record a consolidation result with fewer than two source traces. Below that threshold the operation is a rename, not a consolidation — the event log records the category via the action constant, so this constraint protects the distinction.
var ErrSourceLocked = errors.New("trace is source-locked")
ErrSourceLocked is returned when a mutation is attempted on a source-locked trace from a foreign origin.
var ErrTierMismatch = errors.New("tier mismatch")
ErrTierMismatch is returned when a caller asserts an expected tier on AdminPurge and the actual row tier disagrees. The safety rail prevents fat-finger destruction of long-term rows while the caller thought they were purging something short-term.
Functions ¶
func IsTraceIDExists ¶ added in v0.10.4
IsTraceIDExists reports whether err is or wraps ErrTraceIDExists. Convenience for callers that only need to branch on the kind, not the metadata.
func KeyFingerprint ¶ added in v0.3.0
KeyFingerprint returns a non-secret SHA-256 fingerprint of an MCP shared key, formatted SSH-style as SHA256:<pair>:<pair>:... Safe to log, display in federation_status, and read aloud over an out-of-band channel when confirming a pairing.
func ParseSince ¶ added in v0.11.0
ParseSince parses the standard observability lookback flag. Accepts any duration string time.ParseDuration handles ("90m", "24h"), plus "d" (days) and "w" (weeks) which the stdlib doesn't recognise. Used by every CLI / MCP surface that takes a --since argument so a user who types "7d" once finds it works everywhere — the design doc's "--since <dur> universally" decision (§3).
An empty string returns zero — interpreted by callers as "no window / all time."
func ResolveTLSPaths ¶ added in v0.12.0
func ResolveTLSPaths(cortexDir string, cfg *AccessConfig) (certPath, keyPath string)
ResolveTLSPaths returns absolute cert/key paths from an AccessConfig, resolving any relative paths against cortexDir. Empty inputs map to empty outputs (no implicit defaults — TLS is opt-in). Returns ("", "") when cfg is nil or both fields are empty.
func SanitizeFTS5Query ¶ added in v0.7.0
SanitizeFTS5Query quotes each whitespace-delimited token that contains any FTS5 structural character (., -, :, /, (, ), etc.) so the parser treats it as a literal phrase. Tokens that are already quoted or use explicit FTS5 operators (AND, OR, NOT, prefix*) are passed through unchanged to preserve power-user syntax. A bareword token is anything composed solely of Unicode letters, digits, and underscore, with an optional single trailing '*' for prefix search.
func WriteManifest ¶ added in v0.3.0
WriteManifest writes the manifest back to cortex.md in the given directory. The output is always framed as markdown frontmatter: a `---` fence, the YAML, a closing `---` fence, then m.Body if non-empty. Legacy bare-YAML manifests are silently upgraded to framed form on the first write.
Types ¶
type AccessConfig ¶ added in v0.3.0
type AccessConfig struct {
// line is the shared bearer token. Relative paths are resolved
// against the cortex directory. The manifest itself never holds the
// secret — only a pointer to where it lives.
SharedKeyFile string `yaml:"shared_key_file,omitempty"`
// TLSCertPath and TLSKeyPath are optional paths to the PEM cert
// and private key the HTTP MCP server should present. When set,
// `noema serve --transport http` uses them as the default for
// --tls-cert/--tls-key (CLI flags still win), and
// `noema verify cortex` reads them to surface upcoming expiry.
// Relative paths are resolved against the cortex directory.
TLSCertPath string `yaml:"tls_cert_path,omitempty"`
TLSKeyPath string `yaml:"tls_key_path,omitempty"`
}
AccessConfig holds MCP endpoint authentication settings for cortex.md. When SharedKeyFile is set, the HTTP MCP endpoint runs in shared-key mode and every incoming request must carry a matching Authorization bearer header. See docs/design/mcp-auth-plan.md for the full design.
type AccessKey ¶ added in v0.3.0
type AccessKey struct {
// Value is the raw bearer token. It must never be logged, written to
// the event log, serialised to MCP responses, or echoed in error
// messages. Only the Fingerprint is safe to surface.
Value string
// Source is "env" (NOEMA_MCP_KEY), "file" (read from disk), or ""
// (open mode).
Source string
// Path is the absolute file path the key was read from when
// Source == "file", or the configured-but-overridden file path when
// Source == "env" and the manifest also declared a shared_key_file.
// Empty when neither applies.
Path string
// Fingerprint is a non-secret SHA-256 digest of Value, formatted
// SSH-style (e.g. SHA256:a3:f1:...:c2). Safe to log and display.
Fingerprint string
}
AccessKey is the resolved shared key for the HTTP MCP endpoint, along with metadata about where it came from and a non-secret fingerprint. The zero value represents open mode (no authentication).
func LoadAccessKey ¶ added in v0.3.0
func LoadAccessKey(cortexDir string, cfg *AccessConfig) (AccessKey, error)
LoadAccessKey resolves the active MCP shared key for a cortex.
Resolution order (highest priority first):
- NOEMA_MCP_KEY — if set to a non-empty value, wins. The configured file path is still recorded in AccessKey.Path so the caller can log that the env var overrode it.
- cfg.SharedKeyFile — read relative to cortexDir unless already absolute. The file is validated for permissions, size, and format (see loadKeyFile).
- Open mode — returns the zero AccessKey with a nil error.
Errors are returned when the env var is set to only whitespace, when a configured file cannot be read, when permissions are looser than 0600, when the file exceeds 4 KiB, when it is empty or whitespace only, or when it contains two or more non-empty lines.
func (AccessKey) EnvOverride ¶ added in v0.3.0
EnvOverride reports whether NOEMA_MCP_KEY took precedence over a configured access.shared_key_file. Callers use this to emit the one-line warning on startup.
type BackfillResult ¶ added in v0.3.0
type BackfillResult struct {
BackfilledIDs []string // active traces that received a synthetic create event
SkippedIDs []string // traces with no create event but currently archived/trashed
}
BackfillResult summarises a `noema events backfill` operation. The slices hold trace IDs (not row counts) so the caller can render them line-by-line for the operator's audit trail.
type ConsolidationActivity ¶ added in v0.11.0
type ConsolidationActivity struct {
// Since is the lookback window. JSON-skipped so the wire format
// can use a human-readable string in SinceLabel instead of the
// default time.Duration encoding (nanoseconds as int64), which
// would silently mislead anyone treating the field name as a
// hint about its unit.
Since time.Duration `json:"-"`
SinceLabel string `json:"since"` // "24h0m0s" — parseable via time.ParseDuration
SinceStart time.Time `json:"since_start"`
Daily []ConsolidationDay `json:"daily"`
Totals ConsolidationTotals `json:"totals"`
}
ConsolidationActivity reports the consolidation pipeline's recent behavior — election outcomes, promotions, and real distillation events — bucketed by UTC day and totaled over the window. Lets an operator answer "did consolidation actually do anything overnight, and was any of it the LLM path?" without hand-writing SQL against the events table.
type ConsolidationConfig ¶ added in v0.10.0
type ConsolidationConfig struct {
// Enabled is the master opt-in. The feature ships off by default so
// existing cortexes are unaffected until users explicitly turn it on.
Enabled bool `yaml:"enabled,omitempty"`
// Cron is the nightly trigger time in "HH:MM" local-clock format
// (e.g. "03:00"). Empty means no cron trigger.
Cron string `yaml:"cron,omitempty"`
// IdleMinutes fires a pass after N minutes of no trace mutations.
// Zero disables the idle trigger. Cooldown equal to IdleMinutes is
// enforced so a quiet cortex doesn't consolidate on every tick.
IdleMinutes int `yaml:"idle_minutes,omitempty"`
// ThresholdShort fires a pass when the short-term tier count
// exceeds this many active traces. Zero disables the trigger.
// Hysteresis: once tripped, re-arms only when the count drops
// back below 0.8 * ThresholdShort so a cortex hovering near the
// threshold doesn't thrash.
ThresholdShort int `yaml:"threshold_short,omitempty"`
// WindowHours bounds the candidate pool for a pass to traces
// created within the last N hours. Zero defaults to 24.
WindowHours int `yaml:"window_hours,omitempty"`
// LLMEnabled opts into the LLM-driven distillation path used by
// `noema consolidate`. When false (default), the in-process agent
// runs pure-heuristic 1:1 promotion only. When true, operators are
// expected to run `noema consolidate` periodically (or wire the
// subcommand up to cron / launchd) so clusters get distilled into
// mid-tier memories instead of promoted one-to-one.
LLMEnabled bool `yaml:"llm_enabled,omitempty"`
// AutoDistillationEnabled opts the in-process agent into running the
// LLM distillation pipeline on every scheduled trigger (cron / idle /
// threshold), before the heuristic promotion + graduation passes.
// Default false: LLMEnabled alone wires up `noema consolidate` but
// leaves the background agent on cheap heuristic-only work, matching
// the pre-v0.10.1 behaviour. Requires LLMEnabled, LocalLLMEndpoint,
// and ModelName — ValidateConsolidation refuses the config otherwise.
//
// Failure semantics: if the LLM endpoint is unreachable or the pass
// errors, the distillation result is logged and swallowed so the
// chained heuristic + graduation passes still fire. An offline LLM
// should not block the cheap maintenance work.
AutoDistillationEnabled bool `yaml:"auto_distillation_enabled,omitempty"`
// ModelTier is the prompt-style profile the consolidation pipeline
// uses when calling the LLM: "small" (7B-13B, multi-step template),
// "large" (30B-70B, same plus confidence step), or "frontier"
// (single-shot JSON). See docs/plans/consolidation-plan.md §6 in
// the Noema-design repo for the full profile matrix.
ModelTier string `yaml:"model_tier,omitempty"`
// LocalLLMEndpoint is the OpenAI-compatible base URL to post
// chat-completion requests to. Covers Ollama (/v1), LMStudio,
// llama.cpp server, vLLM, and OpenAI itself. Empty disables the
// LLM path even when LLMEnabled is true; `noema consolidate` exits
// with a clear error rather than trying to guess a default.
LocalLLMEndpoint string `yaml:"local_llm_endpoint,omitempty"`
// ModelName is the model identifier passed in the `model` field of
// the chat-completion request body (e.g. "llama3.1:70b",
// "claude-opus-4-7", "gpt-4o"). Must match what the endpoint
// recognizes — no translation layer here.
ModelName string `yaml:"model_name,omitempty"`
// APIKeyEnv names an environment variable whose value is attached
// as a Bearer token to outgoing requests. Empty means no auth
// header — correct for local runners like Ollama that don't care.
// The key itself never lives in cortex.md; this is a pointer to
// where the operator keeps it, matching the access.shared_key_file
// pattern for the MCP server.
APIKeyEnv string `yaml:"api_key_env,omitempty"`
// Graduation controls the mid→long promotion heuristic (Phase 15).
// Leaving the block unset uses the defaults. Setting
// Graduation.Enabled=false on an existing cortex keeps mid as the
// terminal tier for automatic promotion — useful for operators who
// want to curate the long tier by hand via `noema memory promote`.
Graduation *GraduationConfig `yaml:"graduation,omitempty"`
// WatchdogTimeout overrides the election watchdog's claim-staleness
// ceiling — the duration after which observers treat a
// consolidation_claim with no matching success/fail as orphaned and
// emit a closing fail (reason=watchdog_expired).
//
// The default 10 minutes is generous for consolidator passes backed
// by GPU-class endpoints, but slow LLM endpoints (low-VRAM laptops,
// CPU inference, congested API providers) can take longer than that
// for legitimate distillation work. When the pass takes longer than
// the watchdog budget, peers correctly emit watchdog_expired — but
// the pass eventually succeeds anyway, leaving both events for the
// same window in the log. Cosmetically noisy, not a correctness bug;
// raise the timeout on cortexes whose LLM is slower than the
// federation typical to avoid the noise.
//
// Format: any duration go's time.ParseDuration accepts ("10m",
// "20m", "1h"). Empty preserves the 10-minute default. See
// internal/consolidation/watchdog.go for the consumer.
WatchdogTimeout string `yaml:"watchdog_timeout,omitempty"`
}
ConsolidationConfig controls the background memory-consolidation agent. All three triggers (cron, idle, threshold) are composable — set any combination, the agent fires on whichever triggers first. Leaving all three empty disables the agent even when Enabled is true.
See docs/plans/consolidation-plan.md §4 in the Noema-design repo for the cadence design. Model-tier and local-LLM fields are deferred to later phases; this config block only covers scheduling for now.
func (*ConsolidationConfig) EffectiveModelTier ¶ added in v0.10.0
func (cc *ConsolidationConfig) EffectiveModelTier() string
EffectiveModelTier returns the configured model-tier profile or "large" as the default. Large is the conservative middle ground — it works well on local 30B-70B models and stays safe on frontier models that would also accept tighter prompts.
func (*ConsolidationConfig) EffectiveWatchdogTimeout ¶ added in v0.10.0
func (cc *ConsolidationConfig) EffectiveWatchdogTimeout() time.Duration
EffectiveWatchdogTimeout returns the configured watchdog claim- staleness duration, or the 10-minute default when unset / unparseable. We tolerate parse failure with a fallback rather than refusing to start the agent: a typo in this knob shouldn't take a peer offline, and 10 minutes is a safe ceiling for any setup we've observed.
Manifest validation (separate code path) catches the unparseable case at config-load time and surfaces a clear error to the operator; this method is the runtime-side belt-and-suspenders for any path that bypasses validation.
func (*ConsolidationConfig) EffectiveWindowHours ¶ added in v0.10.0
func (cc *ConsolidationConfig) EffectiveWindowHours() time.Duration
EffectiveWindowHours returns the window duration with the 24h default applied when unset.
func (*ConsolidationConfig) HasTrigger ¶ added in v0.10.0
func (cc *ConsolidationConfig) HasTrigger() bool
HasTrigger reports whether at least one scheduling trigger (cron / idle_minutes / threshold_short) is configured. Used by startConsolidator to decide whether to spin up the agent and by startEligibility to decide whether this peer should advertise a non-zero rank: a peer that advertises eligibility but never runs is a phantom winner that other peers defer to indefinitely, stalling the ring. Treating "no triggers" the same as "consolidation off" in the rank loop closes that footgun.
type ConsolidationDay ¶ added in v0.11.0
type ConsolidationDay struct {
Date string `json:"date"` // YYYY-MM-DD (UTC)
Success int `json:"success"`
Fail int `json:"fail"`
LostElection int `json:"lost_election"`
Claim int `json:"claim"`
Promote int `json:"promote"`
Distill int `json:"distill"`
}
ConsolidationDay buckets one UTC date's event counts. Days with zero activity are omitted to keep the JSON compact — consumers should treat a missing date as "no events that day" rather than "missing data."
LostElection splits the three "preempted" fail reasons (peer_outranked, no_winner_at_recheck, context_canceled) out of Fail. The election gate emits those when the local peer claimed but a higher-ranked peer was going to run anyway — that's the gate working as designed, not a pipeline error. Operators reading the surface needed a way to tell "the LLM endpoint is flapping" apart from "two peers raced and the other one won."
type ConsolidationTotals ¶ added in v0.11.0
type ConsolidationTotals struct {
Success int `json:"success"`
Fail int `json:"fail"`
LostElection int `json:"lost_election"`
Claim int `json:"claim"`
Promote int `json:"promote"`
Distill int `json:"distill"`
}
ConsolidationTotals sums each action over the entire window.
type Cortex ¶
type Cortex struct {
ID string // ULID, stable across renames; the federation identity key
Name string // human-readable display label
Dir string
DB *db.DB
// contains filtered or unexported fields
}
func Open ¶
Open opens an existing Cortex by directory path. It ensures required subdirectories exist and auto-purges expired trash.
func (*Cortex) AdminPurge ¶ added in v0.10.0
AdminPurge is the sanctioned ceremonious-delete path for any trace, including long-term ones the immutability trigger would otherwise block. It is deliberately verbose at the call site (expected tier, reason, and the CLI flag --confirm to reach it) so accidental invocation is hard.
Behaviour:
- expectedTier must equal the trace's actual tier, else ErrTierMismatch. Prevents the classic accident of purging a long-term trace while thinking it's short-term.
- When hard is false (default), the row is tombstoned: body wiped to "[purged: <reason>]", purge_reason/purged_at stamped, file deleted from disk, tags cleared, FTS index updated. The DB row stays so lineage references continue to resolve and federation peers can apply the same tombstone on replay.
- When hard is true, the row and all lineage references to it are removed outright. Reserved for GDPR-style mandates where even the ID must not persist.
Emits ActionPurgeLongTerm for tier='long' soft-purges, ActionPurgeHard for any --hard, and ActionPurge for short/mid soft-purges. The event data payload carries the original content_hash as durable proof of what was destroyed, plus the reason and actor identity.
For long-term rows, the immutability trigger is suspended for the duration of the transaction via DROP+re-CREATE using the trigger's own SQL as recorded in sqlite_master. If the tx rolls back for any reason, SQLite restores the trigger automatically along with the other DDL changes, so a mid-purge failure cannot leave the database in a state where long-term immutability is silently broken.
func (*Cortex) Append ¶ added in v0.6.0
Append adds content to the end of an existing trace's body. It reads the current file, appends the new content (with a newline separator if the existing body doesn't already end with one), recomputes the content hash, and emits a standard "update" event. Designed for fire-and-forget logging where agents append to a running trace without consuming its full body.
func (*Cortex) ApplyExternalPurge ¶ added in v0.9.0
ApplyExternalPurge handles the case where a trace file vanishes from trash/traces/ — the user emptied Noema's trash via the filesystem. Deletes the DB row and emits ActionPurge. This is the only path outside the age-based Purge() that permanently removes a trace through the event log, so federation peers see it too.
func (*Cortex) ArchiveDir ¶
func (*Cortex) BackfillCreateEvents ¶ added in v0.3.0
func (c *Cortex) BackfillCreateEvents(dryRun bool) (BackfillResult, error)
BackfillCreateEvents emits synthetic `create` events for any active trace that lacks one in the event log. This folds traces that pre-date the event log — or that landed via `noema sync`, which intentionally emits no events because it is reconciliation, not a semantic mutation — back into the federated history so peers can replay them.
Each backfilled event uses a fresh ULID, the local cortex_id and origin, the current wall-clock time as the event timestamp, and a JSON snapshot of the trace's current frontmatter + body. The trace's own `created` field (in the markdown frontmatter and the DB row) is left untouched, so the audit trail still surfaces "this happened on <real date>" — the event timestamp only records when the backfill ran. Using wall-clock time keeps per-cortex ULID monotonicity and avoids the event log lying about when the event was actually appended.
Archived and trashed traces are skipped: emitting only a `create` event for them would leave federation diverged (peers would materialise the trace as active and never see the archive/trash). Recover or unarchive the trace first if it needs to federate.
If dryRun is true, no events are written and the vector clock is not touched, but the returned result still lists every trace that would have been backfilled or skipped — so operators can preview before committing.
The iteration is idempotent: traces that already have a create event in the log (whether locally emitted or replayed from a peer) are not in the candidate set, so running this twice is a no-op on the second pass.
func (*Cortex) CheckSourceLock ¶ added in v0.5.0
CheckSourceLock returns ErrSourceLocked if the trace is source-locked by a foreign origin. The check is skipped when forceSourceLock is set.
func (*Cortex) ConsolidationActivity ¶ added in v0.11.0
func (c *Cortex) ConsolidationActivity(since time.Duration) (ConsolidationActivity, error)
ConsolidationActivity queries the events table for the consolidation pipeline's recent actions, bucketed by UTC day. since is the look-back window; a zero or negative duration is treated as "since the beginning of the event log." Active traces only — purged tombstones and archived rows are unaffected because their promote events remain in the log as part of the immutable audit trail.
func (*Cortex) CreateDistilledTrace ¶ added in v0.10.0
func (c *Cortex) CreateDistilledTrace(spec DistilledTraceSpec) (string, error)
CreateDistilledTrace materialises the result of an LLM-driven consolidation pass: a new mid-tier trace whose derived_from lineage points at the short-term sources that fed the distillation. Emits ActionConsolidate alongside the standard ActionCreate so the event log carries both "a new trace exists" and "this is why it exists / which model produced it / how confident" as separate, replayable records.
Source handling: sources stay at their original tier. The distilled trace is the retrievable summary, and derived_from lineage keeps the originals reachable for the cases where the distillation's compression is lossy. Earlier versions auto-promoted every source to mid alongside the distillation, which polluted the mid tier with zero-engagement traces (a single ActionConsolidate run could promote 5+ sources that had no reads, modifies, or votes of their own). Re-clustering protection is now provided by LLMCandidates, which excludes any short-tier trace that already appears as a source in a past ActionConsolidate event — so a source isn't re-fed into a duplicate distillation just because it stayed at short.
func (*Cortex) Demote ¶ added in v0.10.0
Demote steps a trace back a tier: mid -> short. Long demotion is reserved for the admin-purge ceremony (Phase 6) which suspends the immutability trigger explicitly; Demote refuses it here to keep the "long is terminal in routine operation" invariant intact. Emits ActionDemote with {from, to}.
func (*Cortex) DerivedBy ¶ added in v0.3.0
DerivedBy returns all trace IDs that list the given trace as a source.
func (*Cortex) DivergenceCount ¶ added in v0.3.0
DivergenceCount returns the number of unresolved divergence traces.
func (*Cortex) EmbedBackfill ¶ added in v0.14.0
func (c *Cortex) EmbedBackfill(ctx context.Context, e Embedder, model string, opts EmbedBackfillOpts) (EmbedBackfillResult, error)
EmbedBackfill computes and stores embeddings for traces that are missing or stale for model (or all embeddable traces when Force). It is idempotent (a second run with no changes does nothing), resumable (each batch is committed before the next, so a mid-run failure keeps prior progress), and safe to run while serving (WAL). It never blocks a mutation — callers invoke it explicitly (CLI) or on a schedule.
Body text is read from each trace's markdown file (the source of truth); the stored source_hash is the trace's content_hash at selection time, so a later body edit re-marks the row stale.
func (*Cortex) EmbeddingStatus ¶ added in v0.14.0
func (c *Cortex) EmbeddingStatus(model string) (EmbeddingStatus, error)
EmbeddingStatus computes coverage counts for model. A model of "" reports everything as stale/missing (nothing matches an empty model), which is the correct signal for a cortex that hasn't configured semantic search.
func (*Cortex) EmitCoordinationEvent ¶ added in v0.10.0
emitEvent appends an event to the log inside the given transaction, incrementing the local vector clock. All reads/writes go through the tx to avoid SQLite lock contention. Vector clocks are keyed on the cortex ID (a stable ULID), not the cortex name — see docs/design/cortex-uuid-plan.md. EmitCoordinationEvent emits an event that isn't tied to a specific trace mutation — used by the consolidation election protocol for Claim / Success / Fail events (see consolidation-plan.md §14). The windowID serves as trace_id in the events table (the column is NOT NULL but doesn't enforce a foreign key on traces — coordination events use a synthetic ID scoped to the election window).
Data is JSON-marshaled by the caller's choice of struct; pass nil to emit an event with an empty payload.
func (*Cortex) EngagementStats ¶ added in v0.10.4
func (c *Cortex) EngagementStats() (EngagementStats, error)
EngagementStats sums read/search-hit/modify counters across every peer's row for every active trace. Active = not archived, trashed, or purged. The federation MAX-merges these counters per trace at sync time, so summing all rows here gives the federation-wide signal even on a single peer.
func (*Cortex) Events ¶ added in v0.3.0
Events returns the event log for a specific trace, ordered chronologically.
func (*Cortex) EventsSince ¶ added in v0.3.0
EventsSince returns events after the given ULID cursor, up to limit.
func (*Cortex) FindSimilar ¶ added in v0.10.2
func (c *Cortex) FindSimilar(traceID string, opts SimilarOpts) ([]SimilarMatch, error)
FindSimilar returns traces whose token overlap with the source trace scores best under FTS5 BM25. The source trace itself is excluded. Trashed traces are always excluded; archived traces are excluded by default.
The ranking is "document-as-query": tokenize the source's title + body + tag text, drop English stopwords and short tokens, take the top-K most frequent remaining terms, and submit them as an OR-joined FTS5 query. SQLite's BM25 then scores every candidate against that query in one shot. This is intentionally simple — no embedding model, no external service, no schema change. Quality is bounded by FTS5's porter+ASCII tokenizer and the embedded stopword list.
func (*Cortex) FindSimilarAs ¶ added in v0.10.4
func (c *Cortex) FindSimilarAs(traceID string, opts SimilarOpts, actor ReadActor, topN int) ([]SimilarMatch, error)
FindSimilarAs is the actor-aware counterpart to FindSimilar. Same top-N + actor + long-tier rules as SearchAs.
func (*Cortex) GetAs ¶ added in v0.10.0
GetAs is the actor-aware counterpart to Get. It returns the same Row and bumps read_count + last_read_at when the caller is ActorAgent. Other actors (ActorHuman, ActorSystem) short-circuit to plain Get behavior.
The bump writes to trace_usage keyed on (trace_id, local cortex ID) — CRDT-style per-peer counters. Federated peers receive each other's counters via sync_read_signal and the heuristic queries the aggregate. Long-tier traces skip the bump because the immutability trigger blocks updates; revisit if long-tier usage signal ever proves useful.
func (*Cortex) GetClock ¶ added in v0.3.0
func (c *Cortex) GetClock() (federation.VClock, error)
GetClock returns the current vector clock.
func (*Cortex) GraduationCandidates ¶ added in v0.10.0
func (c *Cortex) GraduationCandidates(minAge time.Duration) ([]PromotionCandidate, error)
GraduationCandidates returns every active tier='mid' trace older than minAge. The mirror of PromotionCandidates — that one narrows to the rolling short-term pool for short→mid evaluation; graduation evaluates mid→long on traces that have had time to prove durability, so the inequality flips (`created_at <= cutoff`) and the lower bound is open. Archived/trashed/purged rows are excluded for the same reason as PromotionCandidates.
func (*Cortex) HasConsolidationSuccessAfter ¶ added in v0.10.0
HasConsolidationSuccessAfter reports whether at least one consolidation_success event exists in the local log with timestamp strictly greater than the given cutoff. Used by the consolidation agent's cron retry-on-idle path to decide whether the most recent trigger fire actually resulted in a pass running, locally or on any peer (peer events arrive via federation replay).
The cutoff is compared as RFC3339 strings — lexicographic order matches chronological order for that format with consistent UTC suffix, matching how the events table stores timestamps. A LIMIT 1 keeps the query cheap; we only care about existence, not count.
func (*Cortex) HybridSearch ¶ added in v0.14.0
func (c *Cortex) HybridSearch(ctx context.Context, e Embedder, query string, opts SemanticOpts, weight float64) ([]ScoredRow, error)
HybridSearch fuses lexical (FTS5 BM25) and semantic (embedding cosine) rankings of the query via Reciprocal Rank Fusion, weighted by weight (0 = pure lexical, 1 = pure semantic; clamped). Both rankers contribute their full ranked lists; the fused top opts.Limit is returned.
func (*Cortex) HybridSearchAs ¶ added in v0.14.0
func (c *Cortex) HybridSearchAs(ctx context.Context, e Embedder, query string, opts SemanticOpts, weight float64, actor ReadActor, topN int) ([]ScoredRow, error)
HybridSearchAs is the actor-aware counterpart to HybridSearch.
func (*Cortex) HybridSimilar ¶ added in v0.14.0
func (c *Cortex) HybridSimilar(traceID string, opts SemanticOpts, weight float64) ([]ScoredRow, error)
HybridSimilar fuses lexical FindSimilar and SemanticSimilar rankings for a source trace. Like HybridSearch, weight blends the two (0 = lexical, 1 = semantic).
func (*Cortex) HybridSimilarAs ¶ added in v0.14.0
func (c *Cortex) HybridSimilarAs(traceID string, opts SemanticOpts, weight float64, actor ReadActor, topN int) ([]ScoredRow, error)
HybridSimilarAs is the actor-aware counterpart to HybridSimilar.
func (*Cortex) IngestExternalDelete ¶ added in v0.9.0
IngestExternalDelete handles the case where a trace file vanishes from traces/ or archive/traces/ entirely (user deleted via Finder or rm). Restores the last known body from the local event log into trash/traces/<id>.md so the delete is recoverable, then stamps trashed_at and emits ActionTrash. When no event snapshot exists (pre-event-log traces), skips the restore and emits the event anyway with an empty body so federation peers still see the state transition.
Source-locked foreign traces are refused with ErrSourceLocked — the watcher must skip rather than poison downstream peers with a tamper.
func (*Cortex) LLMCandidates ¶ added in v0.10.4
func (c *Cortex) LLMCandidates(window time.Duration) ([]PromotionCandidate, error)
LLMCandidates returns the short-tier candidate pool for the LLM distillation pass — same shape as PromotionCandidates, but with already-consolidated sources filtered out. A trace is considered "already consolidated" if its ID appears in the source_ids array of any past ActionConsolidate event.
CreateDistilledTrace used to keep these out of the pool by promoting every source to mid as a side effect; that polluted mid with zero-engagement traces. Filtering on the event log instead leaves sources at their natural tier while still preventing the next pass from re-clustering them into a duplicate distillation.
json_each is part of SQLite's JSON1 extension, which is enabled by modernc.org/sqlite at compile time; no driver flag is needed.
func (*Cortex) LastMutationTime ¶ added in v0.10.0
LastMutationTime returns the timestamp of the most recent event in the local log. Used by the consolidation agent's idle trigger to decide whether the cortex has been quiet long enough to consolidate. Returns zero time (not an error) on an empty log so a cortex with no history yet reads as "idle since beginning of time".
func (*Cortex) LocalUsageSince ¶ added in v0.10.0
func (c *Cortex) LocalUsageSince(since string, limit int) ([]federation.TraceUsage, error)
LocalUsageSince returns trace_usage rows owned by the local peer (peer_cortex_id = c.ID) with updated_at > since, ordered by updated_at so callers can advance their cursor to the last row's UpdatedAt on each batch. A peer only publishes its own deltas; remote rows we've synced in are never re-broadcast (prevents amplification loops and keeps bandwidth linear).
A zero-value since returns everything this peer owns.
func (*Cortex) MarkArchivedNoMove ¶ added in v0.9.0
MarkArchivedNoMove updates the DB to mark a trace as archived and emits an ActionArchive event — without moving any files. The filesystem watcher uses this when an external tool (e.g. Finder drag) has already moved the file from traces/ to archive/traces/. Returns nil if the trace is already archived (idempotent). Errors if the trace is in the trash.
func (*Cortex) MarkRecoveredNoMove ¶ added in v0.9.0
MarkRecoveredNoMove clears trashed_at and emits ActionRecover without moving the file. Used when the watcher sees a trashed trace's file reappear in traces/ or archive/traces/.
func (*Cortex) MarkTrashedNoMove ¶ added in v0.9.0
MarkTrashedNoMove stamps trashed_at and emits ActionTrash without moving the file. Used when the watcher sees a file appear in trash/traces/ via an external move. Clears archived_at to match existing Trash semantics. Idempotent if already trashed.
func (*Cortex) MarkUnarchivedNoMove ¶ added in v0.9.0
MarkUnarchivedNoMove clears archived_at and emits ActionUnarchive without moving the file. Used by the watcher when a file reappears in traces/ after being in archive/traces/. Idempotent if already unarchived.
func (*Cortex) MergeClock ¶ added in v0.3.0
func (c *Cortex) MergeClock(remote federation.VClock) error
MergeClock merges a remote vector clock into the local clock. The merge is capped at federation.MaxVClockEntries to prevent a malicious peer from inflating the clock with synthetic cortex IDs.
func (*Cortex) MergeRemoteUsage ¶ added in v0.10.0
func (c *Cortex) MergeRemoteUsage(rows []federation.TraceUsage) error
MergeRemoteUsage upserts a batch of rows from a remote peer into trace_usage using CRDT MAX-merge semantics. Safe against out-of-order arrivals and duplicate deliveries — an older row re-arriving after a newer one leaves the stored values untouched. A peer must never call this with rows whose peer_cortex_id matches its own (that would let a remote overwrite the local peer's authoritative counters); the caller is responsible for that invariant, but as a guard we skip any such rows instead of applying them.
Rows for trace_ids that don't exist locally yet are inserted anyway — the corresponding create event will arrive separately via sync_events and the FK enforces consistency eventually. If the create never shows up (peer disagrees about trace existence) the orphan is harmless; the aggregate query joins traces as LEFT so it's simply ignored.
func (*Cortex) MidEngagementSnapshot ¶ added in v0.10.4
func (c *Cortex) MidEngagementSnapshot(olderThan time.Duration) (MidEngagementSnapshot, error)
MidEngagementSnapshot reports zero-engagement counts for the mid tier — total and the older subset (age >= the supplied threshold, typically the graduation min-age so transient new mids don't inflate the number).
func (*Cortex) MidLineageBreakdown ¶ added in v0.10.4
func (c *Cortex) MidLineageBreakdown() (MidLineageBreakdown, error)
MidLineageBreakdown bucketizes active mid-tier traces by their outbound derived_from count (how many sources THIS trace has, not how many others reference it). v_derived_from_count is the inbound view used by the heuristic; here we group trace_lineage by trace_id directly to get the outbound count.
func (*Cortex) OneSourceMidCount ¶ added in v0.11.0
func (c *Cortex) OneSourceMidCount() (OneSourceMidCount, error)
OneSourceMidCount returns the current count of active mid-tier traces with derived_from_count == 1 (the bucket PR #86 narrowed but can't fully eliminate) and the count of distinct traces promoted into that bucket within the last 7 days. A healthy gate keeps PromotedLast7d at zero; a non-zero value names the operationally useful signal "the 1-source leak path is open again, go investigate."
func (*Cortex) Promote ¶ added in v0.10.0
Promote advances a trace to the next memory tier: short -> mid, or mid -> long. Cross-skips (short -> long) and reverse transitions are refused; callers use Demote for mid -> short, and long is terminal from routine operation (the admin-purge path in Phase 6 handles any legitimate long-term exit). Emits ActionPromote with {from, to} so federation peers replicate the same transition.
Only the DB tier column is updated; the on-disk frontmatter keeps whatever tier value was last written to it and re-syncs to DB state on the next legitimate Cortex.Update (see Phase 1's file-drift rail). Body bytes are untouched, so content_hash stays valid and the filesystem watcher's loopback detection skips the rewrite.
func (*Cortex) PromotionCandidates ¶ added in v0.10.0
func (c *Cortex) PromotionCandidates(tier string, window time.Duration) ([]PromotionCandidate, error)
PromotionCandidates returns every active trace in the given tier whose created_at falls within the rolling window. The caller does the scoring; this method is only responsible for narrowing to the pool worth considering. Archived, trashed, and purged rows are excluded — only memory currently in use is a candidate for promotion.
derived_from_count joins the lineage view added in migration 008 so the scorer can weight "others reference this" alongside reads and modifies without an extra round-trip per candidate.
func (*Cortex) PromotionLatency ¶ added in v0.11.0
func (c *Cortex) PromotionLatency() (PromotionLatency, error)
PromotionLatency walks every promote event in the log and computes the elapsed time since the trace entered the prior tier. short→mid uses traces.created_at as the start (the vast majority of traces are born at short). mid→long uses the most recent promote-to-mid event for that trace, falling back to created_at for traces created directly at mid (manual promotion). The result is sorted in Go to derive p50/p95.
func (*Cortex) PublicKey ¶ added in v0.15.0
PublicKey returns this cortex's "ed25519:<base64>" federation signing public key, or "" if the cortex is unsigned. It is advertised to peers through the cortex_identity handshake so they can pin it and verify this cortex's events.
func (*Cortex) Purge ¶ added in v0.2.0
Purge permanently deletes traces that have been in the trash for more than days days. A days value of 0 is treated as 30.
func (*Cortex) RebuildFTSIfStale ¶ added in v0.9.2
RebuildFTSIfStale repopulates traces_fts when it contains fewer rows than the traces table (including trashed, archived, and active rows — everything that should be searchable). The mismatch is expected exactly once per cortex, immediately after migration 008 runs and replaces the virtual table. On fresh cortexes and subsequent opens the counts match and this is a no-op.
The rebuild walks every row in the traces table, reads the trace's body from disk (since body is not in SQL), and re-inserts all four FTS columns. Rows whose files are missing on disk are inserted with empty body so at least their title and tags remain searchable — operators can fix those with `noema sync` later.
Runs outside any caller's transaction because it iterates many traces and couldn't share a short-lived transaction with them cleanly. Each row is reinserted in its own tx so a mid-rebuild failure leaves the index partially populated rather than empty.
func (*Cortex) Recover ¶ added in v0.2.0
Recover moves a trace out of the trash and back to the active traces directory.
func (*Cortex) Remove ¶
Remove permanently deletes a trace from disk and the database. Use Trash for recoverable deletion.
func (*Cortex) ReplayEvent ¶ added in v0.3.0
ReplayEvent materializes a remote event locally without emitting a new event. The remote event is stored in the local log with its original ID and origin.
func (*Cortex) ResolveDivergence ¶ added in v0.3.0
ResolveDivergence resolves a divergence trace by either picking one of the versions stored in the divergence body (by origin name) or applying a caller-supplied custom merge. Exactly one of acceptOrigin or customBody must be non-empty. The divergence trace is trashed once the original is updated.
func (*Cortex) SearchAs ¶ added in v0.10.4
SearchAs is the actor-aware counterpart to Search. It runs the regular FTS5 search and, when the caller is ActorAgent, bumps search_hit_count on the top-N results. topN <= 0 falls back to DefaultSearchHitTopN.
search_hit_count is a separate signal from read_count by design: a deliberate get_trace (noema_recall) is a stronger signal than "your query happened to surface this trace alongside others." Keeping them distinct lets the graduation gate weight them — currently it just sums them, but separating the columns preserves the option to weight later without a schema change.
Long-tier results are skipped because the immutability trigger blocks any UPDATE on the trace row. trace_usage rows are independent of the trace row, but bumping a counter on a long-tier trace is meaningless since long-tier traces don't graduate further. Trashed/archived rows can still receive bumps — Search already filters them based on opts, and an agent searching --all is meaningfully consuming the result.
func (*Cortex) SemanticSearch ¶ added in v0.14.0
func (c *Cortex) SemanticSearch(ctx context.Context, e Embedder, query string, opts SemanticOpts) ([]ScoredRow, error)
SemanticSearch embeds the query with e and ranks the cortex's stored vectors (for opts.Model) by cosine similarity. Trashed traces are always excluded; archived traces are excluded unless opts.IncludeArchived. An empty query returns no results.
func (*Cortex) SemanticSearchAs ¶ added in v0.14.0
func (c *Cortex) SemanticSearchAs(ctx context.Context, e Embedder, query string, opts SemanticOpts, actor ReadActor, topN int) ([]ScoredRow, error)
SemanticSearchAs is the actor-aware counterpart to SemanticSearch: same top-N search_hit_count bump for ActorAgent as SearchAs.
func (*Cortex) SemanticSimilar ¶ added in v0.14.0
func (c *Cortex) SemanticSimilar(traceID string, opts SemanticOpts) ([]ScoredRow, error)
SemanticSimilar ranks other traces against the stored vector of traceID (no query embedding — it reuses the source trace's own vector). The source trace must already be embedded for opts.Model.
func (*Cortex) SemanticSimilarAs ¶ added in v0.14.0
func (c *Cortex) SemanticSimilarAs(traceID string, opts SemanticOpts, actor ReadActor, topN int) ([]ScoredRow, error)
SemanticSimilarAs is the actor-aware counterpart to SemanticSimilar.
func (*Cortex) SetForceSourceLock ¶ added in v0.5.0
SetForceSourceLock enables or disables the source-lock override. When enabled, mutations on source-locked traces succeed with a warning instead of being refused. Intended for CLI --force flags only.
func (*Cortex) SetTraceTags ¶ added in v0.17.0
SetTraceTags replaces a trace's retrieval tags without mutating immutable trace fields such as updated_at or content_hash. Tags live outside the guarded traces row, so this remains legal for long-tier traces while still keeping the tag index, FTS, file frontmatter, and federation event log in sync.
func (*Cortex) SetTraceTagsAs ¶ added in v0.17.0
SetTraceTagsAs is the actor-aware counterpart to SetTraceTags. It keeps metadata cleanup on the narrow tag mutation path while preserving the same agent modify-count signal used by UpdateAs for mutable tiers.
func (*Cortex) ShortTierCount ¶ added in v0.10.0
ShortTierCount returns the number of active (not archived, trashed, or purged) short-term traces. Used by the consolidation agent's threshold trigger.
func (*Cortex) Sync ¶ added in v0.2.2
func (c *Cortex) Sync() (SyncResult, error)
Sync reconciles the database with the current state of the markdown files on disk. It walks traces/, archive/traces/, and trash/traces/, upserts every file it finds, and reports orphaned DB rows (not deleted — just reported).
func (*Cortex) SyncWithOptions ¶ added in v0.3.0
func (c *Cortex) SyncWithOptions(opts SyncOptions) (SyncResult, error)
SyncWithOptions is Sync with explicit options. See SyncOptions.
func (*Cortex) TagActivity ¶ added in v0.11.0
func (c *Cortex) TagActivity(n int) ([]TagSummary, error)
TagActivity returns the top-N tags ranked by total engagement (search hits primary, reads secondary). Empty cortex returns an empty slice (not nil) so JSON serializes as `[]` rather than `null` — JSON consumers shouldn't have to special-case absence.
func (*Cortex) TierStats ¶ added in v0.10.0
TierStats counts active traces by tier and purged tombstones. Archived and trashed traces are excluded so the numbers reflect "memory currently in use" — the signal users reason about when deciding whether to tune consolidation thresholds.
func (*Cortex) TierVotes ¶ added in v0.10.0
Vote records a tier-preference signal for a trace. Positive delta nudges the consolidation scorer toward promotion; negative delta nudges toward demotion or keeping the trace low-tier. delta must be +1 or -1 — anything else is rejected to keep the event log unambiguous.
ActorSystem callers are rejected because voting is by definition an explicit-intent signal and the system has no intent. Agents vote on a user's behalf (e.g. "user said this really matters"); humans vote from the TUI.
Vote works on all tiers, including long-term: the refined immutability trigger (migration 009) lets tier_votes change on tier='long' rows while still blocking content and identity fields. Votes on long-term are a meaningful signal that a base-truth memory is still being referenced. TierVotes returns the current tier_votes count for a trace. Surfaced to UIs (TUI detail pane, future CLI `noema memory votes`) so users voting on a trace can see their vote's effect. A missing trace returns sql.ErrNoRows.
func (*Cortex) TopSearchedTraces ¶ added in v0.11.0
func (c *Cortex) TopSearchedTraces(n int) ([]PopularTrace, error)
TopSearchedTraces returns the top-N active traces ranked by search_hit_count (primary) and read_count (tiebreaker). Cumulative counters across all-time — a since-window filter doesn't apply because the counter rows don't carry timestamps. If the question is "what's hot lately" rather than "what's been hot all-time," the answer needs a different table (per-day usage snapshots), which is deferred to a future iteration.
func (*Cortex) Trash ¶ added in v0.2.0
Trash moves a trace to the trash directory for deferred deletion.
func (*Cortex) Update ¶
Update rewrites an existing trace's DB row, FTS entry, AND the on-disk markdown file from the (potentially edited) frontmatter+body on disk. Use this for explicit Noema-driven mutations (CLI, MCP, TUI) where Noema is the writer and the canonical frontmatter format should be re-stamped.
Do NOT use this on the watcher's external-edit ingest path: rewriting the file while an editor (Obsidian, VSCode, etc.) has it open kicks off a "modified externally" feedback loop where each Noema rewrite triggers an editor re-save which triggers a watcher event which triggers another Update. Use UpdateFromFile for that path.
func (*Cortex) UpdateAs ¶ added in v0.10.0
UpdateAs is the actor-aware counterpart to Update. It runs the regular update (which internally handles source-lock checks, event emission, FTS refresh, etc.) and bumps modify_count when the caller is ActorAgent. A failed Update short-circuits before the bump.
The bump writes to trace_usage keyed on (trace_id, local cortex ID). Long-term traces can never reach the bump step: the immutability trigger aborts the inner Update transaction first.
func (*Cortex) UpdateFromFile ¶ added in v0.10.3
UpdateFromFile syncs the DB and FTS index from the on-disk file without rewriting the file. Used by the filesystem watcher when an external editor has saved a change — the user's editor is the authoritative writer in that flow, and Noema's job is to mirror the new state into the DB, not to canonicalize the editor's frontmatter format back at it. Skipping the rewrite here breaks the "modified externally" toast loop that would otherwise fight with any editor that has the file open.
Side effect of skipping the rewrite: the file's frontmatter `updated:` and `content_hash:` fields will go stale relative to the DB. The DB is authoritative anyway (every Noema read goes through it), and explicit mutations through Update will re-canonicalize the frontmatter the next time the user edits via CLI/MCP/TUI.
type DistilledTraceSpec ¶ added in v0.10.0
type DistilledTraceSpec struct {
Title string
Body string
Tags []string
Author string
SourceIDs []string
ModelName string
ModelTierProfile string
CohesionConfidence float64
}
DistilledTraceSpec is the payload record_consolidation_result (Phase 9 MCP tool) hands to Cortex. The fields mirror the event data schema in docs/plans/consolidation-plan.md §9 so event replay on a federation peer can reconstruct the same distillation.
type EmbedBackfillOpts ¶ added in v0.14.0
type EmbedBackfillOpts struct {
Force bool // re-embed every embeddable trace, ignoring staleness
Limit int // cap traces processed this run (0 = no cap)
MaxChars int // per-trace text budget (0 = default)
BatchSize int // traces per Embed call (0 = 64)
}
EmbedBackfillOpts tunes a backfill run.
type EmbedBackfillResult ¶ added in v0.14.0
type EmbedBackfillResult struct {
Considered int // candidates selected
Embedded int // vectors computed and stored
}
EmbedBackfillResult reports what a run did.
type Embedder ¶ added in v0.14.0
type Embedder interface {
Embed(ctx context.Context, model string, inputs []string) ([][]float32, error)
}
Embedder turns text into vectors. Defined here (not imported from consolidation) so the cortex package stays free of an import cycle — consolidation.HTTPLLMClient satisfies this structurally and is injected by the CLI/serve layer, which may import both packages.
type EmbeddingStatus ¶ added in v0.14.0
EmbeddingStatus summarizes semantic-search embedding coverage for a given embedding model. "Embeddable" is every non-trashed trace (active + archived); trashed traces are excluded by design. A trace is "embedded" only when it has a row for the active model whose source_hash still matches the trace's content_hash — otherwise it is "stale" (body changed or a different model) or "missing" (no row at all).
type EngagementStats ¶ added in v0.10.4
type EngagementStats struct {
TotalReads int // sum of read_count across active traces
TotalSearchHits int // sum of search_hit_count
TotalModifies int // sum of modify_count
}
EngagementStats summarises usage signal across all active traces in the cortex. Aggregates across every peer's trace_usage rows so the numbers reflect federation-wide consumption, not the local slice. Used by `noema memory stats --detailed` to answer "is anyone actually reading these traces" at a glance — the question that motivated migration 015's search_hit_count, since auto-injection providers were generating only the search_hit_count signal.
type ErrTraceIDExists ¶ added in v0.10.4
type ErrTraceIDExists struct {
ID string
State string
ArchivedAt string
TrashedAt string
PurgedAt string
Wrapped error
}
ErrTraceIDExists is returned by Add when the would-be trace's deterministic ID (`YYYYMMDD-slug`) collides with a row that's already in the cortex — active, archived, or trashed. The PK constraint on traces.id is what surfaces in the underlying SQLite error (extended code 1555 = SQLITE_CONSTRAINT_PRIMARYKEY); this typed wrapper adds the existing row's state so callers can choose the right remedy without an extra round-trip. Soft-deleted (trashed) and archived rows are common collision sources because they hold their `id` slot through their grace window — and `noema list` hides them by default.
State takes one of "active", "archived", "trashed", "purged". "purged" means a soft-purged tombstone whose row still occupies the id slot — a `--hard` purge would have removed the row outright and the Add wouldn't conflict at all. If the row vanishes between the constraint failure and the state lookup (race with another mutator), State is "unknown" and the original DB error is preserved via Wrapped.
func (*ErrTraceIDExists) Error ¶ added in v0.10.4
func (e *ErrTraceIDExists) Error() string
func (*ErrTraceIDExists) Unwrap ¶ added in v0.10.4
func (e *ErrTraceIDExists) Unwrap() error
type FederationConfig ¶ added in v0.3.0
type FederationConfig struct {
Mode string `yaml:"mode,omitempty"` // sync | publish | subscribe
Peers []PeerEntry `yaml:"peers,omitempty"`
Interval string `yaml:"interval,omitempty"` // e.g. "30s", "1m"
Verify string `yaml:"verify,omitempty"` // off | warn | enforce
}
FederationConfig holds peer declarations for cortex.md.
func (*FederationConfig) EffectiveMode ¶ added in v0.5.0
func (fc *FederationConfig) EffectiveMode() string
EffectiveMode returns the configured federation mode, defaulting to "sync".
func (*FederationConfig) EffectiveVerify ¶ added in v0.15.0
func (fc *FederationConfig) EffectiveVerify() string
EffectiveVerify returns the configured signature-verification mode, defaulting to "off" so upgrading a cortex never starts rejecting a peer that hasn't enabled signing yet. Operators opt into warn/enforce.
type GraduationConfig ¶ added in v0.10.0
type GraduationConfig struct {
// Enabled defaults to true when the parent consolidation block is
// enabled. Set to false to pause automatic mid→long graduation
// while leaving the short→mid promoter and LLM distillation
// pipeline running.
Enabled *bool `yaml:"enabled,omitempty"`
// MinAgeDays is the minimum age (in days) before a mid-tier trace
// can graduate. Zero defaults to 14 — two weeks of stability
// signals the trace hasn't been a transient idea.
MinAgeDays int `yaml:"min_age_days,omitempty"`
// MinReadCount is the minimum (read_count + search_hit_count)
// required for graduation. Zero defaults to 3 — at least a few
// reads or top-N search hits to prove the trace carries ongoing
// value. The two columns are summed at gate time so auto-injection
// providers (Hermes, etc.) that consume traces via search rather
// than deliberate get_trace calls can still drive graduation.
MinReadCount int `yaml:"min_read_count,omitempty"`
// RequireUnmodified defaults to true. When true, a trace graduates
// only if modify_count == 0 since creation; any edit resets the
// stability clock. Flip to false in cortexes where edits are
// routine and don't indicate churn.
RequireUnmodified *bool `yaml:"require_unmodified,omitempty"`
}
GraduationConfig controls the mid→long promotion heuristic. A trace graduates when every criterion is simultaneously true — a simple AND-gate rather than a blended score because the long tier is meant to be slow-moving and auditable. Bumping any threshold makes graduation stricter; lowering any makes it looser.
func (*GraduationConfig) EffectiveEnabled ¶ added in v0.10.0
func (gc *GraduationConfig) EffectiveEnabled() bool
EffectiveEnabled returns true when graduation should run. Defaults to true on a nil config so a cortex that enables consolidation without an explicit graduation block gets the tier model completed.
func (*GraduationConfig) EffectiveMinAge ¶ added in v0.10.0
func (gc *GraduationConfig) EffectiveMinAge() time.Duration
EffectiveMinAge returns the configured minimum age, defaulting to 14 days. The return type is time.Duration for direct use in candidate queries; the YAML knob is days for human readability.
func (*GraduationConfig) EffectiveMinReadCount ¶ added in v0.10.0
func (gc *GraduationConfig) EffectiveMinReadCount() int
EffectiveMinReadCount returns the configured floor, defaulting to 3.
func (*GraduationConfig) EffectiveRequireUnmodified ¶ added in v0.10.0
func (gc *GraduationConfig) EffectiveRequireUnmodified() bool
EffectiveRequireUnmodified returns the stability-gate flag, defaulting to true on a nil config.
type ListOptions ¶
type Manifest ¶
type Manifest struct {
ID string `yaml:"id,omitempty"`
Name string `yaml:"name"`
Purpose string `yaml:"purpose,omitempty"`
Owner string `yaml:"owner,omitempty"`
Created string `yaml:"created"`
Version int `yaml:"version"`
Access *AccessConfig `yaml:"access,omitempty"`
Federation *FederationConfig `yaml:"federation,omitempty"`
Signing *SigningConfig `yaml:"signing,omitempty"`
Watch *WatchConfig `yaml:"watch,omitempty"`
Consolidation *ConsolidationConfig `yaml:"consolidation,omitempty"`
Search *SearchConfig `yaml:"search,omitempty"`
// Body is the free-form markdown content that follows the YAML
// frontmatter in cortex.md. Never serialized through YAML; populated
// by ReadManifest and consumed by WriteManifest to preserve any
// prose the user keeps below the manifest.
Body string `yaml:"-"`
}
Manifest is the cortex.md file at the root of each Cortex.
func Create ¶
Create initialises a new Cortex on disk and registers it. dir is the parent directory; the cortex is created as dir/<name>/. Returns the freshly written manifest so callers can surface the new cortex's ULID to the user (see `noema init`).
func ReadManifest ¶ added in v0.2.2
ReadManifest parses the cortex.md manifest in the given cortex directory. cortex.md is a markdown file with YAML frontmatter: a `---` fence, the manifest YAML, a closing `---` fence, and an optional free-form body. For back-compat with cortexes written before the framing change, a file that does not begin with `---` is parsed as whole-file YAML.
func (Manifest) PeerLabelCollidesWithSelf ¶ added in v0.3.0
PeerLabelCollidesWithSelf reports whether the proposed peer label is the same as this cortex's own name. This is a federation safety guardrail: even after the cortex-id migration (docs/design/cortex-uuid-plan.md), a label collision is confusing in display surfaces and should be rejected at config time.
func (Manifest) ResolvedEmbeddingAPIKeyEnv ¶ added in v0.14.0
ResolvedEmbeddingAPIKeyEnv mirrors ResolvedEmbeddingEndpoint for the API-key env-var name.
func (Manifest) ResolvedEmbeddingEndpoint ¶ added in v0.14.0
ResolvedEmbeddingEndpoint returns the embedding endpoint, falling back to consolidation.local_llm_endpoint when the search block leaves it empty.
func (Manifest) ValidateConsolidation ¶ added in v0.10.0
ValidateConsolidation checks that the consolidation block is internally consistent. Today's only cross-field rule: when AutoDistillationEnabled is true, LLMEnabled + LocalLLMEndpoint + ModelName must all be set, because the trigger path has no CLI flags to fall back on. Returns nil when consolidation is disabled or the block is absent.
func (Manifest) ValidateFederation ¶ added in v0.5.0
ValidateFederation checks that the federation mode and per-peer modes in the manifest are recognized values. Returns nil when there is no federation block or when all values are valid.
func (Manifest) ValidateSearch ¶ added in v0.14.0
ValidateSearch enforces the search block's cross-field rules: an invalid default_mode is rejected even when semantic search is off (so a typo surfaces early), and enabling semantic search requires an explicit embedding model plus a resolvable, well-formed endpoint. Returns nil when the block is absent and no mode override is set.
type MidEngagementSnapshot ¶ added in v0.10.4
type MidEngagementSnapshot struct {
ZeroEngagement int
ZeroEngagementOlder int // ZeroEngagement filtered to age >= 14 days
}
MidEngagementSnapshot reports how many mid-tier traces have accumulated any usage signal at all. ZeroEngagement is the count of active mid traces with no reads, no search hits, and no modifies — the cohort that's accumulating in the cortex without earning its keep, and the natural input to a future archival pass. Older expresses the same cohort filtered to traces older than the graduation min-age (default 14 days) so transient new traces don't inflate the number.
type MidLineageBreakdown ¶ added in v0.10.4
MidLineageBreakdown reports how mid-tier traces decompose by their derived_from count. The buckets answer the diagnostic question that surfaced during the Hermes session-summary audit: how much of the mid tier is real consolidation vs. provenance-only forwarding?
- NoSources: stand-alone mids (heuristic-promoted, manually curated, etc.) — neither bad nor good, just not synthesis.
- SingleSource: one derived_from. After MinLineageSourcesForCredit landed in the heuristic, no NEW traces in this bucket should be promoting via the lineage bonus alone. A growing count here is still a smell because it usually points at a writeback pattern emitting "summary" traces that aren't really summarising.
- MultiSource: ≥2 derived_from — real distillations.
type OneSourceMidCount ¶ added in v0.11.0
type OneSourceMidCount struct {
Current int `json:"current"`
PromotedLast7d int `json:"promoted_last_7d"`
}
OneSourceMidCount is the leak detector for the 1-source mid bucket. PRs #86 and #90 closed two distinct paths that were promoting single-derived-from traces past the heuristic; this surface lets us see at a glance whether either gate ever leaks again. Current is the live count; PromotedLast7d is the count of distinct traces that landed in the mid tier with derived_from_count=1 within the last 7 days (the operationally useful signal — Current can grow only via recent promotions, so a healthy gate keeps PromotedLast7d at zero).
type PeerEntry ¶ added in v0.3.0
type PeerEntry struct {
Name string `yaml:"name"`
Endpoint string `yaml:"endpoint"`
CA string `yaml:"ca,omitempty"` // path to CA cert for TLS verification
Mode string `yaml:"mode,omitempty"` // sync | paused
// PubKey optionally hard-pins this peer's "ed25519:<base64>" federation
// signing key. When set, the peer MUST advertise exactly this key at the
// identity handshake or the sync is refused — out-of-band operator intent
// that takes precedence over trust-on-first-use and closes the TOFU
// first-contact window for high-assurance peers. Empty means TOFU.
PubKey string `yaml:"pubkey,omitempty"`
}
PeerEntry is a peer declared in cortex.md.
func (PeerEntry) EffectiveMode ¶ added in v0.5.0
EffectiveMode returns the configured peer mode, defaulting to "sync".
type PopularTrace ¶ added in v0.11.0
type PopularTrace struct {
ID string `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
Tier string `json:"tier"`
SearchHits int `json:"search_hits"`
ReadCount int `json:"read_count"`
}
PopularTrace is one row in the top-N-by-search-popularity table. SearchHits and ReadCount are federation-wide aggregates (sum across every peer's trace_usage row for this trace), matching the convention EngagementStats already uses.
type PromotionCandidate ¶ added in v0.10.0
type PromotionCandidate struct {
ID string
Tier string
Type string
ReadCount int
ModifyCount int
SearchHitCount int
TierVotes int
DerivedFromCount int
CreatedAt string
}
PromotionCandidate carries the signals the consolidation scorer reads when deciding whether to promote a trace. See docs/plans/consolidation-plan.md §5 in the Noema-design repo for how these feed into the blended scoring function.
type PromotionLatency ¶ added in v0.11.0
type PromotionLatency struct {
ShortToMid PromotionStats `json:"short_to_mid"`
MidToLong PromotionStats `json:"mid_to_long"`
}
PromotionLatency reports the distribution of time-to-promotion for short→mid and mid→long transitions. Latency is computed in Go from the event log rather than in SQL because mid→long requires walking each trace's tier history to find the timestamp when it entered the mid tier (a previous promote event, or created_at if it was born there).
type PromotionStats ¶ added in v0.11.0
type PromotionStats struct {
Count int `json:"count"`
// Duration fields skip JSON serialization (would emit raw
// nanoseconds); the *Label sibling carries the same value as a
// parseable string ("129h36m12s"). Callers doing math use the
// time.Duration; JSON consumers parse the string with
// time.ParseDuration.
P50 time.Duration `json:"-"`
P50Label string `json:"p50"`
P95 time.Duration `json:"-"`
P95Label string `json:"p95"`
}
PromotionStats is the per-transition rollup. Durations serialize as nanoseconds (Go's time.Duration default); the CLI text renderer formats them as human-readable spans for terminal output.
type ReadActor ¶ added in v0.10.0
type ReadActor int
ReadActor identifies who initiated a Cortex read or update. It gates the memory-tier usage counters (read_count, modify_count, last_read_at): only ActorAgent bumps them, because only agent consumption is a meaningful signal for the consolidation-scoring function in later phases. TUI browsing, interactive CLI inspection, watcher reconciliation, federation replay, and the consolidation agent's own candidate fetch are all deliberately excluded so the signal stays clean.
The design rationale — and the reason the "system" slot matters — is in docs/plans/consolidation-plan.md §3 in the Noema-design repo.
const ( // ActorAgent marks reads/updates initiated by an AI agent via MCP // (stdio or HTTP). These are the only operations that bump counters. ActorAgent ReadActor = iota // ActorHuman marks reads/updates initiated by a human operator, // through the TUI or interactive CLI. These do not bump counters — // human browsing inflates signal without reflecting trace usefulness. ActorHuman // ActorSystem marks internal operations: the filesystem watcher // reconciling external edits, federation event replay, the // consolidation agent reading candidates for clustering. These // must not bump counters — otherwise evaluating a trace for // promotion would inflate its own inputs, a closed feedback loop. ActorSystem )
type Row ¶
type Row struct {
ID string
Title string
Type string
Tier string
Author string
Origin string
Tags []string
DerivedFrom []string
ArchivedAt string
TrashedAt string
CreatedAt string
UpdatedAt string
ContentHash string
SourceLocked bool
SourceHash string
}
Row is a DB row joined with tags, returned by list/search operations.
type ScoredRow ¶ added in v0.14.0
ScoredRow is one ranked result: a trace row plus a score. For semantic search the score is cosine similarity (higher is closer); for hybrid it is the fused reciprocal-rank score (also higher-is-better, but on a different scale — treat it as opaque ordering).
type SearchConfig ¶ added in v0.14.0
type SearchConfig struct {
// SemanticEnabled is the master opt-in for embedding-based search.
// When false (the default), only lexical FTS5 search is available.
SemanticEnabled bool `yaml:"semantic_enabled,omitempty"`
// EmbeddingEndpoint is the OpenAI-compatible base URL for the
// /embeddings call. Empty inherits consolidation.local_llm_endpoint.
EmbeddingEndpoint string `yaml:"embedding_endpoint,omitempty"`
// EmbeddingModel is the embedding model identifier. Required when
// SemanticEnabled — there is no default.
EmbeddingModel string `yaml:"embedding_model,omitempty"`
// APIKeyEnv names an env var holding the bearer token for the
// embedding endpoint. Empty inherits consolidation.api_key_env.
APIKeyEnv string `yaml:"api_key_env,omitempty"`
// DefaultMode is the search mode used when a caller doesn't specify
// one: lexical|semantic|hybrid. Empty means lexical, which keeps
// existing callers byte-for-byte unchanged.
DefaultMode string `yaml:"default_mode,omitempty"`
// HybridWeight is the vector weight (0..1) in hybrid rank fusion.
// Zero/unset means the default 0.5 (equal lexical/semantic).
HybridWeight float64 `yaml:"hybrid_weight,omitempty"`
// MaxChars caps the trace text sent to the embedding model, to stay
// within model input limits. Zero/unset means defaultEmbedMaxChars.
MaxChars int `yaml:"max_chars,omitempty"`
// EmbedIntervalSeconds is how often the serve-time embed maintainer
// runs a backfill pass to embed new/edited traces. Zero/unset means
// defaultEmbedIntervalSeconds. Only used under `noema serve`.
EmbedIntervalSeconds int `yaml:"embed_interval_seconds,omitempty"`
}
SearchConfig is the optional `search:` block in cortex.md. It is off by default; semantic search requires SemanticEnabled plus a resolvable embedding endpoint and an explicit embedding model. There is no baked-in default model — we never imply a model we don't ship. The embedding endpoint and API-key env-var fall back to the consolidation block when left empty, so a single local-LLM host can serve both distillation and embeddings from one configuration.
func (*SearchConfig) EffectiveDefaultMode ¶ added in v0.14.0
func (sc *SearchConfig) EffectiveDefaultMode() string
EffectiveDefaultMode returns the configured default mode or "lexical" when unset.
func (*SearchConfig) EffectiveEmbedInterval ¶ added in v0.14.0
func (sc *SearchConfig) EffectiveEmbedInterval() time.Duration
EffectiveEmbedInterval returns the serve-time backfill cadence, defaulting to 5 minutes.
func (*SearchConfig) EffectiveHybridWeight ¶ added in v0.14.0
func (sc *SearchConfig) EffectiveHybridWeight() float64
EffectiveHybridWeight returns the hybrid vector weight, defaulting to 0.5 when unset and clamping to [0,1] otherwise. (An explicit 0 reads as unset and yields the default; use mode=lexical for pure lexical.)
func (*SearchConfig) EffectiveMaxChars ¶ added in v0.14.0
func (sc *SearchConfig) EffectiveMaxChars() int
EffectiveMaxChars returns the per-trace embedding text budget, defaulting to 32k characters.
func (*SearchConfig) SemanticOn ¶ added in v0.14.0
func (sc *SearchConfig) SemanticOn() bool
SemanticOn reports whether semantic search is enabled. Nil-safe.
type SemanticOpts ¶ added in v0.14.0
type SemanticOpts struct {
Model string // embedding model whose vectors to search
Limit int // max results (default 10)
IncludeArchived bool // include archived traces (default false)
}
SemanticOpts tunes a semantic or hybrid query.
type SigningConfig ¶ added in v0.15.0
type SigningConfig struct {
// PublicKey is the "ed25519:<base64>" public key. Advertised in the
// cortex_identity handshake so peers can pin it and verify this
// cortex's events.
PublicKey string `yaml:"public_key,omitempty"`
// PrivateKeyFile is a path to the sidecar file whose first non-empty
// line is the base64-encoded 32-byte Ed25519 seed. Relative paths are
// resolved against the cortex directory.
PrivateKeyFile string `yaml:"private_key_file,omitempty"`
}
SigningConfig holds the cortex's Ed25519 federation signing material. PublicKey is safe to keep in the manifest and is what gets advertised to peers via cortex_identity; PrivateKeyFile points to a 0600 sidecar holding the base64 seed, never the secret itself — the same "manifest holds a pointer, not the secret" rule as AccessConfig.SharedKeyFile. A cortex with no SigningConfig emits unsigned events and federates exactly as before.
type SigningKey ¶ added in v0.15.0
type SigningKey struct {
// Private is the expanded private key used to sign emitted events. Nil
// in unsigned mode. Like the MCP shared key, it must never be logged,
// written to the event log, or echoed in errors.
Private ed25519.PrivateKey
// Public is the "ed25519:<base64>" public key advertised to peers. Safe
// to log and display. Empty in unsigned mode.
Public string
}
SigningKey is a cortex's resolved Ed25519 signing material. The zero value represents unsigned mode: a cortex that has not run `noema keygen` emits events without a signature and federates exactly as it did before signing existed.
func LoadSigningKey ¶ added in v0.15.0
func LoadSigningKey(cortexDir string, cfg *SigningConfig) (SigningKey, error)
LoadSigningKey resolves a cortex's signing key from its manifest config. It returns the zero value (unsigned mode) when no signing config or no private-key file is present. The sidecar file is held to the same 0600 / size / single-line rules as the MCP shared key.
When the manifest also records a public_key, it is checked against the key derived from the private seed: a mismatch is a hard error, because a manifest public_key that disagrees with the private key would make every signature this cortex emits fail verification on its peers — a silent, fleet-wide federation outage that is far better caught at startup.
func (SigningKey) Signing ¶ added in v0.15.0
func (k SigningKey) Signing() bool
Signing reports whether usable signing material was loaded.
type SimilarMatch ¶ added in v0.10.2
SimilarMatch is one ranked result from FindSimilar. Score is the raw FTS5 BM25 value (lower is closer); callers should treat it as opaque ordering, not an absolute confidence number.
type SimilarOpts ¶ added in v0.10.2
type SimilarOpts struct {
Limit int // max matches returned (default 10)
IncludeArchived bool // include archived traces in candidate set (default false)
TopTermsK int // distinctive terms to extract from the source (default 25)
}
SimilarOpts tunes FindSimilar. All fields zero-default to sane values, so passing a zero-value SimilarOpts is fine.
type SyncOptions ¶ added in v0.3.0
type SyncOptions struct {
// Recover, when true, attempts to rebuild missing files for orphaned DB
// rows from the local event log. Off by default so manual `rm` of a trace
// file remains a valid way to mark it for cleanup.
Recover bool
}
SyncOptions controls optional Sync behaviors.
type SyncResult ¶ added in v0.2.2
type SyncResult struct {
Added int // files found on disk but not in DB
Updated int // files found on disk and already in DB (re-synced)
Recovered int // orphaned DB rows whose files were rebuilt from the event log
Orphaned int // IDs in DB with no corresponding file on disk (after recovery)
// Drifted counts long-tier traces whose on-disk file differs from the DB
// row in an immutable field (title, type, body hash, etc.). Sync refuses
// to reconcile such drift — the long-tier immutability trigger would
// abort, and even if it didn't, the right surface for "long-tier file
// drifted" is `noema verify drift`, not silent overwrite in either
// direction. Drifted rows still get archive/trash visibility reconciled.
// Surface them in the CLI so users know to investigate.
Drifted int
// DriftedIDs lists the IDs of drifted long-tier traces, in walk order.
// Capped at 10 to keep terminal output manageable on a cortex with a
// pathological number of drifted rows; the full set is recoverable via
// `noema verify drift` if needed.
DriftedIDs []string
}
SyncResult summarises what Sync found.
type TagSummary ¶ added in v0.11.0
type TagSummary struct {
Tag string `json:"tag"`
TraceCount int `json:"trace_count"`
SearchHits int `json:"search_hits"`
ReadCount int `json:"read_count"`
ModifyCount int `json:"modify_count"`
}
TagSummary is one row in the per-tag activity table. TraceCount is the number of distinct active traces carrying the tag; the SearchHits / ReadCount / ModifyCount sums attribute the trace's total signal to each of its tags, so a trace with three tags contributes its 10 reads three times across the table (once per tag). That's the right semantic for "which tags are accumulating engagement" — a multi-tagged trace IS engagement for all of its tags. Comparing two tag rows is fair because both columns use the same attribution rule.
type TierStats ¶ added in v0.10.0
TierStats reports how many traces sit in each memory tier plus the count of purged (tombstoned) rows. Phase 6 MVP data source for the `noema memory stats` CLI; later phases expand the dashboard with consolidation quality metrics from the event log.
type WatchConfig ¶ added in v0.9.0
type WatchConfig struct {
// Enabled is a pointer so an omitted field means "default on."
// Explicitly setting `enabled: false` disables the watcher.
Enabled *bool `yaml:"enabled,omitempty"`
// DebounceMs is the per-path debounce window in milliseconds.
// Zero means use the default (300ms).
DebounceMs int `yaml:"debounce_ms,omitempty"`
// AutoOnboard controls what happens when a new file is dropped into
// traces/ without conforming frontmatter (no id, empty id, invalid
// id format, or filename mismatch). Nil / unset defaults to true:
// the watcher rescues the file by synthesizing valid frontmatter,
// generating an ID from the title or filename, and renaming the
// file to match. Makes Obsidian Web Clipper, Drafts, and shortcut
// macros work without teaching users Noema's filename-ID rules.
// Set to false to restore the prior skip-and-log behaviour.
AutoOnboard *bool `yaml:"auto_onboard,omitempty"`
}
WatchConfig controls the filesystem watcher that turns external edits to trace files (Obsidian, VS Code, Finder, etc.) into first-class mutation events. When unset, the watcher is enabled by default during `noema serve --transport http`.
func (*WatchConfig) AutoOnboardEnabled ¶ added in v0.9.1
func (wc *WatchConfig) AutoOnboardEnabled() bool
AutoOnboardEnabled reports whether the watcher should rescue non- conforming files dropped into traces/. Nil manifest or nil AutoOnboard pointer both mean "on" so users get the graceful behaviour by default.
func (*WatchConfig) EffectiveDebounce ¶ added in v0.9.0
func (wc *WatchConfig) EffectiveDebounce() time.Duration
EffectiveDebounce returns the configured debounce duration, defaulting to 300ms when unset.
func (*WatchConfig) WatchEnabled ¶ added in v0.9.0
func (wc *WatchConfig) WatchEnabled() bool
WatchEnabled reports whether the watcher should run given the parsed manifest value. Nil manifest or nil Enabled pointer both mean "on."