refactor: remove legacy types.ts file and update frontend to use new contracts feat: add applyActions function to manage action application and world state mutation chore: remove empty .gitkeep file from sqlite data directory refactor: update frontend App component to align with new API contracts and improve UX docs: revise project.md to reflect updated architecture and system requirements docs: update thoughts.md with current status, architecture decisions, and remaining checks
6.9 KiB
CharacterGarden — Iterative Implementation Plan
Copilot Operating Rules
Work in small, reviewable steps.
After every completed step:
- Update
thoughts.md - Record files changed
- Record assumptions made
- Record next step
- Do not skip ahead unless the current step is complete
Do not redesign the project without updating this plan.
Phase 1 — Contracts First
Step 1.1 — Create contracts folder
Create:
app/src/contracts/
Add:
app/src/contracts/action.ts
app/src/contracts/turn.ts
app/src/contracts/validation.ts
app/src/contracts/world.ts
app/src/contracts/entity.ts
Goal: all shared types live here.
Step 1.2 — Define Action contract
In action.ts:
export type Action = {
actorId: string;
type: string;
targetId?: string;
locationId?: string;
metadata?: Record<string, unknown>;
};
No other action shape should be used.
Step 1.3 — Define ValidationResult contract
In validation.ts:
export type ValidationResult = {
actionIndex: number;
success: boolean;
reason?: string;
message?: string;
};
Step 1.4 — Define Turn contract
In turn.ts:
import type { Action } from "./action";
import type { ValidationResult } from "./validation";
export type Turn = {
id: string;
rawText: string;
actions: Action[];
validation: ValidationResult[];
createdAt: number;
};
Step 1.5 — Define Entity contract
In entity.ts:
export type Entity = {
id: string;
name: string;
type: string;
attributes: Record<string, unknown>;
};
Step 1.6 — Define WorldState contract
In world.ts:
import type { Entity } from "./entity";
export type WorldState = {
id: string;
entities: Record<string, Entity>;
metadata: Record<string, unknown>;
createdAt: number;
};
Phase 2 — Enforce Layer Boundaries
Step 2.1 — Refactor truth engine imports
Update truthEngine.ts so it imports:
import type { Action } from "./contracts/action";
import type { ValidationResult } from "./contracts/validation";
import type { WorldState } from "./contracts/world";
Truth engine must only receive structured actions.
Step 2.2 — Remove text parsing from truth engine
Search truthEngine.ts for:
- string parsing
- natural language interpretation
- prompt logic
- LLM calls
Move any such logic out.
Truth engine should expose:
export function validateActions(
actions: Action[],
worldState: WorldState
): ValidationResult[] {
// deterministic validation only
}
Step 2.3 — Create parser layer
Create:
app/src/parser/
app/src/parser/parseTextToActions.ts
Function:
import type { Action } from "../contracts/action";
export function parseTextToActions(text: string): Action[] {
// temporary simple parser
return [];
}
For now, returning [] is acceptable.
Step 2.4 — Create world state engine
Create:
app/src/world/
app/src/world/applyActions.ts
Function:
import type { Action } from "../contracts/action";
import type { ValidationResult } from "../contracts/validation";
import type { WorldState } from "../contracts/world";
export function applyActions(
actions: Action[],
results: ValidationResult[],
worldState: WorldState
): WorldState {
// apply only successful actions
return worldState;
}
Phase 3 — Build First Deterministic Test Domain
Use a simple door/key room before anything complex.
Step 3.1 — Seed initial world
Create initial world state:
- actor:
player - room:
room_start - door:
door_1 - key:
key_1
Door starts locked.
Key starts in room.
Player starts in room.
Step 3.2 — Support action types
Truth engine should recognize:
inspect
take
open
move
Unknown action types fail with:
reason: "unknown_action"
Step 3.3 — Validate take action
Rules:
- Actor must exist
- Target must exist
- Target must be in same location
- Target must be takeable
Failure reasons:
actor_not_foundtarget_not_foundnot_in_same_locationnot_takeable
Step 3.4 — Validate open action
Rules:
- Actor must exist
- Target must exist
- Target must be openable
- If locked, actor must have matching key
Failure reasons:
actor_not_foundtarget_not_foundnot_openablelocked_requires_key
Step 3.5 — Apply successful take
If take succeeds:
- move item into actor inventory
Step 3.6 — Apply successful open
If open succeeds:
- set door attribute
open: true
Phase 4 — Wire Full Turn Processing
Step 4.1 — Create turn processor
Create:
app/src/turns/processTurn.ts
Function:
export async function processTurn(rawText: string): Promise<Turn> {
// parse
// validate
// apply
// persist
// return turn
}
Step 4.2 — Enforce pipeline order
The turn processor must call:
parseTextToActions
validateActions
applyActions
persistTurn
In that order.
No layer may skip ahead.
Step 4.3 — Add debug response
API should return:
{
rawText,
actions,
validation,
worldState
}
This is for MVP debugging.
Phase 5 — Persistence
Step 5.1 — Add database tables
Minimum SQLite tables:
turns
actions
validation_results
entities
world_states
Step 5.2 — Persist every turn
Each call to processTurn must save:
- raw text
- parsed actions
- validation results
- resulting world state snapshot
Step 5.3 — Add reset endpoint
Add an endpoint to reset world state to seed state.
This is needed for testing.
Phase 6 — LLM Adapter Reintroduction
Only after deterministic flow works.
Step 6.1 — LLM parser adapter
Add optional LLM parser:
parseTextToActionsWithLLM(text: string, worldState: WorldState): Promise<Action[]>
It must output only valid Action[].
Step 6.2 — LLM narrative adapter
Add:
generateNarrative(turn: Turn, worldState: WorldState): Promise<string>
The narrative adapter may describe results but must not alter them.
Phase 7 — Frontend Debug UI
Step 7.1 — Show raw text input
User can submit a turn.
Step 7.2 — Show parsed actions
Display action JSON.
Step 7.3 — Show validation results
Display success/failure reasons.
Step 7.4 — Show world state
Display current world state JSON.
MVP Completion Criteria
MVP is complete when this works:
- User enters:
take key - Parser returns a
takeaction - Truth engine validates it
- World state moves key to inventory
- User enters:
open door - Truth engine verifies key ownership
- Door becomes open
- All steps are visible in debug UI
- All turns are persisted
Do Not Do Yet
Do not implement:
- autonomous agents
- complex memory retrieval
- embeddings
- relationship simulation
- long-term summaries
- branching timelines
Until the deterministic MVP is working.