Documentation
¶
Overview ¶
Package store is ProjX's declared-knowledge layer — the second deterministic root (core = facts from code; store = facts you declare). Plain data only: no AI, no UI, no logic beyond records and a read/write API.
INTERFACE-FIRST by design: this contract is what context/workflow/graph/verify depend on. The concrete SQLite schema is deferred until real records teach its shape (maximal interface, minimal obligation). An in-memory impl backs it today.
Index ¶
- Constants
- Variables
- func AgentContext(st Store, sel Filter) string
- func AgentContextDelta(st Store, task string, seen map[string]int64) (string, map[string]int64)
- func AgentContextDeltaSel(st Store, task string, seen map[string]int64, sel SelectorFunc, focus string) (string, map[string]int64)
- func AgentContextFloor(st Store) string
- func AgentContextForTask(st Store, task string) string
- func AgentContextForTaskSel(st Store, task string, sel SelectorFunc, focus string) string
- func AgentPreamble(st Store) string
- func CageModeOn(s Store) bool
- func CaptureHint(task string) string
- func Classify(task string) string
- func ClassifyConfident(task string) (class string, matched bool)
- func ClassifyStore(s Store, task string) (class string, matched bool)
- func Decompose(message string) []string
- func DenyRules(s Store) []string
- func DispatcherModeOn(s Store) bool
- func Export(project Store) string
- func ExportJSON(recs []Record) ([]byte, error)
- func GateDenied(s Store, path string) (pattern string, denied bool)
- func GatePatterns(s Store) []string
- func IntegrationNames(s Store) []string
- func IsMutatingTool(name string) bool
- func IsRiskyTask(s Store, task string) bool
- func IsSoftRule(s Store, name string) bool
- func ManagedBlock(s Store) string
- func MaxUpdatedAt(st Store) int64
- func NormGatePath(p string) string
- func OverrideAuthorityOn(s Store) bool
- func ParseSelectedKeys(reply string, candidates []string) []string
- func ParseTierReply(content string) (tier string, confident bool)
- func ProvenanceFor(by string) string
- func RenderCLIArgs(template, prompt, model string) []string
- func Route(s Store, task string) (class, cmd string)
- func RuleTier(s Store, name string) string
- func SeedFloor(s Store) int
- func SessionContext(st Store, cps CheckpointStore, session, task string, reset bool, ...) string
- func SessionSuggest(st Store, cps CheckpointStore, session string) (msg string, block bool)
- func SoftRuleNames(s Store) []string
- func SpliceManagedBlock(existing, block string) string
- func TaskSliceOverflows(st Store, task string) bool
- func Tier(r Record) string
- func WorkerAllowBins(s Store) []string
- func WorkerDirectiveText(s Store) string
- func WorkerFullAutonomy(s Store) bool
- type Checkpoint
- type CheckpointStore
- type CompletionSpec
- type Conflict
- type Filter
- type Kind
- type Mem
- type MergeResult
- type Record
- type Resolution
- type RouteDecision
- type SQLite
- type Scope
- type SeedRec
- type SelectorFunc
- type Store
- type TriageFunc
- type Workspace
Constants ¶
const ( ClaudeBegin = "<!-- PROJX:BEGIN — managed by ProjX from the project store; edits inside are overwritten -->" ClaudeEnd = "<!-- PROJX:END -->" )
const ( EnforcementHard = "hard" // gate denies; no override (secrets / off-limits floor) EnforcementSoft = "soft" // denies by default; a recorded reasoned override may proceed EnforcementAdvisory = "advisory" // context-injected only; never gated )
const ( TransportCLI = "cli" TransportHTTPOpenAI = "http-openai" )
Transport values.
const ( // ProvenanceHuman: a person committed or approved the record. ProvenanceHuman = "human-confirmed" // ProvenanceGate: the verify gate ran a real build/test and it passed. ProvenanceGate = "gate-verified" // ProvenanceAgent: an AI committed it without an independent check. ProvenanceAgent = "agent-asserted" )
const ( SettingRoutePin = "setting/route-pin" // Body = class: hard-lock every task to this tier SettingRouteFloor = "setting/route-floor" // Body = class: minimum tier; triage may go above )
Routing-setting record keys. They live in the store as KRoute records under a `setting/` key prefix so they are routing knowledge that travels + is journaled, yet are excluded from context injection (dropSettings).
const DefaultWorkerAllow = "git go gofmt goimports make npm npx pnpm yarn node projx-engine cat ls grep rg find sed awk head tail wc"
DefaultWorkerAllow is the SEED content and the fallback when the record is absent — so a legacy/un-reseeded project still gets a working worker floor. This is DATA (seeded into the store), not a hardcoded policy: the store record is the source of truth and fully editable; this constant only bootstraps it.
const DefaultWorkerDirective = "# YOUR ROLE: WORKER (executor) — READ THIS FIRST\n" +
"You are a spawned worker agent, NOT the trunk. Your job is to COMPLETE this task yourself: " +
"read, edit files, and run whatever tools are needed, then stop. Editing files is expected and permitted for you.\n" +
"The project's \"dispatch, don't mutate\" convention below governs the TRUNK session ONLY — it does NOT apply to you. " +
"Do NOT dispatch, delegate, spawn another agent, or ask to — just do the work directly.\n\n---\n\n"
DefaultWorkerDirective is the SEED content for the worker directive, and the fallback WorkerDirectiveText returns when the store has no record yet (a legacy project that hasn't re-seeded, or the store is briefly unreachable) — so a worker is never left without this reframing just because the record is missing.
const SessionSuggestText = "ProjX: you were asked to @remember something this session, but nothing was " +
"committed to the project store. If it's worth keeping, commit it now:\n" +
" projx-engine store commit --kind doc --key <area>/<feature> --body \"<the fact>\"\n" +
"Otherwise, briefly note that it wasn't worth storing and you're done."
SessionSuggestText is the @remember nudge surfaced by SessionSuggest / the Stop hook.
const SettingCageMode = "setting/cage-mode"
SettingCageMode is the DECLARED, project-level default for OS-level agent confinement. Cage stays opt-in — this setting only lets a project turn it ON by default (seeded "off"); the PROJX_CAGE env var, when set, always overrides it explicitly for one launch. Not the gate/dispatcher-mode axis — orthogonal.
const SettingDispatcherMode = "setting/dispatcher-mode"
── Trunk-dispatch (dispatcher-mode) ───────────────────────────────────────── The interaction law: the main session is a DISPATCHER, never a worker. When dispatcher-mode is ON, the trunk is denied file-mutating tools so every change is routed to a spawned tier-agent; a projx-spawned worker (PROJX_ROLE=worker) is exempt. This is a policy gate — NOT the cage/sandbox (which stays separately opt-in). Stored as a setting gate-rule (skipped by GatePatterns → never a deny glob).
const (
SettingIntegrationActive = "setting/integration-active" // Body = active integration name
)
Integration setting record keys.
const SettingOverrideAuthority = "setting/override-authority"
SettingOverrideAuthority keys the HUMAN-CONTROLLED delegation flag that decides whether the AI may override a soft rule at all. Default OFF: the AI can REQUEST an override but must not self-grant one. The human delegates by setting this ON (which, like the override itself, only they can do out-of-band — the hook blocks the AI from flipping it). See doc/enforcement-follow-override-plan and the override-authority ADR.
const SettingWorkerAllow = "setting/worker-allow"
SettingWorkerAllow keys the DECLARED list of shell commands a dispatched worker may run WITHOUT prompting — the "basic permissions" floor. The body is command names separated by commas / spaces / newlines. Anything NOT listed still prompts (the human grants the rest = "reach and ask for more"). Seeded with DefaultWorkerAllow; change it with `store commit --kind convention --key setting/worker-allow --body "git, go, …"` — no recompile. Key starts with "setting/" so it stays out of normal preamble render.
const SettingWorkerAutonomy = "setting/worker-autonomy"
SettingWorkerAutonomy keys the human-granted "full autonomy" escalation for workers. When its body is affirmative ("full"/"on"/"true"), a dispatched worker runs with ALL permissions — the verbal "you're allowed to do whatever you need" override, expressed as data. Default (absent) = basic permissions (the safe-list). The ProjX gate still blocks secrets/off-limits even under full autonomy.
const SettingWorkerDirective = "setting/worker-directive"
SettingWorkerDirective keys the EDITABLE worker-role directive: the text prepended to a spawned worker's context (PROJX_ROLE=worker) so it reframes the trunk's "dispatch, don't mutate" law as not-its-own and does the task directly instead of re-dispatching. Seeded at floor time with DefaultWorkerDirective as its body — edit it with `store commit --kind convention --key setting/worker-directive --body "…"`, no recompile needed. Key starts with "setting/" so dropSettings excludes it from normal preamble rendering (only WorkerDirectiveText's explicit fetch surfaces it).
Variables ¶
var DefaultIntegration = CompletionSpec{ Name: "claude-code", Transport: TransportCLI, Template: "claude -p {prompt} --model {model}", Model: "haiku", }
DefaultIntegration is the shipped fallback: drive the harness's own agent CLI in one-shot print mode. It is DATA (a replaceable default), not logic — override it by declaring an integration and marking it active. The `{prompt}`/`{model}` placeholders are substituted as whole argv elements (no shell), so prompts with spaces are safe.
var DefaultRuleTiers = map[string]string{ "dispatcher-mode": EnforcementSoft, "confirm-before-push": EnforcementSoft, "commit-style": EnforcementSoft, }
DefaultRuleTiers is the built-in tier for named POLICY rules when a store carries no explicit Enforcement for them. These are the overridable soft rules the override command and hook understand. A store record with an explicit Enforcement overrides this, so a project can retier a rule (e.g. make dispatcher-mode hard) as data.
var ErrNoID = errors.New("store: record has no ID")
ErrNoID is returned by Put when a record has no ID.
var FloorConventions = []SeedRec{
{"dispatch don't mutate", "The main session is a DISPATCHER, not a worker. Do not edit files directly from the trunk — route each task to its tier and spawn an agent to do the work (`projx-engine dispatch --run \"<task>\"`). The trunk reads, plans, dispatches, and VERIFIES the returned diff; spawned agents do the mutation. Tight iterative work = keep messaging one spawned agent rather than re-spawning. When dispatcher-mode is on this is enforced by a gate, not left to willpower."},
}
FloorConventions are the PROJECT-scope behaviour rules — only the ones tied to per-project mechanics. Universal law (secrets, engineering discipline, off-limits gates) lives at GLOBAL scope and INHERITS down (global → workspace → project), so it is NOT re-seeded here — each scope declares only its own distinct layer and they compound. Per-language rules belong in stacks (profiles.go); per-repo rules in a project seed.toml.
var FloorGates = []SeedRec{}
FloorGates: NONE at project scope by default. Off-limits gates are universal and are seeded ONCE at GLOBAL scope, inheriting into every workspace/project. A project (or workspace) can still declare its OWN additional gates — they compound on top of global.
var FloorKeywordSeeds = []SeedRec{ {"deep-reasoning", strings.Join(deepKeywords, " ")}, {"cheap-fast", strings.Join(cheapKeywords, " ")}, {"default", strings.Join(stdKeywords, " ")}, {"risk", strings.Join(riskKeywords, " ")}, }
FloorKeywordSeeds seed the classifier's built-in vocabulary into the store as EDITABLE setting/route-keywords/<class> records ("make it a rule, not a hardcode"): once seeded, ClassifyStore reads ONLY the store's copy, so removing or adding a word actually changes routing — not just extends it. deepKeywords/cheapKeywords/ stdKeywords above remain the seed DEFAULTS and the fallback for a store that hasn't been (re-)seeded yet (see ClassifyStore).
var FloorRoutes = []SeedRec{
{"cheap-fast", "claude --permission-mode acceptEdits --model claude-haiku-4-5-20251001"},
{"default", "claude --permission-mode acceptEdits --model claude-sonnet-4-6"},
{"deep-reasoning", "claude --permission-mode acceptEdits --model claude-opus-4-8"},
{"elevate", "claude --permission-mode acceptEdits --model claude-fable-5"},
}
FloorRoutes are the default class -> launch-command tiers seeded into every project. The model IDs live HERE, once — not hardcoded in the engine binary, so updating a tier is a store edit, not a recompile. The tier commands launch an AUTONOMOUS worker: --permission-mode acceptEdits lets it apply file edits without an interactive approval (a dispatched worker is headless — no one is there to click "allow"). ProjX's own PreToolUse gate (off-limits paths + the optional cage) remains the real guardrail. Model IDs live HERE, once (store-editable).
Functions ¶
func AgentContext ¶
AgentContext renders the contract with TASK SLICING. The protocol + the LAW sections (gate rules, conventions) are ALWAYS included in full (small, binding); the reference sections (ADRs, declared structure, docs, history) are narrowed by sel.KeyPrefix / sel.Text — typically derived from the task — so only the relevant records load ("query, don't dump"). A zero sel includes everything (== the full AgentPreamble). Deterministic, read-only; an empty store still yields the protocol so the agent always knows the rules.
func AgentContextDelta ¶
AgentContextDelta renders the PER-MESSAGE contract for a session that has ALREADY been injected the records named in `seen` (recordID -> the UpdatedAt at injection time). Unlike AgentContextForTask it does NOT repeat the protocol lecture — the agent received it at SessionStart and it is still in context. The LAW (gate rules + conventions) is always re-sent in full: it is small, binding, and must not be forgotten between turns. Reference records (ADR / doc / declared-structure / history) are included ONLY when they (a) match the task slice AND (b) are new or changed versus `seen` — so an unchanged record already in the agent's context is not paid for again. Returns the rendered text plus the UPDATED seen map (every task-relevant record now in the agent's context, by id -> UpdatedAt) for the caller to persist as the session checkpoint. A nil `seen` means "fresh session" (everything relevant is new). Deterministic and read-only. Equivalent to AgentContextDeltaSel with nil sel.
func AgentContextDeltaSel ¶
func AgentContextDeltaSel(st Store, task string, seen map[string]int64, sel SelectorFunc, focus string) (string, map[string]int64)
AgentContextDeltaSel is AgentContextDelta with an injectable relevance selector and a focus repo: when sel is non-nil it chooses the relevant reference records semantically (v2); nil uses the deterministic v1 token match. focus (a repo/group) boosts that repo's matched records. An empty/failed selection falls back to v1 so a turn is never starved.
func AgentContextFloor ¶
AgentContextFloor renders the LEAN session-start floor: the full protocol lecture plus the LAW (gate rules + conventions) in full — and nothing else. The reference knowledge (ADRs, docs, declared structure, history) is deliberately NOT dumped here; it loads per-message via AgentContextDelta as a task makes it relevant ("why load the full context if you don't have to"). This is the SessionStart counterpart to the per-message delta: small + binding at the top of a session, the rest streamed in on demand. Pure and read-only; a nil/empty store still yields the protocol.
func AgentContextForTask ¶
AgentContextForTask is the task-driven entry point used by the launch hooks: it renders the law + only the reference records relevant to the task, selected by the v1 DETERMINISTIC token match (significant tokens vs each record's Key+Body). Equivalent to AgentContextForTaskSel with a nil selector.
func AgentContextForTaskSel ¶
func AgentContextForTaskSel(st Store, task string, sel SelectorFunc, focus string) string
AgentContextForTaskSel renders the task-sliced contract using sel to choose the relevant reference records (v2 semantic selection) — or, when sel is nil, the deterministic v1 token match. The law (gates+conventions) always loads in full. Falls back to the FULL context when there is no selection signal at all (nil sel AND no usable tokens), so the agent is never starved of context.
func AgentPreamble ¶
AgentPreamble renders the FULL contract (everything in the store). It is AgentContext with a zero selector — kept for callers that want the whole store.
func CageModeOn ¶
CageModeOn reports whether a project has declared cage-mode ON by default (a setting/cage-mode gate-rule with an affirmative body); false (uncaged) if absent.
func CaptureHint ¶
CaptureHint returns instructions for turning the user's aside into a clean store commit when the task signals capture intent; "" otherwise. Appended to the task context by the launch hooks so a "remember: …" becomes a real, well-formed store.commit instead of a markdown scribble.
func ClassifyConfident ¶
ClassifyConfident maps a task to a capability class by keyword and reports whether a keyword actually MATCHED (true) or the task fell through to the default class with no signal (false). The matched flag is what the decider uses to tell a confident keyword route from the ambiguous middle that warrants cheap model triage. Deterministic; no LLM. Priority: deep-reasoning > cheap-fast > default.
func Decompose ¶
Decompose splits a multi-task message into discrete task fragments on natural connectors and numbered/bulleted list markers. Returns a single-element slice (the trimmed message) when there is no clear split — so a normal one-task message is untouched. Deterministic and order-preserving.
func DenyRules ¶
DenyRules turns the store's gate rules into agent file-tool deny rules — "Read(glob)" / "Edit(glob)" — over the GatePatterns set. This is the SINGLE gate->deny definition shared by the engine, the engine cell, and the Workbench (each previously derived it on its own).
func DispatcherModeOn ¶
DispatcherModeOn reports whether the trunk-dispatch discipline is enabled (a setting/dispatcher-mode gate-rule with an affirmative body).
func Export ¶
Export renders the project store's declared knowledge as an architecture.md-style markdown document. It is strictly READ-ONLY: it pulls records via List and never writes. Records are grouped into fixed sections (Architecture Decisions, Declared Structure, Conventions, Docs, History); empty sections are omitted. Within a section records are sorted by Key (then ID) and rendered as "### {Key}" followed by the record Body.
func ExportJSON ¶
ExportJSON serializes records (typically one scope) to portable JSON — the durable form of a "your store" for git-backing, new-brain import, and promotion carry. (Distinct from Export, which renders the project store as read-only markdown.)
func GateDenied ¶
GateDenied reports whether path is denied by any of the store's gate patterns, returning the matching pattern. Matching is doublestar glob semantics (** crosses path segments, * does not) — the same semantics the gate's deny globs use — so secret/**, **/*.key, .env*, and **/.ssh/** all match correctly (a prefix matcher would not). One definition for native gate checks and the cell's gate endpoint.
func GatePatterns ¶
GatePatterns returns the normalized off-limits glob patterns declared by the store's gate rules: each rule's Body (falling back to its Key), with a trailing "/" expanded to a recursive "/**". setting/* rules are skipped — config/secrets are never a project gate. This is the SINGLE source of the gate pattern set: DenyRules renders these as agent Read()/Edit() denies, and a path-matching gate check tests file paths against them (so the deny globs and the check can never drift).
func IntegrationNames ¶
IntegrationNames lists the names of every declared integration (sorted for stable output), read from the `setting/integration/<name>` records.
func IsMutatingTool ¶
IsMutatingTool reports whether a tool name writes to files.
func IsRiskyTask ¶
IsRiskyTask reports whether a task is correctness-critical (concurrency, serialization, auth/crypto, money, migrations, destructive ops). The decider raises the tier FLOOR to deep-reasoning for these so triage can't send subtle work to a cheap model. The keyword set is editable via the store's setting/route-keywords/risk record (else the built-in).
func IsSoftRule ¶
IsSoftRule reports whether a named rule is soft (deny-by-default, overridable).
func ManagedBlock ¶
ManagedBlock renders the full managed block (markers + body) from the store.
func MaxUpdatedAt ¶
MaxUpdatedAt returns the largest UpdatedAt across every record (0 for an empty store) — the cheap "has anything been committed since?" signal the Stop suggestion uses.
func NormGatePath ¶
NormGatePath normalizes a path/pattern for gate matching: backslashes→/, strip a leading "./" or "/", drop a trailing "/". So "./secret/x", "secret/x", and "/secret/x" compare the same way and a path can't dodge a rule via a prefix variant.
func OverrideAuthorityOn ¶
OverrideAuthorityOn reports whether the human has delegated override authority to the AI (a setting/override-authority gate-rule with an affirmative body). Default false — absent means NOT delegated, so the AI cannot self-authorize a bypass.
func ParseSelectedKeys ¶
ParseSelectedKeys extracts a JSON array from a selector model's reply and keeps only keys present in the candidate set (the model cannot invent or rename a key). Order follows the reply; nil when no usable array is present.
func ParseTierReply ¶
ParseTierReply extracts a valid capability tier + confidence from a triage model's reply. It reads the strict JSON shape {"tier":..,"confident":..} but tolerates surrounding prose and falls back to a bare tier word (confident=false so the decider escalates rather than trusting a sloppy reply). Returns ("", false) when no valid tier is present.
func ProvenanceFor ¶
ProvenanceFor maps a commit actor ("ui"|"agent") to its default provenance. Empty actor or anything else → unknown ("").
func RenderCLIArgs ¶
RenderCLIArgs turns a cli template into an argv, substituting {prompt}/{model} as whole elements (never string-spliced, so spaces in the prompt are safe). An empty template yields nil.
func Route ¶
Route classifies task and resolves the launch command from the store's KRoute records. Returns the class and the command (cmd is "" if no record for the class).
func RuleTier ¶
RuleTier resolves the tier of a rule referenced by NAME (e.g. "dispatcher-mode"). An explicit Enforcement on a matching store record wins; otherwise the built-in default; otherwise advisory. s may be nil (defaults only).
func SeedFloor ¶
SeedFloor writes the floor contract (conventions + gate rules) into s as project-scoped records. Put replaces by ID, so re-seeding is harmless. Returns the number of records written.
func SessionContext ¶
func SessionContext(st Store, cps CheckpointStore, session, task string, reset bool, sel SelectorFunc) string
SessionContext owns the per-turn lifecycle and returns the text to inject:
- reset (PreCompact): mark the floor lost, inject nothing.
- task=="" (SessionStart): the lean floor, fresh checkpoint.
- otherwise (UserPromptSubmit): the delta — law re-asserted + only new/changed task-relevant records; or, right after a reset, the full floor+slice refill.
sel is the optional v2 semantic selector (nil → deterministic v1). It mutates the session's checkpoint via cps.
func SessionSuggest ¶
func SessionSuggest(st Store, cps CheckpointStore, session string) (msg string, block bool)
SessionSuggest is the Stop suggestion: SUGGEST-ONLY, and only when an @remember was flagged this session and nothing was committed afterward. Returns the nudge text + whether to surface it (block), and disarms the flag so it fires at most once.
func SoftRuleNames ¶
SoftRuleNames lists the names of soft rules: the built-in defaults plus any store rule explicitly marked soft. Used for override-command messaging.
func SpliceManagedBlock ¶
SpliceManagedBlock replaces the managed block inside existing with block, preserving any user content around it. An empty document gets a minimal header.
func TaskSliceOverflows ¶
TaskSliceOverflows reports whether the deterministic v1 slice for this task would exceed the per-section cap in any reference section — i.e. the keyword match was AMBIGUOUS (too many hits). Consumers use this to auto-escalate to the semantic v2 selector ONLY when v1 is too broad: deterministic-when-easy, model-when-needed.
func Tier ¶
Tier returns a record's enforcement tier: its explicit Enforcement if set, else a value derived from its identity — off-limits gate globs are hard, a known soft setting is soft, everything else advisory. This keeps pre-schema-#4 rows correct.
func WorkerAllowBins ¶
WorkerAllowBins returns the declared worker safe-list (setting/worker-allow) as command names, or DefaultWorkerAllow when the store is nil / the record is absent or blank. Separators may be commas, spaces, or newlines so the human can write it naturally.
func WorkerDirectiveText ¶
WorkerDirectiveText returns the declared worker directive from the store (the setting/worker-directive convention), or DefaultWorkerDirective if s is nil, the record is absent, or its body is blank.
func WorkerFullAutonomy ¶
WorkerFullAutonomy reports whether the human has granted workers full autonomy (a setting/worker-autonomy gate-rule with an affirmative body). Default false.
Types ¶
type Checkpoint ¶
type Checkpoint struct {
// Seen maps recordID -> the UpdatedAt at the time it was last injected, so the delta
// suppresses records already in the agent's context and re-sends changed ones.
Seen map[string]int64 `json:"seen"`
// NeedFloor is set on PreCompact: the next turn must re-send the floor before the slice.
NeedFloor bool `json:"need_floor"`
// Flagged records an @remember this session; FlaggedAt is the store's high-water mark
// at that moment, so Stop can tell whether anything was committed afterward.
Flagged bool `json:"flagged_remember"`
FlaggedAt int64 `json:"flagged_at"`
// Focus is the repo/group the session is currently working in — set automatically as
// the agent edits that repo's files (or explicitly via @focus). It boosts that repo's
// records in the slice, and "shifts" when work moves to another repo.
Focus string `json:"focus,omitempty"`
}
Checkpoint is the per-session delta state. JSON tags are stable on disk so a file written by one face round-trips through another.
type CheckpointStore ¶
type CheckpointStore interface {
Load(session string) Checkpoint
Save(session string, cp Checkpoint)
}
CheckpointStore persists one Checkpoint per session id. Load returns the zero Checkpoint for an unknown/corrupt session (a fresh session); neither Load nor Save returns an error — losing a checkpoint only costs a little redundant context, never a blocked turn.
type CompletionSpec ¶
type CompletionSpec struct {
Name string `json:"-"`
Transport string `json:"transport"` // "cli" | "http-openai"
Template string `json:"template,omitempty"` // cli: argv template with {prompt} {model} placeholders
BaseURL string `json:"base_url,omitempty"` // http-openai: OpenAI-compatible base
APIKeyEnv string `json:"api_key_env,omitempty"` // http-openai: NAME of the env var holding the key (never the key itself)
Model string `json:"model,omitempty"` // optional default model for this provider
}
CompletionSpec is a resolved provider definition for one-shot model calls.
func IntegrationSpec ¶
func IntegrationSpec(s Store, name string) (CompletionSpec, bool)
IntegrationSpec reads a named integration record. Returns ok=false if absent or malformed.
func ResolveCompletion ¶
func ResolveCompletion(s Store) (CompletionSpec, bool)
ResolveCompletion returns the active integration spec and whether one was declared. When none is declared it returns DefaultIntegration/false, so a caller can tell an explicit choice from the built-in fallback.
type Conflict ¶
type Conflict struct {
ID string
Base Record
Incoming Record
Resolution Resolution // TookIncoming / KeptBase = auto-resolved (LWW); Unresolved = needs you
}
Conflict is one ID present on BOTH sides with a DIFFERENT body. The merge auto-resolves by last-write-wins when the timestamps differ; a true tie (equal/zero UpdatedAt) is reported Unresolved for the caller to reconcile.
type Filter ¶
Filter selects records. The zero value matches everything; a non-nil/non-empty field narrows. Pointers distinguish "unset" from "the zero Scope/Kind".
KeyPrefix and Text power task-sliced retrieval (the "query, don't dump" pillar): KeyPrefix matches the Key as a path prefix ("minecraft/login" → minecraft/login, minecraft/login/backend, …); Text is a case-insensitive substring of Key OR Body. Both are case-insensitive so a query needn't know exact casing. SQLite mirrors these as WHERE clauses (see SQLite.List) so results match Mem.List exactly.
type Kind ¶
type Kind int
Kind is the typed record vocabulary. The store holds records; the engines give them meaning.
const ( KRecipe Kind = iota // a workflow recipe (global scope only) KConvention // a style/behavior rule baked into context KADR // an architecture decision record KDoc // an explanation / subsystem note KHistory // an append-only change record (commit output) KGateRule // a gate/redaction rule KDeclaredStructure // declared module/system grouping for the graph KRoute // a capability class -> agent launch command (model tier) )
type Mem ¶
type Mem struct {
// contains filtered or unexported fields
}
Mem is an in-memory Store — the first implementation behind the interface. It exists so context/workflow/verify can be built and tested against the contract today; a SQLite-backed Store swaps in later without touching any caller.
List returns records in deterministic ID order so callers and tests reproduce.
func (*Mem) Delete ¶
Delete removes a record by ID. Deleting a missing ID is a no-op (not an error).
type MergeResult ¶
type MergeResult struct {
Merged []Record // the consolidated set (ID-sorted), with auto-resolutions applied
Conflicts []Conflict // every same-ID-different-body case (check .Resolved())
Added int // ids only in incoming
Unchanged int // ids identical on both sides (deduped)
AutoWon int // conflicts settled by last-write-wins
NeedReview int // conflicts left Unresolved (a human must pick)
}
MergeResult is the outcome: the consolidated record set plus a per-ID accounting and the conflicts (auto-resolved AND the ones still needing a pick).
func Merge ¶
func Merge(base, incoming []Record) MergeResult
Merge consolidates `incoming` into `base` by ID:
- id only in base -> kept
- id only in incoming -> added
- same id, same content -> deduped (the newer-stamped copy is kept for metadata)
- same id, diff content -> CONFLICT: newer UpdatedAt wins (LWW); equal/zero -> Unresolved
Unresolved conflicts keep the BASE record in Merged (no silent overwrite — you stay the author) until the caller picks; resolve with Apply.
type Record ¶
type Record struct {
ID string
Kind Kind
Scope Scope
Key string
Body string
// UpdatedAt is the last-write clock (0 = unknown), unix-MILLIS-based but guaranteed
// strictly increasing per store (see stamp). Millis (not seconds) + the monotonic
// guarantee mean two edits in the same instant never tie — important because the wasm
// cell's wall clock can be second-resolution. merge/import preserve an incoming value
// so last-write-wins holds across machines. Origin = which brain/machine wrote it.
UpdatedAt int64
Origin string
// Enforcement tiers how strictly this rule is applied: "hard" (gate denies, no
// override — the secrets/off-limits floor), "soft" (denies by default but a
// recorded, reasoned override may proceed), or "advisory" (context-injected, not
// gated). Empty means "derive by identity" (see Tier) so rows written before this
// column existed behave correctly with no data backfill. Persisted since schema #4.
Enforcement string
// Provenance records HOW a record's claim was established: "human-confirmed" (a person
// committed/approved it), "gate-verified" (the verify gate ran build/test and it passed),
// "agent-asserted" (an AI committed it without an independent check), or "" (unknown).
// Lets a reader distinguish a proven fact from a narrator's claim. Persisted since #5.
Provenance string
}
Record is one typed entry. Body is an opaque payload (text/JSON); its concrete schema is deferred. Key is a human handle, unique within (Scope, Kind) — e.g. a recipe name or an ADR id.
func ImportJSON ¶
ImportJSON parses an ExportJSON blob.
func IntegrationActiveRecord ¶
IntegrationActiveRecord builds the record naming the active integration.
func IntegrationRecord ¶
func IntegrationRecord(spec CompletionSpec) Record
IntegrationRecord builds the store record for a declared integration (JSON body).
type Resolution ¶
type Resolution int
Resolution is how a same-ID conflict was (or should be) settled.
const ( TookIncoming Resolution = iota // incoming record won (newer, or chosen) KeptBase // base record won (newer, or chosen) Unresolved // tie that needs a human pick (Newer == Newer == 0, etc.) )
type RouteDecision ¶
type RouteDecision struct {
Class string // cheap-fast | default | deep-reasoning | elevate
Cmd string // launch command from the KRoute tier map ("" if none)
Source string // override | pin | keyword | triage | triage-escalated | default (+floor)
Reason string // short human explanation
}
RouteDecision is the resolved routing choice plus how it was reached.
func RouteDecide ¶
func RouteDecide(s Store, task string, triage TriageFunc) RouteDecision
RouteDecide is the DECIDER: it resolves a task to a tier by the locked precedence ladder — cheapest possible, escalating only when earned ("lowest model, highest yield"):
- per-message @-override (@cheap/@haiku, @sonnet/@default, @opus/@deep) — always wins, bypasses pin AND floor (the user asked for it explicitly, this once).
- standing PIN setting — hard-lock to one tier; triage is skipped entirely.
- deterministic classifier — a matched keyword routes for free (store-augmentable).
- cheap haiku triage — only for the ambiguous middle; escalate-on-uncertainty.
- default tier — no signal.
Steps 3–5 are then raised to the standing FLOOR (a minimum tier) if one is set. Pure given a pure triage func; deterministic when triage is nil.
type SQLite ¶
type SQLite struct {
// contains filtered or unexported fields
}
SQLite is a SQLite-backed Store. It behaves identically to Mem — insert-or-replace Put, (Record, bool) Get, no-op Delete, ID-sorted List — but persists through a sqlConn. On a native build that conn is modernc.org/sqlite (a local file or :memory:); built as a wasm cell it is the Pulp host's storage.sqlite capability. The store logic below is identical for both; only the conn differs (see conn_*.go).
func Open ¶
Open opens (or creates) a SQLite-backed Store and runs any pending migrations. On native, path is a file path or ":memory:". As a Pulp cell, path is ignored — the host owns the database file (<storage-root>/<cell>/data.db). The returned *SQLite must be closed with Close.
func (*SQLite) Delete ¶
Delete removes a record by ID. Deleting a missing ID is a no-op (not an error).
type Scope ¶
type Scope int
Scope identifies which physical file a record lives in. THREE scopes across TWO files:
Global, Workspace -> YOUR store (one file, travels with you between machines) Project -> PROJECT store (one per project, stays with the repo)
const ( // ScopeGlobal: how you work everywhere — recipes, conventions, style. Agnostic. ScopeGlobal Scope = iota // ScopeWorkspace: this machine's cockpit state — repo list, default gate posture. ScopeWorkspace // ScopeProject: what's true about this codebase — ADRs, architecture, history, // this project's gate rules. ScopeProject )
func (Scope) Owner ¶
Owner reports which physical LEVEL a scope belongs to: "global" (machine/user), "workspace" (an optional multi-repo folder), or "project" (one repo). The composer routes a write to the store owning that level, falling back UP a level when the owning store is absent — so project-only and project+global setups work with no workspace store.
type SeedRec ¶
type SeedRec struct{ Key, Body string }
SeedRec is a key/body pair awaiting a Kind + Scope at seed time.
type SelectorFunc ¶
SelectorFunc proposes which reference records are relevant to a task. Given the task and the candidate reference-record keys (the index), it returns the subset of keys to include. A consumer (the engine, backed by a cheap model) injects a real one for v2 SEMANTIC selection; nil falls back to the v1 DETERMINISTIC token selector (the offline floor). This is the "cheap-model-proposed query" the plan layers on top of v1.
type Store ¶
type Store interface {
Put(Record) error
Get(id string) (Record, bool)
List(Filter) []Record
Delete(id string) error
}
Store is the read/write contract every backend satisfies. Both YOUR store and the PROJECT store are Store values; the caller picks which by Record.Scope's Owner().
type TriageFunc ¶
TriageFunc is the injectable cheap-model triage seam: given an ambiguous task it returns a proposed class and whether it is confident. The store library stays OS-/network-free — a consumer (the engine) supplies a real haiku-backed func; nil means "deterministic only" (ambiguous tasks fall to the default tier).
type Workspace ¶
type Workspace struct {
Global Store // machine/user level (also absorbs workspace-scoped writes when Space is nil)
Space Store // OPTIONAL workspace level (nil = not in a workspace)
Project Store // per-repo level (nil = not pointed at a repo)
}
Workspace ties up to THREE physical stores into one logical Store, realizing the multi-LEVEL model: machine/user GLOBAL, an optional WORKSPACE (a multi-repo folder with its own rules), and the per-repo PROJECT store. Any level except Global may be nil — point it at a bare repo (project + global, no workspace), a workspace with no project open, or just global. Reads COMPOSE whatever is present; writes route by Record.Scope.Owner(), falling back UP a level when the owning store is absent, so a missing level never drops a write and project-only just works.
func NewComposite ¶
NewComposite is the 3-level constructor. space may be nil (project-only / no workspace); project may be nil (a workspace with nothing open). global is always required.
func NewWorkspace ¶
NewWorkspace is the 2-level (back-compat) constructor: yours holds global + workspace records, project holds project records. Equivalent to NewComposite(yours, nil, project).
func (*Workspace) Delete ¶
Delete removes the ID from every present store (a missing ID is a no-op).
func (*Workspace) Get ¶
Get returns the record with the given ID, checking Project → Space → Global; the most specific level's hit wins.