Skip to content

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 strategies

Step 1: Choose Your Storage Backend

GESA is storage-agnostic. Depending on episode volume:

VolumeRecommended Backend
< 1,000 episodesFlat JSON or SQLite
1,000–100,000Cloudflare D1 or PostgreSQL
> 100,000Vector 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.


→ Episode Schema | → API Reference