Skip to content

flows: f.memory via ai-hist (relayhistory) — SURFACE §2 rule 3 #307

Description

@kjgbot

Summary

Add f.memory to the flow surface, backed by ai-hist (relayhistory) v0.4.1+. This closes the memory bullet of SURFACE.md §2 rule 3 (line 67).

The kernel vocabulary is closed; f.memory is a helper, not a primitive. Its writes compile to journaled effect steps. Its reads inform the run but are not journaled (they don't mutate).

Verbatim spec quote — SURFACE.md §2 rule 3

f.memory — relayhistory: f.memory.recall(query), f.memory.why(task), f.memory.learn(finding).

And the harness example on line 34 declares memory scopes on the flow header:

memory: { script: true, agent: true }, // gate 5 — relayhistory-backed

The full memory scope contract lives at docs/GATE5-MEMORY-CONTRACT.md.

Verified state of relayhistory / ai-hist (2026-09-11)

  • Package: ai-hist v0.4.1 (npm; TS SDK) — also ships as a Rust CLI and stdio MCP server.
  • Path (source): /Users/khaliqgant/Projects/AgentWorkforce/relayhistory
  • TS SDK surface (sdk-ts/src/index.ts):
    • openAiHist({...}): Promise<AiHist>
    • class AiHist with methods for search, list, stats, handoff, trajectory
    • Types: HistoryEntry, SessionMeta, HandoffCandidate, Tag, TaggedSession, SessionSummary, ListOptions, SearchOptions, Stats, TrajectoryEntry, TrajectorySearchOptions, GetHandoffOptions, OpenOptions, OpenSourceInfo
    • Additional export: defaultDbPath(), resumeCommand(...)
  • MCP server exposes (per README): search_history, get_context, search_trajectories, why_for_task.
  • Trajectory shape: decisions (question → chosen → reasoning → alternatives) + retrospectives (learnings, confidence).
  • Local-first: SQLite + FTS5, no keys/network required. Cloud sync opt-in via ai-hist/cloud export.

This is enough to back the spec's recall / why / learn triad directly. No new store to build.

Scope of this slice

Surface changes (packages/surface)

Add memory: MemoryHelper to Ctx in packages/surface/src/context.ts.

New file packages/surface/src/memory.ts exporting the helper contract:

export interface MemoryRecallOptions {
  limit?: number;         // default 20
  project?: string;       // scope to a project
  source?: 'claude' | 'codex' | 'cursor' | 'grok' | 'relay' | 'trajectory' | 'opencode';
}

export interface MemoryFinding {
  question: string;       // what was being decided
  chosen: string;         // the decision
  reasoning: string;      // why
  alternatives?: string[];
  confidence?: number;    // 0..1
}

export interface MemoryHelper {
  recall(query: string, options?: MemoryRecallOptions): Promise<HistoryEntry[]>;
  why(task: string): Promise<TrajectoryEntry[]>;
  learn(finding: MemoryFinding): Promise<void>;
}

HistoryEntry and TrajectoryEntry are re-exported from ai-hist types so authors get the same shapes.

Header contract

The flow header accepts a memory: field:

memory?: { script?: boolean; agent?: boolean }

per SURFACE line 34 and GATE5-MEMORY-CONTRACT.md. Semantics of script vs agent scope are already governed by that contract; this slice does not renegotiate them, it wires the surface method through to the SDK.

Preflight (packages/sdk/src/preflight.ts)

  • If the flow header declares memory: {...} OR the compiler detects f.memory.* usage in an authored TS body, verify that:
    1. ai-hist binary is reachable on $PATH or the JS SDK ai-hist is importable from node_modules, and
    2. defaultDbPath() resolves to a readable location (create-if-missing is fine).
  • If either check fails, refuse with memory_unreachable and name the specific probe that failed.
  • Add memory_unreachable to failure-kinds.ts.

Executor (packages/sdk/src/authored-flow-executor.ts)

  • Attach f.memory to the injected context.
  • recall / why are read operations — call the SDK directly, do not journal a step. They return their SDK result.
  • learn is a write operation — journal one effect step with type: memory.learn and the finding as the effect payload. Use the existing effect record/confirm path so exactly-once dedup applies. Idempotency key = flow-run-id + step-id (same convention as slice B's slack write).

Dependencies (packages/sdk/package.json, packages/surface/package.json)

Add ai-hist at ^0.4.1 (or the current v0.x compatible range) as a runtime dep in packages/sdk and a type dep (re-exported types) in packages/surface. Do not add a transitive better-sqlite3 unless the SDK requires it — prefer letting ai-hist own its native binding.

Acceptance evidence

The PR body must include verbatim command output demonstrating each of the following:

  1. Basic recall works. An authored TS smoke flow calls const bugs = await f.memory.recall('auth bug'); console.log(bugs.length); against a seeded fixture ai-hist DB in testdata/memory/ and prints a nonzero count.
  2. learn round-trips through why. The same flow calls await f.memory.learn({...}) with a distinctive question, then await f.memory.why(<that-question>) and asserts the finding appears with chosen/reasoning intact.
  3. Preflight refuses when unreachable. flows check on a flow that declares memory: (or uses f.memory.*) against a PATH with no ai-hist and no node_modules/ai-hist fails with memory_unreachable, exit 2.
  4. Fresh DB, cloud disabled. A test that creates a temp defaultDbPath(), opens it via openAiHist, uses only recall/learn, and never touches ai-hist/cloud.
  5. Idempotency. learn called twice with the same idempotency key produces one journal effect record.
  6. New vitest file packages/sdk/tests/f-memory.test.ts (or similar) with the four above cases, plus a preflight-refuses-on-missing case.

Files to touch

Add:

  • packages/surface/src/memory.ts
  • packages/sdk/tests/f-memory.test.ts
  • testdata/memory/ (small fixture ai-hist DB or a seed script that builds one deterministically)

Modify:

  • packages/surface/src/context.ts — add memory: MemoryHelper
  • packages/surface/src/index.ts — re-export memory types
  • packages/surface/package.json — add ai-hist types-only dep
  • packages/sdk/src/authored-flow-executor.ts — wire f.memory on ctx
  • packages/sdk/src/preflight.ts — memory_unreachable probe
  • packages/sdk/src/failure-kinds.ts — new kind
  • packages/sdk/package.json — add ai-hist runtime dep
  • docs/SURFACE.md — a small clarifying paragraph under §2 rule 3 memory bullet linking to GATE5-MEMORY-CONTRACT.md and stating that learn journals as effect; do not restate the spec

Not in scope

  • Cloud push. The ai-hist/cloud export exists but this slice ships local SQLite only. Cloud sync is a follow-up once f.memory write shape is stable.
  • Pair mode ("cited warnings drawn from your team's own prior work" per ai-hist README §Why). That's a separate surface, not this triad.
  • Server-side scrubbing / PII redaction. Cloud-only concern.
  • Renegotiating GATE5-MEMORY-CONTRACT.md. Consume it as-is; don't touch script vs agent scope semantics.
  • Cross-flow memory sharing. Each flow's memory scope is defined by its header; sharing is a GATE5 concern.

Interaction with adjacent slices

  • Slice B (f.slack) establishes the "helper compiled to effect step" pattern. f.memory.learn follows that same pattern; read the B PR for the exact effect-record/confirm shape before duplicating a scheme.
  • Slice A (flows build) — f.memory usage should be included in the preflight declaration written into the immutable bundle, so at deploy-time we can still verify memory reachability.
  • Slice C (f.mcp) — ai-hist's stdio MCP server is a real MCP server. If slice C ships f.mcp, the ai-hist MCP could in principle be reached via tools: { mcp: ['ai-hist'] } instead of via f.memory. That is not this slice. Keep f.memory as the ergonomic path per SURFACE §2 rule 3.

References

  • SURFACE.md §2 rule 3 (line 65-69) and line 34 (memory header example)
  • docs/GATE5-MEMORY-CONTRACT.md — memory scope contract
  • /Users/khaliqgant/Projects/AgentWorkforce/relayhistory/sdk-ts/src/index.ts — ai-hist TS SDK surface
  • /Users/khaliqgant/Projects/AgentWorkforce/relayhistory/README.md — ai-hist capabilities
  • Slice B PR — helper-namespace-to-effect-step pattern

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions