Quick Start
Minimum Viable GESA
GESA can be started with four components. The episode store is the only hard requirement — the rest can be added incrementally.
The Four Required Components
1. Episode schema → Where decisions are stored
2. Similarity fn → How to retrieve relevant episodes
3. Temperature → How bold vs conservative to be
4. Generator → How to produce candidate strategiesStep 1: Choose Your Storage Backend
GESA is storage-agnostic. Depending on episode volume:
| Volume | Recommended Backend |
|---|---|
| < 1,000 episodes | Flat JSON or SQLite |
| 1,000–100,000 | Cloudflare D1 or PostgreSQL |
| > 100,000 | Vector database + D1 for metadata |
| Production (Cloudflare) | D1 (persistent) + KV (7-day TTL buffer) |
The StratIQX production schema uses D1 for long-term storage and KV with 7-day TTL for the active episode buffer — temporal tiering by architecture.
Step 2: Create the Episode Table
Minimum viable schema (SQL):
sql
CREATE TABLE episodes (
id TEXT PRIMARY KEY,
timestamp DATETIME NOT NULL,
domain TEXT NOT NULL,
dimension TEXT,
drift_before REAL,
drift_after REAL,
fetch_score REAL,
temperature REAL,
action TEXT, -- JSON
outcome TEXT, -- JSON, populated after observation
context TEXT, -- JSON
tags TEXT, -- JSON array
success BOOLEAN,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_episodes_domain ON episodes(domain);
CREATE INDEX idx_episodes_timestamp ON episodes(timestamp);
CREATE INDEX idx_episodes_drift ON episodes(drift_before);Step 3: Implement the Similarity Function
typescript
function episodeSimilarity(
candidate: Episode,
query: EpisodeContext
): number {
const domainMatch = candidate.domain === query.domain ? 1.0 : 0.0
const driftProximity = 1 - Math.abs(candidate.driftBefore - query.driftScore) / 100
const dimensionMatch = candidate.dimension === query.dimension ? 1.0 : 0.5
const tempProximity = 1 - Math.abs(candidate.temperature - query.temperature) / 100
const outcomePolarity = candidate.success ? 0.6 : 0.4 // Include both; weight successes higher
return (
0.30 * domainMatch +
0.25 * Math.max(0, driftProximity) +
0.20 * dimensionMatch +
0.15 * Math.max(0, tempProximity) +
0.10 * outcomePolarity
)
}
function retrieveEpisodes(
store: EpisodeStore,
context: EpisodeContext,
limit = 10
): Episode[] {
const now = Date.now()
const decayConstant = DECAY_CONSTANTS[context.domain] ?? 90 * 24 * 60 * 60 * 1000
return store
.getAll()
.filter(e => e.outcome !== null) // Only completed episodes
.map(e => ({
episode: e,
score: episodeSimilarity(e, context) *
Math.exp(-(now - e.timestamp) / decayConstant)
}))
.sort((a, b) => b.score - a.score)
.slice(0, limit)
.map(({ episode }) => episode)
}Step 4: Set Up Temperature
typescript
class GESATemperature {
private T: number
private readonly alpha: number
constructor(profile: 'fast' | 'standard' | 'slow' = 'standard', T0 = 100) {
this.T = T0
this.alpha = { fast: 0.85, standard: 0.95, slow: 0.99 }[profile]
}
get current(): number { return this.T }
cool(): void {
this.T = this.T * this.alpha
}
reset(): void {
this.T = 100
}
}Step 5: Implement the Generator (Minimal)
A minimal rule-based generator for getting started:
typescript
function generateCandidates(
episodes: Episode[],
context: EpisodeContext,
temperature: number
): CandidateStrategy[] {
// Group retrieved episodes by action type
const actionGroups = groupBy(episodes, e => e.action.type)
return Object.entries(actionGroups)
.map(([actionType, eps]) => {
const successRate = eps.filter(e => e.success).length / eps.length
const avgGapChange = mean(eps.map(e => (e.driftBefore - e.driftAfter) ?? 0))
return {
strategy: actionType,
confidence: Math.round(successRate * 100),
episodicSupport: eps.length,
explorationBias: temperature > 50,
reasoning: `${eps.length} similar episodes; ${Math.round(successRate * 100)}% success rate; avg gap closure ${avgGapChange.toFixed(1)}`,
episodeRefs: eps.map(e => e.id),
alternativesConsidered: Object.keys(actionGroups).length,
gapVelocity: context.gapVelocity ?? 0
}
})
.filter(c => temperature > 50 || c.episodicSupport >= 3) // Temperature filter
.sort((a, b) => b.confidence - a.confidence)
}Step 6: The GESA Loop (Assembled)
typescript
class GESA {
constructor(
private store: EpisodeStore,
private temperature: GESATemperature,
private generator: GeneratorFn
) {}
async cycle(context: EpisodeContext): Promise<GESARecommendation> {
// 1. OBSERVE — context is the observation
// 2. RETRIEVE
const episodes = await retrieveEpisodes(this.store, context)
// 3 + 4. GENERATE + ANNEAL
const candidates = this.generator(episodes, context, this.temperature.current)
// 5. SELECT
const selected = candidates[0]
// Return recommendation (6. ACT is caller's responsibility)
return {
...selected,
temperature: this.temperature.current,
}
}
async storeOutcome(episodeId: string, outcome: EpisodeOutcome): Promise<void> {
// 7. STORE
await this.store.updateOutcome(episodeId, outcome)
// 8. COOL
this.temperature.cool()
}
}Cold Start Checklist
Before GESA outputs are reliable:
- [ ] Episode store created with correct schema
- [ ] At least 5 episodes stored (for retrieval to function at all)
- [ ] At least 20 episodes per domain (for reliable recommendations)
- [ ] Temperature profile selected and temperature set to T₀ = 100
- [ ] Domain decay constant configured
- [ ] Generator implemented (rule-based is sufficient to start)
Below 20 episodes: GESA recommendations are low-confidence explorations. This is correct behaviour — the system is learning.