core

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package core holds the domain types shared by every other package.

Two conventions matter here and are load-bearing everywhere else:

  • Money is integer micro-dollars (int64), never float64. Budget adherence is a tested metric (max overshoot must be ~0); float accumulation over thousands of reserve/settle operations drifts, so the architecture sketch's `float64` USD fields are represented as `USDMicros` throughout.
  • A session's budget, spend, holds, and escrow are all expressed in the session's own BudgetUnit, so they are directly comparable int64s.

Index

Constants

View Source
const DefaultDocumentTTL = 7 * 24 * time.Hour

DefaultDocumentTTL bounds how long source text is kept.

Seven days. Long enough that a research session spread over a working week can still verify its own claims, short enough that a machine does not silently accumulate a corpus of other people's pages. Sessions are usually deleted long before this; the TTL is for the ones nobody deletes.

View Source
const MicrosPerUSD = 1_000_000

MicrosPerUSD is the fixed-point scale for money. All monetary values in Mole are int64 micro-dollars.

Variables

This section is empty.

Functions

func EncodeActorTypes

func EncodeActorTypes(as []ActorType) string

EncodeActorTypes / DecodeActorTypes keep the list in one TEXT column rather than a join table; the set is tiny and never queried by membership.

func FormatAmount

func FormatAmount(amount int64, unit BudgetUnit) string

FormatAmount renders a budget amount in the session's unit.

func FormatUSD

func FormatUSD(micros int64) string

FormatUSD renders micro-dollars for humans. Sub-cent amounts keep enough precision to be useful when a single fetch costs $0.0004.

func NewClaimID

func NewClaimID() string

func NewCrossingID

func NewCrossingID() string

func NewDocumentID

func NewDocumentID() string

func NewEdgeID

func NewEdgeID() string

func NewLeadID

func NewLeadID() string

func NewReservationID

func NewReservationID() string

func NewRowID

func NewRowID() string

func NewSessionID

func NewSessionID() string

func NewSpanID

func NewSpanID() string

func NewToolCallID

func NewToolCallID() string

func ParseUSD

func ParseUSD(s string) (int64, error)

ParseUSD converts a dollar string ("2", "2.50", "$2.50") to micro-dollars. Rejects more precision than micro-dollars rather than silently rounding — a budget that quietly loses precision is exactly the bug this type exists to prevent.

func PromptFence

func PromptFence() string

PromptFence returns an unguessable delimiter suffix for wrapping untrusted content in a prompt (§3.2).

A fixed delimiter is not a boundary. A page whose text contains "</content>" closes the region that was supposed to contain it, and everything after that line reads as instruction. Naming the delimiter randomly per call leaves the content nothing to imitate, and — unlike escaping the body — keeps it byte-identical, which §11.5's quote verification requires.

Shared rather than reimplemented per package. Three call sites was the point at which one of them would eventually reach for math/rand.

Types

type ActorType

type ActorType string
const (
	ActorWeb          ActorType = "web"
	ActorAcademic     ActorType = "academic"
	ActorLocalCompute ActorType = "local_compute"
	// ActorToolkit marks a session an external agent drives through the toolkit
	// tools. No actor of this type exists — that is the point. mole dispatches no
	// lead for it, so the executor, the planner and the abandonment sweep all
	// have to be able to tell it apart from a session mole is running itself.
	ActorToolkit ActorType = "toolkit"
)

func DecodeActorTypes

func DecodeActorTypes(s string) []ActorType

func (ActorType) Valid

func (a ActorType) Valid() bool

type BudgetUnit

type BudgetUnit string
const (
	BudgetUSD    BudgetUnit = "usd"
	BudgetTokens BudgetUnit = "tokens"
)

func (BudgetUnit) Valid

func (u BudgetUnit) Valid() bool

type CallType

type CallType string
const (
	CallSearch    CallType = "search"
	CallFetch     CallType = "fetch"
	CallLLM       CallType = "llm"
	CallLocalQry  CallType = "local_query"
	CallAcademic  CallType = "academic_query"
	CallSandbox   CallType = "sandbox"
	CallEmbedding CallType = "embedding"
)

