Documentation
¶
Overview ¶
Package native holds the Go-side implementations of Ogham tools for the absorption path described in docs/plans/2026-04-16-go-cli-enterprise.md. A subcommand is "native" when it bypasses the Python sidecar and reads/ writes the database directly via pgx.
The default path for every subcommand is still the sidecar. Native is opt-in via --native until each tool has been dogfooded enough.
Index ¶
- Constants
- Variables
- func ActiveProfile(cfg *Config) string
- func ClearActiveProfile() error
- func CountDecayEligible(ctx context.Context, cfg *Config, profile string) (int, error)
- func CountExpired(ctx context.Context, cfg *Config, profile string) (int, error)
- func CreateRelationship(ctx context.Context, cfg *Config, opts CreateRelationshipOptions) error
- func DecayFactor(ageDays, lambda, beta float64) float64
- func DefaultPath() string
- func DeriveSidecarExtras(cfg *Config) string
- func Inscribe(ctx context.Context, cfg *Config, sessionID, cwd string, opts HookOptions) (string, error)
- func LoadEnvFiles() []string
- func OverallScore(dims []DimensionResult) float64
- func PipelineCounts(ctx context.Context, cfg *Config, profile string) (map[string]int64, error)
- func Recall(ctx context.Context, cfg *Config, cwd string, opts HookOptions) (string, error)
- func Save(path string, cfg *Config) error
- func SaveEnvFile(path string, cfg *Config) error
- func SentinelPath() string
- func SessionStart(ctx context.Context, cfg *Config, cwd string, opts HookOptions) (string, error)
- func SwitchProfile(profile string) error
- type AdvanceLifecycleOptions
- type AuditEvent
- type AutoLink
- type Breakdown
- type CheckResult
- type CleanupResult
- type ConfidenceResult
- type Config
- type ConnectionSuggestion
- type CreateRelationshipOptions
- type Database
- type DayCount
- type DecayResult
- type DeleteResult
- type DimensionResult
- type Embedder
- type Embedding
- type ExploreOptions
- type ExtendedHealthReport
- type FindRelatedOptions
- type GraphMemory
- type GraphNode
- type HealthOptions
- type HookOptions
- type LinkUnlinkedOptions
- type LinkUnlinkedResult
- type LintCategory
- type LintReport
- type LintWikiOptions
- type ListOptions
- type MaskedConfig
- type MaskedDatabase
- type MaskedEmbedding
- type Memory
- type ProfileCount
- type ProfileTTL
- type QueryTopicSummaryResult
- type Report
- type SearchOptions
- type SearchResult
- type Stage
- type StageReport
- type Stats
- type StoreDecisionOptions
- type StoreEventOptions
- type StoreFactOptions
- type StoreOptions
- type StorePreferenceOptions
- type StoreResult
- func Store(ctx context.Context, cfg *Config, content string, opts StoreOptions) (*StoreResult, error)
- func StoreDecision(ctx context.Context, cfg *Config, opts StoreDecisionOptions) (*StoreResult, error)
- func StoreEvent(ctx context.Context, cfg *Config, opts StoreEventOptions) (*StoreResult, error)
- func StoreFact(ctx context.Context, cfg *Config, opts StoreFactOptions) (*StoreResult, error)
- func StorePreference(ctx context.Context, cfg *Config, opts StorePreferenceOptions) (*StoreResult, error)
- type SupabaseKeyKind
- type TLDRLevel
- type TopicSummary
- type UpdateOptions
- type UpdateResult
- type WalkKnowledgeOptions
- type WalkKnowledgeResult
- type Zone
Constants ¶
const ( AdvanceDefaultDwellHours = 1.0 AdvanceDefaultSurpriseGate = 0.3 AdvanceDefaultImportanceGate = 0.5 AdvanceDefaultEditingWindowMinutes = 30 )
AdvanceLifecycleDefaults match Python's lifecycle.py constants.
const ( LintDefaultSampleSize = 10 LintDefaultStableDays = 90 LintDefaultOrphanGraceMinutes = 5 )
LintDefaults mirror Python's wiki_lint module-level defaults.
const ( StatusOK = "ok" StatusNotCached = "not_cached" )
QueryTopicSummaryStatus enumerates the response shapes.
const DecayThreshold = 0.25
DecayThreshold is the confidence floor below which a memory is considered "decayed" for the DecayCount stat. 0.25 sits well below the schema default of 0.5 and is empirically near-invisible in hybrid_search ranking -- memories that reach it are candidates for cleanup or reinforcement.
Variables ¶
var ValidRelationshipTypes = []string{
"supports", "contradicts", "supersedes", "refines", "references",
}
Relationship types the schema accepts. Matches the PostgreSQL `relationship_type` enum defined in schema_postgres.sql. Kept as a sorted slice so clients can discover the valid set.
Functions ¶
func ActiveProfile ¶ added in v0.7.0
ActiveProfile returns the profile to use for tool calls. Precedence, highest first:
- $OGHAM_PROFILE env var
- ~/.ogham/active_profile sentinel file
- cfg.Profile (loaded from TOML / applyEnv)
- "default"
A cfg of nil is tolerated for callers that only have a HOME-scoped view (the CLI `ogham profile current` command, for example, doesn't need the full config to answer the question).
func ClearActiveProfile ¶ added in v0.7.0
func ClearActiveProfile() error
ClearActiveProfile deletes the sentinel file. Subsequent ActiveProfile calls fall through to cfg.Profile (the TOML baseline). Missing-file is a no-op (not an error) so callers can treat it as idempotent.
func CountDecayEligible ¶
CountDecayEligible returns the number of memories that would be affected by a decay pass. Mirrors Python's Hebbian-decay predicate.
func CountExpired ¶
CountExpired is a dry-run count of expired memories in a profile.
func CreateRelationship ¶ added in v0.7.0
func CreateRelationship(ctx context.Context, cfg *Config, opts CreateRelationshipOptions) error
CreateRelationship inserts a single edge into memory_relationships. Used by store_decision.related_memories to materialise the supporting links after the new decision is stored. Idempotent on the unique key (source_id, target_id, relationship) -- repeat calls return the existing edge rather than erroring.
func DecayFactor ¶ added in v0.7.0
DecayFactor returns Shodh's hybrid decay multiplier for a memory of given age. Pure function -- no I/O.
0-3 days: exponential exp(-lambda * age) 3+ days: power-law (age/3)^(-beta)
Mirrors Python's src/ogham/lifecycle.py::hybrid_decay_factor for cross-stack parity.
func DefaultPath ¶
func DefaultPath() string
DefaultPath returns the standard config location. Same file as the sidecar-mode config -- just reads different sections.
func DeriveSidecarExtras ¶
DeriveSidecarExtras computes the correct `uv tool run --from ogham-mcp[...]` extras spec from a config. Combines the backend extra (postgres for the direct-DB Python backend; nothing for the Supabase PostgREST backend which is the base install) with the embedding provider extra (gemini, voyage, mistral -- openai and ollama are in the base).
Returns a comma-separated string ready to be placed inside the [...] brackets of a ogham-mcp install spec, or "" if no extras are needed.
func Inscribe ¶ added in v0.7.3
func Inscribe(ctx context.Context, cfg *Config, sessionID, cwd string, opts HookOptions) (string, error)
Inscribe drains session context to Ogham before compaction.
Mirrors Python ogham.hooks.pre_compact: build a short plaintext summary identifying the session and store it with tags marking it as a compaction drain. Returns the content that was stored (or that would have been stored when DryRun is true) so the caller can echo it for verification.
Inscribe is best-effort: a store failure returns the content + a wrapped error, so the caller can log without breaking the PreCompact hook flow that triggered it.
func LoadEnvFiles ¶
func LoadEnvFiles() []string
LoadEnvFiles returns env-var assignments (KEY=VALUE) drawn from the same files Python ogham reads, in the same precedence order. Higher-priority entries appear later in the slice so callers that do
cmd.Env = append(os.Environ(), LoadEnvFiles()...)
end up with Python-equivalent precedence (later wins under Go's exec).
Order, lowest to highest:
- ~/.ogham/config.env (global fallback)
- ./.env (project-local, overrides global)
func OverallScore ¶ added in v0.7.3
func OverallScore(dims []DimensionResult) float64
OverallScore is the mean of dimension scores, rounded to one decimal. Empty slice yields 0.0 -- mirrors Python's overall_score behaviour.
func PipelineCounts ¶ added in v0.7.0
PipelineCounts returns the {stage: count} distribution for the dashboard Lifecycle card. Read-only -- does not advance anything.
Mixed-version safe: if the memory_lifecycle table is absent (pre-026 DB), returns all rows under 'fresh' without error.
func Recall ¶ added in v0.7.3
Recall returns markdown context to restore after compaction.
Mirrors Python ogham.hooks.post_compact: hybrid_search using a "recent work and decisions" query, limit 10, longer content truncation (300 vs 200) than SessionStart since post-compact users often need more detail to rebuild context. Same best-effort pattern as SessionStart -- never propagate search failures.
func Save ¶
Save writes cfg to path as TOML with 0600 perms (API keys live here). Creates parent directories as needed. Existing keys not known to native.Config are preserved by reading the file first, merging on top of the parsed structure, and re-emitting; this avoids clobbering fields like api_key + gateway_url that the sidecar-mode loader owns.
func SaveEnvFile ¶
SaveEnvFile writes an env-file (KEY=VALUE, one per line) mirror of the TOML config's Python-relevant variables. Python ogham reads this file either directly (its config loader checks ~/.ogham/config.env) or via shell sourcing. Keeping it in sync means Python + Go agree on config without either having to parse the other's format.
func SentinelPath ¶ added in v0.7.0
func SentinelPath() string
SentinelPath returns ~/.ogham/active_profile. Exposed so tests can override the HOME env to isolate the sentinel from the user's real profile without polluting it.
func SessionStart ¶ added in v0.7.3
SessionStart returns markdown context to inject at session start.
Mirrors Python ogham.hooks.session_start: hybrid_search using the project name as the query, format the top results as a markdown bullet list under a "## Session Context" heading. Returns empty string when no results so the caller can treat that as "no context" without an error.
Best-effort by design: a search failure returns ("", nil) rather than propagating the error. Claude Code's SessionStart hook should never block the editor on an Ogham outage.
func SwitchProfile ¶ added in v0.7.0
SwitchProfile writes profile to the sentinel file atomically (temp file + rename) so a crash mid-write cannot leave a half-written or empty sentinel for the next read.
Refuses empty or whitespace-only names -- the caller should use ClearActiveProfile() to explicitly fall back to the TOML baseline.
Types ¶
type AdvanceLifecycleOptions ¶ added in v0.7.1
type AdvanceLifecycleOptions struct {
DwellHours float64
SurpriseGate float64
ImportanceGate float64
EditingWindowMinutes int
}
AdvanceLifecycleOptions tunes the gates. Zero values pick the Python-matching defaults.
type AuditEvent ¶
type AuditEvent struct {
EventTime time.Time `json:"event_time"`
Profile string `json:"profile"`
Operation string `json:"operation"`
MemoryID *string `json:"memory_id,omitempty"`
Details any `json:"details,omitempty"`
}
AuditEvent mirrors a row from the audit_log table (projected to the columns the CLI displays).
func Audit ¶
func Audit(ctx context.Context, cfg *Config, profile, operation string, limit int) ([]AuditEvent, error)
Audit returns the most recent audit events for a profile.
func AuditEntries ¶ added in v0.7.0
func AuditEntries(ctx context.Context, cfg *Config, profile, operation string, before time.Time, limit int) ([]AuditEvent, error)
AuditEntries is the paginated variant used by the dashboard's Audit view. Adds a `before` cursor so HTMX infinite scroll can request the next page by passing the oldest-visible event_time back. All other semantics match Audit: profile scope, optional operation filter, event_time DESC ordering.
Passing a zero time for before is equivalent to Audit(limit).
type AutoLink ¶ added in v0.7.0
type AutoLink struct {
ID string `json:"id"`
Similarity float64 `json:"similarity"`
Content string `json:"content,omitempty"`
}
AutoLink is a prospective link candidate surfaced at store time. v0.5 preview does not write the memory_links row; that lands in a follow-up commit so the orchestrator + extraction + surprise path can ship independently.
type CheckResult ¶
type CheckResult struct {
Name string `json:"name"`
OK bool `json:"ok"`
Duration time.Duration `json:"duration_ns"`
Detail string `json:"detail,omitempty"`
Error string `json:"error,omitempty"`
}
CheckResult captures the outcome of a single parallel probe. Fields are named so JSON output is self-describing.
type CleanupResult ¶
type ConfidenceResult ¶ added in v0.7.0
type ConfidenceResult struct {
ID string `json:"id"`
Profile string `json:"profile"`
Confidence float64 `json:"confidence"`
}
ConfidenceResult carries the post-update confidence score so callers can surface the new value to the user. Mirrors the Python tool shape -- reinforce/contradict both return the same payload, only the "status" label differs.
func UpdateConfidence ¶ added in v0.7.0
func UpdateConfidence(ctx context.Context, cfg *Config, id string, signal float64, profile string) (*ConfidenceResult, error)
UpdateConfidence applies a signal to an existing memory's confidence via the database's update_confidence function. Both reinforce_memory (strength 0.5-1.0) and contradict_memory (strength 0.0-0.5) route through here -- the SQL function handles the math (EMA-style blend of the existing confidence with the new signal).
The strength parameter is validated at the MCP handler layer so we don't reject legitimate migration / backfill calls from direct Go callers that may want to force-set a value.
type Config ¶
type Config struct {
Database Database `toml:"database"`
Embedding Embedding `toml:"embedding"`
Profile string `toml:"profile"`
}
Config is the native-mode view of ~/.ogham/config.toml. It deliberately does not re-declare fields from internal/config.Config -- sidecar-mode commands still go through that loader. Native-mode adds the extra keys a Go tool needs to talk to Postgres and an embedding provider directly.
func Load ¶
Load reads TOML then layers env vars on top. Before reading env vars it auto-loads any dotenv files found at the standard locations (~/.ogham/config.env and ./.env) -- same search order Python uses, same behaviour the sidecar path already has via connectSidecar. A user whose Python sidecar works today will have a working native path without any extra configuration.
Env precedence (lowest to highest): process env, ~/.ogham/config.env, project ./.env, then TOML overrides.
func (*Config) ResolveBackend ¶
ResolveBackend picks the backend with this precedence:
- explicit cfg.Database.Backend if set
- SupabaseURL populated => supabase
- URL populated => postgres
- error asking the user to configure one
func (*Config) SidecarEnv ¶
SidecarEnv returns the env vars a spawned Python sidecar needs. Lets the Go CLI treat TOML as canonical and push values into the subprocess environment so Python sees them as it always has.
type ConnectionSuggestion ¶ added in v0.7.0
type ConnectionSuggestion struct {
ID string `json:"id"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
Tags []string `json:"tags,omitempty"`
}
ConnectionSuggestion is a memory that shares entities with a target but has no explicit relationship edge. Surfaces "hidden" connections through the entity graph.
func SuggestConnections ¶ added in v0.7.0
func SuggestConnections(ctx context.Context, cfg *Config, memoryID string, minShared, limit int) ([]ConnectionSuggestion, error)
SuggestConnections finds memories that share at least min_shared entities with memory_id but have no explicit relationship to it yet. Python-parity inline SQL: joins memory_entities against a target's entity set and excludes anything already in memory_relationships.
type CreateRelationshipOptions ¶ added in v0.7.0
type CreateRelationshipOptions struct {
SourceID string
TargetID string
Relationship string // one of ValidRelationshipTypes
Strength float64 // default 1.0
CreatedBy string // default "user"
Metadata map[string]any
}
CreateRelationshipOptions captures one edge creation. strength gets clamped to [0, 1]; createdBy defaults to "user" for tool-driven writes (auto-linker uses "auto" directly).
type Database ¶
type Database struct {
// Backend is "postgres" (pgx direct) or "supabase" (PostgREST).
// Auto-detected when empty: SUPABASE_URL => supabase, DATABASE_URL => postgres.
Backend string `toml:"backend"`
// URL is the Postgres connection string when Backend == "postgres".
URL string `toml:"url"`
// SupabaseURL is the project base URL (https://xxx.supabase.co) when
// Backend == "supabase". REST endpoint derives as {URL}/rest/v1.
SupabaseURL string `toml:"supabase_url"`
SupabaseKey string `toml:"supabase_key"`
}
type DayCount ¶ added in v0.7.0
DayCount is a single cell in the Calendar heatmap: "on this UTC day, the active profile stored N memories." Day is always UTC-normalised to midnight so the dashboard renderer doesn't have to worry about server-local-tz drift.
func StoreCountsByDay ¶ added in v0.7.0
StoreCountsByDay returns per-day memory counts for the active profile covering the trailing `days` calendar days (inclusive of today). Zero or missing days are NOT filled in -- the Calendar renderer walks a complete 365-day grid and overlays the returned counts via a map lookup, defaulting missing days to zero.
days <= 0 is treated as 365 (the Calendar heatmap default).
type DecayResult ¶
type DeleteResult ¶
DeleteResult is returned by Delete on success.
type DimensionResult ¶ added in v0.7.3
type DimensionResult struct {
Name string `json:"name"`
Score float64 `json:"score"`
Zone Zone `json:"zone"`
Detail string `json:"detail"`
}
DimensionResult is one row of the extended health report. Mirrors Python ogham.health_dimensions.DimensionResult so the JSON wire format is identical between Python sidecar and Go native paths.
func ComputeCorpusSize ¶ added in v0.7.3
func ComputeCorpusSize(ctx context.Context, cfg *Config, profile string) DimensionResult
ComputeCorpusSize scores the profile's memory count. Scoring:
count >= 100 => 10.0 (GREEN) count 10-99 => 5.0 -> 7.99 (linear) (AMBER) count 1-9 => 0.x -> 4.99 (linear) (RED) count == 0 => 0.0 (RED, "empty profile")
Uses GetStats for both backends -- already battle-tested across Supabase / Postgres in the rest of the CLI.
func ComputeDBFreshness ¶ added in v0.7.3
func ComputeDBFreshness(ctx context.Context, cfg *Config, profile string) DimensionResult
ComputeDBFreshness scores how recently the given profile wrote a memory. The scoring curve matches Python ogham.health_dimensions:
age <= 24h => 10.0 (GREEN) 24h-72h => 7.99 -> 5.0 (AMBER, linear) 72h-30d => 4.99 -> 0.0 (RED, linear; capped at 30d excess) no rows => 0.0 (RED -- "dead writer")
A query error surfaces as RED with the error in Detail.
func ComputeE2EProbe ¶ added in v0.7.3
func ComputeE2EProbe(ctx context.Context, cfg *Config, profile string) DimensionResult
ComputeE2EProbe scores a synthetic round-trip against the live store. Writes a single memory with a unique tag, searches for it, then deletes it. Any failure -> RED 0.0. Success -> GREEN 10.0 with the total wall-clock duration in Detail.
Uses native.Store + native.Search + native.Delete -- no new SQL. Cleanup runs in a defer so a failed search still removes the row.
type Embedder ¶
type Embedder interface {
// Embed returns the embedding vector for text. Length must equal the
// dimension the Embedder was configured with.
Embed(ctx context.Context, text string) ([]float32, error)
// Name returns a short identifier for error/log messages.
Name() string
// Dimension returns the output vector length. Callers that need to
// assert schema compatibility (vector(512) etc.) can check here.
Dimension() int
}
Embedder produces a fixed-dimension embedding vector for a single text. All providers in the absorption path implement this same contract so the rest of the code doesn't care which provider is configured.
func NewEmbedder ¶
NewEmbedder constructs an Embedder from a native.Config. Providers are added as they are absorbed; Gemini is the first, matching the user's current .env configuration. Returns a clear error on unknown providers rather than silently falling back to a default.
type Embedding ¶
type Embedding struct {
Provider string `toml:"provider"`
APIKey string `toml:"api_key"`
Model string `toml:"model"`
Dimension int `toml:"dimension"`
// BaseURL lets providers that can be self-hosted (Ollama today;
// OpenAI via Azure / LocalAI proxies in v0.5+) point at a custom
// endpoint instead of the provider default. Empty string means
// "use provider default". Populated from OLLAMA_URL today; v0.5
// embedders will add OPENAI_BASE_URL / MISTRAL_BASE_URL as they land.
BaseURL string `toml:"base_url"`
}
type ExploreOptions ¶ added in v0.7.0
type ExploreOptions struct {
Depth int // default 1
MinStrength float64 // default 0.5
Limit int // default 5
Tags []string // filter seed results
Source string // filter seed results
Profile string
}
ExploreOptions configures the graph-walk seed + traversal.
type ExtendedHealthReport ¶ added in v0.7.3
type ExtendedHealthReport struct {
Profile string `json:"profile"`
OverallScore float64 `json:"overall_score"`
OverallZone Zone `json:"overall_zone"`
Dimensions []DimensionResult `json:"dimensions"`
PortedDimensions int `json:"ported_dimensions"`
TotalDimensions int `json:"total_dimensions"`
DeferredNotice string `json:"deferred_notice,omitempty"`
DurationMs float64 `json:"duration_ms"`
}
ExtendedHealthReport is the JSON payload `ogham health --extended` emits. Mirrors the shape Python's compose_health produces, with the caveat that this Path-B batch ships only 3 of the 8 dimensions. PortedDimensions / TotalDimensions make the partial state explicit so users (and dashboards) don't read this as a stable 8-row format until the follow-up session lands.
func ComposeExtendedHealth ¶ added in v0.7.3
func ComposeExtendedHealth(ctx context.Context, cfg *Config, profile string) ExtendedHealthReport
ComposeExtendedHealth runs every dimension in this batch and returns a single report. Each dimension is wrapped in a recover so one panicking probe doesn't take the whole report down -- matches the Python exception-catching pattern in compose_health.
type FindRelatedOptions ¶ added in v0.7.0
type FindRelatedOptions struct {
Depth int
MinStrength float64
RelationshipTypes []string
Limit int
}
FindRelatedOptions tunes the graph walk from a known memory.
type GraphMemory ¶ added in v0.7.0
type GraphMemory struct {
ID string `json:"id"`
Content string `json:"content"`
Metadata map[string]any `json:"metadata,omitempty"`
Source string `json:"source,omitempty"`
Tags []string `json:"tags,omitempty"`
Relevance float64 `json:"relevance,omitempty"`
Confidence float64 `json:"confidence,omitempty"`
Depth int `json:"depth"`
Relationship string `json:"relationship,omitempty"`
EdgeStrength float64 `json:"edge_strength,omitempty"`
ConnectedFrom string `json:"connected_from,omitempty"`
}
GraphMemory is a memory row returned from graph-walk queries. The extra fields (Depth, Relationship, EdgeStrength, ConnectedFrom) describe how this row was reached -- depth 0 means direct seed match, depth > 0 means we traversed N edges to get here.
func ExploreKnowledge ¶ added in v0.7.0
func ExploreKnowledge(ctx context.Context, cfg *Config, query string, opts ExploreOptions) ([]GraphMemory, error)
ExploreKnowledge runs hybrid search for query, then walks the relationship graph Depth hops deep. Returns seed matches at depth 0 and connected memories at depth 1+.
func FindRelated ¶ added in v0.7.0
func FindRelated(ctx context.Context, cfg *Config, memoryID string, opts FindRelatedOptions) ([]GraphMemory, error)
FindRelated traverses the relationship graph from memory_id and returns reachable memories with depth + edge strength annotations.
type GraphNode ¶ added in v0.7.1
type GraphNode struct {
ID string `json:"id"`
Content string `json:"content"`
Metadata map[string]any `json:"metadata,omitempty"`
Source string `json:"source,omitempty"`
Tags []string `json:"tags,omitempty"`
Confidence float64 `json:"confidence,omitempty"`
Depth int `json:"depth"`
Relationship string `json:"relationship,omitempty"`
EdgeStrength float64 `json:"edge_strength,omitempty"`
ConnectedFrom string `json:"connected_from,omitempty"`
DirectionUsed string `json:"direction_used,omitempty"`
}
GraphNode is a row returned from wiki_walk_graph. Carries the path metadata (depth, edge strength, direction) alongside the memory's own fields. Distinct from GraphMemory (used by hybrid-search-driven graph walks) because the wiki walk has direction_used, no relevance, and confidence is canonically present.
type HealthOptions ¶
type HealthOptions struct {
// LiveEmbedder performs a real embedding API call if true. Costs
// one provider token per run; defaults off so `ogham health` is free.
LiveEmbedder bool
}
HealthOptions lets callers opt into live external calls.
type HookOptions ¶ added in v0.7.3
HookOptions controls the hook entry points (SessionStart, Recall, Inscribe). All fields are optional; zero values mean "use the default that mirrors the Python ogham.hooks implementation".
Profile follows the standard precedence: explicit Profile here > cfg.Profile > "default".
DryRun is honoured by Inscribe -- when true, the drain memory is composed and returned but NOT written to the store. SessionStart and Recall ignore DryRun (they're read-only).
type LinkUnlinkedOptions ¶ added in v0.7.0
LinkUnlinkedOptions mirrors the Python tool. Defaults match Python: batch_size=100, threshold=0.85, max_links=5.
type LinkUnlinkedResult ¶ added in v0.7.0
type LinkUnlinkedResult struct {
Status string `json:"status"` // "linked" | "nothing_to_link"
Profile string `json:"profile"`
Processed int `json:"processed"`
BatchSize int `json:"batch_size"`
}
LinkUnlinkedResult is the payload for the MCP tool.
func LinkUnlinked ¶ added in v0.7.0
func LinkUnlinked(ctx context.Context, cfg *Config, opts LinkUnlinkedOptions) (*LinkUnlinkedResult, error)
LinkUnlinked backfills auto-links for memories that don't have any yet. Returns the count of memories processed (not links created). Call repeatedly until processed=0 after a batch import.
type LintCategory ¶ added in v0.7.1
type LintCategory struct {
Count int `json:"count"`
Sample []map[string]any `json:"sample"`
// OlderThanDays is non-zero only for stale_lifecycle (Python
// includes this in its response so callers can see the threshold
// they queried at).
OlderThanDays int `json:"older_than_days,omitempty"`
// Skipped is set on summary_drift when includeDrift=false so the
// caller can tell "0 drift" from "we didn't check".
Skipped bool `json:"skipped,omitempty"`
}
LintCategory is the count + sample shape every category returns. Kept generic so the JSON output mirrors the Python report structure.
type LintReport ¶ added in v0.7.1
type LintReport struct {
Profile string `json:"profile"`
Healthy bool `json:"healthy"`
IssueCount int `json:"issue_count"`
Contradictions LintCategory `json:"contradictions"`
Orphans LintCategory `json:"orphans"`
StaleLifecycle LintCategory `json:"stale_lifecycle"`
StaleSummaries LintCategory `json:"stale_summaries"`
SummaryDrift LintCategory `json:"summary_drift"`
}
LintReport is the aggregate response from LintWiki.
func LintWiki ¶ added in v0.7.1
func LintWiki(ctx context.Context, cfg *Config, profile string, opts LintWikiOptions) (*LintReport, error)
LintWiki computes the read-only lint report. Profile defaults to the active profile when empty.
type LintWikiOptions ¶ added in v0.7.1
type LintWikiOptions struct {
SampleSize int // default 10
StableDays int // default 90
IncludeDrift bool // default true (caller passes opts; LintWiki sets if zero-value)
// contains filtered or unexported fields
}
LintWikiOptions mirrors the Python lint_report keyword args.
func (*LintWikiOptions) SetIncludeDrift ¶ added in v0.7.1
func (o *LintWikiOptions) SetIncludeDrift(v bool)
SetIncludeDrift makes the includeDrift flag explicit. Calling this distinguishes "I want drift skipped" from "I left the field at the zero value (false) by accident." The default when unset is true.
type ListOptions ¶
ListOptions captures the optional filters for List. Mirrors Python's list_recent_memories signature with two dashboard-friendly additions:
- Before: if non-zero, restricts the result set to memories strictly older than the given timestamp. Used by the Timeline view's HTMX infinite scroll -- the client passes the oldest-visible created_at back as `?before=...` and the server returns the next page.
- OnDate: if non-zero, restricts to memories whose created_at falls on the same UTC calendar day as the given time. Used by the Calendar heatmap drill-in (`/timeline?on=YYYY-MM-DD`).
Before and OnDate are mutually exclusive in practice -- OnDate narrows to a single day, Before paginates. If both are set we apply both; the Timeline handler never sends both together.
type MaskedConfig ¶
type MaskedConfig struct {
Profile string `json:"profile"`
Database MaskedDatabase `json:"database"`
Embedding MaskedEmbedding `json:"embedding"`
Paths map[string]string `json:"paths"`
// Warnings is a list of operator-facing diagnostics. Empty when the
// config looks healthy. Currently flags configured-but-unprivileged
// Supabase keys (anon / publishable) before the first request 401s.
Warnings []string `json:"warnings,omitempty"`
}
MaskedConfig is a safe-to-print view of Config with secret values replaced by "<redacted>".
func Mask ¶
func Mask(cfg *Config) MaskedConfig
Mask returns a redacted view of cfg. URL passwords and all API keys are replaced with "<redacted>". Empty fields are left empty so the reader can see which settings are missing.
type MaskedDatabase ¶
type MaskedDatabase struct {
Backend string `json:"backend"`
URL string `json:"url,omitempty"`
SupabaseURL string `json:"supabase_url,omitempty"`
SupabaseKey string `json:"supabase_key,omitempty"`
// SupabaseKeyKind reports the role behind the configured key:
// "secret" / "service_role" (good), "anon" / "publishable" (bad,
// will 401 on RPCs), or "unknown" (couldn't tell — try anyway).
SupabaseKeyKind string `json:"supabase_key_kind,omitempty"`
}
type MaskedEmbedding ¶
type Memory ¶
type Memory struct {
ID string `json:"id"`
Content string `json:"content"`
Tags []string `json:"tags"`
Source string `json:"source,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
Memory is the native-mode view of a row from the memories table. Narrow on purpose -- native tools deliberately do not project every column; they ship the subset the CLI actually needs.
type ProfileCount ¶
ProfileCount is one row from get_profile_counts(): a profile name plus the number of non-expired memories in it.
func ListProfiles ¶
func ListProfiles(ctx context.Context, cfg *Config) ([]ProfileCount, error)
ListProfiles returns every profile that currently holds at least one non-expired memory. Matches Python's list_profiles tool which calls the get_profile_counts() function.
type ProfileTTL ¶
ProfileTTL describes a row from profile_settings. TTLDays is nil when the profile has no explicit TTL configured (memories then never expire unless the caller sets expires_at directly on store).
func GetProfileTTL ¶
GetProfileTTL reads the TTL (days) currently configured for a profile. A nil return (no row + no error) means no TTL is configured.
func SetProfileTTL ¶
func SetProfileTTL(ctx context.Context, cfg *Config, profile string, ttlDays int) (*ProfileTTL, error)
SetProfileTTL upserts a TTL for a profile. Pass a negative ttlDays to clear the setting entirely (match Python's None semantics).
type QueryTopicSummaryResult ¶ added in v0.7.1
type QueryTopicSummaryResult struct {
Status string `json:"status"`
ID string `json:"id,omitempty"`
TopicKey string `json:"topic_key"`
Profile string `json:"profile"`
Version int `json:"version,omitempty"`
StatusField string `json:"summary_status,omitempty"` // fresh|stale|regenerating
SourceCount int `json:"source_count,omitempty"`
ModelUsed string `json:"model_used,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
SourceHash string `json:"source_hash,omitempty"`
Level string `json:"level,omitempty"` // form actually returned
RequestedLevel string `json:"requested_level,omitempty"` // what caller asked for (if differs from Level)
Body string `json:"body,omitempty"` // selected text (one_line|short|body)
Message string `json:"message,omitempty"` // populated for not_cached
}
QueryTopicSummaryResult is the MCP-shaped response. Mirrors Python's _format_summary_response with the v0.13 `level` / `requested_level` pair appended.
When Status == "not_cached" only TopicKey + Profile + Message are populated -- the rest are zero values.
func QueryTopicSummary ¶ added in v0.7.1
func QueryTopicSummary(ctx context.Context, cfg *Config, profile, topic string, level TLDRLevel) (*QueryTopicSummaryResult, error)
QueryTopicSummary fetches a cached summary for (profile, topic) and projects it down to the requested level. cfg.Profile is used when `profile` is empty.
type Report ¶
type Report struct {
OK bool `json:"ok"`
Duration time.Duration `json:"duration_ns"`
Backend string `json:"backend"`
Provider string `json:"provider,omitempty"`
Checks []CheckResult `json:"checks"`
}
Report is the aggregate return from HealthCheck. OK is true only if every probe succeeded.
func HealthCheck ¶
HealthCheck runs the configured probes in parallel. The whole report completes in roughly the time of the slowest probe.
Probes currently included:
- backend: Supabase PostgREST HEAD or Postgres pgx ping
- embedder: config validation only by default (avoids a paid Gemini call on every `ogham health`); opt in to the live API call via opts.LiveEmbedder = true
type SearchOptions ¶
type SearchOptions struct {
Limit int
Tags []string
Source string
Profile string // single-profile override; empty => use cfg.Profile
Profiles []string
EntityTags []string
// RecencyDecay > 0 tilts scoring toward newer memories. 0 = no decay.
RecencyDecay float64
}
SearchOptions captures the optional arguments hybrid_search_memories accepts. Nil slices mean "no filter". Zero values mean "use RPC default".
type SearchResult ¶
type SearchResult struct {
ID string `json:"id"`
Content string `json:"content"`
Source string `json:"source,omitempty"`
Profile string `json:"profile"`
Tags []string `json:"tags"`
Metadata map[string]any `json:"metadata,omitempty"`
Similarity float64 `json:"similarity"`
KeywordRank float64 `json:"keyword_rank"`
Relevance float64 `json:"relevance"`
CreatedAt time.Time `json:"created_at"`
}
SearchResult mirrors the column set returned by hybrid_search_memories. Narrower projection than the memories table -- only what the CLI surfaces. Metadata carries the jsonb column as a decoded map so downstream callers (BEAM harness, MCP tool handlers) can read keys like msg_ids / chat_id without a second round-trip to fetch the same row.
func Search ¶
func Search(ctx context.Context, cfg *Config, query string, opts SearchOptions) ([]SearchResult, error)
Search runs hybrid_search_memories using whichever backend is configured:
- "supabase": PostgREST RPC call (no direct DB connection needed)
- "postgres": pgx direct connection to Postgres
Both paths embed the query first via the configured provider.
type StageReport ¶ added in v0.7.1
type StageReport struct {
Profile string `json:"profile"`
FreshToStable int `json:"fresh_to_stable"`
EditingClosed int `json:"editing_closed"`
LifecycleAbsent bool `json:"lifecycle_absent,omitempty"`
}
StageReport is the {fresh_to_stable, editing_closed} pair Python returns. Mirrors lifecycle.StageReport so the JSON shape is identical.
func AdvanceLifecycle ¶ added in v0.7.1
func AdvanceLifecycle(ctx context.Context, cfg *Config, profile string, opts AdvanceLifecycleOptions) (*StageReport, error)
AdvanceLifecycle runs the two-batch sweep for a profile. Returns counts for each transition.
type Stats ¶
type Stats struct {
Profile string `json:"profile"`
Total int64 `json:"total"`
Sources []Breakdown `json:"sources"`
TopTags []Breakdown `json:"top_tags"`
Untagged int64 `json:"untagged_count"`
WithTTL int64 `json:"with_ttl_count"`
Expiring int64 `json:"expiring_within_7d"`
// ConnectedPct is the percentage (0-100) of active memories that
// appear as either source_id or target_id in memory_relationships.
// Answers "how much of my graph is wired up?" -- a profile at 5%
// connected is effectively a flat list; one at 60%+ is a real graph.
// When Total is 0 we return 0 (no rows, no percentage to compute).
ConnectedPct float64 `json:"connected_pct"`
// DecayCount is the number of active memories whose confidence has
// fallen below DecayThreshold. Confidence decays over time in the
// schema (see memories.confidence + the nightly decay job); memories
// below the floor are nearly invisible to hybrid_search because the
// relevance formula multiplies by confidence. Surfacing this as a
// headline number lets operators spot profiles that need pruning or
// reinforcement.
DecayCount int64 `json:"decay_count"`
}
Stats is a lightweight summary of the active profile. Narrower than Python's get_stats (which also includes decay counters, orphan counts, cache stats, and configuration echo) -- the CLI uses of stats are "how many memories", "what's my top sources/tags", and "am I on the right profile", so this covers the headline numbers without the heavier aggregations.
type StoreDecisionOptions ¶ added in v0.7.0
type StoreEventOptions ¶ added in v0.7.0
type StoreFactOptions ¶ added in v0.7.0
type StoreOptions ¶ added in v0.7.0
type StoreOptions struct {
Tags []string
Source string
Profile string // empty -> cfg.Profile (which itself falls back to "default")
// Language is the 2-letter ISO code the extraction pipeline uses
// for scoring, date parsing, and recurrence detection. Empty string
// falls back to "en". Callers should set this from metadata[language]
// on re-embed / migration paths, or leave it empty to default to
// English. Values not in the embedded YAML registry produce a debug
// slog warning and fall back silently to English.
Language string
// DryRun skips the actual DB write. Still runs extraction + embedding
// + surprise so the caller sees what would happen. Useful for the
// Day 4 --native-store-preview flag where we want to confirm the
// pipeline is green without mutating the store.
DryRun bool
// Metadata is caller-supplied structured context that gets merged
// over the extracted metadata (dates, etc.) before the DB write.
// Primary consumer: the typed-store wrappers (store_decision,
// store_fact, store_event, store_preference) which inject type-
// specific structure. Extracted keys lose on collision so the
// caller's intent wins. Nil is fine -- leaves extraction output
// alone.
//
// If Metadata contains a "language" string key AND StoreOptions.Language
// is empty, the metadata value is adopted as the effective language.
// This mirrors Python's service.py behaviour where metadata carries
// the language signal end-to-end.
Metadata map[string]any
}
StoreOptions captures the per-request parameters Store accepts. Nil/empty fields use the config default.
type StorePreferenceOptions ¶ added in v0.7.0
type StoreResult ¶ added in v0.7.0
type StoreResult struct {
ID string `json:"id,omitempty"`
Profile string `json:"profile"`
Tags []string `json:"tags"`
Entities []string `json:"entities"`
Dates []string `json:"dates"`
Importance float64 `json:"importance"`
Surprise float64 `json:"surprise"`
LinkedTo []AutoLink `json:"linked_to,omitempty"`
Elapsed time.Duration `json:"elapsed"`
DryRun bool `json:"dry_run,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
StoreResult is returned by Store. ID is empty when DryRun=true.
func Store ¶ added in v0.7.0
func Store(ctx context.Context, cfg *Config, content string, opts StoreOptions) (*StoreResult, error)
Store is the Day 4 native orchestrator. Mirrors the shape of Python's store_memory tool:
- Serial extraction (entities, dates, importance) -- ~microseconds
- errgroup parallel: - embedder.Embed(content) - searchByText(content[:200]) used to compute surprise
- surprise = 1.0 - max(similarity from step 2); default 0.5 on empty
- auto-link: top-N above threshold become links in the result; the actual INSERT into memory_links is deferred to the next commit
- DB write via backend (postgres direct; supabase still goes through the Python sidecar until a native PostgREST write lands)
The returned StoreResult is safe to marshal to JSON; cmd/store.go emits it to --json users.
func StoreDecision ¶ added in v0.7.0
func StoreDecision(ctx context.Context, cfg *Config, opts StoreDecisionOptions) (*StoreResult, error)
func StoreEvent ¶ added in v0.7.0
func StoreEvent(ctx context.Context, cfg *Config, opts StoreEventOptions) (*StoreResult, error)
func StoreFact ¶ added in v0.7.0
func StoreFact(ctx context.Context, cfg *Config, opts StoreFactOptions) (*StoreResult, error)
func StorePreference ¶ added in v0.7.0
func StorePreference(ctx context.Context, cfg *Config, opts StorePreferenceOptions) (*StoreResult, error)
type SupabaseKeyKind ¶ added in v0.7.2
type SupabaseKeyKind string
SupabaseKeyKind classifies a Supabase API key for warning purposes. Only "anon" and "publishable" are meaningful failure cases — both lack the privileges hybrid_search_memories and the memories table need. service_role + secret are the keys Ogham actually wants. unknown covers parse failures and is treated as "no warning, give it a try".
const ( SupabaseKeyAnon SupabaseKeyKind = "anon" SupabaseKeyPublishable SupabaseKeyKind = "publishable" SupabaseKeyServiceRole SupabaseKeyKind = "service_role" SupabaseKeySecret SupabaseKeyKind = "secret" SupabaseKeyUnknown SupabaseKeyKind = "unknown" )
func ClassifySupabaseKey ¶ added in v0.7.2
func ClassifySupabaseKey(key string) SupabaseKeyKind
ClassifySupabaseKey inspects a Supabase API key and reports its kind.
The new prefixed keys (sb_secret_*, sb_publishable_*) are opaque and can be classified by prefix alone. The legacy keys are JWTs whose payload carries a "role" claim — we base64-decode the middle segment and read it. No signature check is performed; the key is going to be sent to Supabase anyway, which is the real authority.
func (SupabaseKeyKind) IsRPCCapable ¶ added in v0.7.2
func (k SupabaseKeyKind) IsRPCCapable() bool
IsRPCCapable reports whether a key is expected to authorise the RPC and SELECT-on-memories paths Ogham relies on. Used both by the loud 401 hint and by `ogham config show` to flag misconfigured keys before the first request even leaves the box.
type TLDRLevel ¶ added in v0.7.1
type TLDRLevel string
TLDRLevel selects the resolution of a topic summary read.
- LevelOneLine -- ~30-50 tokens, single sentence
- LevelShort -- ~150-300 tokens, one paragraph
- LevelBody -- ~1000 words, full markdown body (the v0.12 default)
Mirrors the Python LevelType = Literal["one_line", "short", "body"] type alias used by `query_topic_summary(level=...)`.
func ParseTLDRLevel ¶ added in v0.7.1
ParseTLDRLevel coerces a user-supplied string to a TLDRLevel. Empty input maps to LevelBody to preserve the v0.12 caller contract -- callers that don't pass `level` get the full body, same as before. Unknown values are an error rather than a silent fallback so a typo in a CLI flag doesn't quietly downgrade what got returned.
type TopicSummary ¶ added in v0.7.1
type TopicSummary struct {
ID string `json:"id"`
TopicKey string `json:"topic_key"`
ProfileID string `json:"profile"`
Content string `json:"content"`
TLDROneLine *string `json:"tldr_one_line,omitempty"`
TLDRShort *string `json:"tldr_short,omitempty"`
SourceCount int `json:"source_count"`
SourceCursor *string `json:"source_cursor,omitempty"`
SourceHash string `json:"source_hash,omitempty"` // hex-encoded
TokenCount *int `json:"token_count,omitempty"`
Importance float64 `json:"importance"`
ModelUsed string `json:"model_used"`
Version int `json:"version"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
StaleReason *string `json:"stale_reason,omitempty"`
}
TopicSummary is the post-033 topic_summaries row shape.
TLDROneLine and TLDRShort are *string because they are NULLABLE on pre-033 rows and the read path needs to distinguish "explicitly empty" (string pointer to "") from "not populated yet" (nil).
SourceHash is rendered to hex in the JSON tag because raw bytea is awkward over JSON; the Python `_format_summary_response` does the same.
type UpdateOptions ¶ added in v0.7.0
type UpdateOptions struct {
Content *string
Tags []string
Metadata map[string]any
// Profile override; empty -> cfg.Profile -> "default".
Profile string
}
UpdateOptions captures per-request update semantics. Pointer / nil-slice semantics let callers distinguish "field omitted" from "explicit clear":
Content == nil -> leave content untouched
Content != nil -> replace content (and re-embed)
Tags == nil -> leave tags untouched
Tags != nil, len 0 -> clear tags (PostgreSQL empty array)
Metadata == nil -> leave metadata untouched
Metadata != nil, len 0 -> clear metadata (jsonb '{}')
Matches the Python update_memory tool contract (None vs [] vs {}).
type UpdateResult ¶ added in v0.7.0
type UpdateResult struct {
ID string `json:"id"`
Profile string `json:"profile"`
UpdatedAt time.Time `json:"updated_at"`
FieldsUpdated []string `json:"fields_updated"`
ReEmbedded bool `json:"re_embedded"`
}
UpdateResult is returned on success. FieldsUpdated is the list of column names that were actually written, so callers can log audit.
func Update ¶ added in v0.7.0
func Update(ctx context.Context, cfg *Config, id string, opts UpdateOptions) (*UpdateResult, error)
Update re-writes an existing memory. When opts.Content is non-nil the content is re-embedded via the configured embedder before the row is written -- the old embedding would poison future similarity search if we left it behind.
Returns an error when no fields are specified for update (parity with Python: raise ValueError("No updates provided")).
type WalkKnowledgeOptions ¶ added in v0.7.1
type WalkKnowledgeOptions struct {
// Depth is the maximum number of edges to traverse. Server clamps to
// 0..5 (anything beyond explodes the rowset on dense profiles). 0
// means "no walk" -- you'd just get the seed node back.
Depth int
// Direction is "outgoing", "incoming", or "both". Empty -> "both".
Direction string
// MinStrength filters edges by strength (0..1). Default 0.0 = keep all.
MinStrength float64
// RelationshipTypes optionally restricts to a set of relationship
// labels (e.g. ["depends_on", "cites"]). Empty -> no filter.
RelationshipTypes []string
// Limit caps the result rowset. Default 50 mirrors Python.
Limit int
}
WalkKnowledgeOptions tunes the recursive walk. Defaults match the Python wiki tool layer.
type WalkKnowledgeResult ¶ added in v0.7.1
type WalkKnowledgeResult struct {
StartID string `json:"start_id"`
Depth int `json:"depth"`
Direction string `json:"direction"`
NodeCount int `json:"node_count"`
Nodes []GraphNode `json:"nodes"`
}
WalkKnowledgeResult mirrors the Python tool wrapper:
{ start_id, depth, direction, node_count, nodes }
func WalkKnowledge ¶ added in v0.7.1
func WalkKnowledge(ctx context.Context, cfg *Config, startID string, opts WalkKnowledgeOptions) (*WalkKnowledgeResult, error)
WalkKnowledge runs the recursive graph walk from startID. Returns the flattened set of reachable memories with depth/edge metadata.
Source Files
¶
- active_profile.go
- config.go
- embedding.go
- embedding_cached.go
- envfile.go
- graph.go
- health.go
- health_dimensions.go
- hooks.go
- lifecycle.go
- lifecycle_advance.go
- list.go
- maintenance.go
- profiles.go
- redact.go
- save.go
- search.go
- stats.go
- store.go
- supabase.go
- supabase_keytype.go
- typed_store.go
- types.go
- update.go
- wiki_lint.go
- wiki_query_topic_summary.go
- wiki_walk_knowledge.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package cache implements the shared SQLite-backed embedding cache.
|
Package cache implements the shared SQLite-backed embedding cache. |
|
Package extraction implements the regex-driven entity + date extraction that runs at store_memory time.
|
Package extraction implements the regex-driven entity + date extraction that runs at store_memory time. |