### 1. Big picture: what “project chat” is

Project chat, implemented via `ConversationModeService` and the `conversation_mode` router, is the general-purpose “research agent” sitting over your projects and artifacts. It has two modes:

- **`PORTFOLIO_CHAT`**: a conversational interface over one or more projects (and often a whole portfolio), with access to project metadata, reports, files, MCP tools, and visualization components.
- **`ARTIFACT`**: a conversational interface that can generate or update structured artifacts (the same pipeline you use for pptx slide decks), but now invoked as a tool within a broader conversation.

The router is thin and purely about HTTP + SSE + tracing. The service does all the real work: context resolution, prompt and model configuration, tool surface construction, MCP setup, history building, streaming orchestration with Anthropic, tool execution, and persistence.

The rest of this document drills into each of those areas in detail, then explains how the conversation path hands off into the artifact pipeline.

---

### 2. Context resolution: `resolve_conversation_context`

The **core grounding step** for project chat is `resolve_conversation_context`. Everything that follows (tools, prompts, MCP, history) depends on this context object.

Given a `conversation_id` and a `mode` (`PORTFOLIO_CHAT` or `ARTIFACT`), it:

1. **Loads the `conversation_summary` row**  
   It calls `ConversationSummaryRepository.get({"id": conversation_id})`. Failure here is a hard error: `Conversation {id} not found`.

2. **Resolves project type & template mapping**  
   - Reads `project_type_id` from the conversation.
   - Reads `task_id` (if present). When both `task_id` and `project_type_id` exist, it:
     - Uses `TaskWorkflowTemplateRepository.get_by_task_id_and_project_type_id` to fetch workflow templates for that task + project type.
     - Filters those to find:
       - **Artifact templates** (`TemplateType.ARTIFACT`) and chooses the default/highest priority one → `resolved_template_id`.
       - **Chat templates** (`TemplateType.CHAT`) and chooses the default/highest priority one → `resolved_chat_template_id`.
     - Consults `TaskProjectTypeRepository.get_by_task_id_and_project_type_id` to resolve the **widget template** → `resolved_widget_template_id`.

   If it fails to resolve a chat template ID, it raises `Conversation missing chat template`, because chat behavior is always template-driven.

3. **Resolves artifact linkage**  
   - Looks in `conversation.conversation_metadata["artifact_id"]`. If present and parseable as UUID → `resolved_artifact_id`.
   - If not found, queries `ArtifactRepository.get_by_condition` for any artifact records tied to this conversation (sorted by version desc, limit 1); if present, takes their `artifact_id`.  
   This gives project chat a stable view of “which artifact is associated with this conversation”, allowing it to continue or update artifacts over time.

4. **Resolves file IDs**  
   - Reads `conversation.file_ids`. This may be a JSON-like string or a list.
   - Safely evals the string or copies the list into `resolved_file_ids`.  
   These IDs are later used by `_get_files_content` and, more importantly, passed into prompts so the model understands which assets it can draw from.

5. **Resolves project IDs**  
   - Reads `conversation.project_ids` (again string or list) into `project_ids`.  
   For **portfolio chat** this is the set of projects over which analysis should run. For **artifact** mode, it’s the project(s) that own the artifact.

6. **Authorization and scoping rules**  
   - For `PORTFOLIO_CHAT`: it enforces that `conversation.user_id == current_user_id`. This is a user-owned conversation.
   - For `ARTIFACT` (and similar project-level conversations): it enforces that the current user has access to every project ID in `project_ids` via `ProjectUserService.check_user_exists_in_project`.  
   Any violation yields an `Access Restricted` error.

7. **Title and conversation type**  
   - Reads `title` and sets `has_title` boolean.
   - Reads `conversation_type` (defaults to `PORTFOLIO_CHAT` if absent).

8. **Active vs available tools from metadata**  
   - Reads `conversation_metadata["active_tools"]` and `["available_tools"]` (JSON strings) and parses them, defaulting to empty lists on failure.  
   These fields let you toggle tools on/off at runtime without touching backend code.

Finally, it constructs a **`ConversationContext`**:

- `conversation_id`, `template_id`, `chat_template_id`, `widget_template_id`, `artifact_id`.
- `mode`, `conversation_type`.
- `file_ids`, `project_ids`, `project_type_id`.
- `has_title`.
- `active_tools`, `available_tools`.

