# Plan: Open Tasks (Ollama Integration in InMemorySessionService) This document plans the two remaining backend open tasks: wiring the two-call Ollama pattern into **createSession** and **submitTurn** in [InMemorySessionService](src/main/java/de/neitzel/roleplay/business/InMemorySessionService.java). --- ## Overview | Task | Method | Goal | |------|--------|------| | 1 | `createSession` | Produce opening narrative and initial state via Ollama (narrative + state extraction). | | 2 | `submitTurn` | Produce turn narrative and state update via Ollama (narrative + state update), then merge into session. | Both follow the same pattern: build **TurnContext** (JSON) → **Call 1** (narrative) → **Call 2** (state update with narrative in user message) → map **StateUpdateResponse** and narrative into API responses and session state. --- ## Prerequisites and Dependencies - **[OllamaClient](src/main/java/de/neitzel/roleplay/fascade/OllamaClient.java)** – already provides `generateNarrative(model, systemPrompt, userContent)` and `generateStateUpdate(model, systemPrompt, userContent)`. - **Common types** ([common](src/main/java/de/neitzel/roleplay/common/)) – `TurnContext`, `StateUpdateResponse`, `SituationSnapshot`, `CharacterSet`, `CharacterSnapshot`, `UserAction`, `Recommendation`, `SituationUpdate`, `CharacterUpdate`, `OpenThreadsChanges`, `CharacterResponse`, `Suggestion` (with `SuggestionType`, `RiskLevel`). - **API models** (`de.neitzel.roleplay.fascade.model`) – generated from OpenAPI: `SessionResponse`, `TurnResponse`, `SituationState`, `CharacterState`, `Suggestion`, `CharacterResponseItem`, etc. - **Prompts** – defined in [ROLEPLAY_CONCEPT.md](docs/ROLEPLAY_CONCEPT.md) (§4.3 for init, §4.4 for turn). Should be extracted to constants or a small prompt provider (e.g. in `business` or `common`) so they stay consistent and maintainable. --- ## Task 1: createSession – Opening scene and initial state ### Flow 1. Create `SessionResponse` with sessionId, model, language, safetyLevel, turnNumber (0) – **unchanged**. 2. Build initial **situation** and **characters** from `request.getScenario()` (reuse existing `buildSituationFromScenario` / `buildCharactersFromScenario`). 3. Build **TurnContext** for session init: - **currentSituation**: Map from `SituationState` (API) to `SituationSnapshot` (common). Initial values: setting, currentScene from scenario; timeline/openThreads/externalPressures/worldStateFlags can be empty or minimal. - **characters**: Map from `CharacterState` list to `CharacterSet` (userCharacter + aiCharacters). Map each to `CharacterSnapshot` (id, name, role, personalityTraits, speakingStyle, goals from scenario; currentMood/status/knowledge/relationships/recentActionsSummary can be null or default). - **userAction**: `null` (no user action at init). - **recommendation**: `null`. - **recentHistorySummary**: `null` or `""`. 4. Serialise `TurnContext` to JSON (snake_case) via Jackson `ObjectMapper`. 5. **Call 1** – `ollamaClient.generateNarrative(model, INIT_SYSTEM_PROMPT, contextJson)`. 6. **Call 2** – `ollamaClient.generateStateUpdate(model, STATE_EXTRACT_SYSTEM_PROMPT, contextJson + "\n\nNarrative that was just generated:\n" + narrative)`. 7. Set **session** from Call 1 + Call 2: - `session.setNarrative(narrative)` (from Call 1). - Apply **StateUpdateResponse** to session: - **updated_situation** → merge into `session.getSituation()` (or build new `SituationState`): currentScene, append newTimelineEntries to timeline, apply openThreadsChanges (add/remove from openThreads), set worldStateFlags. - **updated_characters** → merge into `session.getCharacters()` by characterId: update currentMood, append knowledgeGained to knowledge, apply relationshipChanges to relationships; keep id/name/role/isUserCharacter from existing list. - **suggestions** → map common `Suggestion` list to API `Suggestion` list and `session.setSuggestions(...)`. 8. Store session and return. ### Design choices - **Context builder**: Introduce a small helper (or service) to build `TurnContext` from scenario + optional session state, and to serialise it. Keeps `InMemorySessionService` focused on orchestration. - **Situation/character mapping**: Reuse or introduce mappers between API models (`SituationState`, `CharacterState`) and common types (`SituationSnapshot`, `CharacterSnapshot`) so both createSession and submitTurn can share the same logic. - **Error handling**: If Ollama fails (network, parse), decide: throw and fail session creation, or fall back to placeholder narrative and log. Prefer throwing and letting the resource layer return 5xx, with a clear message. --- ## Task 2: submitTurn – Turn narrative and state update ### Flow 1. Load session; if missing return `Optional.empty()` – **unchanged**. 2. Increment turn number – **unchanged**. 3. Build **TurnContext** for this turn: - **currentSituation**: From `session.getSituation()` → `SituationSnapshot` (setting, currentScene, timeline, openThreads, externalPressures, worldStateFlags). - **characters**: From `session.getCharacters()` → `CharacterSet` (userCharacter + aiCharacters as `CharacterSnapshot` list). - **userAction**: From `turnRequest.getUserAction()` → map API `UserActionRequest` to common `UserAction` (type, content, selectedSuggestionId). - **recommendation**: From `turnRequest.getRecommendation()` → map to common `Recommendation` (desiredTone, preferredDirection, focusCharacters), or null. - **recentHistorySummary**: For now empty string or a short placeholder; later can be derived from stored turn history if added. 4. Serialise `TurnContext` to JSON. 5. **Call 1** – `ollamaClient.generateNarrative(model, TURN_NARRATIVE_SYSTEM_PROMPT, contextJson)`. 6. **Call 2** – `ollamaClient.generateStateUpdate(model, STATE_EXTRACT_SYSTEM_PROMPT, contextJson + "\n\nNarrative that was just generated:\n" + narrative)`. 7. Build **TurnResponse**: - turnNumber, narrative (from Call 1). - characterResponses, updatedSituation, updatedCharacters, suggestions from `StateUpdateResponse` (mapped to API model types). 8. **Merge** state update into session (same as createSession): apply updated_situation to `session.getSituation()`, updated_characters to `session.getCharacters()`, replace `session.setSuggestions(...)`, set `session.setNarrative(narrative)`, `session.setTurnNumber(nextTurn)`. 9. Save session and return `Optional.of(turnResponse)`. ### Design choices - **Shared merge logic**: Extract “apply StateUpdateResponse to SessionResponse” into a private method (or small helper) used by both createSession and submitTurn. - **TurnResponse mapping**: Map `StateUpdateResponse` (common) to `TurnResponse` (API): responses → characterResponses, updatedSituation, updatedCharacters, suggestions. API uses same structure; ensure enum/value names match (e.g. speech/action/reaction, risk_level). --- ## Shared implementation elements ### 1. Prompts - **INIT_SYSTEM_PROMPT** (opening narrative) – from concept §4.3 Call 1. - **STATE_EXTRACT_SYSTEM_PROMPT** (JSON extraction) – from concept §4.3 Call 2 (same for init and turn). - **TURN_NARRATIVE_SYSTEM_PROMPT** (turn continuation) – from concept §4.4 Call 1. Store as constants in a single class (e.g. `OllamaPrompts` in `business`) or in a configurable provider. ### 2. Context building - **Session init**: `TurnContext` from scenario-derived situation + characters; no userAction/recommendation. - **Turn**: `TurnContext` from current session situation + characters + userAction + recommendation. Both need **API → common** mapping for situation and characters when building the context. A dedicated mapper or builder class keeps the service clean. ### 3. State merge (StateUpdateResponse → SessionResponse) - **Situation**: Apply `SituationUpdate`: set currentScene if present; append newTimelineEntries to timeline; apply openThreadsChanges (add/remove from openThreads); replace or merge worldStateFlags. - **Characters**: For each `CharacterUpdate` find character by characterId; update currentMood; append knowledgeGained to knowledge; merge relationshipChanges into relationships. - **Suggestions**: Replace session suggestions with the new list (mapped from common to API). ### 4. API ↔ common mapping - **Common** uses snake_case (Jackson `@JsonNaming`); **API** models are OpenAPI-generated (camelCase). When mapping StateUpdateResponse (common) into SessionResponse / TurnResponse (API), copy fields and convert enums where needed (e.g. ResponseType, SuggestionType, RiskLevel). - **TurnContext** is always built from common types and serialised to JSON as-is for Ollama. --- ## Suggested order of implementation 1. **Prompts** – Add `OllamaPrompts` (or similar) with the three system prompt strings from the concept doc. 2. **Mappers** – Add mapping from API `SituationState`/`CharacterState` (and scenario) to common `SituationSnapshot`/`CharacterSet`/`CharacterSnapshot`; and from common `StateUpdateResponse`/`Suggestion`/etc. to API types for SessionResponse/TurnResponse. Optionally a dedicated “context builder” that takes scenario or session + turnRequest and returns `TurnContext`. 3. **State merge** – Implement “apply StateUpdateResponse to SessionResponse” (situation, characters, suggestions) in one place. 4. **createSession** – Inject `OllamaClient` and `ObjectMapper`; build context from scenario; call two-step Ollama; merge state; set narrative/situation/characters/suggestions on session. 5. **submitTurn** – Build context from session + turnRequest; call two-step Ollama; build TurnResponse; merge state into session; return TurnResponse. 6. **Tests** – Update or add unit tests: mock `OllamaClient` in `InMemorySessionServiceTest` to verify two-call pattern and state merge; optionally integration test with a real or stubbed Ollama. --- ## Risks and mitigations | Risk | Mitigation | |------|------------| | Ollama slow/unavailable | Timeouts already set in rest-client config; fail fast and return 502/503 from resource layer. | | Model returns invalid JSON (Call 2) | Already throws `OllamaParseException`; map to 502 or 503 in a mapper if not already. | | Large context / token limit | Keep recentHistorySummary short; later add condensing of older turns. | | Enum mismatches (API vs common) | Use same enum names in OpenAPI as in common (e.g. speech, action, reaction); document mapping in one place. | --- ## Files to touch (summary) | File / area | Change | |------------|--------| | New: e.g. `business/OllamaPrompts.java` | System prompt constants for init narrative, turn narrative, state extraction. | | New or existing: mappers / context builder | Build `TurnContext` from scenario or session + turnRequest; map StateUpdateResponse → SessionResponse / TurnResponse. | | [InMemorySessionService](src/main/java/de/neitzel/roleplay/business/InMemorySessionService.java) | Inject `OllamaClient`, `ObjectMapper`; implement createSession and submitTurn with two-call pattern and state merge; remove TODOs and placeholder narratives. | | [InMemorySessionServiceTest](src/test/java/de/neitzel/roleplay/business/InMemorySessionServiceTest.java) | Add or adjust tests with mocked OllamaClient to verify createSession and submitTurn call Ollama and merge state. | | Optional: exception mapper | Map `OllamaParseException` (and network errors) to a suitable HTTP response if not already. | --- ## Done criteria - **createSession**: Opening narrative and initial situation/characters/suggestions come from Ollama (Call 1 + Call 2); no placeholder text. - **submitTurn**: Turn narrative and updated situation/characters/suggestions come from Ollama; session state is updated; TurnResponse contains narrative and structured state (characterResponses, updatedSituation, updatedCharacters, suggestions). - Unit tests cover the two-call flow with mocked OllamaClient and assert on state merge. - No remaining TODOs in InMemorySessionService for Ollama integration.