feat: Implement scene rulebook and validation engine

- Added a new SceneRulebook system to manage data-driven validation rules for actions.
- Introduced rule checks for actions like "take", "open", "move", "introduce", and "describe".
- Created a rulebook engine to evaluate conditions and enforce rules during action validation.
- Enhanced action handling with support for scene entry and character descriptions.
- Updated the architecture documentation to reflect the new rule-based validation approach.
- Added new endpoints and improved the persistence layer for rulebooks.
This commit is contained in:
2026-04-26 13:33:05 -04:00
parent 998635f542
commit ff9b86c3e9
16 changed files with 2013 additions and 412 deletions

View File

@@ -1,355 +1,364 @@
# CharacterGarden — System Bible (MVP Architecture)
# CharacterGarden MVP — Strict Architecture
## Purpose
## 🧠 Core Principle
CharacterGarden is a deterministic roleplay and simulation framework.
The system separates:
* Natural language (LLM / user input)
* Structured intent (parsed actions)
* Truth validation (rules engine)
* World state (persistent simulation)
The system MUST remain deterministic at the core.
> The system is a **deterministic simulation engine**.
> The LLM is **not a source of truth**. It is an **input/output translator only**.
---
## Core Principle
# 🧩 System Architecture
The LLM is NOT the source of truth.
The LLM is used ONLY for:
* Parsing natural language → structured actions
* Generating narrative output
ALL validation, state changes, and rules are handled by deterministic code.
User / LLM Input (Prose)
[Parser Layer]
[Normalization Layer]
[Truth Engine (Validation)]
[State Mutation Engine]
[Persistence Layer]
[LLM Output Generation]
---
## System Pipeline
# 🔒 Non-Negotiable Rules
All input MUST pass through the following pipeline:
1. PROSE INPUT
* Source: user or AI
* Format: free text
2. PARSER LAYER
* Converts text → structured actions
* May use LLM or rule-based parsing
* Output MUST follow strict schema (see Action Contract)
3. ACTION CONTRACT (STRICT)
* Only valid structured objects allowed past this point
* No free text allowed beyond this stage
4. TRUTH ENGINE
* Deterministic validation of actions
* No LLM usage allowed
* Returns validation results
5. WORLD STATE UPDATE
* Applies valid actions
* Rejects or partially applies invalid ones
6. RESPONSE GENERATION
* LLM converts results into narrative output
1. Truth Engine MUST NEVER parse natural language
2. Only structured Actions may mutate world state
3. All mutations must be validated before execution
4. World state is modified ONLY through engine functions
5. LLM output is never trusted without validation
6. Every turn must be fully traceable
---
## Hard Rule
# 📦 Core Data Contracts
The Truth Engine MUST NEVER:
* Parse natural language
* Infer missing intent
* Use probabilistic logic
* Call an LLM
If it does, the architecture is broken.
---
## Action Contract (REQUIRED)
All actions MUST conform to this schema:
## Action (STRICT UNION TYPE)
```ts
export type Action = {
actorId: string;
type: string;
targetId?: string;
locationId?: string;
metadata?: Record<string, any>;
export type Action =
| { type: "move"; actorId: string; targetId: string }
| { type: "take"; actorId: string; targetId: string }
| { type: "open"; actorId: string; targetId: string }
| { type: "close"; actorId: string; targetId: string }
| { type: "use"; actorId: string; targetId: string; toolId?: string }
| { type: "speak"; actorId: string; content: string }
| { type: "introduce"; actorId: string; targetId?: string; metadata?: Record<string, unknown> };
```
⚠️ DO NOT use generic `type: string` actions.
---
## NormalizedAction
```ts
export type NormalizedAction = Action;
```
If invalid → reject BEFORE validation.
---
## ValidationResult
```ts
export type ValidationResult = {
success: boolean;
reasonCode:
| "OK"
| "NOT_FOUND"
| "NOT_PRESENT"
| "LOCKED"
| "INVALID_TARGET"
| "MISSING_REQUIREMENT"
| "OUT_OF_TURN"
| "UNKNOWN";
message: string;
};
```
No additional fields allowed unless explicitly added here.
---
## Turn Structure
Each turn MUST be stored and processed as:
## Turn (Traceable Execution Unit)
```ts
export type Turn = {
id: string;
rawText: string;
actions: Action[];
validation: ValidationResult[];
createdAt: number;
parsedActions: unknown[];
normalizedActions: NormalizedAction[];
validationResults: ValidationResult[];
appliedActions: NormalizedAction[];
timestamp: number;
};
```
---
## Validation Result Contract
# 🌍 World State (STRICT SCHEMA)
```ts
export type ValidationResult = {
actionIndex: number;
success: boolean;
reason?: string;
message?: string;
};
```
Examples:
* "not_your_turn"
* "object_not_found"
* "door_locked"
---
## Required System Layers
### 1. Parser Layer
Function:
```ts
parseTextToActions(text: string): Action[]
```
Responsibilities:
* Convert text → valid Action[]
* Resolve references ("he", "the door")
* May fail or return empty list
Allowed:
* LLM usage
* Heuristics
Not allowed:
* World state mutation
* Validation logic
---
### 2. Truth Engine
Function:
```ts
validateActions(actions: Action[], worldState: WorldState): ValidationResult[]
```
Responsibilities:
* Validate each action deterministically
* No mutation
---
### 3. World State Engine
Function:
```ts
applyActions(actions: Action[], results: ValidationResult[], worldState: WorldState): WorldState
```
Responsibilities:
* Apply ONLY valid actions
* Maintain consistency
---
## World State Requirements
World state MUST:
* Be serializable
* Be versionable
* Support rollback
* Support branching
---
## Database Requirements
Use SQLite.
Minimum tables:
* turns
* actions
* validation_results
* entities
* world_states
Each turn MUST be persisted.
---
## Entity System
Entities MUST have stable IDs.
Example:
```ts
type Entity = {
export type Entity = {
id: string;
name: string;
type: string;
attributes: Record<string, any>;
locationId?: string;
attributes?: Record<string, unknown>;
};
export type Location = {
id: string;
name: string;
connectedTo: string[];
};
export type WorldState = {
entities: Record<string, Entity>;
locations: Record<string, Location>;
inventory: Record<string, string[]>; // actorId → itemIds
flags: Record<string, boolean>;
};
```
Parser MUST resolve references to entity IDs.
---
# 🧠 System Layers
---
## Failure Handling
## 1. Parser Layer (`parser/`)
Failure is FIRST-CLASS.
**Responsibility:**
- Convert prose → rough actions
- May use LLM
Example:
**Output:**
```ts
unknown[]
```
⚠️ Parser output is NOT trusted.
---
## 2. Normalization Layer (`parser/normalizeActions.ts`)
**Responsibility:**
- Enforce schema
- Resolve references (`he``john`)
- Fill missing fields
- Reject invalid structures
**Input:**
```ts
unknown[]
```
**Output:**
```ts
NormalizedAction[]
```
---
## 3. Truth Engine (`engine/validate.ts`)
**Responsibility:**
- Determine if action is valid
- MUST be deterministic
- MUST NOT mutate state
**Input:**
```ts
NormalizedAction + WorldState
```
**Output:**
```ts
ValidationResult
```
---
## 4. Mutation Engine (`engine/apply.ts`)
**Responsibility:**
- Apply ONLY successful actions
- Mutate world state
**Input:**
```ts
NormalizedAction + WorldState
```
**Output:**
```ts
WorldState
```
---
## 5. Persistence Layer (`storage/`)
**Responsibility:**
- Store:
- world state
- turns
- history
---
# 🔄 Turn Execution Pipeline
```ts
{
success: false,
reason: "door_locked",
message: "The door cannot be opened."
function processTurn(rawText: string): Turn {
const parsed = parse(rawText);
const normalized = normalize(parsed);
const validationResults = normalized.map(action =>
validate(action, worldState)
);
const successfulActions = normalized.filter((_, i) =>
validationResults[i].success
);
const newState = apply(successfulActions, worldState);
const turn: Turn = {
id: generateId(),
rawText,
parsedActions: parsed,
normalizedActions: normalized,
validationResults,
appliedActions: successfulActions,
timestamp: Date.now()
};
persist(turn, newState);
return turn;
}
```
The system MUST:
---
* Return failures clearly
* Allow LLM to narrate failures
* NOT silently fix invalid actions
# 🧠 Reference Resolution Rules
Handled in normalization layer.
Examples:
| Input | Output |
|-------------|--------------|
| "he" | actorId |
| "the door" | door_1 |
| "my key" | key_owned_by_actor |
Must be:
- deterministic
- context-aware
- testable
---
## LLM Adapter Rules
# 🧪 Debug & Traceability (REQUIRED)
The LLM Adapter MUST:
Every turn MUST store:
* Never mutate world state
* Never validate actions
* Only transform data
- raw input
- parsed output
- normalized actions
- validation results
- applied actions
This enables:
- replay
- debugging
- AI correction
---
## Development Phases
# ⚙️ Initial Action Rules (MVP)
### Phase 1 — Contract Enforcement (CURRENT)
## move
- actor must exist
- target must be connected location
* Define Action, Turn, ValidationResult
* Refactor all code to use contracts
* Remove any free-text logic from truth engine
## take
- item must be in same location
- item not already owned
## open
- target must exist
- if locked → fail unless key present
## introduce
- creates entity OR brings into scene
---
### Phase 2 — Minimal Truth Engine
# 🚫 Anti-Patterns (DO NOT DO)
Implement test domain:
* Tic-tac-toe OR simple door system
Goal:
* Fully deterministic validation
* No LLM required for correctness
❌ Let LLM mutate state
❌ Store unvalidated actions
❌ Use dynamic/untyped actions
❌ Skip normalization
❌ Combine validation + mutation
❌ Allow hidden side effects
---
### Phase 3 — Parser Improvement
# 🚀 Future Extensions (Planned)
* Add LLM parsing
* Add reference resolution
* Improve action extraction
- Memory system (vector + summaries)
- Belief vs truth separation
- Multi-agent turns
- Time-based simulation
- Rule plugins per scenario
- UI action inspector
---
### Phase 4 — Memory System
# 🧠 Mental Model
* Persist entities
* Track history
* Add retrieval support
This system is:
> A deterministic simulation engine with LLM-based I/O
NOT:
> A chatbot with memory
---
## Non-Goals (For Now)
# ✅ Definition of Done (MVP)
* No complex AI reasoning inside truth engine
* No autonomous agents
* No multi-agent planning
- [ ] Actions fully typed
- [ ] Normalization layer implemented
- [ ] Validation fully deterministic
- [ ] State mutation isolated
- [ ] Turn trace persisted
- [ ] Simple scenario (door + key) works
---
## thoughts.md Requirement
# 💬 Guidance for Copilot
Copilot MUST maintain a file:
When generating code:
```
/thoughts.md
```
After each major change, it MUST append:
* What was implemented
* Why it was implemented
* What assumptions were made
* What remains unclear
This is REQUIRED to maintain continuity across sessions.
---
## Definition of Done (MVP)
The system is complete when:
* A user can input text
* It is parsed into structured actions
* Actions are validated deterministically
* World state updates correctly
* A narrative response is generated
WITHOUT requiring the LLM for correctness.
---
## Final Rule
If any part of the system depends on the LLM to maintain logical correctness,
the architecture has failed.
The LLM is an interface layer, not a reasoning authority.
- Prefer explicit types over generic objects
- Avoid dynamic structures
- Keep functions pure where possible
- Do not introduce hidden state
- Follow pipeline strictly