This `_context` hangs on the service and is reused by template loading, tool building, model configuration, and history construction.

---

### 3. Template and knowledge configuration

Once context is known, `ConversationModeService` loads a **template configuration** via `_load_template_config`, cached with `async_ttl_cache`.

This config merges **artifact template** and **chat template** information into a single structure:

1. **Artifact template portion (if `template_id` is present)**  
   It queries `ArtifactTemplateRepository.get_by_id_and_tenant`. If the template is active, it builds:

   - `artifact_template.Tools` – list of artifact-related tool names.
   - `artifact_template.Tool Instructions` – text that tells the model how to use those tools.
   - `artifact_template.Tool Input Guidance` – how to fill tool argument fields.
   - `artifact_template.Knowledge` – resolved content for each knowledge prompt name in `template.knowledge`, loaded via `get_prompt_content`.
   - `artifact_template.Recommended_Sections` – from `template.mandatory_sections`, used by the artifacts pipeline’s Stage 0.5 planner.

   This structure is basically the **artifact pipeline config**, embedded into conversation mode so the “generate_artifact” tool can be wired correctly.

2. **Chat template portion (if `chat_template_id` is present)**  
   It queries `ChatTemplateRepository.get_by_id_and_tenant`. If active, it:

   - Resolves chat template knowledge keys (`chat_template.knowledge`) via `get_prompt_content`.
   - Stores:
     - `chat_template.skills` – skill IDs (used for capability selection).
     - `chat_template.llm_metadata` – provider/model overrides, etc.
     - `chat_template.prompt_config` – named prompts, including keys like `PROJECT_CHAT_SYSTEM_PROMPT_KEY` and `PROJECT_CHAT_ARTIFACT_SYSTEM_PROMPT_KEY` mapped to actual prompt names.

   Then it sets **top-level template config**:

   - `Tools` – the tools defined on the chat template.
   - `Knowledge` – resolved chat-level knowledge prompts.

   This is the **primary tool/knowledge surface** for project chat; artifact template config is nested under `artifact_template` and used only when artifact tools are invoked.

3. **Prompt config and knowledge caching**  
   The chat template’s `prompt_config` is stored in `_prompt_config` and later used by `_load_prompt` to map logical keys (`PROJECT_CHAT_SYSTEM_PROMPT_KEY`, etc.) to concrete prompt names in the config service. Knowledge content remains cached in `_template_config` and is also integrated into prompts and tool instructions.

The pattern here is consistent with grid builder: **everything is template-driven**. Tools, skills, system prompts, and knowledge are all derived from template configuration rather than hard-coded into the service.

---

### 4. Prompt strategy

Project chat uses **layered prompts**, each with distinct responsibility:

- **Project chat system prompt** (`PROJECT_CHAT_SYSTEM_PROMPT_KEY` or `PROJECT_TYPE_CHAT_SYSTEM_PROMPT_KEY`)  
  Governs general conversational behavior over projects/portfolios:
  - How to interpret user questions.
  - How to use project summaries, files, reports, and tools.
  - How to reason and respond.

- **Artifact system prompt** (`PROJECT_CHAT_ARTIFACT_SYSTEM_PROMPT_KEY` or `PROJECT_TYPE_CHAT_ARTIFACT_SYSTEM_PROMPT_KEY`)  
  Governs how the model should behave when generating or updating artifacts (aligning with the artifacts pipeline you already have). It defines:
  - What an artifact is.
  - How to structure sections, attributes, and metadata.
  - How to interact with the “generate_artifact” tool and its outputs.

- **Micro-component system prompt** (`MICRO_COMPONENT_SYSTEM_PROMPT_KEY`)  
  Used when the conversation needs to generate smaller visual components or display elements (e.g., micro-charts, metric cards) outside of full artifacts.

- **Component selector prompt** (`SKILLS_COMPONENT_SELECTOR_PROMPT`)  
  Used internally when deciding which visualization components to use for a given analysis, tightly coupled with the component registry.

These prompts are loaded via `_load_system_prompt_tasks`:

- It returns a list of coroutines that:
  - Load the project chat system prompt.
  - Load the artifact system prompt.
  - Load the micro-component prompt.
  - Load the component registry (`get_registry_from_config(tenant_id, used_for="portfolio_chat")`).

