# How Project N.O.M.A.D. Manages AI Chat Sessions with Ollama: Architecture and Implementation

> Discover how Project N.O.M.A.D. manages AI chat sessions with Ollama using a three-layer architecture. Learn about database models, service layer logic, and HTTP controllers for seamless RAG and streaming.

- Repository: [Crosstalk Solutions/project-nomad](https://github.com/Crosstalk-Solutions/project-nomad)
- Tags: architecture
- Published: 2026-03-16

---

**Project N.O.M.A.D. manages AI chat sessions with Ollama through a three-layer architecture comprising database models for persistence, a service layer for business logic, and HTTP controllers that handle streaming responses, RAG context injection, and automatic title generation.**

Project N.O.M.A.D. (Network Operations Management and Development) is an open-source platform that orchestrates local LLM interactions through Ollama. The system implements a robust chat session management architecture that persists conversations, supports real-time streaming, and optionally enriches queries with retrieval-augmented generation (RAG) context. This article examines the complete lifecycle of AI chat sessions in Project N.O.M.A.D., from database schema to streaming implementation.

## Three-Layer Architecture for Chat Management

Project N.O.M.A.D. organizes its chat functionality into distinct layers that separate data persistence, business logic, and HTTP handling.

### Database Models

The persistence layer uses two core models defined in the `admin/app/models/` directory:

- **`ChatSession`** ([`admin/app/models/chat_session.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/models/chat_session.ts)): Stores session metadata including title, model name, and timestamps.
- **`ChatMessage`** ([`admin/app/models/chat_message.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/models/chat_message.ts)): Stores individual messages with a `session_id` foreign key, role (user/assistant), content, and timestamps.

These models establish a one-to-many relationship where each session contains multiple messages, enabling full conversation history retrieval.

### Service Layer

The **`ChatService`** ([`admin/app/services/chat_service.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/services/chat_service.ts)) encapsulates all business logic for session management. Key methods include:

- **`createSession`** (lines 31-38): Initializes a new chat session and returns its UUID.
- **`addMessage`**: Persists user and assistant messages to the database.
- **`generateTitle`**: Automatically creates session titles using the `DEFAULT_QUERY_REWRITE_MODEL` defined in [`constants/ollama.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/constants/ollama.ts), falling back to message truncation if the model is unavailable.
- **`getAllSessions`** (lines 14-24): Retrieves all sessions with eager-loaded messages.
- **`deleteSession`** and **`deleteAllSessions`** (lines 76-88): Handle cleanup of individual or all sessions.

The service layer communicates with Ollama through **`OllamaService`** ([`admin/app/services/ollama_service.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/services/ollama_service.ts)), which wraps the `ollama` NPM client and provides methods like `chat`, `chatStream`, and `checkModelHasThinking`.

### Controller and Routes

The **`OllamaController`** ([`admin/app/controllers/ollama_controller.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/controllers/ollama_controller.ts)) serves as the HTTP entry point for chat operations. Routes are registered in [`admin/start/routes.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/start/routes.ts) under the `/api/ollama` prefix, including:

- `POST /api/chat/sessions`: Creates new sessions via `ChatsController.store`.
- `GET /api/chat/sessions`: Lists all sessions.
- `GET /api/chat/sessions/:id`: Retrieves specific session history.
- `DELETE /api/chat/sessions/all`: Admin endpoint for bulk deletion.
- `POST /api/ollama/chat`: Primary chat endpoint handling both streaming and non-streaming requests.

## Complete Session Lifecycle

Understanding how Project N.O.M.A.D. manages AI chat sessions requires examining the full lifecycle from creation to cleanup.

### Creating a New Session

When a user initiates a conversation, the frontend sends a `POST` request to `/api/chat/sessions`. The `ChatService.createSession` method inserts a new row into the database and returns a UUID that subsequent requests use to maintain conversation context.

```typescript
// POST /api/chat/sessions
await fetch('/api/chat/sessions', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'My New Chat' })
})
  .then(r => r.json())
  .then(data => console.log('Session ID:', data.id));

```

*Implementation reference:* `ChatService.createSession` in [`admin/app/services/chat_service.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/services/chat_service.ts) (lines 31-38).

### Streaming AI Responses

For real-time interactions, Project N.O.M.A.D. implements Server-Sent Events (SSE). When the controller receives a request with `stream: true`, it sets appropriate SSE headers and calls `OllamaService.chatStream`. The controller writes each chunk in the format `data: <json>\n\n` directly to the client while accumulating the full response in memory.

```typescript
const payload = {
  model: 'llama3',
  messages: [{ role: 'user', content: 'Explain quantum tunneling' }],
  stream: true,
  sessionId: 12   // optional, to persist the dialogue
};

const evtSource = new EventSource('/api/ollama/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload)
});

evtSource.onmessage = ev => {
  const chunk = JSON.parse(ev.data);
  if (chunk.message?.content) {
    console.log('Chunk:', chunk.message.content);
  }
};

```