type Claim

type Claim struct {
	ID        string
	SessionID string
	LeadID    string
	Text      string

	Source      string // URL, DOI, or "connector:<name>#<query_hash>"
	ToolCallID  string
	Quote       string
	QuoteOffset int64

	PublishedAt *time.Time
	RetrievedAt time.Time

	// Verification lineage. A per-row recheck counter cannot work: a follow-up
	// lead produces a NEW claim, which would start the counter over. Depth is
	// inherited and incremented so the cap actually binds.
	RootClaimID string
	VerifyDepth int

	// AssertionStrength is how clearly the SOURCE states this, as reported by
	// the extracting model. A property of the document, not of the world, and
	// that is all the mine prompt asks for.
	//
	// Not confidence, and kept separate from it because it was serving as
	// confidence: §11.3 rejects self-reported confidence as uncalibrated and
	// "mostly encoding fluency", and the report was ordered by it.
	AssertionStrength float64

	// Confidence is derived from graph structure (§11.3) — independent
	// corroborating publishers, source class, contradicting edges, grounding,
	// recency. Never asked of a model.
	//
	// Zero until the Verifier has scored the claim, which is the honest value:
	// nothing corroborates a claim that has not been compared to anything.
	Confidence float64
	Grounded   *bool
	// GroundingNote says how a grounding check reached its verdict, including the
	// two outcomes Grounded cannot express: the quote vanished from its source, or
	// the source could not be reached. Both leave Grounded nil, because neither is
	// evidence about the claim (§11.5).
	GroundingNote string

	// VerifiedAt records that the Verifier has scored this claim. Nil means it
	// has not, which Confidence cannot express on its own: §11.3's formula
	// legitimately returns 0 for an uncorroborated claim carrying a contradiction,
	// so a zero there means "scored badly" as often as "never examined".
	VerifiedAt *time.Time

	CreatedAt time.Time
	// Seq is this claim's position within the batch it was written in, assigned
	// by the store. It exists to make claim order total and reproducible.
	//
	// CreatedAt cannot do it alone: one timestamp is stamped for a whole batch,
	// so every claim from a lead ties, and the tie was being broken by SQLite's
	// unspecified row order. Order matters because citation numbers are assigned
	// by it, and under replay the resulting prompt is the cassette key.
	Seq int
}

Claim is an atomic extracted fact.

Quote is required at extraction time and checked verbatim against the extracted text before the claim is accepted. That check is cheap, deterministic, and rejects fabricated citations at the actor boundary rather than letting the verifier discover them later.

type ClaimEdge

type ClaimEdge struct {
	ID        string
	SessionID string
	FromID    string
	ToID      string
	Kind      EdgeKind
	Weight    float64
	CreatedBy string
	Rationale string
	CreatedAt time.Time
}

type Cost

type Cost struct {
	USDMicros int64

	InputTokens      int64
	OutputTokens     int64
	CacheReadTokens  int64
	CacheWriteTokens int64
}

Cost is what one tool call spent, always recorded in both units.

The token breakdown is not decoration. Cache reads bill at ~0.1x input and cache writes at 1.25x (5m TTL) or 2x (1h TTL); this design caches heavily, so a single flat token count would misprice sessions badly in both directions.

func SumCosts

func SumCosts(cs []Cost) Cost

SumCosts adds a slice of costs. Used when settling a reservation against the several tool calls one actor run produced.

func (Cost) Add

func (c Cost) Add(o Cost) Cost

func (Cost) BudgetAmount

func (c Cost) BudgetAmount(unit BudgetUnit) int64

BudgetAmount converts a cost into the session's accounting unit. This is the only place the two units meet, which is what makes switching a session's unit a display-and-gating choice rather than a data migration.

func (Cost) IsZero

func (c Cost) IsZero() bool

func (Cost) String

func (c Cost) String() string

func (Cost) TotalTokens

func (c Cost) TotalTokens() int64

