Layer 4: Meta-Learning
The Fourth Question: How well is prediction working?
Layer 4 closes the feedback loop in the Wake Intelligence brain. While Layers 1–3 answer Why, How, and What, Layer 4 asks How well — and then adjusts.
Overview
Layer 3 (Propagation Engine) calculates prediction scores using a weighted formula:
score = temporal_weight * temporal_score
+ causal_weight * causal_score
+ frequency_weight * frequency_score2
3
The default weights are 0.4 / 0.3 / 0.3 — but these are not universally optimal. A long-lived documentation project favors frequency (revisited regularly). A sprint-style decision project favors causality (decision chains matter). A daily standup workflow favors recency.
Layer 4 observes which predictions were accurate and adjusts the weights per project.
How It Works
1. Record Outcomes
Every time update_predictions runs and a context is subsequently accessed, Layer 4 records a prediction_outcome:
{
context_id: "...",
project: "api-service",
predicted_score: 0.78,
was_accessed: true,
temporal_score: 0.82,
causal_score: 0.61,
frequency_score: 0.74
}2
3
4
5
6
7
8
9
2. Accumulate Evidence
Learning activates after 20+ outcomes for a project. Below 20, default weights are used.
3. Adjust Weights
With sufficient outcomes, Layer 4 computes which signal was most predictive and shifts the weights toward it:
// Simplified: move weights toward the signal that was right more often
newTemporalWeight += temporalAccuracyDelta * 0.05;
newCausalWeight += causalAccuracyDelta * 0.05;
newFrequencyWeight += frequencyAccuracyDelta * 0.05;2
3
4
4. Clamp and Redistribute
After adjustment, weights are clamped to [0.1, 0.6] to prevent any signal from dominating or being ignored. The redistribute() function ensures the weights still sum to exactly 1.0 after clamping:
function redistribute(weights: ProjectWeights): ProjectWeights {
const clamped = clampAll(weights); // each weight → [0.1, 0.6]
const total = sum(clamped);
const deficit = 1.0 - total;
// distribute deficit proportionally to non-pinned weights
return distributeDeficit(clamped, deficit);
}2
3
4
5
6
7
The invariant: temporal_weight + causal_weight + frequency_weight === 1.0 always holds.
Architecture
New Domain Types
interface PredictionOutcome {
id: string;
project: string;
contextId: string;
predictedScore: number;
wasAccessed: boolean;
timestamp: string;
temporalScore?: number;
causalScore?: number;
frequencyScore?: number;
}
interface ProjectWeights {
project: string;
temporalWeight: number; // default 0.4, clamped [0.1, 0.6]
causalWeight: number; // default 0.3, clamped [0.1, 0.6]
frequencyWeight: number; // default 0.3, clamped [0.1, 0.6]
lastUpdated: string;
outcomeCount: number;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Service: MetaLearningService
MetaLearningService lives in the domain layer and owns:
recordOutcome(outcome)— persist a prediction resultupdateWeights(project)— recalculate weights from ≥20 outcomesgetWeights(project)— retrieve current weights (default if < 20 outcomes)getLearningStats(project)— human-readable weight summary
Repository: IMetaLearningRepository
Port interface for outcome and weight storage:
interface IMetaLearningRepository {
saveOutcome(outcome: PredictionOutcome): Promise<void>;
findOutcomes(project: string, limit?: number): Promise<PredictionOutcome[]>;
saveWeights(weights: ProjectWeights): Promise<void>;
findWeights(project: string): Promise<ProjectWeights | null>;
}2
3
4
5
6
Implemented by D1MetaLearningRepository (Cloudflare D1 adapter).
Database Schema
Two new tables (Migration 0005):
CREATE TABLE prediction_outcomes (
id TEXT PRIMARY KEY,
project TEXT NOT NULL,
context_id TEXT NOT NULL,
predicted_score REAL NOT NULL,
was_accessed INTEGER NOT NULL DEFAULT 0,
timestamp TEXT NOT NULL,
temporal_score REAL,
causal_score REAL,
frequency_score REAL
);
CREATE TABLE project_weights (
project TEXT PRIMARY KEY,
temporal_weight REAL NOT NULL DEFAULT 0.4,
causal_weight REAL NOT NULL DEFAULT 0.3,
frequency_weight REAL NOT NULL DEFAULT 0.3,
last_updated TEXT NOT NULL,
outcome_count INTEGER NOT NULL DEFAULT 0
);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MCP Tools
get_learning_stats
Returns current learned weights and outcome count for a project.
get_learning_stats({ project: "api-service" })
→ Temporal weight: 0.52 (default: 0.40)
Causal weight: 0.28 (default: 0.30)
Frequency weight: 0.20 (default: 0.30)
Based on 47 outcomes.2
3
4
5
6
See get_learning_stats tool reference.
Integration with Layer 3
PropagationService.calculateScore() accepts an optional ProjectWeights parameter. When Layer 4 weights exist for a project, they override the default 0.4 / 0.3 / 0.3:
const weights = await metaLearningService.getWeights(project);
const score = propagationService.calculateScore(context, weights);2
This integration is transparent — the API and MCP tools work the same way, but scores improve over time as learning accumulates.
Monitoring
Use get_learning_stats periodically to check whether learning has activated and which signal is winning for each project:
// Check all active projects
for (const project of activeProjects) {
get_learning_stats({ project });
}2
3
4
If a project's temporal weight has grown to 0.58, its access patterns are strongly time-based — contexts accessed recently are reliably accessed again.
See Also
- Layer 3: Propagation Engine - Prediction scoring
- get_learning_stats - View current weights
- update_predictions - Trigger score refresh
- Database Schema - Migration 0005 details