*Implementation reference:* `OllamaController.chat` streaming branch in [`admin/app/controllers/ollama_controller.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/controllers/ollama_controller.ts) (lines 24-35 and 29-35).

### Persisting Messages and Context

The session management system maintains conversation continuity through automatic persistence:

1. **User message storage**: When `OllamaController.chat` receives a request containing `sessionId`, it immediately stores the incoming user message via `ChatService.addMessage`.
2. **Assistant response accumulation**: During streaming, the controller aggregates response chunks. After the stream completes, it persists the full assistant reply using `ChatService.addMessage`.
3. **Context window management**: For subsequent turns, the controller retrieves message history from the database to provide the Ollama model with full conversation context.

### Automatic Title Generation

Project N.O.M.A.D. automatically generates meaningful session titles when conversations begin. If a session contains two or fewer messages, `ChatService.generateTitle` executes a lightweight title-generation prompt using the default query rewrite model. If that model is unavailable, the system falls back to truncating the user's first message to create the title.

## Advanced Chat Features

Beyond basic CRUD operations, Project N.O.M.A.D. implements sophisticated features for enhanced AI interactions.

### Retrieval-Augmented Generation (RAG)

The chat controller optionally enriches prompts with relevant context from a document store. When query rewriting produces a non-empty string, the controller:

1. Calls `RagService.searchSimilarDocuments` to fetch relevant documents.
2. Calculates token budgets using `getContextLimitsForModel` based on the specific model's context window.
3. Injects a system message containing the selected context before forwarding the request to Ollama.

This implementation allows the LLM to reference private knowledge bases while maintaining the conversational flow.

### Model Thinking Capabilities

For models that support chain-of-thought reasoning, Project N.O.M.A.D. detects thinking capabilities through `OllamaService.checkModelHasThinking`. When enabled, the controller passes a `think` parameter (or `'medium'` specifically for `gpt-oss` models) to activate internal reasoning traces, providing users with insight into the model's decision-making process.

### Session Management API

Administrators and users can manage conversation history through dedicated endpoints:

**Retrieving all sessions:**

```typescript
fetch('/api/chat/sessions')
  .then(r => r.json())
  .then(sessions => console.table(sessions));

```

*Implementation reference:* `ChatService.getAllSessions` in [`admin/app/services/chat_service.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/services/chat_service.ts) (lines 14-24).

**Deleting all sessions:**

```typescript
await fetch('/api/chat/sessions/all', { method: 'DELETE' });

```

*Implementation reference:* `ChatService.deleteAllSessions` in [`admin/app/services/chat_service.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/services/chat_service.ts) (lines 76-88).

## Summary

- **Three-layer architecture**: Database models (`ChatSession`, `ChatMessage`), service layer (`ChatService`, `OllamaService`), and HTTP controllers (`OllamaController`) work together to manage AI chat sessions with Ollama.
- **Streaming implementation**: Server-Sent Events provide real-time token streaming with proper SSE formatting (`data: <json>\n\n`) and automatic message persistence after stream completion.
- **RAG integration**: The system optionally injects retrieved document context into system messages, respecting model-specific token limits via `getContextLimitsForModel`.
- **Automatic metadata**: Sessions receive AI-generated titles using `ChatService.generateTitle` when conversations begin, falling back to message truncation if the title generation model is unavailable.
- **Full lifecycle support**: From creation via `createSession` to cleanup via `deleteAllSessions`, the platform maintains conversation history with foreign-key relationships between sessions and messages.

## Frequently Asked Questions

### How does Project N.O.M.A.D. persist chat history?

Project N.O.M.A.D. persists chat history using Lucid ORM models defined in [`admin/app/models/chat_session.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/models/chat_session.ts) and [`admin/app/models/chat_message.ts`](https://github.com/Crosstalk-Solutions/project-nomad/blob/main/admin/app/models/chat_message.ts). The `ChatSession` model stores session metadata, while `ChatMessage` stores individual messages with a foreign key relationship to their parent session. The `ChatService.addMessage` method handles the actual database insertion for both user and assistant messages.

### What is the difference between streaming and non-streaming chat requests?

Streaming requests set `stream: true` in the payload, causing `OllamaController.chat` to invoke `OllamaService.chatStream` and return Server-Sent Events with headers appropriate for SSE. Non-streaming requests use `OllamaService.chat`, which waits for the complete response before returning. Both modes persist the final assistant message to the database, but only streaming provides real-time token delivery to the client.

### How does the system handle context limits when using RAG?

When RAG is enabled, the controller calls `getContextLimitsForModel` to determine the maximum token budget based on the specific Ollama model being used. It then fetches relevant documents via `RagService.searchSimilarDocuments` and truncates or selects content to fit within the remaining context window after accounting for the conversation history and system prompts.

### Can different chat sessions use different Ollama models?

Yes. The `ChatSession` model includes a `model` field that stores the specific Ollama model name for that session. When `OllamaController.chat` processes requests, it uses the model specified in the session record (or the request payload), allowing different conversations to run simultaneously with different models (e.g., `llama3` for one session, `mistral` for another).