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
- func EncodeActorTypes(as []ActorType) string
- func FormatAmount(amount int64, unit BudgetUnit) string
- func FormatUSD(micros int64) string
- func NewClaimID() string
- func NewCrossingID() string
- func NewDocumentID() string
- func NewEdgeID() string
- func NewLeadID() string
- func NewReservationID() string
- func NewRowID() string
- func NewSessionID() string
- func NewSpanID() string
- func NewToolCallID() string
- func ParseUSD(s string) (int64, error)
- func PromptFence() string
- type ActorType
- type BudgetUnit
- type CallType
- type Claim
- type ClaimEdge
- type Cost
- type Crossing
- type CrossingOutcome
- type Document
- type EdgeKind
- type Lead
- type LeadCost
- type LeadStatus
- type Mode
- type Reservation
- type ReservationStatus
- type Role
- type Session
- type SessionStatus
- type Span
- type ToolCall
Constants ¶
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.
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 ¶
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 ¶
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 NewReservationID ¶
func NewReservationID() string
func NewSessionID ¶
func NewSessionID() string
func NewToolCallID ¶
func NewToolCallID() string
func ParseUSD ¶
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 ¶
type BudgetUnit ¶
type BudgetUnit string
const ( BudgetUSD BudgetUnit = "usd" BudgetTokens BudgetUnit = "tokens" )
func (BudgetUnit) Valid ¶
func (u BudgetUnit) Valid() bool
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 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 ¶
SumCosts adds a slice of costs. Used when settling a reservation against the several tool calls one actor run produced.
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) TotalTokens ¶
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.
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.
type EdgeKind ¶
type EdgeKind string
func (EdgeKind) Symmetric ¶
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.
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 ¶
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 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.
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 ¶
Available is the spendable balance: everything not already spent, held by an outstanding reservation, or escrowed for output.
func (*Session) RemainingFraction ¶
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 ¶
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.
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.