TotalTokens counts every token that moved, including cache reads and writes. Cache-read tokens are cheaper, not free, and they are real tokens — a token budget that ignored them would not bound anything.

func (Cost) Validate

func (c Cost) Validate() error

type Crossing

type Crossing struct {
	ID        string
	SessionID string
	LeadID    string

	// Connector is the registered name, and Query/QueryHash identify the
	// statement. A local claim cites "connector:<name>#<hash>", so this is what
	// makes a claim traceable back to the question that produced it.
	Connector string
	Query     string
	QueryHash string

	Outcome CrossingOutcome
	// Detail is mole's own reason string — a refusal's text, never a value from
	// the result.
	Detail string

	RowsDescribed   int64
	Columns         int
	ColumnsWithheld int
	Buckets         int
	Suppressed      int
	BeyondTopK      int
	Tests           int
	Truncated       bool
	CreatedAt       time.Time
}

Crossing is one record of data leaving the user's machine (§12.1).

§12.1: "Every crossing is logged, so a user can audit exactly what left their machine." A log line satisfies the letter of that and not the use: logs are rotated, are off by default at Info in some setups, and cannot be queried per session. A user asking "what did mole send about my sales data last Tuesday" needs a table.

The rule that shapes every field: this record carries NO value from the data. It carries the statement, its hash, how much was described, how much was withheld, and the outcome. An audit trail that is another copy of the thing the user was worried about is worse than none — and the statement itself is safe to keep because §12.3 forbids the model from authoring one: a query can only be a hypothesis template filled with identifiers the profile already published.

type CrossingOutcome

type CrossingOutcome string

CrossingOutcome is what happened to the envelope.

const (
	// CrossingCrossed: an envelope was returned to a model.
	CrossingCrossed CrossingOutcome = "crossed"
	// CrossingRefused: the gate or the parse guard said no. Nothing crossed, and
	// this is the normal case rather than an error — the gate exists to refuse.
	CrossingRefused CrossingOutcome = "refused"
	// CrossingWithheld: the envelope was built and then held back because the
	// exfil check found a value in it (gate.ErrLeak).
	//
	// Its own outcome, not folded into refused, because they mean opposite things
	// about this code: a refusal is the design working, and a withholding is a
	// rule upstream having broken in a way the backstop caught. §14.3's metric is
	// the count of these, and it must be zero.
	CrossingWithheld CrossingOutcome = "withheld"
)

type Document

type Document struct {
	ID        string
	SessionID string

	URL   string
	Title string
	// Text is exactly what the caller was handed. It must not be re-normalised
	// after storage: a stored offset that no longer locates its quote is
	// provenance that lies.
	Text string
	// Truncated reports that the extractor cut the page short, so a quote from
	// beyond the cut will fail verification for a reason that is not the caller's
	// fault.
	Truncated bool

	// PublishedAt is when the source says it was published, when it says at all.
	// Zero means unstated, which is the common case and is not the same fact as
	// "published at the epoch" — the staleness rule needs a date on both sides
	// before it will rewrite anything.
	PublishedAt time.Time

	FetchedAt time.Time
	ExpiresAt time.Time
}

Document is source text mole fetched and kept, so a quote can be checked against it later (toolkit mode).

mole discards source text everywhere else. This exists for one reason: when an agent's model mines a claim and asks mole to record it, the quote has to be verified against text MOLE fetched. Verifying against text the agent supplied would prove nothing, since a model that invents a quote can invent the passage.

func (Document) Expired

func (d Document) Expired(now time.Time) bool

Expired reports whether a document is past its TTL at the given instant.

type EdgeKind

type EdgeKind string
const (
	EdgeSupports    EdgeKind = "supports"
	EdgeContradicts EdgeKind = "contradicts"
	EdgeDuplicateOf EdgeKind = "duplicate_of"
	EdgeSupersedes  EdgeKind = "supersedes"
	EdgeRefines     EdgeKind = "refines"
)

func (EdgeKind) Symmetric

func (k EdgeKind) Symmetric() bool

Symmetric reports whether the edge asserts the same thing in both directions.

A storage constraint, not just semantics. `UNIQUE (from_id, to_id, kind)` treats A→B and B→A as different rows, so one disagreement discovered from both ends is stored twice — and §11.3 penalizes confidence per contradicting edge, which would count that disagreement twice. InsertEdges orders the endpoints of a symmetric edge so the UNIQUE constraint can see the duplicate.

`supports`, `supersedes` and `refines` stay directional: a specific finding supporting a general conclusion is not the same statement reversed, and supersedes is decided from PublishedAt, which has an arrow in it.

func (EdgeKind) Valid

func (k EdgeKind) Valid() bool

type Lead

type Lead struct {
	ID        string
	SessionID string
	ActorType ActorType
	Query     string
	ParentID  *string
	Depth     int
	Priority  int
	Status    LeadStatus

	// RootClaimID marks a lead spawned to resolve something about a claim —
	// §11.4's verification lineage. Nil on an ordinary planner lead.
	//
	// Claims the lead produces inherit it, so a chain of follow-ups all trace to
	// the claim that started the investigation. §11.4's point is that this cannot
	// be a per-claim counter: a follow-up produces a NEW claim, and a counter on
	// the row would start over every time.
	RootClaimID *string
	// VerifyDepth is how many follow-ups deep this lead sits. Claims inherit it.
	VerifyDepth int

	LeaseOwner   *string
	LeaseExpires *time.Time

	CreatedAt time.Time
	UpdatedAt time.Time
}

Lead is a unit of work. Leases (not a bare status column) are what make the daemon crash-recoverable: a worker takes a lease and heartbeats it, and a boot-time sweep requeues leads whose lease expired.

type LeadCost

type LeadCost struct {
	ActorType ActorType
	Depth     int
	Cost      Cost
}

LeadCost is one finished lead's settled cost, with what produced it.

The estimator's warm start (§8): a reservation is predicted per (actor type, depth), and attributing a settled cost to either needs the join to leads that M3 made possible. The Cost travels whole rather than pre-converted, so the same row serves a token-budgeted session and a dollar-budgeted one.

type LeadStatus

type LeadStatus string
const (
	LeadQueued       LeadStatus = "queued"
	LeadLeased       LeadStatus = "leased"
	LeadDone         LeadStatus = "done"
	LeadFailed       LeadStatus = "failed"
	LeadSkippedCache LeadStatus = "skipped_cached"
)

type Mode

type Mode string
const (
	ModeReport  Mode = "report"
	ModeDataset Mode = "dataset"
	ModeChain   Mode = "chain"
	ModeAsk     Mode = "ask"
)

func (Mode) Valid

func (m Mode) Valid() bool

type Reservation

type Reservation struct {
	ID         string
	SessionID  string
	LeadID     *string
	Amount     int64
	Status     ReservationStatus
	CreatedAt  time.Time
	ExpiresAt  time.Time
	ResolvedAt *time.Time
}

Reservation is a hold placed on budget before dispatch. Charging after the fact lets a pool of N workers overshoot the ceiling by N lead-costs; holding first bounds the overshoot to estimate error on a single lead.

type ReservationStatus

type ReservationStatus string
const (
	ReservationHeld     ReservationStatus = "held"
	ReservationSettled  ReservationStatus = "settled"
	ReservationReleased ReservationStatus = "released"
)

type Role

type Role string

Role records which stage of the loop spent a tool call. It is what makes "how much went to verification vs. execution" a GROUP BY rather than an archaeology project.

const (
	RolePlanner  Role = "planner"
	RoleExecutor Role = "executor"
	RoleVerifier Role = "verifier"
	RoleOutput   Role = "output"
)

func (Role) Valid

func (r Role) Valid() bool

type Session

