Mining

How a document or code module becomes typed graph facts — durable queues and time-boxed leases that a self-refilling pipeline keeps stocked, and a work-order façade that collapses a ten-call setup ceremony into one.

Verified 2026-07-09 @ dfd671a3

What this is

Mining is the extraction step that turns a document or code module into rows in the knowledge graph — a queue holding what needs doing, a run tracking who’s doing it and how far they’ve gotten, and a time-boxed lease handing one unit of work to one agent at a time. sophia.mine is the front door: one call resolves which pipeline a document needs and returns a work order naming the exact next tool calls, instead of an agent hand-driving queue creation, run claiming, and session approval itself.

Why it exists

Before this pipeline existed as a single front door, mining a document meant queue_createadd_itemsrun_claimbegin_import_sessioncheck_approval → re-call → get_document_for_miningsubmit_claim_graph — a roughly ten-call ceremony an agent had to get right every time, per the façade’s own source comment. sophia.mine collapses that to sophia.mine plus the one submit call the returned work order names.

The second problem was worse than ceremony: coverage didn’t refill itself. Before this quarter’s self-sustaining-queue work, newly ingested code modules and documents sat at a NULL mining intent until someone manually enqueued them — a queue that only drains and never restocks eventually empties, and stays empty. The fix is three cheap hooks: new code modules and mining-intent documents auto-enqueue at ingest with a derived priority (import fan-in, churn, recency, curated-entity status), and a bounded nightly pass tops up anything that slipped through and re-ranks what’s already queued. None of this calls an LLM — the daemon only ever flips an intent column and sets a priority; the extraction itself stays agent labor.

How it works

A mining queue (subscriber_mining_queue + its items) is a durable list of sources to mine — one per origin (a large document’s shard plan, a repo’s code-module backlog, an ad-hoc single-document handoff). A mining run (sophia.mining_run_claim → repeated mining_run_report progress events → mining_run_complete or mining_run_fail) is the accountability unit: it moves through claimedrunning → (optionally blocked) → completed/failed, and every step lands in an append-only event stream (sophia.mining_run_events) rather than a mutable status field. A source- bearing submit whose run doesn’t resolve, or whose queue doesn’t hold that source, is rejected outright with mining_run_required and writes zero rows — the run isn’t a courtesy field, it’s a hard close.

Two lease shapes hand work to a specific agent. sophia.lease_next_shard claims one window of a large document’s shard plan (default 5-minute TTL). sophia.lease_next_code_module claims one queued code module for deep analysis — metadata only (path, language, symbol count), not source text, with a lease TTL of 10 minutes per the tool’s own description, matching what an earlier page on this site already verified for the same lease. Once a claim is written, it still has to clear the same substring-verification gate this site’s Truth page owns in depth: a claim’s evidence has to be a literal quote from the source or the write never happens — submit_code_module_summary runs that same for-loop check against any evidence_quotes it’s given, and a single miss rejects the whole submit.

queue → run → lease → submit → (candidate | knowledge)
flowchart LR
  ING["ingest (doc or code module)"] -->|auto-enqueue, derived priority| Q[("mining queue")]
  Q --> R["mining run: claimed → running"]
  R --> L["lease_next_shard /
lease_next_code_module
(time-boxed)"]
  L --> SUB{"submit: run resolves?
evidence substring-checked?"}
  SUB -->|no| REJ["rejected, zero rows written"]
  SUB -->|yes| KG[("knowledge graph")]
  KG -.->|review-gated| CAND[("mining_candidate,
review_state=pending")]
  CAND -->|promote_candidate| ACC["accepted_knowledge"]
  CAND -->|reject_candidate| REJ2["review_state=rejected
(not deleted)"]

What your agent does with it

// Tool names, params, and response shapes verified against source
// (miningFacadeTools.ts, codebaseTools.ts) — not called live, since
// lease_* and submit_* take real leases/writes and are out of scope
// for this page's read-only research pass.

// 1. One resolve call instead of the ~10-call setup ceremony:
const order = await sophia.mine({ artifact_id: 'doc-8f2c…', depth: 'full' });
// → { ok: true, mode: 'single', run_id: 'run-…', queue_id: 'queue-sys-adhoc-user_handoff',
//   submit_with: 'submit_claim_graph', import_session_required: true,
//   next_steps: [ 'sophia.get_document_for_mining({ artifact_id })',
//     "sophia.begin_import_session({ description, entity_scope, item_types: ['claim_graph'] })",
//     "sophia.submit_claim_graph({ artifact_id, run_id, import_session_id, claim_graph })" ] }

// 2. Code-module lease/submit loop (10-minute lease TTL):
const lease = await sophia.lease_next_code_module({ entity_id: repoId });
// → { module_id, rel_path, language, lease_token, expires_at, run_id }
// run_id is auto-opened server-side — the lease response carries it so the
// submit's requireActiveRun check has something to validate against.
await sophia.submit_code_module_summary({
module_id: lease.module_id,
lease_token: lease.lease_token,
run_id: lease.run_id,
summary_what_it_does: '…', summary_api_surface: '…', summary_dependencies: '…',
confidence: 'medium',
evidence_quotes: ['…'], // substring-verified; any miss rejects the whole submit
});

sophia.lease_next_code_module deliberately withholds source text — a leased module’s structure comes from sophia.get_module_skeleton, and full source from sophia.get_module_source — the same drill-down tools any agent can use outside mining. Confidence is self-graded (high / medium / low), and an honest gaps list is preferred over a fabricated section — the submit tool says so directly: “submitting but flag for re-mining” beats silently guessing.

Boundaries

Mining costs real LLM tokens — this is the “pay once” half of the pay-once-query-forever trade The State Layer describes; a mining run is where that cost is actually paid — once, at mining time, not on every later read. (For long documents extraction is windowed; The state layer covers the exact mechanics.) The daemon itself never makes that call: auto-enqueue and the nightly catch-up pass only flip an intent column and set a priority, by design — the extraction call is agent labor, dispatched through the lease tools above.

Coverage is incremental, not a one-time sweep. Checked live against this dogfood instance’s repo entity while writing this page (sophia.list_unmined_modules, one call per status): 515 code modules done, 877 queued, 15 errored, 0 sitting at NULL — roughly 37% done, with the self-sustaining queue keeping the NULL bucket empty rather than letting it silently pile up between mining sessions. A changed file’s whole mining intent re-queues on content-hash drift today, not just the touched lines — finer delta-scoped re-mining is a known, not-yet-built direction.

Not every write lands as an accepted fact. submit_claim_graph writes directly to sophia_knowledge, but a separate review lane (subscriber_mining_candidate, surfaced via sophia.mining_candidates_list) starts every row at review_state: 'pending' and only moves to accepted/rejected through sophia.promote_candidate / sophia.reject_candidate — both gated behind an active run whose queue’s intent is specifically "promotion". Rejection there is a review verdict, not archival: the underlying fact, if one exists, is never mutated by a reject. Knowledge Graph covers what a row looks like once it’s through either path.