feat(interpreter): implement hybrid intent resolution with LLM and deterministic fallback
- Added new contracts for intent interpretation, including InterpreterOutput and ResolverMode. - Implemented deterministic intent resolver with clarity checks for ambiguous references and empty input. - Developed LLM intent resolver that communicates with an external model, handling JSON responses and fallback clarifications. - Created an interpretTurn function to manage intent resolution based on the selected resolver mode. - Introduced validation for interpreter output to ensure integrity before processing actions. - Established a turn manager to orchestrate turn processing, including action validation and world state mutation. - Added integration tests to verify the functionality of the new intent resolution system. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
426
project.md
426
project.md
@@ -1,364 +1,138 @@
|
||||
# CharacterGarden MVP — Strict Architecture
|
||||
# CharacterGarden MVP - Current Architecture (April 2026)
|
||||
|
||||
## 🧠 Core Principle
|
||||
## Core Principle
|
||||
|
||||
> The system is a **deterministic simulation engine**.
|
||||
> The LLM is **not a source of truth**. It is an **input/output translator only**.
|
||||
The simulation engine is deterministic and authoritative.
|
||||
The LLM layer is an intent interpreter and resolver, not a source of truth.
|
||||
|
||||
---
|
||||
|
||||
# 🧩 System Architecture
|
||||
## Live System Layers
|
||||
|
||||
User / LLM Input (Prose)
|
||||
↓
|
||||
[Parser Layer]
|
||||
↓
|
||||
[Normalization Layer]
|
||||
↓
|
||||
[Truth Engine (Validation)]
|
||||
↓
|
||||
[State Mutation Engine]
|
||||
↓
|
||||
|
|
||||
[Intent Interpreter Layer]
|
||||
|
|
||||
[Turn Manager]
|
||||
|
|
||||
[Truth Engine + Scene Rulebook Validation]
|
||||
|
|
||||
[World Mutation Engine]
|
||||
|
|
||||
[Persistence Layer]
|
||||
↓
|
||||
[LLM Output Generation]
|
||||
|
|
||||
[Frontend/API Response]
|
||||
|
||||
---
|
||||
## Non-Negotiable Rules
|
||||
|
||||
# 🔒 Non-Negotiable Rules
|
||||
1. Truth engine must never parse natural language.
|
||||
2. Only structured actions can mutate world state.
|
||||
3. Every mutation must pass validation before apply.
|
||||
4. Rulebook rules are data-driven and editable.
|
||||
5. Interpreter output is traceable and never auto-trusted when unresolved.
|
||||
6. Every turn remains replayable end-to-end.
|
||||
|
||||
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
|
||||
## Current Canonical Actions
|
||||
|
||||
---
|
||||
- inspect
|
||||
- move
|
||||
- open
|
||||
- take
|
||||
- introduce
|
||||
- describe
|
||||
- transfer
|
||||
|
||||
# 📦 Core Data Contracts
|
||||
## Action Contracts (Current)
|
||||
|
||||
## Action (STRICT UNION TYPE)
|
||||
- Action shape is contract-based in app/src/contracts/action.ts
|
||||
- Validation contracts in app/src/contracts/validation.ts
|
||||
- Turn contracts in app/src/contracts/turn.ts
|
||||
- Interpreter contracts in app/src/contracts/intent.ts
|
||||
|
||||
```ts
|
||||
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> };
|
||||
```
|
||||
## Scene Rulebook (Data-Driven Validation)
|
||||
|
||||
⚠️ DO NOT use generic `type: string` actions.
|
||||
Validation is now externalized into SceneRulebook definitions.
|
||||
|
||||
---
|
||||
Key capabilities already implemented:
|
||||
|
||||
## NormalizedAction
|
||||
- Actor authorization checks (actorIdIn, actorNameIn)
|
||||
- Conditional creation checks (actionMetadataEq)
|
||||
- Inventory ownership checks (itemInInventory)
|
||||
- Existing deterministic checks (entity type/exists, same location, attribute checks)
|
||||
|
||||
```ts
|
||||
export type NormalizedAction = Action;
|
||||
```
|
||||
This supports:
|
||||
|
||||
If invalid → reject BEFORE validation.
|
||||
- Restricting who can introduce characters
|
||||
- Restricting who can create missing items via take
|
||||
- Validating transfer only when actor owns item and recipient is valid
|
||||
|
||||
---
|
||||
## Turn Execution (Current)
|
||||
|
||||
## ValidationResult
|
||||
1. Interpret raw turn text using interpreter module.
|
||||
2. If unresolved:
|
||||
- return clarification/rejection state
|
||||
- persist trace turn with no applied actions
|
||||
3. If resolved:
|
||||
- validate actions with truth engine + active rulebook
|
||||
- apply successful actions
|
||||
- persist turn, actions, validation results, and world state
|
||||
|
||||
```ts
|
||||
export type ValidationResult = {
|
||||
success: boolean;
|
||||
reasonCode:
|
||||
| "OK"
|
||||
| "NOT_FOUND"
|
||||
| "NOT_PRESENT"
|
||||
| "LOCKED"
|
||||
| "INVALID_TARGET"
|
||||
| "MISSING_REQUIREMENT"
|
||||
| "OUT_OF_TURN"
|
||||
| "UNKNOWN";
|
||||
message: string;
|
||||
};
|
||||
```
|
||||
## Interpreter + Turn Manager (New)
|
||||
|
||||
---
|
||||
- Interpreter module: app/src/interpreter/interpretTurn.ts
|
||||
- Turn manager orchestrator: app/src/turns/turnManager.ts
|
||||
- processTurn delegates to turn manager: app/src/turns/processTurn.ts
|
||||
|
||||
## Turn (Traceable Execution Unit)
|
||||
Current interpreter statuses:
|
||||
|
||||
```ts
|
||||
export type Turn = {
|
||||
id: string;
|
||||
rawText: string;
|
||||
- resolved
|
||||
- needs_clarification
|
||||
- rejected
|
||||
|
||||
parsedActions: unknown[];
|
||||
normalizedActions: NormalizedAction[];
|
||||
## Current Domain Behaviors
|
||||
|
||||
validationResults: ValidationResult[];
|
||||
- take can create missing items when createIfMissing is present and actor is authorized by rulebook
|
||||
- introduce can create missing characters when rulebook allows
|
||||
- transfer moves items between inventories when rulebook checks pass
|
||||
|
||||
appliedActions: NormalizedAction[];
|
||||
## Persistence (Current)
|
||||
|
||||
timestamp: number;
|
||||
};
|
||||
```
|
||||
SQLite tables already backing turns and world snapshots:
|
||||
|
||||
---
|
||||
- turns
|
||||
- actions
|
||||
- validation_results
|
||||
- entities
|
||||
- world_states
|
||||
- rulebooks
|
||||
|
||||
# 🌍 World State (STRICT SCHEMA)
|
||||
## API Surface (Current)
|
||||
|
||||
```ts
|
||||
export type Entity = {
|
||||
id: string;
|
||||
name: string;
|
||||
locationId?: string;
|
||||
attributes?: Record<string, unknown>;
|
||||
};
|
||||
- GET /api/state
|
||||
- POST /api/turn
|
||||
- POST /api/reset
|
||||
- GET /api/rulebook
|
||||
- PUT /api/rulebook
|
||||
- GET /api/rulebooks
|
||||
|
||||
export type Location = {
|
||||
id: string;
|
||||
name: string;
|
||||
connectedTo: string[];
|
||||
};
|
||||
## Operational Rule: Validation in Docker
|
||||
|
||||
export type WorldState = {
|
||||
entities: Record<string, Entity>;
|
||||
locations: Record<string, Location>;
|
||||
inventory: Record<string, string[]>; // actorId → itemIds
|
||||
flags: Record<string, boolean>;
|
||||
};
|
||||
```
|
||||
Build and runtime checks should run in containers, not host Node.
|
||||
|
||||
---
|
||||
- Backend build: docker compose run --rm app npm run build
|
||||
- Frontend build: docker compose run --rm frontend npm run build
|
||||
|
||||
# 🧠 System Layers
|
||||
## Immediate Path Forward
|
||||
|
||||
---
|
||||
1. LLM adapter hardening
|
||||
- Tune prompt/schema validation for model drift.
|
||||
- Add configurable model + timeout policy per environment.
|
||||
|
||||
## 1. Parser Layer (`parser/`)
|
||||
2. Rulebook governance
|
||||
- Keep versioned rulebook migration path active as rule schema evolves.
|
||||
- Split rules into policy packs: creation, transfer, social.
|
||||
|
||||
**Responsibility:**
|
||||
- Convert prose → rough actions
|
||||
- May use LLM
|
||||
|
||||
**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
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 🧠 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
|
||||
|
||||
---
|
||||
|
||||
# 🧪 Debug & Traceability (REQUIRED)
|
||||
|
||||
Every turn MUST store:
|
||||
|
||||
- raw input
|
||||
- parsed output
|
||||
- normalized actions
|
||||
- validation results
|
||||
- applied actions
|
||||
|
||||
This enables:
|
||||
- replay
|
||||
- debugging
|
||||
- AI correction
|
||||
|
||||
---
|
||||
|
||||
# ⚙️ Initial Action Rules (MVP)
|
||||
|
||||
## move
|
||||
- actor must exist
|
||||
- target must be connected location
|
||||
|
||||
## 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
|
||||
|
||||
---
|
||||
|
||||
# 🚫 Anti-Patterns (DO NOT DO)
|
||||
|
||||
❌ Let LLM mutate state
|
||||
❌ Store unvalidated actions
|
||||
❌ Use dynamic/untyped actions
|
||||
❌ Skip normalization
|
||||
❌ Combine validation + mutation
|
||||
❌ Allow hidden side effects
|
||||
|
||||
---
|
||||
|
||||
# 🚀 Future Extensions (Planned)
|
||||
|
||||
- Memory system (vector + summaries)
|
||||
- Belief vs truth separation
|
||||
- Multi-agent turns
|
||||
- Time-based simulation
|
||||
- Rule plugins per scenario
|
||||
- UI action inspector
|
||||
|
||||
---
|
||||
|
||||
# 🧠 Mental Model
|
||||
|
||||
This system is:
|
||||
|
||||
> A deterministic simulation engine with LLM-based I/O
|
||||
|
||||
NOT:
|
||||
|
||||
> A chatbot with memory
|
||||
|
||||
---
|
||||
|
||||
# ✅ Definition of Done (MVP)
|
||||
|
||||
- [ ] Actions fully typed
|
||||
- [ ] Normalization layer implemented
|
||||
- [ ] Validation fully deterministic
|
||||
- [ ] State mutation isolated
|
||||
- [ ] Turn trace persisted
|
||||
- [ ] Simple scenario (door + key) works
|
||||
|
||||
---
|
||||
|
||||
# 💬 Guidance for Copilot
|
||||
|
||||
When generating code:
|
||||
|
||||
- Prefer explicit types over generic objects
|
||||
- Avoid dynamic structures
|
||||
- Keep functions pure where possible
|
||||
- Do not introduce hidden state
|
||||
- Follow pipeline strictly
|
||||
3. Testing depth
|
||||
- Expand Docker-executed integration tests for:
|
||||
- createIfMissing authorization matrix
|
||||
- transfer ownership/location checks
|
||||
- unresolved clarification flows
|
||||
- multi-action turn behavior
|
||||
|
||||
Reference in New Issue
Block a user