The service then gathers these in parallel before building the Anthropic call, giving it:

- A **portfolio chat persona**.
- An **artifact generation persona**.
- A **micro-component persona**.
- The **component catalog** used for visualization tools.

`_load_prompt` itself is flexible:

- If called with a `prompt_key`, it first looks in `_prompt_config` for a mapping to a concrete prompt name.
- If no mapping is found, it uses the key directly as the prompt name.
- It then calls `get_prompt_content(prompt_name, tenant_id)` to get the latest content.

This lets you:

- Map different logical behaviors to different prompt content per tenant or per template.
- Override system prompts at the template level without changing code.

---

### 5. Tool surface for project chat

The tool surface is defined primarily in `conversation_tools.py` and wired into the service via:

```python
from app.services.conversation_mode_service_helper.conversation_tools import (
    ARTIFACT_TOOL,
    build_simple_create_component_tool,
    SET_TITLE_TOOL,
    PROJECT_SUMMARY_TOOL,
    build_mcp_tool,
    execute_artifact_tool,
    execute_micro_component_tool,
    execute_set_title_tool,
    execute_project_summary_tool,
    project_summary_fetch,
    _get_all_projects,
    get_user_info,
    execute_plan_component_tool,
)
```

At a high level, tools fall into these categories:

#### Artifact tool: `generate_artifact`

- **Definition**: `ARTIFACT_TOOL` (in `conversation_tools.py`).
  - Name: `"generate_artifact"`.
  - Input schema: `{"intent": string, "title": string?}`.
- **Execution**: `execute_artifact_tool`.
  - Creates and drives an `ArtifactAgent` (from the template artifacts pipeline).
  - Uses artifact template config, Stage 0.5 planning, Stage 1 section generation, Stage 2 visualization where appropriate.
  - Emits `ArtifactStartEvent`, `ArtifactSectionEvent`, `ArtifactDoneEvent` SSE events back into the project chat stream.

This is the main **handoff point** from project chat into the artifact pipeline: the conversation agent calls `generate_artifact` when the user’s intent is “produce/update an analysis slide deck,” and `execute_artifact_tool` owns the multi-stage pipeline we documented before.

#### Project summary and project-scoped tools

- `PROJECT_SUMMARY_TOOL`:
  - Name: `"get_project_summaries"`.
  - Input: `project_ids: [string UUID]`.
  - Semantics: fetches ontology, files, reports for one or more projects.  
  Execution goes through `execute_project_summary_tool` and/or `project_summary_fetch`, which combine repository calls into a consolidated multi-project summary. This is the **primary data retrieval** tool for portfolio questions.

- `_get_all_projects`, `get_user_info`:
  - Support tools for fetching lists of projects or user metadata; used by the main agent to understand context or personalize responses.

#### Visualization / component tools

- `build_simple_create_component_tool(component_registry)`:
  - Returns a tool definition named `"create_component"` whose `tool_type` `enum` is populated from the component registry.
  - Description is auto-generated from the registry’s business/technical guidance.
  - Input: `"transformation_instructions"` + `"tool_type"`.
  - Execution: `execute_micro_component_tool` or `execute_plan_component_tool`, depending on the flow.  
  These tools let the agent **turn text-level insights into structured viz components** suitable for embedding into dashboards or slides.

#### MCP tool wrapper

- `build_mcp_tool(allowed_tools, ragnarok_headers)`:
  - Returns a **meta-definition** for an MCP server, not a single function.
  - Specifies:
    - `type: "mcp"`.
    - `server_label`, `server_url` (typically `config_obj.MCP_SERVER_URL + "/mcp/sse"`).
    - `allowed_tools`: the MCP tools this server exposes for the template.
    - `headers`: with `x-api-key` from tenant-specific Ragnarok config.

The conversation service uses template config (`Tools`, artifact template `Tools`) plus tenant’s MCP server config to build a complete MCP tool surface that the LLM can use inline. All these tool definitions are supplied to the Anthropic Responses API as part of `tools` and `tool_choice` configuration.

---

### 6. MCP context and instructions

For MCP, project chat mirrors the grid builder design:

- `_create_mcp_context` (not fully shown in the snippet but analogous to the ArtifactAgent’s version) uses `mcp_client_service.create_context` with:
  - `tenant_id`
  - `project_ids`
  - and potentially `file_ids`
  - TTL and scope `"session"`