type Session struct {
	ID         string
	Prompt     string
	Mode       Mode
	ActorTypes []ActorType

	BudgetUnit BudgetUnit
	Budget     int64 // micro-dollars, or tokens, per BudgetUnit
	Spent      int64 // materialized sum of the ledger
	Held       int64 // sum of outstanding reservations
	Escrow     int64 // held back for output + final verify

	// Unit-independent ceilings. Without these, token mode has a hole: search
	// and fetch calls cost no tokens, so an unbounded fetch loop would be free.
	MaxToolCalls  int64
	MaxLeads      int64
	MaxWallClock  time.Duration
	ToolCallCount int64
	LeadCount     int64

	Status    SessionStatus
	CreatedAt time.Time
	UpdatedAt time.Time
	// Report is the session's rendered answer, written when it finalizes.
	//
	// Persisted because §5.1's research.result is defined as returning it, and
	// the MCP flow — report, poll status, result — has no other moment where the
	// answer reaches the caller. Before this it lived only in memory: the CLI
	// printed it, and the daemon paid for it out of escrow and dropped it.
	Report string
	// ReportDegraded says why the prose is missing or unsynthesized, so an empty
	// Report can be told from a failed one.
	ReportDegraded string
}

Session is one research task.

Spent, Held, and Escrow are NOT independent state. Spent is a materialized sum of the tool_calls ledger, written in the same transaction as each cost row, so it can always be recomputed after a crash (see budget.Ledger.Verify).

func (*Session) Available

func (s *Session) Available() int64

Available is the spendable balance: everything not already spent, held by an outstanding reservation, or escrowed for output.

func (*Session) HitCeiling

func (s *Session) HitCeiling(now time.Time) (bool, string)

func (*Session) RemainingFraction

func (s *Session) RemainingFraction(now time.Time) float64

RemainingFraction is how much of the session's allowance is left, in [0,1].

HitCeiling's continuous twin, and it takes the minimum over the same limits for the same reason HitCeiling checks all of them: a session two minutes from max_wallclock with 90% of its dollars unspent has 10% left, not 90%. Reporting spend alone would promise room that cannot be used.

Exists because §9.1's replan prompt asks the planner to judge whether the open sub-questions are "worth more budget" while the digest reported nothing about budget — an instruction the planner had no way to answer.

Available() rather than Budget-Spent, so escrow is excluded: tokens held back for the report are not spendable on more research, and counting them would overstate what is left at exactly the moment the answer matters.

func (*Session) Toolkit

func (s *Session) Toolkit() bool

HitCeiling reports whether any unit-independent limit has been reached. Toolkit reports whether this session is driven by an external agent rather than by mole's own executor.

It changes what several things mean. No lead is ever dispatched, so an idle session is not an abandoned one; nothing mole runs writes to it, so the executor's assumptions about tool calls and leads do not hold; and closing it is the agent's job rather than a runner's.

func (*Session) Validate

func (s *Session) Validate() error

type SessionStatus

type SessionStatus string
const (
	StatusRunning   SessionStatus = "running"
	StatusDone      SessionStatus = "done"
	StatusExhausted SessionStatus = "budget_exhausted"
	StatusCancelled SessionStatus = "cancelled"
	StatusFailed    SessionStatus = "failed"
)

func (SessionStatus) Terminal

func (s SessionStatus) Terminal() bool

Terminal reports whether the session can no longer spend.

func (SessionStatus) Valid

func (s SessionStatus) Valid() bool

type Span

type Span struct {
	ID        string
	SessionID string
	LeadID    *string
	ParentID  *string
	Name      string
	StartedAt time.Time
	EndedAt   *time.Time
	Status    string
	Attrs     map[string]string
}

Span is one trace record. One span per lead is the M0 requirement; the type is general enough for nested spans inside an actor run.

type ToolCall

type ToolCall struct {
	ID         string
	SessionID  string
	LeadID     *string
	Role       Role
	Type       CallType
	Model      string
	Input      string // redacted or hashed for local queries
	Cost       Cost
	Err        string
	DurationMS int64
	CreatedAt  time.Time
}

ToolCall is one row of the append-only cost ledger. The ledger is the source of truth for spend; Session.Spent is a materialized sum of it.

func (*ToolCall) Validate

func (t *ToolCall) Validate() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL