Skip to main content
Version: Next

Data Models Reference

This page documents the core data structures used across DecisionBox.

DiscoveryResult​

A complete discovery run output. Stored in the discoveries MongoDB collection.

FieldTypeDescription
idstringMongoDB ObjectID
project_idstringProject that owns this discovery
run_idstringThe run that produced this result. Lets a resumed run retire the partial result its previous attempt left behind, and keep that result out of its own "previously discovered" context. Absent on documents written before resume shipped, which every reader treats as "not this run".
domainstringDomain (e.g., gaming, social)
categorystringCategory (e.g., match3, idle, casual, content_sharing)
run_typestringfull (all areas), partial (some areas or some failed), failed (all areas failed)
areas_requestedstring[]Area IDs requested (empty for full run)
discovery_datetimestampWhen the discovery ran
total_stepsintNumber of exploration steps executed
durationint64Duration in nanoseconds
insightsInsight[]Discovered patterns
recommendationsRecommendation[]Actionable advice
summarySummaryAggregate stats
created_attimestampWhen result was saved
updated_attimestampLast write timestamp

Discovery logs are stored in split collections rather than embedded on the document: discovery_exploration_steps, discovery_analysis_steps, discovery_validation_results, and discovery_recommendation_log. See Discovery log collections for the rationale and the document shapes.

Insight​

A discovered pattern or finding. Generated by the analysis phase.

FieldTypeDescription
idstringDeterministic ID: {area}-{index} (e.g., churn-1, monetization-3). Auto-generated if LLM omits it.
analysis_areastringWhich area found this (e.g., churn, levels)
namestringSpecific descriptive name (e.g., "Day 0-to-Day 1 Drop: 67% Never Return")
descriptionstringPlain-text description with exact numbers and percentages. The raw, formatting-free form read by API consumers, list/preview UIs, and embeddings
description_mdstringGitHub-Flavored Markdown rendition of description (emphasis, lists, small sub-headings, simple tables), rendered formatted on the insight detail view. Omitted on unformatted and pre-Markdown (legacy) insights — the dashboard falls back to description
severitystringcritical, high, medium, or low
affected_countintNumber of affected users (COUNT DISTINCT user_id)
risk_scorefloat640.0 to 1.0 risk assessment
confidencefloat640.0 to 1.0 confidence level
metricsmapFlexible key-value metrics (e.g., {"churn_rate": 0.67, "avg_sessions": 3.2})
indicatorsstring[]Specific metric indicators (e.g., "Session drop: 12min → 4min")
target_segmentstringDescription of affected user segment
source_stepsint[]Exploration step numbers that support this insight
validationInsightValidationWarehouse verification result (if validated)
discovered_attimestampWhen this insight was generated

InsightValidation​

Attached to an insight after validation by the verifier + refuter pair.

FieldTypeDescription
verifierStructuredVerdictDefender-frame agent verdict with per-claim evidence rows
refuterStructuredVerdictSkeptic-frame agent verdict (omitted when refuter is disabled or the refuter run failed)
combinedstringCombined verdict: confirmed, supported, rejected, partial, unverifiable, validation_disabled, or skipped_budget_cap
refuter_disabledboolTrue when the refuter was intentionally skipped for this document
validated_attimestampWhen validation was performed
input_tokensintLLM input tokens consumed during validation
output_tokensintLLM output tokens produced during validation

Legacy fields (status, original_count, verified_count, query, reasoning) remain populated on documents written before the verifier+refuter pipeline shipped; the dashboard renders the new fields when present and falls back to the legacy fields otherwise.

Recommendation​

An actionable suggestion based on discovered insights.

FieldTypeDescription
idstringRecommendation ID
categorystringCategory: churn, engagement, monetization, difficulty
titlestringSpecific action title
descriptionstringPlain-text explanation with numbers. The raw form read by API consumers and previews
description_mdstringGitHub-Flavored Markdown rendition of description, rendered formatted on the recommendation detail view. Omitted on unformatted and legacy recommendations
priorityint1 (critical) to 5 (optional). P1 = highest priority.
target_segmentstringExact segment criteria
segment_sizeintNumber of users in the segment
expected_impactImpactExpected improvement
actionsstring[]Numbered implementation steps
related_insight_idsstring[]UUIDs of insights this recommendation addresses, verbatim from the same discovery's insights[].id (e.g., ["6e9261f5-c4ec-404b-bdf0-760a4644f384"]). Recommendations whose related_insight_ids cannot be resolved to an eligible insight are dropped server-side before persistence; see the RecommendationStep recommendations_dropped* counters
confidencefloat640.0 to 1.0 confidence

Impact​

Expected impact of a recommendation.

FieldTypeDescription
metricstringWhat metric improves (e.g., retention_rate, revenue)
estimated_improvementstringExpected improvement (e.g., "+15-20%", "+$4,975/month")
reasoningstringWhy this improvement is expected

If an LLM emits expected_impact as a bare prose string instead of the object (a common, model-independent behaviour), the agent coerces it into Impact{reasoning: <string>} at parse time rather than discarding the recommendation. The same tolerance applies to the numeric scalar fields: priority accepts descriptive words ("high", "critical", "P2") as well as numbers, and segment_size / confidence accept numeric strings. Providers that support schema-constrained output are additionally asked to emit the object shape at generation time.

Summary​

Aggregate stats for a discovery run.

FieldTypeDescription
total_insightsintNumber of insights generated
total_recommendationsintNumber of recommendations generated
queries_executedintNumber of SQL queries executed
errorsstring[]Error messages from failed analysis areas (if any)

ExplorationStep​

One step in the autonomous exploration phase. Drives one user-visible exploration turn — usually one LLM call, but a turn whose response can't be parsed is retried (up to a small retry budget) and every retry's token usage is summed onto the same step.

FieldTypeDescription
stepintStep number (1-based)
timestamptimestampWhen this step ran
actionstringOne of query_data, lookup_schema, search_tables, complete, complete_rejected
thinkingstringAI's reasoning for this query
query_purposestringShort description of query intent
querystringThe SQL statement the model proposed. Not necessarily the one that produced query_result — see query_executed.
query_executedstringThe statement that actually ran, present only when the self-healing fixer rewrote the proposal. Omitted when the proposal ran unchanged. Read through it rather than choosing between the two fields: a repair can change what the answer means (an observed run had APPROX_QUANTILES(x,4)[OFFSET(2)] rewritten to PERCENTILE_CONT(0.5) WITHIN GROUP, an approximate median for an exact one). Both are kept because they answer different questions — the proposal is the training signal, the executed statement is the only one that explains the rows.
row_countintNumber of rows returned
execution_time_msint64Query execution time in milliseconds
errorstringError message if query failed
fix_attemptsintNumber of applied LLM repairs for this step's query (0 if it ran cleanly on the first try or if every fix call failed). fix_attempts <= len(fix_history).
fixedboolTrue if the query was auto-fixed after a SQL error
fix_historyFixAttempt[]Per-attempt fix log; omitted (or empty) when no fix was needed. Includes failed attempts (LLM error, unparseable response, filter rejection) — those carry fixer_error set. See FixAttempt.
tokens_inintInput tokens consumed (sum across any parse-retry rounds on this step)
tokens_outintOutput tokens generated (sum across any parse-retry rounds on this step)

FixAttempt​

One entry per LLM call the self-healing SQL fix loop made for a single exploration step. Stored as elements of ExplorationStep.fix_history in chronological order. Every entry — including failed attempts whose proposed SQL was never applied — is recorded so downstream tooling has visibility into the full repair trajectory, not just the last call.

FieldTypeDescription
stepintThe parent ExplorationStep.step number, duplicated for self-contained export.
attemptintZero-based retry index inside the step. Matches the attempt argument the executor passes to the SQL fixer.
prompt_instringFully rendered prompt the fixer sent to the LLM (system instruction + user message).
response_outstringRaw LLM response text — before SQL extraction. Populated even when extraction failed.
sql_beforestringThe broken SQL handed to the fixer.
sql_afterstringThe SQL the fixer proposed. Empty when the fixer failed to extract any parseable SQL (in that case fixer_error is set).
error_instringThe warehouse error message that triggered this fix call.
fixer_errorstringWhen non-empty, the reason the proposal was NOT applied: LLM transport error, unparseable response, or post-fix security-filter rejection. When empty, the proposal was applied and the warehouse was retried with sql_after.
input_tokensintInput tokens consumed by this fix call.
output_tokensintOutput tokens generated by this fix call.
duration_msint64LLM call duration in milliseconds.
timestamptimestampWhen this fix attempt was recorded.

The parent ExplorationStep.fix_attempts counter is the number of applied repairs (fixer_error empty), so fix_attempts <= len(fix_history) with the gap being failed-fixer rows.

AnalysisStep​

Full LLM dialog for one analysis area. Captures the complete prompt and response.

FieldTypeDescription
area_idstringAnalysis area ID (e.g., churn)
area_namestringDisplay name (e.g., Churn Risks)
run_attimestampWhen this analysis ran
relevant_queriesintNumber of exploration queries used as context
tokens_inintInput tokens consumed
tokens_outintOutput tokens generated
duration_msint64LLM call duration in milliseconds
insight_countintNumber of insights extracted
errorstringError message if analysis failed

RecommendationStep​

Full LLM dialog for the recommendation-generation phase, stored in the discovery_recommendation_log collection. One row per discovery run.

FieldTypeDescription
run_attimestampWhen the recommendation phase ran
promptstringFully rendered recommendation prompt sent to the LLM
insight_countintNumber of eligible insights handed to the recommender
responsestringRaw LLM response text — before JSON cleanup
tokens_inintInput tokens consumed by the LLM call
tokens_outintOutput tokens generated by the LLM call
duration_msint64LLM call duration in milliseconds
recommendationsRecommendation[]The parsed recommendations actually persisted to the discovery (after the related_insight_ids validity check below has dropped any unresolvable ones)
statusstringOptional observability marker. Empty on the happy path; set to skipped_no_eligible_insights when every upstream insight was rejected by the validator so the recommender was never invoked, to recommendation_parse_error when the phase produced zero recommendations because the LLM response could not be parsed, or to recommendation_figures_unresolved when every recommendation was dropped because its prose referenced figures the model never declared. The dashboard renders the reason for an empty recommendations section.
recommendations_droppedintNumber of recommendations the orchestrator discarded before persistence — the sum of the reason-specific counters below (recommendations_dropped_parse + recommendations_dropped_missing_ids + recommendations_dropped_unknown_id + recommendations_dropped_unresolved_figures). Omitted on a clean run.
recommendations_dropped_parseintSubset of recommendations_dropped skipped because their individual JSON object could not be decoded (e.g. a field with the wrong type). Recommendations are decoded per item, so one malformed entry is dropped and counted here rather than discarding the whole batch. Omitted on a clean run.
recommendations_dropped_missing_idsintSubset of recommendations_dropped that arrived with an empty or absent related_insight_ids array. Omitted on a clean run.
recommendations_dropped_unknown_idintSubset of recommendations_dropped that cited at least one id not present in the eligible insight set — typically slug-style identifiers (category:severity:theme) hallucinated by the LLM in place of a real UUID. Use this counter to measure regression rates per LLM provider. Omitted on a clean run.
recommendations_dropped_unresolved_figuresintSubset of recommendations_dropped whose prose still carried a figure reference after the render pass — a number the model promised and never declared in its figures array, so nothing could write a checked value into the sentence. Publishing one would show a literal {{f1}} where the amount belongs. Omitted on a clean run.
recommendation_parse_retriesintNumber of corrective re-prompts the phase issued after a response yielded zero parseable recommendations (bounded by RECOMMENDATION_PARSE_MAX_RETRIES). Omitted when zero.
errorstringError message if recommendation generation failed (LLM transport error or unparseable response)

ValidationResult​

Warehouse verification of an insight's claims.

FieldTypeDescription
insight_idstringID of the validated insight
analysis_areastringArea this insight belongs to
claimed_countintCount claimed by the AI
verified_countintCount verified from the warehouse
statusstringconfirmed, adjusted, rejected, error
reasoningstringExplanation of the result
querystringThe verification SQL query
validated_attimestampWhen validation was performed
input_tokensintInput tokens summed across every verifier LLM call for this insight (initial verification, lookup-loop rounds, forced final round). Omitted on legacy rows.
output_tokensintOutput tokens summed across the same set. Omitted on legacy rows.

DiscoveryRun​

Live status of a running discovery. Stored in discovery_runs collection, updated in real-time.

FieldTypeDescription
idstringRun ID
project_idstringProject being discovered
statusstringpending, running, completed, failed, cancelled
phasestringCurrent phase: init, schema_discovery, exploration, analysis, validation, recommendations, saving, complete
phase_detailstringHuman-readable phase description
progressint0 to 100 percentage
started_attimestampWhen the run started
updated_attimestampLast status update
completed_attimestampWhen the run finished (null if running)
errorstringError message (if failed)
stepsRunStep[]Live step feed
total_queriesintTotal SQL queries executed
successful_queriesintQueries that returned results
failed_queriesintQueries that errored
insights_foundintInsights generated so far
attemptintHow many times the run has been started: 1 on create, incremented by each resume.
last_resumed_attimestampWhen the latest attempt was requested.
last_checkpoint_stepintHighest exploration step with a checkpoint. > 0 on a failed run is what makes it resumable; zeroed on completion. Stamped only after the checkpoint row is durably written, so it never advertises a checkpoint that does not exist. It can still exceed the replayable prefix if an earlier write failed and a later one succeeded — replay then stops at the gap, and a resume with nothing left to replay is refused rather than silently re-exploring.
active_msint64Cumulative active compute across attempts, so elapsed time excludes the hours a failed run sat waiting to be noticed. Recorded at each attempt's terminal write, so an attempt killed before reaching it contributes nothing — a slight undercount on hard crashes.
lifecycleRunLifecycleEvent[]Append-only transition log.
max_steps, min_stepsintThe run's own step budget, so a resume replays what the caller chose rather than the agent's defaults.
areasstring[]The run's own area selection.
effortstringThe run's own effort level.

All the fields from attempt down are optional and absent on runs that predate checkpointing — such a run reads as attempt 0 with no checkpoint, which is correct: it has nothing to resume.

RunLifecycleEvent​

One transition in a run's history. A single mutable status field is enough while a run has one attempt; once a run can be resumed it shows only the LATEST attempt and silently overwrites every earlier one.

FieldTypeDescription
statusstringThe status the run entered
attimestampWhen
reasonstringFree text: the failure message, or what triggered the transition
attemptintWhich attempt this event belongs to
llm_provider, llm_modelstringThe model that served this attempt, set on the terminal event the agent writes — which is how "which model ran which attempt" stays answerable

RunStep​

One step in the live progress feed.

FieldTypeDescription
phasestringWhich phase this step belongs to
step_numintStep number
timestamptimestampWhen this step occurred
typestringquery, insight, analysis, validation, recommendation, error
messagestringStep description
llm_thinkingstringAI's reasoning text
querystringSQL query (if type=query)
query_resultstringQuery result summary
row_countintRows returned
query_time_msintQuery execution time
query_fixedboolWhether query was auto-fixed
insight_namestringInsight name (if type=insight)
insight_severitystringInsight severity (if type=insight)
errorstringError message (if type=error)

ExplorationCheckpoint​

One exploration step of an in-flight run. Stored in the discovery_checkpoints collection, keyed by run_id, and the reason a crashed run can be resumed instead of paying for its whole exploration phase again.

One document per step rather than an array on the run document, for the same reason the discovery logs were split out: an embedded array grows with run length and a long run hits the 16MB BSON limit exactly when the checkpoint matters most.

Not the same thing as discovery_exploration_steps, which is the tail-written audit log keyed by discovery_id, carries full rows and fix history, and is read by the dashboard. These rows exist only while a run is in flight, carry a bounded row sample, and are deleted when the run completes or is cancelled — with DISCOVERY_CHECKPOINT_RETENTION as the backstop.

FieldTypeDescription
run_idstringThe run this checkpoint belongs to
project_idstringProject that owns the run
step_numberint1-based step number; 0 is the reserved exploration-summary document
attemptintWhich attempt wrote this row (observability — a mixed set across a prefix is how an operator sees where the previous attempt stopped)
kindstringstep or exploration_summary
created_attimestampTTL anchor
(inline)ExplorationStepThe step itself, with its result set reduced to a bounded row sample and its fix history and raw LLM dialog stripped
datasource_idstringReplay argument: the datasource the action targeted
lookup_schemastring[]Replay argument for a lookup_schema step
search_tables, search_top_kstring, intReplay arguments for a search_tables step
correlation_a, correlation_bstringReplay arguments for a get_correlations step
reject_reasonstringReason class behind a complete_rejected step, so the nudge can be re-derived
completed, completion_msg, total_steps, exploration_duration_ms—Summary document only (kind = exploration_summary). Its presence is what lets a resumed run skip exploration entirely.

The summary document's attempt carries a second meaning: it identifies the exploration a resumed run is about to work over, and it is the value every AnalysisAreaCheckpoint is stamped with and filtered on.

Indexes: (run_id, step_number) unique — which makes a retried write a replace rather than a duplicate — and a created_at TTL named ttl_discovery_checkpoint.

The replay arguments are stored separately because an ExplorationStep records what came BACK (rows, counts, errors, digest) but has no field for what was ASKED — there is nowhere on it to put a lookup's table refs or a search's query text, and replay has to rebuild the model's own prior turn.

See Discovery lifecycle for what a checkpoint deliberately does not keep, and why.

AnalysisAreaCheckpoint​

One completed analysis area of an in-flight run. Stored in the discovery_analysis_checkpoints collection, keyed by (run_id, area_id), and the reason a resumed run analyses only the areas its previous attempt had not finished.

A sibling collection rather than another kind inside discovery_checkpoints: that collection's unique index is (run_id, step_number) and an area has no step number, and both of its readers scan a run's rows in step order and stop the replayable prefix at the first unexpected number.

Not the same thing as discovery_analysis_steps, which is the tail-written audit log keyed by discovery_id and read by the dashboard. These rows exist only while a run is in flight and are deleted when the run completes or is cancelled — with DISCOVERY_CHECKPOINT_RETENTION, the same setting that governs the exploration checkpoints, as the backstop.

FieldTypeDescription
run_idstringThe run this checkpoint belongs to
project_idstringProject that owns the run
attemptintWhich attempt analysed this area. The upsert is fenced on it, so an orphaned earlier attempt cannot overwrite a newer one's area
exploration_attemptintThe attempt that wrote the exploration summary this area was analysed over. A resumed run restores only the rows matching the exploration it is itself working over — an area's insights cite exploration step numbers, so one analysed over a different exploration would attribute its evidence to the wrong queries
insights_validatedintHow many of the area's insights were submitted to the verifier, so a resumed run restores the per-run validation budget instead of handing the areas it still has to run a fresh one
created_attimestampTTL anchor
(inline)AnalysisStepThe area itself — prompt, response, token counts, picker telemetry, its insights with their validation verdicts and evidence-quality labels, and its validation results. Nothing is stripped: an area's size is already bounded by the picker's token budget, and this is the same payload Phase 7 writes to discovery_analysis_steps

The stored insight ids are the ones the attempt that produced the area assigned, and a resumed run does not reuse them: it mints fresh ids as it merges the area back in, and re-points the area's validation results at them. An insight id keys the embedded insight, the standalone row and the Qdrant point, so an id carried across attempts would collide with what the previous attempt had already written — see Discovery lifecycle for the three failures that follow.

Indexes: (run_id, area_id) unique — which makes re-analysing an area a replace rather than a duplicate — and a created_at TTL named ttl_discovery_analysis_checkpoint.

The inlined AnalysisStep supplies area_id, so there is no separate field for it; the key half of the unique index comes from the step itself.

A row exists only for an area that produced a parsed result, finished its validation and had its evidence-quality labels attached. An area whose analysis failed carries no row, so a resume retries it — see Discovery lifecycle for why that distinction is what makes a deadline-killed run recoverable.

Feedback​

User feedback on insights, recommendations, or exploration steps.

FieldTypeDescription
idstringFeedback ID
project_idstringProject ID
discovery_idstringDiscovery run ID
target_typestringinsight, recommendation, exploration_step
target_idstringID of the rated item
ratingstringlike or dislike
commentstringOptional comment (typically with dislikes)
created_attimestampWhen feedback was submitted

Project​

Project configuration. Stored in projects collection.

FieldTypeDescription
idstringMongoDB ObjectID
namestringProject name
descriptionstringProject description
domainstringDomain (e.g., gaming, social)
categorystringCategory (e.g., match3, idle, casual, content_sharing)
warehouseWarehouseConfigData warehouse configuration
llmLLMConfigLegacy single-provider LLM configuration. Still written, as the mirror of the default layer, so an agent image older than the API reads a truthful setup. Resolve the live value through layer_models.
blurb_llmLLMConfigLegacy table-description override. The mirror of the table_descriptions layer, on the same terms. Cleared when that layer equals the default.
llm_providersLLMProviderEntry[]The project's named LLM connections. Absent on a project that has never been configured for per-layer models — the accessors then synthesise a default entry from llm (and a blurb entry from blurb_llm) at read time, so no backfill is needed to READ an old document.
layer_modelsmap[string]LayerModelWhich connection and model each layer uses, keyed by layer, plus the reserved default row every layer without one inherits. Non-empty means authoritative: blurb_llm is not consulted, so resetting a table-description override cannot rediscover the stale mirror.
llm_config_versionintThe per-layer-configuration generation this project has been configured for. 0 (or absent) means never: discovery is refused and the dashboard sends the user to settings.
profilemapDomain-specific profile (from JSON Schema form)
promptsProjectPromptsPer-project prompt overrides
statusstringProject status
last_run_attimestampWhen the last discovery ran
last_run_statusstringLast run result
created_attimestampWhen the project was created
updated_attimestampLast update

WarehouseConfig​

FieldTypeDescription
providerstringProvider ID: bigquery, redshift
project_idstringGCP project ID (BigQuery)
datasetsstring[]Dataset/schema names
locationstringData location
filter_fieldstringMulti-tenant filter column
filter_valuestringMulti-tenant filter value
configmapProvider-specific key-value config

LLMConfig​

FieldTypeDescription
providerstringProvider ID: claude, openai, ollama, vertex-ai, bedrock
modelstringModel identifier (free text)
configmapProvider-specific key-value config (e.g., project_id, location for Vertex AI)

LLMProviderEntry​

One named connection. config holds connection fields only; model-scoped settings live on the LayerModel that uses it.

FieldTypeDescription
idstringStable id within the project. The reserved default is the entry every layer inherits and the only one an older agent image can read credentials for, so it is a slot that gets rewritten rather than a pointer that gets re-aimed; blurb is the table-description entry's reserved id. Letters, digits, underscores and hyphens, at most 32 characters — the composed cloud secret name is length- and charset-limited.
labelstringHuman-readable name shown in the model pickers
providerstringProvider ID, as in LLMConfig
configmapConnection fields: region, base_url, auth_method, project_id, custom TLS, …

LayerModel​

One layer's choice. config is merged over the provider entry's config.

FieldTypeDescription
provider_idstringThe entry this layer uses. Empty is read as default.
modelstringModel id. May be empty only when an endpoint_id is set — an endpoint serves its own model and must never be sent another layer's.
configmapModel-scoped settings: max_input_tokens, max_output_tokens, wire_override, endpoint_id, reasoning_enabled, prompt_cache

Next Steps​