Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ EMBEDDING_DIMENSIONS=1024
# See https://docs.atomicmemory.ai/platform/consuming-core.
# CORE_RUNTIME_CONFIG_MUTATION_ENABLED=false

# --- Internal retrieval tuning ---
# Defaults mirror the balanced adaptive policy. These are experimental knobs
# for benchmark sweeps and should not be treated as stable product config.
# ADAPTIVE_SIMPLE_LIMIT=5
# ADAPTIVE_MEDIUM_LIMIT=5
# ADAPTIVE_COMPLEX_LIMIT=8
# ADAPTIVE_MULTI_HOP_LIMIT=12
# ADAPTIVE_AGGREGATION_LIMIT=25
# LITERAL_LIST_PROTECTION_ENABLED=false
# LITERAL_LIST_PROTECTION_MAX_PROTECTED=3
# OBSERVATION_DATE_EXTRACTION_ENABLED=false
# QUOTED_ENTITY_EXTRACTION_ENABLED=false
# TEMPORAL_QUERY_CONSTRAINT_ENABLED=false
# TEMPORAL_QUERY_CONSTRAINT_BOOST=2

# --- Railway ---
# On Railway, DATABASE_URL is injected by the Postgres plugin.
# Set OPENAI_API_KEY in Railway service variables.
Expand Down
24 changes: 24 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3802,6 +3802,18 @@
},
"skip_repair": {
"type": "boolean"
},
"stage_count": {
"type": "number"
},
"stage_names": {
"items": {
"type": "string"
},
"type": "array"
},
"trace_id": {
"type": "string"
}
},
"required": [
Expand Down Expand Up @@ -4316,6 +4328,18 @@
},
"skip_repair": {
"type": "boolean"
},
"stage_count": {
"type": "number"
},
"stage_names": {
"items": {
"type": "string"
},
"type": "array"
},
"trace_id": {
"type": "string"
}
},
"required": [
Expand Down
16 changes: 16 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,14 @@ paths:
type: string
skip_repair:
type: boolean
stage_count:
type: number
stage_names:
items:
type: string
type: array
trace_id:
type: string
required:
- candidate_ids
- candidate_count
Expand Down Expand Up @@ -2906,6 +2914,14 @@ paths:
type: string
skip_repair:
type: boolean
stage_count:
type: number
stage_names:
items:
type: string
type: array
trace_id:
type: string
required:
- candidate_ids
- candidate_count
Expand Down
45 changes: 45 additions & 0 deletions src/app/runtime-config-route-snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Shared runtime-config route snapshot shape and formatter.
*
* Both the composed runtime container and the legacy route module need the
* same public config subset. Keeping the projection here prevents drift in
* `/v1/memories/health` and `/v1/memories/config` responses.
*/

import type { EmbeddingProviderName, LLMProviderName, RuntimeConfig } from '../config.js';

export interface RuntimeConfigRouteSnapshot {
retrievalProfile: string;
embeddingProvider: EmbeddingProviderName;
embeddingModel: string;
llmProvider: LLMProviderName;
llmModel: string;
clarificationConflictThreshold: number;
maxSearchResults: number;
hybridSearchEnabled: boolean;
iterativeRetrievalEnabled: boolean;
entityGraphEnabled: boolean;
crossEncoderEnabled: boolean;
agenticRetrievalEnabled: boolean;
repairLoopEnabled: boolean;
runtimeConfigMutationEnabled: boolean;
}

export function readRuntimeConfigRouteSnapshot(config: RuntimeConfig): RuntimeConfigRouteSnapshot {
return {
retrievalProfile: config.retrievalProfile,
embeddingProvider: config.embeddingProvider,
embeddingModel: config.embeddingModel,
llmProvider: config.llmProvider,
llmModel: config.llmModel,
clarificationConflictThreshold: config.clarificationConflictThreshold,
maxSearchResults: config.maxSearchResults,
hybridSearchEnabled: config.hybridSearchEnabled,
iterativeRetrievalEnabled: config.iterativeRetrievalEnabled,
entityGraphEnabled: config.entityGraphEnabled,
crossEncoderEnabled: config.crossEncoderEnabled,
agenticRetrievalEnabled: config.agenticRetrievalEnabled,
repairLoopEnabled: config.repairLoopEnabled,
runtimeConfigMutationEnabled: config.runtimeConfigMutationEnabled,
};
}
53 changes: 15 additions & 38 deletions src/app/runtime-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import type { RetrievalProfile } from '../services/retrieval-profiles.js';
import { MemoryService } from '../services/memory-service.js';
import { initEmbedding } from '../services/embedding.js';
import { initLlm } from '../services/llm.js';
import {
readRuntimeConfigRouteSnapshot,
type RuntimeConfigRouteSnapshot,
} from './runtime-config-route-snapshot.js';

/**
* Explicit runtime configuration subset currently needed by the runtime
Expand Down Expand Up @@ -62,6 +66,11 @@ import { initLlm } from '../services/llm.js';
*/
export interface CoreRuntimeConfig {
adaptiveRetrievalEnabled: boolean;
adaptiveSimpleLimit: number;
adaptiveMediumLimit: number;
adaptiveComplexLimit: number;
adaptiveMultiHopLimit: number;
adaptiveAggregationLimit: number;
agenticRetrievalEnabled: boolean;
auditLoggingEnabled: boolean;
consensusMinMemories: number;
Expand All @@ -79,6 +88,8 @@ export interface CoreRuntimeConfig {
linkExpansionEnabled: boolean;
linkExpansionMax: number;
linkSimilarityThreshold: number;
literalListProtectionEnabled: boolean;
literalListProtectionMaxProtected: number;
maxSearchResults: number;
mmrEnabled: boolean;
mmrLambda: number;
Expand All @@ -98,6 +109,8 @@ export interface CoreRuntimeConfig {
rerankSkipMinGap: number;
rerankSkipTopSimilarity: number;
retrievalProfileSettings: RetrievalProfile;
temporalQueryConstraintBoost: number;
temporalQueryConstraintEnabled: boolean;
}

/** Repositories constructed by the runtime container. */
Expand All @@ -116,28 +129,7 @@ export interface CoreRuntimeServices {
}

export interface CoreRuntimeConfigRouteAdapter {
current: () => {
retrievalProfile: string;
embeddingProvider: import('../config.js').EmbeddingProviderName;
embeddingModel: string;
llmProvider: import('../config.js').LLMProviderName;
llmModel: string;
clarificationConflictThreshold: number;
maxSearchResults: number;
hybridSearchEnabled: boolean;
iterativeRetrievalEnabled: boolean;
entityGraphEnabled: boolean;
crossEncoderEnabled: boolean;
agenticRetrievalEnabled: boolean;
repairLoopEnabled: boolean;
/**
* Startup-validated flag for whether PUT /v1/memories/config should mutate
* runtime config. Production deploys leave this false; dev/test toggles
* it on via the CORE_RUNTIME_CONFIG_MUTATION_ENABLED env var. Routes
* read this snapshot — never re-check env at request time.
*/
runtimeConfigMutationEnabled: boolean;
};
current: () => RuntimeConfigRouteSnapshot;
update: (updates: {
similarityThreshold?: number;
audnCandidateThreshold?: number;
Expand Down Expand Up @@ -224,22 +216,7 @@ export function createCoreRuntime(deps: CoreRuntimeDeps): CoreRuntime {
config,
configRouteAdapter: {
current() {
return {
retrievalProfile: config.retrievalProfile,
embeddingProvider: config.embeddingProvider,
embeddingModel: config.embeddingModel,
llmProvider: config.llmProvider,
llmModel: config.llmModel,
clarificationConflictThreshold: config.clarificationConflictThreshold,
maxSearchResults: config.maxSearchResults,
hybridSearchEnabled: config.hybridSearchEnabled,
iterativeRetrievalEnabled: config.iterativeRetrievalEnabled,
entityGraphEnabled: config.entityGraphEnabled,
crossEncoderEnabled: config.crossEncoderEnabled,
agenticRetrievalEnabled: config.agenticRetrievalEnabled,
repairLoopEnabled: config.repairLoopEnabled,
runtimeConfigMutationEnabled: config.runtimeConfigMutationEnabled,
};
return readRuntimeConfigRouteSnapshot(config);
},
update(updates) {
return updateRuntimeConfig(updates);
Expand Down
40 changes: 40 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export interface RuntimeConfig {
crossAgentCandidateThreshold: number;
clarificationConflictThreshold: number;
adaptiveRetrievalEnabled: boolean;
adaptiveSimpleLimit: number;
adaptiveMediumLimit: number;
adaptiveComplexLimit: number;
adaptiveMultiHopLimit: number;
adaptiveAggregationLimit: number;
repairLoopEnabled: boolean;
hybridSearchEnabled: boolean;
repairLoopMinSimilarity: number;
Expand Down Expand Up @@ -70,6 +75,8 @@ export interface RuntimeConfig {
chunkOverlapTurns: number;
consensusExtractionEnabled: boolean;
consensusExtractionRuns: number;
observationDateExtractionEnabled: boolean;
quotedEntityExtractionEnabled: boolean;
entropyGateEnabled: boolean;
entropyGateThreshold: number;
entropyGateAlpha: number;
Expand Down Expand Up @@ -110,6 +117,10 @@ export interface RuntimeConfig {
agenticRetrievalEnabled: boolean;
rerankSkipTopSimilarity: number;
rerankSkipMinGap: number;
literalListProtectionEnabled: boolean;
literalListProtectionMaxProtected: number;
temporalQueryConstraintEnabled: boolean;
temporalQueryConstraintBoost: number;
deferredAudnEnabled: boolean;
deferredAudnBatchSize: number;
compositeGroupingEnabled: boolean;
Expand Down Expand Up @@ -201,6 +212,16 @@ function parseLlmSeed(value: string | undefined): number | undefined {
return Number.isFinite(parsed) ? parsed : undefined;
}

function parsePositiveIntEnv(name: string, fallback: number): number {
const raw = optionalEnv(name);
if (!raw) return fallback;
const parsed = parseInt(raw, 10);
if (!Number.isFinite(parsed) || parsed < 1) {
throw new Error(`${name} must be a positive integer`);
}
return parsed;
}

function parseVectorBackend(value: string | undefined): VectorBackendName {
if (!value) return 'pgvector';
if (value === 'pgvector' || value === 'ruvector-mock' || value === 'zvec-mock') return value;
Expand Down Expand Up @@ -235,6 +256,11 @@ export const config: RuntimeConfig = {
crossAgentCandidateThreshold: parseFloat(optionalEnv('CROSS_AGENT_CANDIDATE_THRESHOLD') ?? '0.75'),
clarificationConflictThreshold: 0.8,
adaptiveRetrievalEnabled: (process.env.ADAPTIVE_RETRIEVAL_ENABLED ?? String(retrievalProfileSettings.adaptiveRetrievalEnabled)) === 'true',
adaptiveSimpleLimit: parsePositiveIntEnv('ADAPTIVE_SIMPLE_LIMIT', 5),
adaptiveMediumLimit: parsePositiveIntEnv('ADAPTIVE_MEDIUM_LIMIT', 5),
adaptiveComplexLimit: parsePositiveIntEnv('ADAPTIVE_COMPLEX_LIMIT', 8),
adaptiveMultiHopLimit: parsePositiveIntEnv('ADAPTIVE_MULTI_HOP_LIMIT', 12),
adaptiveAggregationLimit: parsePositiveIntEnv('ADAPTIVE_AGGREGATION_LIMIT', 25),
repairLoopEnabled: (process.env.REPAIR_LOOP_ENABLED ?? String(retrievalProfileSettings.repairLoopEnabled)) === 'true',
hybridSearchEnabled: (process.env.HYBRID_SEARCH_ENABLED ?? String(retrievalProfileSettings.hybridSearchEnabled)) === 'true',
repairLoopMinSimilarity: parseFloat(process.env.REPAIR_LOOP_MIN_SIMILARITY ?? String(retrievalProfileSettings.repairLoopMinSimilarity)),
Expand Down Expand Up @@ -286,6 +312,8 @@ export const config: RuntimeConfig = {
chunkOverlapTurns: parseInt(optionalEnv('CHUNK_OVERLAP_TURNS') ?? '1', 10),
consensusExtractionEnabled: (optionalEnv('CONSENSUS_EXTRACTION_ENABLED') ?? 'false') === 'true',
consensusExtractionRuns: parseInt(optionalEnv('CONSENSUS_EXTRACTION_RUNS') ?? '3', 10),
observationDateExtractionEnabled: (optionalEnv('OBSERVATION_DATE_EXTRACTION_ENABLED') ?? 'false') === 'true',
quotedEntityExtractionEnabled: (optionalEnv('QUOTED_ENTITY_EXTRACTION_ENABLED') ?? 'false') === 'true',
entropyGateEnabled: (optionalEnv('ENTROPY_GATE_ENABLED') ?? 'false') === 'true',
entropyGateThreshold: parseFloat(optionalEnv('ENTROPY_GATE_THRESHOLD') ?? '0.35'),
entropyGateAlpha: parseFloat(optionalEnv('ENTROPY_GATE_ALPHA') ?? '0.5'),
Expand Down Expand Up @@ -326,6 +354,10 @@ export const config: RuntimeConfig = {
agenticRetrievalEnabled: (optionalEnv('AGENTIC_RETRIEVAL_ENABLED') ?? 'false') === 'true',
rerankSkipTopSimilarity: parseFloat(optionalEnv('RERANK_SKIP_TOP_SIMILARITY') ?? '0.85'),
rerankSkipMinGap: parseFloat(optionalEnv('RERANK_SKIP_MIN_GAP') ?? '0.05'),
literalListProtectionEnabled: (optionalEnv('LITERAL_LIST_PROTECTION_ENABLED') ?? 'false') === 'true',
literalListProtectionMaxProtected: parsePositiveIntEnv('LITERAL_LIST_PROTECTION_MAX_PROTECTED', 3),
temporalQueryConstraintEnabled: (optionalEnv('TEMPORAL_QUERY_CONSTRAINT_ENABLED') ?? 'false') === 'true',
temporalQueryConstraintBoost: parseFloat(optionalEnv('TEMPORAL_QUERY_CONSTRAINT_BOOST') ?? '2'),
deferredAudnEnabled: (optionalEnv('DEFERRED_AUDN_ENABLED') ?? 'false') === 'true',
deferredAudnBatchSize: parseInt(optionalEnv('DEFERRED_AUDN_BATCH_SIZE') ?? '20', 10),
compositeGroupingEnabled: (optionalEnv('COMPOSITE_GROUPING_ENABLED') ?? 'true') === 'true',
Expand Down Expand Up @@ -413,6 +445,9 @@ export const INTERNAL_POLICY_CONFIG_FIELDS = [
// Repair loop tuning
'repairLoopMinSimilarity', 'repairSkipSimilarity',
'repairDeltaThreshold', 'repairConfidenceFloor',
// Adaptive retrieval tuning
'adaptiveSimpleLimit', 'adaptiveMediumLimit', 'adaptiveComplexLimit',
'adaptiveMultiHopLimit', 'adaptiveAggregationLimit',
// MMR
'mmrEnabled', 'mmrLambda',
// Link expansion
Expand All @@ -428,6 +463,7 @@ export const INTERNAL_POLICY_CONFIG_FIELDS = [
'extractionCacheEnabled', 'embeddingCacheEnabled',
'chunkedExtractionEnabled', 'chunkSizeTurns', 'chunkOverlapTurns',
'consensusExtractionEnabled', 'consensusExtractionRuns',
'observationDateExtractionEnabled', 'quotedEntityExtractionEnabled',
'entropyGateEnabled', 'entropyGateThreshold', 'entropyGateAlpha',
// Affinity clustering
'affinityClusteringThreshold', 'affinityClusteringMinSize',
Expand All @@ -449,6 +485,10 @@ export const INTERNAL_POLICY_CONFIG_FIELDS = [
'queryAugmentationMinSimilarity',
// Rerank tuning
'rerankSkipTopSimilarity', 'rerankSkipMinGap',
// Literal/list answer selection
'literalListProtectionEnabled', 'literalListProtectionMaxProtected',
// Temporal query selection
'temporalQueryConstraintEnabled', 'temporalQueryConstraintBoost',
// Fast AUDN
'fastAudnEnabled', 'fastAudnDuplicateThreshold',
// Observation / deferred
Expand Down
Loading
Loading