Skip to content
Open
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
371 changes: 371 additions & 0 deletions apps/memos-local-plugin/core/config/defaults-pr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
/**
* The default config tree. Mirrors `schema.ts` exactly. When merging YAML,
* we deep-merge over this tree so users only need to specify what they want
* to change.
*/

import type { ResolvedConfig } from "./schema.js";

const FIXED_VIEWER_PORTS: Readonly<Record<string, number>> = Object.freeze({
openclaw: 18799,
hermes: 18800,
});

/** Runtime adapters own these well-known ports even for legacy YAML files. */
export function effectiveViewerPort(agent?: string): number | undefined {
return agent ? FIXED_VIEWER_PORTS[agent] : undefined;
}

export const DEFAULT_CONFIG: ResolvedConfig = {
version: 1,
viewer: {
// Per-agent default lives in `templates/config.<agent>.yaml`:
// - openclaw → 18799
// - hermes → 18800
// The fallback here only matters when neither config file exists
// (early bootstrap, tests, etc.).
port: 18799,
bindHost: "127.0.0.1",
openOnFirstTurn: false,
},
bridge: {
port: 18911,
mode: "stdio",
},
embedding: {
provider: "local",
endpoint: "",
model: "Xenova/all-MiniLM-L6-v2",
apiKey: "",
providerIgnore: [],
providerOrder: [],
openRouter: false,
cache: {
enabled: true,
maxItems: 20_000,
},
},
llm: {
provider: "",
endpoint: "",
model: "",
temperature: 0,
fallbackToHost: true,
apiKey: "",
timeoutMs: 45_000,
maxRetries: 3,
providerIgnore: [],
providerOrder: [],
openRouter: false,
maxTokens: 1024,
headers: {},
},
l3Llm: {
// Empty by default — falls back to the shared `llm` settings.
// Operators set this when they want a stronger model for L3
// abstraction. L3 runs off the turn-response path, so a slower
// but more reliable model improves world-model quality without
// affecting companion latency.
provider: "",
endpoint: "",
model: "",
apiKey: "",
temperature: 0,
timeoutMs: 60_000,
providerIgnore: [],
providerOrder: [],
openRouter: false,
maxTokens: 1024,
},
skillEvolver: {
// Empty by default — falls back to the shared `llm` settings.
// Operators set this when they want a stronger model (e.g.
// claude-sonnet / gpt-5-thinking) for skill crystallisation.
provider: "",
endpoint: "",
model: "",
apiKey: "",
temperature: 0,
timeoutMs: 60_000,
providerIgnore: [],
providerOrder: [],
openRouter: false,
maxTokens: 1024,
},
storage: {
ftsTokenizer: "trigram",
},
algorithm: {
lightweightMemory: {
enabled: true,
},
capture: {
maxTextChars: 4_000,
maxToolOutputChars: 2_000,
embedTraces: true,
alphaScoring: true,
// OpenClaw's tool messages don't include explicit "reflection"
// blocks; without synthesis the alpha scorer sees an empty
// reflection and forces α = 0 (see `core/capture/alpha-scorer.ts`
// line 97). That makes reflection-weighted backprop degenerate
// into pure γ-discount and produces flat V distributions —
// L2 association + skill crystallization both starve. Enable
// synth by default so even turns without explicit reflections
// still contribute useful α values.
synthReflections: true,
llmConcurrency: 4,
// Bound topic-end reflect work so dirty startup recovery cannot replay
// a huge historical episode into thousands of paid LLM calls.
maxReflectLlmCalls: 128,
// Recovered episodes are reconstructed from persisted traces; replay
// orphans are usually matching drift, so do not insert duplicate rows.
maxRecoveryOrphanInserts: 0,
// V7 §3.2 batched variant. With "auto" we issue a single LLM call
// per episode for both reflection synth and α scoring as long as
// the episode is short enough — this collapses 2N per-step calls
// (N synth + N α) into 1 batched call. Long episodes (>12 steps)
// automatically fall back to the per-step path so the prompt
// never overflows the model's context window. R_human + backprop
// remain task-end events handled by `core/reward`, unchanged.
batchMode: "auto",
batchThreshold: 12,
// `reflectionContextMode` controls which extra prompt context blocks
// topic-end reflection receives:
// - "none": no TASK CONTEXT and no DOWNSTREAM STEP PREVIEW
// - "task": inject TASK CONTEXT only
// - "downstream": inject DOWNSTREAM STEP PREVIEW only
// - "task_downstream": inject both blocks
// `longEpisodeReflectMode` controls the fallback used when an episode is
// too long for batch scoring:
// - "per_step_parallel": keep the current parallel per-step path. Each
// step is reflected independently, using only the context blocks
// enabled by `reflectionContextMode` that are available without
// downstream preview.
// - "per_step_downstream": still run per-step work in parallel, but
// prebuild a bounded DOWNSTREAM STEP PREVIEW for each step (step+1
// through step+N, capped by `downstreamStepCount`) and inject it when
// `reflectionContextMode` includes "downstream".
reflectionContextMode: "task_downstream",
longEpisodeReflectMode: "per_step_downstream",
downstreamStepCount: 3,
taskContextMaxChars: 800,
downstreamContextMaxChars: 1_200,
downstreamPerStepMaxChars: 400,
synthOutcomeMaxChars: 600,
},
reward: {
gamma: 0.9,
tauSoftmax: 0.5,
decayHalfLifeDays: 30,
llmScoring: true,
implicitThreshold: 0.2,
// 10 minutes was too long for interactive chat — users moved on
// to the next task before reward ever fired, so no R_human was
// ever computed and V stayed 0 for every trace. 30 s gives the
// user a short window to reply ("thanks", "no, try again") that
// the scorer picks up as explicit feedback; when nothing
// arrives, the implicit fallback fires promptly so downstream
// L2/L3/Skill stages aren't starved of signal.
feedbackWindowSec: 30,
summaryMaxChars: 2_000,
llmConcurrency: 2,
// Default lowered 2→1 to support single-shot CLI patterns
// (`hermes chat -q "..."`, `openclaw run --once`). With the old
// floor every CLI single-query episode was abandoned with
// "对话轮次不足", starving reward → L2 → Skill of any signal.
// Multi-turn TUI flows still trigger reward as before because
// they always satisfy the looser bound. Operators wanting the
// strict pre-2026Q2 behaviour can set 2 in config.yaml.
minExchangesForCompletion: 1,
// Lowered 80→40 to match the relaxed exchanges floor. 40 chars
// is "ok"/"thanks" + a real follow-up clause; below that we
// still skip as a triviality gate.
minContentCharsForCompletion: 40,
toolHeavyRatio: 0.7,
minAssistantCharsForToolHeavy: 80,
},
l2Induction: {
minSimilarity: 0.65,
candidateTtlDays: 30,
minEpisodesForInduction: 1,
// Lowered from 0.05 → 0.005. Reward backprop V values for typical
// multi-step turns (5-15 steps) are clustered around 0.02-0.5 even
// for successful episodes; the old 0.05 floor was throwing away
// most of the signal before induction could see it. Negative-V
// traces are still excluded (they'd never count as "with-set"
// evidence anyway).
minTraceValue: 0.005,
useLlm: true,
traceCharCap: 3_000,
gainEmaAlpha: 0.4,
archiveGain: -0.05,
},
l3Abstraction: {
// Lowered from 3 → 2. The original threshold required THREE
// distinct active policies in the same domain cluster before any
// world model could form, which in real usage takes weeks to
// accumulate even for a focused user. Two compatible active
// policies is the smallest meaningful cluster.
minPolicies: 1,
// Lowered from 0.1 → 0.02. With the Bayesian-shrinkage gain
// formula (see core/memory/l2/gain.ts), a genuinely useful policy
// that fires on a single-success path now scores around 0.05-0.20
// (proportional to V_with - 0.5). 0.02 is well below that floor
// but still cleanly rejects net-neutral noise.
minPolicyGain: 0.02,
minPolicySupport: 1,
// Lowered from 0.6 → 0.3 so the typical 2-3 active policies in
// an early-life install can still cluster into a world model;
// strict 0.6 starved L3 in real usage.
clusterMinSimilarity: 0.3,
policyCharCap: 800,
traceCharCap: 500,
traceEvidencePerPolicy: 1,
useLlm: true,
// Lowered from 1 → 0 so abstraction can run as soon as the
// ingredients show up, not on a per-day cadence.
cooldownDays: 0,
confidenceDelta: 0.05,
minConfidenceForRetrieval: 0.2,
},
skill: {
// Lowered from 2 → 1: a single supporting episode is enough to
// attempt skill crystallization. Quality is still gated by
// minGain + candidate trials below.
minSupport: 1,
// Lowered from 0.1 → 0.02. Same rationale as l3.minPolicyGain:
// the new shrinkage-anchored gain formula gives positive scores
// proportional to V_with − 0.5, so 0.1 was unreachable for any
// policy that didn't have an explicit failure-cohort contrast.
// 0.02 is enough to filter neutral-noise policies while still
// letting genuinely-useful patterns crystallize.
minGain: 0.02,
// Lowered from 5 → 1. Demanding multiple trials in `candidate`
// before a skill can graduate meant skills rarely promoted in
// real usage; 1 lets the candidate→active transition happen
// immediately on first successful invocation.
candidateTrials: 1,
// Lowered from 6 hours → 0: no cooldown, skills can re-evolve
// as soon as new evidence arrives.
cooldownMs: 0,
traceCharCap: 500,
evidenceLimit: 6,
useLlm: true,
etaDelta: 0.1,
archiveEta: 0.1,
minEtaForRetrieval: 0.1,
},
feedback: {
failureThreshold: 3,
failureWindow: 5,
valueDelta: 0.5,
minLowValueThreshold: 0.01,
useLlm: true,
attachToPolicy: true,
cooldownMs: 60_000,
traceCharCap: 500,
evidenceLimit: 4,
},
session: {
followUpMode: "merge_follow_ups",
mergeMaxGapMs: 2 * 60 * 60 * 1000,
maxTurnsPerEpisode: 30,
classifyTimeoutMs: 5000,
bgLlmConcurrency: 2,
},
retrieval: {
tier1TopK: 3,
tier2TopK: 5,
tier3TopK: 2,
candidatePoolFactor: 4,
weightCosine: 0.6,
weightPriority: 0.4,
mmrLambda: 0.7,
includeLowValue: false,
rrfConstant: 60,
minSkillEta: 0.1,
// Lowered from 0.35 → 0.25 so partial-match traces still surface
// for users with smaller corpora.
minTraceSim: 0.25,
episodeGoalMinSim: 0.45,
tagFilter: "auto",
keywordTopK: 20,
// Lowered from 0.4 → 0.2 with the 2026 ranker overhaul: the new
// base relevance already uses channel rank as a first-class
// signal, so the old 0.4 floor was over-pruning keyword hits
// with modest V·decay.
relativeThresholdFloor: 0.2,
skillEtaBlend: 0.15,
smartSeed: true,
smartSeedRatio: 0.7,
multiChannelBypass: true,
skillInjectionMode: "summary",
skillSummaryChars: 200,
llmFilterEnabled: true,
// Tighter than the legacy default (5) so the LLM filter has a
// small budget; combined with the richer prompt (v3) this keeps
// packets concise without over-dropping.
llmFilterMaxKeep: 4,
// Set to 2: skip the LLM precision pass when there's only one
// candidate (no point ranking a single item). Anything with 2+
// candidates still goes through the filter to drop off-topic
// hits before injection.
llmFilterMinCandidates: 2,
llmFilterCandidateBodyChars: 500,
// Default 0 — no time-window bound, keeping the legacy
// brute-force scan behaviour for fresh installs that haven't
// grown past the threshold where the bound starts paying off.
// Operators with >50K traces are expected to flip this on (we
// suggest 86_400_000 = 24h, or 2_592_000_000 = 30 days).
vectorScanMaxAgeMs: 0,
},
},
hub: {
enabled: false,
role: "client",
port: 18912,
address: "",
teamName: "",
teamToken: "",
userToken: "",
nickname: "",
},
telemetry: { enabled: true },
logging: {
level: "info",
detailedView: false,
timezone: "UTC",
console: { enabled: true, pretty: true, channels: ["*"] },
file: {
enabled: true,
format: "json",
rotate: { maxSizeMb: 50, maxFiles: 14, gzip: true },
retentionDays: 30,
},
audit: {
enabled: true,
rotate: { monthly: true, gzip: true },
},
llmLog: { enabled: true, redactPrompts: false, redactCompletions: false },
perfLog: { enabled: true, sampleRate: 1.0 },
eventsLog: { enabled: true },
redact: {
extraKeys: ["api_key", "secret", "token", "password", "authorization"],
extraPatterns: [],
},
channels: {},
},
};

/**
* Set of dotted-path field names whose values must never be sent to the
* viewer or any non-localhost surface. Used by `server/routes/config.ts`.
*/
export const SECRET_FIELD_PATHS: readonly string[] = Object.freeze([
"embedding.apiKey",
"llm.apiKey",
"skillEvolver.apiKey",
"l3Llm.apiKey",
"hub.teamToken",
"hub.userToken",
]);
Loading