sim-one-alpha

SIM-ONE Alpha Schema Strategy

This document describes how runtime schemas are organized in sim-one-alpha, why we chose Valibot, and when to promote a schema to a shared location.

Why we need a schema layer

Types alone cannot validate data at runtime. Flue supports structured output via session.task(..., { result: Schema }), tools receive untrusted arguments from the model, and records in the repository’s SQLite and LanceDB stores must be validated at their read and write boundaries. The same rule applies when another persistence backend is added. A single source of truth for each shape prevents the TypeScript type and the runtime validator from drifting apart.

Schema library: Valibot

We use Valibot because:

Directory layout

src/core/schemas/
  Shared Valibot schemas for cross-cutting runtime contracts.
  One file per domain. Promote a schema here only when it is reused across files or subsystems.

src/engine/workers/<domain>/schemas.ts (optional)
  Worker-local schemas that are only consumed inside that worker.
  Useful when a schema is private to a single tool or workflow.

src/core/types/
  Pure TypeScript type contracts. May re-export inferred types from
  `src/core/schemas/` so consumers can continue importing from one place.

Decision rules

Keep a schema next to its consumer when:

Example: a one-off validation schema inside a single tool file.

Promote a schema to src/core/schemas/ when:

Example: CodingImplementerResultSchema lives in src/core/schemas/coding-worker.ts because it is shared between the implementer tool, the delegation path, and the public CodingSubagentRunResult type contract.

Pattern: schema + derived type + re-export

Define the schema and derive its type in the schema file:

// src/core/schemas/coding-worker.ts
import * as v from 'valibot';

export const CodingImplementerResultSchema = v.object({
  fileEdits: v.array(
    v.object({
      path: v.string(),
      oldText: v.string(),
      newText: v.string(),
      expectedOccurrences: v.optional(v.number()),
    })
  ),
  writeFiles: v.array(
    v.object({
      path: v.string(),
      content: v.string(),
    })
  ),
  verificationCommands: v.array(
    v.object({
      name: v.string(),
      command: v.string(),
      required: v.optional(v.boolean()),
      reason: v.optional(v.string()),
      cwd: v.optional(v.string()),
      timeoutSeconds: v.optional(v.number()),
    })
  ),
});

export type CodingImplementerResult = v.InferOutput<typeof CodingImplementerResultSchema>;

Import and re-export from the domain type contract so existing consumers do not need to change paths:

// src/engine/workers/coding-worker/types.ts
import { CodingImplementerResultSchema } from '../../../core/schemas/coding-worker.js';

export { CodingImplementerResultSchema };
export type CodingImplementerResult = import('../../../core/schemas/coding-worker.js').CodingImplementerResult;

Use the schema for structured Flue output:

// src/engine/workers/coding-worker/workflow/coordination.ts
import { CodingImplementerResultSchema } from '../../../../core/schemas/coding-worker.js';

const response = await session.task(prompt, {
  agent: codingImplementerSubagentName,
  result: CodingImplementerResultSchema,
});

return {
  subagent: 'implementer',
  summary: `Implementer submitted ${response.data.fileEdits.length} edit(s)...`,
  evidence: [agent, childSession],
  structuredOutput: { type: 'implementer', result: response.data },
};

Schema Promotion Rules

Relationship to SIM-ONE

SIM-ONE governs memory, security, and development process. The schema layer supports SIM-ONE by: