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:
559
project.md
559
project.md
@@ -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
|
||||
Reference in New Issue
Block a user