- `_get_mcp_instructions` (in this service) then builds a text snippet instructing the model to:
  - Always pass the exact `tenant_id`, `project_ids`, and `context_id` into tools that require them.
  - Treat `context_id` as the key for retrieving project-specific documents, reports, and other assets.

These instructions are appended to the system prompt and ensure:

- Correct scoping: no “guessing” of IDs.
- Consistency with artifacts and grid builder MCP usage.
- Safe multi-tenancy at the tool layer.

---

### 7. History building and request enrichment

Before calling the model, project chat needs a full **message history** for the conversation:

- `_build_conversation_history(prompt: str | None)`:
  - Tries Redis: `conversation_cache_service.get_conversation_history`.
  - If cache miss: loads up to 500 records from `ConversationHistoryRepository.get_with_conditions` (by `conversation_id`, ascending).
  - Converts ORM-like results to JSON with `make_json_serializable`.
  - Populates the cache with this history.

It coalesces each record into an OpenAI/Anthropic-style message list:

- If `record["input"]` is present:
  - Skips the record if there was **no output** and no `detailed_output`, to avoid consecutive user messages that the API rejects.
  - Otherwise, appends a `"user"` message with the input content.

- If `record["detailed_output"]`:
  - Passes it through `_process_detailed_output` (delegating to `app.utils.detailed_output_replay.process_detailed_output`).
  - That utility reconstructs synthetic messages representing past tool calls, reasoning segments, artifact outputs, etc.
  - These are appended to the message list to give the model a **rich past trace** instead of a blob of text.

- Else if only `record["output"]` (plain text):
  - Appends an `"assistant"` message with that text.

Then, if the current request has a `prompt`, it optionally enriches it with project metadata via `format_user_query_with_project_metadata` and appends a final `"user"` message with that enriched prompt.

This gives the model:

- A **structured, replayable history** matching the tool and artifact semantics you’ve streamed before.
- A current user message that already embeds key project context (e.g., project name, sector, etc.), improving quality and reducing repetition.

---

### 8. Streaming execution and SSE mapping

The router and service collaborate to provide a full **SSE streaming experience**.

#### Router layer

`process_conversation` (router):

- Validates auth & roles.
- Restores trace parent from `conversation_metadata["trace_context"]`.
- Starts a span `"conversation.message"`.
- Creates `ConversationModeService`.
- Writes a `CONNECTED` event immediately.
- Starts:
  - A consumer coroutine that iterates `service.process_conversation(request)` and pushes events into a queue.
  - A ping coroutine that injects a `PROCESSING` “keep-alive” event every 15 seconds.
- Reads from the queue, yields each SSE string, and inspects events to detect `COMPLETED` and accumulate latency metrics.
- Cleans up by calling `service.cleanup()`.

#### Service layer

`process_conversation` (service, further down in file):

- Ensures `_context`, `_template_config`, `_system_prompt`, `_project_metadata`, `_component_registry`, `_llm_middleware`, `_ragnarok_headers` are populated.
- Builds a conversation history as above.
- Constructs the final system prompt:
  - Project chat system prompt.
  - Embedded artifact prompt (so the model can call `generate_artifact` correctly).
  - Embedded micro-component prompt.
  - MCP instructions.
  - Knowledge content.
  - Any project metadata or file context.
- Builds an Anthropic Responses API call using `LLMMiddleware`:
  - With the message list.
  - With tool definitions (artifact, summary, MCP, create_component, set_title, etc.).
  - With `response_format` tuned to SSE-like output.

As streaming events come back from Anthropic, the service:

- Distinguishes **protocol events** (message_start, content_block_delta, etc.) from tool events.
- For each, it constructs a corresponding SSE event object:
  - `TextDeltaEvent`, `TextDoneEvent` for text chunks.
  - `ReasoningStartEvent`, `ReasoningDeltaEvent`, `ReasoningDoneEvent` for chain-of-thought (where enabled).
  - `ToolCallStartEvent`, `ToolCallDeltaEvent`, `ToolCallDoneEvent` and `DisplayToolCall*` for tool call arguments.
  - `ToolResultEvent` and `DisplayToolResultEvent` for tool outputs.
  - `MCPToolCallEvent`, `MCPToolResultEvent`, `MCPToolCompleteEvent`, `MCPToolListEvent` for MCP tool integration.
  - `ArtifactStartEvent`, `ArtifactSectionEvent`, `ArtifactDoneEvent` when the artifact tool is running.
  - `FileGeneratedEvent` when tools produce downloadable assets.
  - `ConversationTitleEvent` when the model sets or updates the conversation title.
  - `UsageEvent` for token usage and model stats.
- Serializes each SSE event to `"data: {json}\n\n"` via `.to_sse()` or `create_sse_event`, and yields it back to the router.

For **tool calls**, `process_conversation` uses `_execute_tool_with_tracing` to:

- Wrap the tool executor generator in a span.
- Attach arguments (`gen_ai.tool.call.arguments`) and results (`gen_ai.tool.call.result`) as span attributes.
- Mark the span success or failure based on the tool output.

All intermediate events from the tool execution (e.g., progress updates, partial results, artifact sections) are yielded to the SSE stream as they happen.

---

### 9. Handoff to the artifact pipeline

The handoff path to the artifact pipeline is deliberate and layered:

1. **User expresses intent inside project chat**:  
   e.g., “Create a full competitive landscape analysis deck for Azure vs AWS vs GCP.”

2. **Project chat model decides to call `generate_artifact`**:  
   Based on the system + artifact prompts and the `ARTIFACT_TOOL` definition, the model emits a tool call named `"generate_artifact"` with `intent` and optional `title`.

3. **`execute_artifact_tool` runs**:  
   - It instantiates an `ArtifactAgent` with:
     - `user_id`, `tenant_id`, `conversation_id`
     - `project_ids`
     - `template_id` from `ConversationContext`
     - `files` (from `file_ids`)
     - `model_config` based on project chat’s provider/model
   - It then drives the multi-stage artifacts pipeline:
     - Stage 0.5 planning (`Stage05Plan`).
     - Stage 1 text artifact generation (`SectionsReport`).
     - Stage 2 component selection (if used in that flow).
     - Stage 3 persistence via `ArtifactPersistenceService.save_artifact`.

4. **Artifact SSE events flow back into project chat**:  
   While running, `ArtifactAgent` emits its own SSE-like events (wrapped as `ArtifactStartEvent`, `ArtifactSectionEvent`, `ArtifactDoneEvent` within the conversation mode SSE vocabulary). These events are:

   - Sent out to the client, so the UI can show per-section progress and final artifact metadata.
   - Recorded into `detailed_output` so they can be faithfully replayed into message history on future calls.

5. **Artifact identifiers and metadata update the conversation**:  
   - `ArtifactPersistenceService` stores a new artifact record (or new version) with `artifact_id`, `version`, `sections`, `attributes`, etc.
   - `ConversationSummaryRepository` stores the `artifact_id` in `conversation_metadata`, so future `resolve_conversation_context` calls will bind the conversation back to the same artifact.
   - Future project chat turns can:
     - Update the same artifact (e.g., add sections, revise sections).
     - Or create a new artifact if requested, all within the same conversation if desired.

From the user’s perspective, there is no “mode switch.” They stay in project chat; the system decides when to route their intent into the artifact engine, and the SSE stream seamlessly combines portfolio-level reasoning, tool outputs, and artifact generation progress.

---

### 10. Summary

Project chat is a fully templated, tool-using research agent that:

- **Grounds itself** via `ConversationContext` and strong authorization checks.
- **Loads and merges template configs** for both chat and artifacts, plus knowledge and prompt configuration.
- **Builds a rich tool surface** over projects (summary tools), artifacts (`generate_artifact`), micro-components (`create_component`), and MCP servers, all correctly scoped by tenant and project.
- **Constructs layered prompts** for project chat, artifact behavior, micro-components, and component selection.
- **Maintains a structured, replayable history**, including detailed outputs for tools and artifacts.
- **Streams Anthropic events as a normalized SSE vocabulary**, integrating tool calls, MCP, artifact events, and usage.
- **Hands off cleanly into the artifact pipeline** via the `generate_artifact` tool, then reintegrates its outputs into the same conversation.

This separation of concerns—router vs service, context vs tools vs prompts, project chat vs artifacts—gives you a very flexible architecture: you can evolve templates, prompts, tools, and MCP configuration independently while keeping a single, consistent project chat interface for end users.