> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeus.ttr.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversational Assistant Architecture

> End-to-end technical architecture for Zeus's current web assistant and its plans integration boundary

## Overview

This document explains the **current implementation** of Zeus's conversational assistant in the web app. It is intended for engineers who need to understand how the assistant is wired together today, how to replicate it, and where the boundary sits between:

* LLM-generated behavior
* deterministic application code
* the separate plans system

This page describes the code that currently powers:

* the user-facing chat page at `/chat`
* the admin chat manager that can inject an assistant reply into a user thread
* in-chat CRUD for self-managed assets and manual transactions
* in-chat goal creation
* structured assistant UI such as smart actions, quick forms, asset pickers, and welcome cards

This page does **not** describe the target-state product vision from the plans BRD. It describes the implementation that exists now.

## What The Assistant Is Today

At a high level, the assistant is a **Next.js + AI SDK + ToolLoopAgent** system with four major responsibilities:

1. Persist user and assistant messages into `chat_threads` and `chat_messages`.
2. Send the latest chat context into a configured model provider.
3. Let the model call a bounded set of typed tools.
4. Convert tool outputs into deterministic structured UI specs that the frontend renders and turns back into follow-up chat messages.

### Primary Surfaces

| Surface            | Purpose                         | Current capability                                                                               |
| ------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------ |
| `/chat`            | Main assistant UI for end users | Inspect synced data, mutate manual transactions/assets, create goals, explain suggestions        |
| `/api/chat`        | Assistant API                   | Persist messages, load context, run the agent, stream output                                     |
| `/api/chat/thread` | Thread bootstrap                | Create a new chat thread                                                                         |
| Admin chat manager | Support/operator surface        | Review any thread and trigger an assistant reply without persisting an admin-visible user prompt |

### Current Responsibility Split

| Area          | Current assistant responsibility                                                   | Not handled by the assistant directly                                                   |
| ------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Transactions  | Read synced data, create/update/delete manual transactions                         | Mutating synced SePay or Plaid transactions                                             |
| Assets        | Read synced/manual assets, create/update/delete manual assets                      | Mutating synced linked assets                                                           |
| Goals         | Read goals, create a new financial goal in chat                                    | Advanced goal planning math                                                             |
| Monthly plans | Explain related goals, suggestions, and next steps; route users into plan surfaces | Create, activate, update, dismiss, or rebalance `monthly_plans` through assistant tools |
| Suggestions   | Read active suggestions and explain them                                           | Generate suggestions through the chat model itself                                      |

## System Map

```mermaid theme={null}
flowchart LR
    User["User in /chat"] --> Client["assistant-chat-page-client.tsx"]
    Client --> Transport["AI SDK useChat + DefaultChatTransport"]
    Transport --> ChatApi["/api/chat"]
    ChatApi --> ChatStore["chat_threads + chat_messages"]
    ChatApi --> Agent["createTransactionAssistant()"]
    Agent --> Model["Configured LLM provider"]
    Agent --> Tools["Typed tools in transaction-agent.ts"]
    Tools --> Services["App services<br/>bankhub / budget-goal / suggestions"]
    Agent --> Stream["AI SDK UI message stream"]
    Stream --> ResponseParts["buildAssistantResponseDataParts()"]
    ResponseParts --> Spec["JSON Render spec"]
    Spec --> Renderer["AssistantStructuredUi"]
    Renderer --> Events["Browser CustomEvent handlers"]
    Events --> Client
```

## End-To-End Lifecycle

### 1. Thread Creation

The frontend creates a thread by calling `POST /api/chat/thread`.

* Route: `apps/web/app/api/chat/thread/route.ts`
* Service: `createChatThread()` in `apps/web/lib/chat/service.ts`
* Persistence target: `chat_threads`

This step is fully deterministic. No model call happens here.

### 2. Frontend Message Submission

The main chat page lives in `apps/web/components/assistant-chat-page-client.tsx`.

The page uses `useChat` from `@ai-sdk/react` with a `DefaultChatTransport` configured for `/api/chat`.

The transport explicitly preserves the default AI SDK payload fields and adds `threadId`:

| Field       | Source            | Why it matters                                  |
| ----------- | ----------------- | ----------------------------------------------- |
| `id`        | AI SDK transport  | Preserves the chat session/message contract     |
| `messages`  | AI SDK transport  | Carries the UI message list                     |
| `trigger`   | AI SDK transport  | Preserves the SDK trigger context               |
| `messageId` | AI SDK transport  | Preserves message-level tracking when present   |
| `threadId`  | Zeus custom field | Tells the backend which persisted thread to use |

This is important because the backend route expects a valid `threadId`, and the project standard is to preserve the default AI SDK payload rather than replacing it with a partial custom body.

### 3. Backend Request Handling

`POST /api/chat` in `apps/web/app/api/chat/route.ts` performs this sequence:

1. Authenticates the API user.
2. Validates that `messages` and `threadId` exist.
3. Confirms the thread belongs to the user.
4. Finds the latest user message from the submitted message list.
5. Persists that latest user message with `upsertChatMessage()`.
6. Loads recent persisted context with `listRecentChatContext()`.
7. Calls `createTransactionAssistant(user, contextMessages)`.
8. Streams the assistant response back to the client.
9. On stream finish, persists the final assistant message.

### 4. Context Window vs Stored History

The assistant stores the **full** thread history for pagination, but only reloads a small recent window into model context.

| Concern                                  | Current implementation                                  |
| ---------------------------------------- | ------------------------------------------------------- |
| Full persisted history                   | `chat_messages` stores the whole thread                 |
| History pagination                       | `GET /api/chat` returns pages using cursors             |
| Model context size                       | `getAssistantContextLimit()` returns `10`               |
| Messages actually sent back to the model | The most recent 10 persisted UI messages for the thread |

This split is deliberate. History and model context are not the same thing.

### 5. Streaming Response Shape

The route streams two kinds of output back to the client:

1. The AI SDK UI message stream from the agent itself.
2. Additional deterministic `data-spec` parts built after tool execution.

The `execute` block in `/api/chat` merges:

* `result.toUIMessageStream(...)`
* `buildAssistantResponseDataParts({ messages, toolResults })`

The result is a single assistant message that can contain:

* plain text
* reasoning parts
* tool input/output parts
* `data-spec` parts for structured UI

### 6. Frontend Rendering and Refresh

The chat page renders each assistant message by splitting it into:

* visible markdown text
* thought/reasoning traces
* tool activity cards
* structured UI via `AssistantStructuredUi`

When the assistant finishes, the page invalidates cached queries for:

* transactions
* dashboard
* assets
* active AI suggestions

That refresh is deterministic frontend behavior. The model does not decide which caches to refresh.

## Model Provider Configuration

Model setup lives in `apps/web/lib/platform-ai/service.ts`.

### Configuration Sources

| Source                           | Purpose                                                                               |
| -------------------------------- | ------------------------------------------------------------------------------------- |
| `zeus_google_ai_provider` cookie | Stores each user's encrypted Google AI Studio key and selected model for this browser |
| `/api/ai-provider/google`        | Validates, saves, clears, and reports Google provider readiness                       |

### Provider Modes

| Provider mode | Implementation                                                                 |
| ------------- | ------------------------------------------------------------------------------ |
| Google        | `createGoogleGenerativeAI({ apiKey })` with `gemini-3.1-flash-lite` by default |

`getConfiguredPlatformAiModel()` resolves the effective settings and returns:

* `settings`
* `model`

The assistant does not use a server-owned provider key. If no valid user cookie exists, request-bound AI routes return `AI_PROVIDER_REQUIRED` and the client opens the Google provider modal.

### Tracing

When PostHog is available, the model is wrapped with `withTracing(...)`.

This adds deterministic observability around:

* user id
* workflow name
* model execution

The model still generates the content, but the tracing wrapper and workflow labeling are deterministic application concerns.

## Workflow Selection And Prompting

Workflow routing lives in `apps/web/lib/assistant/transaction-agent.ts`.

### Deterministic Workflow Detection

Before the agent runs, the code derives a **workflow signal** from recent user messages.

Important deterministic steps:

* `getUserMessageText()` extracts plain user text from UI message parts.
* `getWorkflowSignalText()` combines terse replies like `cash` or `yes` with prior user context when needed.
* `pickWorkflow()` uses regex heuristics to classify the request.

Current workflows include:

| Workflow                                         | Trigger shape                                                  | Active tool subset                                    |
| ------------------------------------------------ | -------------------------------------------------------------- | ----------------------------------------------------- |
| `coach`                                          | Budgets, goals, suggestions, house/retirement/planning intents | Read tools + goal planning UI + `createGoal`          |
| `bundle-create`                                  | Spending + amount hints                                        | Asset listing/creation + transaction creation helpers |
| `asset-create` / `asset-update` / `asset-delete` | Asset-specific CRUD intents                                    | Manual asset tools                                    |
| `create` / `update` / `delete`                   | Manual transaction CRUD intents                                | Manual transaction tools                              |
| `inspect`                                        | Generic read/query flows                                       | Read-only inspection tools                            |

This routing is **algorithmic**, not model-inferred.

### Prompt Layer

After workflow selection, the agent builds a long instruction prompt that includes:

* assistant persona and tone
* automatic defaults for date, currency, and transaction direction
* rules about read-only synced data
* rules about using UI-first tools
* workflow-specific guidance
* provider/model metadata

Then `prepareStep()` recalculates the workflow and narrows `activeTools` for that step.

That means the assistant runs inside a bounded environment:

* the model chooses whether to call a tool
* deterministic code decides which tools are even available on that step

### Iteration Limit

The agent is created with:

* `ToolLoopAgent`
* `stopWhen: stepCountIs(6)`

That six-step ceiling is deterministic guardrail logic.

## Tool Integration

All tools are declared in `createTransactionAssistant()`. Every tool has:

* a plain-language description
* a Zod input schema
* a deterministic `execute()` function

The model can decide **when** to use a tool and **which valid inputs** to propose. It cannot bypass the schema or the service-layer rules.

### Read Tools

| Tool                   | Backing service        | Current purpose                        | Deterministic enforcement                 |
| ---------------------- | ---------------------- | -------------------------------------- | ----------------------------------------- |
| `listTransactions`     | `listUserTransactions` | Read latest synced/manual transactions | Limits, source filtering, returned fields |
| `listLinkedAccounts`   | `listUserBankAccounts` | Read linked bank accounts              | Read-only output mapping                  |
| `listAssets`           | `listUserAssets`       | Read synced + manual assets            | Source filtering and normalized output    |
| `listBudgets`          | `listBudgets`          | Read budgets for coaching              | Numeric parsing and normalized response   |
| `listGoals`            | `listGoals`            | Read goals/plans for coaching          | Numeric parsing and normalized response   |
| `listSmartSuggestions` | `listUserSuggestions`  | Read active AI suggestions             | Status/limit filtering                    |

### Mutation Tools

| Tool                      | Backing service           | Current write scope               | Deterministic enforcement                                 |
| ------------------------- | ------------------------- | --------------------------------- | --------------------------------------------------------- |
| `createGoal`              | `createGoal`              | Create a `financial_goals` record | Zod schema, decimal normalization, deadline normalization |
| `createManualTransaction` | `createManualTransaction` | Create manual ledger entries      | Input schema, service-layer validation, asset id handoff  |
| `updateManualTransaction` | `updateManualTransaction` | Update manual transactions only   | Fails on synced transactions                              |
| `deleteManualTransaction` | `deleteManualTransaction` | Delete manual transactions only   | Fails on synced transactions                              |
| `createManualAsset`       | `createManualAsset`       | Create self-managed assets        | Kind enum, default currency, service validation           |
| `updateManualAsset`       | `updateManualAsset`       | Update self-managed assets only   | Fails on synced assets                                    |
| `deleteManualAsset`       | `deleteManualAsset`       | Delete self-managed assets only   | Fails on synced assets                                    |

### UI-Only Tools

These tools do not directly mutate product data. They return descriptors that later become deterministic JSON Render specs.

| Tool                        | Purpose                                        | Output style             |
| --------------------------- | ---------------------------------------------- | ------------------------ |
| `suggestAssetCreation`      | Offer first asset choices when no assets exist | Asset suggestion options |
| `showWelcomeSuggestions`    | Show starter prompts                           | Welcome suggestions      |
| `showQuickForm`             | Collect multiple fields at once                | Generic quick form       |
| `showGoalPlanningForm`      | Collect goal creation fields inside chat       | Goal-specific quick form |
| `showQuickCreateSuggestion` | Offer one-tap asset creation choices           | Quick-create cards       |
| `showSmartActions`          | Offer tappable follow-up buttons               | Smart action buttons     |

### What The Model Decides vs What Code Decides

| Decision                                             | Owner                    |
| ---------------------------------------------------- | ------------------------ |
| Whether to answer in text only or call a tool        | LLM                      |
| Which tool to call from the allowed set              | LLM                      |
| Whether the tool input matches the schema            | Deterministic validation |
| What the tool actually does in the database/services | Deterministic code       |
| How tool results become structured UI                | Deterministic code       |

## Structured UI Pipeline

The assistant does not render custom HTML directly from the model. It renders **deterministic JSON UI specs**.

```mermaid theme={null}
sequenceDiagram
    participant Model as LLM
    participant Tool as Tool execute()
    participant Parts as buildAssistantResponseDataParts()
    participant Client as AssistantStructuredUi
    participant Events as Browser events
    participant Chat as /api/chat

    Model->>Tool: Call typed tool
    Tool-->>Parts: Structured output
    Parts->>Client: data-spec part
    Client->>Events: Render buttons/forms/pickers
    Events->>Chat: Send follow-up prompt or form payload
```

### How Specs Are Built

`buildAssistantResponseDataParts()` in `apps/web/lib/assistant/response-parts.ts` inspects:

* the current message list
* flattened tool results

It then chooses a deterministic UI spec such as:

* welcome suggestions
* asset suggestion cards
* quick create cards
* quick forms
* smart actions
* asset selectors
* generic tool result summaries

This step is pure application logic. The model does not author the rendered component tree.

### How Specs Are Rendered

`AssistantStructuredUi` in `apps/web/components/assistant-structured-ui.tsx` uses:

* `@json-render/react`
* a local component registry

The registry maps spec element types like `UiSmartActions`, `UiQuickForm`, and `UiAssetSelector` to React components.

### How UI Turns Back Into Assistant Input

Structured UI components dispatch browser events such as:

* `assistant:action-selected`
* `assistant:existing-asset-selected`
* `assistant:form-submit`
* `assistant:quick-create-selected`

The main chat page listens for those events and converts them back into chat prompts.

Important example:

* Goal forms include hidden fields like `__intent=create_goal`.
* `buildAssistantFormPrompt()` converts that form submission into a deterministic prompt:
  * `Create a financial goal using this form data JSON exactly: ...`
* `prepareStep()` instructs the assistant to treat that JSON as authoritative and call `createGoal` directly when enough data exists.

### Why This Matters

The current assistant is a **hybrid system**:

* the LLM selects actions and writes conversational text
* deterministic code owns the UI contract and event interpretation

That makes the UI reproducible and easier to debug than free-form model-rendered HTML.

## Persistence And Admin Operations

### Core Tables

| Table                            | Purpose                                                  |
| -------------------------------- | -------------------------------------------------------- |
| `chat_threads`                   | Groups messages into a user-owned thread                 |
| `chat_messages`                  | Stores each UI message as JSONB                          |
| `zeus_google_ai_provider` cookie | Stores per-browser encrypted Google provider credentials |

### Chat Persistence Rules

`upsertChatMessage()` stores:

* message id
* thread id
* user id
* role
* full UI message payload

Thread `updatedAt` is also refreshed on each message write.

### History Pagination

`GET /api/chat` uses cursor-based pagination implemented in `listChatHistoryPage()`.

Important behavior:

* a user can request older persisted messages
* the API returns `hasMore`, `prevCursor`, and `contextLimit`
* the frontend prepends older messages into the visible thread
* older messages do **not** automatically expand the LLM context window

### Admin-Triggered Replies

Admin reply generation uses:

* route: `apps/web/app/api/admin/chat/threads/[threadId]/reply/route.ts`
* helper: `generateAssistantMessage()` in `apps/web/lib/chat/assistant.ts`

Current behavior:

1. Admin supplies an internal prompt.
2. The route loads the target user's recent context.
3. The route creates a synthetic ephemeral user message for the model call only.
4. The assistant reply is generated.
5. The assistant reply is marked with `metadata.adminInitiated = true`.
6. Only the assistant reply is persisted into the thread.

The admin prompt itself is **not** stored as a visible user message.

## Plans Integration Boundary

The current system has two different planning-related data models.

### `financial_goals`

This table represents long-lived user goals such as:

* house purchase
* retirement
* savings
* education

The assistant can directly interact with this subsystem today:

* `listGoals`
* `createGoal`
* `showGoalPlanningForm`

### `monthly_plans`

This table represents month-scoped spending/allocation plans such as:

* expected income
* fixed expenses
* essential spending
* goal contribution
* safe-to-spend
* warning state
* reminder schedule

This subsystem is managed through `plan-service.ts` and `/api/v1/plans`.

The chat assistant does **not** currently expose direct monthly-plan mutation tools.

### Current Boundary Summary

| Capability                                  | Current owner                      |
| ------------------------------------------- | ---------------------------------- |
| Create a long-lived goal in chat            | Assistant tools                    |
| Read budgets/goals/suggestions for coaching | Assistant tools                    |
| Create or update `monthly_plans`            | Plans API + plans UI               |
| Activate a draft monthly plan               | Plans API + plans UI               |
| Rebalance a monthly plan                    | Plans API + plans UI               |
| Dismiss monthly warnings                    | Plans API + plans UI               |
| Daily reminder and month-end jobs           | Plans service + cron/admin trigger |

### How Chat Still Connects To Plans

Even without direct monthly-plan tools, the assistant still intersects with planning in three ways:

1. It can create a `financial_goals` record that later becomes selectable as a plan's `primaryGoalId`.
2. It reads budgets, goals, and active suggestions to coach the user about next steps.
3. It can route users into plan-related surfaces through prompts and CTAs such as:
   * `/plans/create`
   * `/plans/[id]`
   * `/chat?prompt=...`

### Suggestion Layer Interaction

The suggestion system in `apps/web/lib/ai-suggestions/service.ts` is deterministic, not LLM-generated.

It builds rule-based suggestion candidates such as:

* `spending_watch`
* `budget_guardrail`
* `goal_nudge`
* `next_best_action`

The assistant consumes these suggestions through `listSmartSuggestions`, and the chat page also uses them for starter prompts.

After in-chat goal creation, `triggerEventDrivenSuggestionRefresh()` runs as a guarded follow-up so suggestion refresh failure does not block goal creation.

## Deterministic Plan Engine

Monthly plan logic lives primarily in:

* `apps/web/lib/plan-service.ts`
* `apps/web/lib/plan-math.ts`
* `/api/v1/plans/*`

This subsystem is separate from the LLM assistant.

### Core Plan Math

| Function                         | Purpose                                                                    | LLM involvement |
| -------------------------------- | -------------------------------------------------------------------------- | --------------- |
| `computeAllocations`             | Split income into essential spending, goal contribution, and safe-to-spend | None            |
| `computeWarningState`            | Detect spend pace depletion and goal pace drift                            | None            |
| `computeDailyGuide`              | Compute per-day safe-to-spend                                              | None            |
| `computeRebalanceRecommendation` | Recommend tightening spending or boosting goal contribution                | None            |
| `evaluateReminderSchedule`       | Decide whether a daily reminder is due in the user's timezone              | None            |
| `buildMonthEndSummary`           | Generate deterministic planned-vs-actual summary and next suggestion       | None            |

### Plan API Surface

| Route                                     | Current purpose                         |
| ----------------------------------------- | --------------------------------------- |
| `GET /api/v1/plans`                       | List the user's monthly plans           |
| `POST /api/v1/plans`                      | Create a draft monthly plan             |
| `GET /api/v1/plans/[id]`                  | Fetch one plan                          |
| `PATCH /api/v1/plans/[id]`                | Update allocations or spending snapshot |
| `DELETE /api/v1/plans/[id]`               | Delete a draft plan                     |
| `POST /api/v1/plans/[id]/activate`        | Activate a draft plan                   |
| `POST /api/v1/plans/[id]/rebalance`       | Apply deterministic rebalance logic     |
| `POST /api/v1/plans/[id]/dismiss-warning` | Dismiss the current warning             |

### Cron And Admin Triggers

The plans system also has explicit lifecycle entrypoints:

| Entry point                     | Purpose                                                        |
| ------------------------------- | -------------------------------------------------------------- |
| `POST /api/cron/plans`          | Run daily plan jobs behind a cron secret                       |
| `POST /api/admin/plans/trigger` | Allow admins to run plan jobs or accept rebalance for one user |

The daily plan jobs include:

* finalizing stale active plans
* sending due plan reminders
* sending month-end notifications

All of that is deterministic service logic. The assistant model is not used there.

## LLM vs Algorithm Matrix

| Behavior                                                          | Owner         |
| ----------------------------------------------------------------- | ------------- |
| Chat thread creation                                              | Deterministic |
| Chat history pagination                                           | Deterministic |
| Deciding which workflow a prompt belongs to                       | Deterministic |
| Writing the assistant's conversational text                       | LLM           |
| Deciding whether to call a tool                                   | LLM           |
| Restricting which tools are available on a step                   | Deterministic |
| Validating tool inputs                                            | Deterministic |
| Executing tool side effects                                       | Deterministic |
| Turning tool outputs into JSON Render specs                       | Deterministic |
| Rendering quick forms, buttons, and pickers                       | Deterministic |
| Turning structured UI clicks into follow-up prompts               | Deterministic |
| Choosing starter prompts from active suggestions                  | Deterministic |
| Creating a `financial_goals` record from a completed in-chat form | Hybrid        |
| Creating a `monthly_plans` record                                 | Deterministic |
| Monthly plan allocation math                                      | Deterministic |
| Warning detection and rebalance recommendations for plans         | Deterministic |
| Daily reminder scheduling                                         | Deterministic |
| Suggestion generation in `ai-suggestions/service.ts`              | Deterministic |

### Why `createGoal` Is Hybrid

Goal creation is the clearest hybrid path in the current system:

* deterministic code renders the goal planning form
* deterministic code turns the form into a strict JSON prompt
* the LLM decides to call `createGoal`
* deterministic service code writes the actual record

## Key Source Files

| Responsibility                | Main files                                                                                 |
| ----------------------------- | ------------------------------------------------------------------------------------------ |
| Chat page and transport       | `apps/web/components/assistant-chat-page-client.tsx`                                       |
| Chat API                      | `apps/web/app/api/chat/route.ts`                                                           |
| Thread creation               | `apps/web/app/api/chat/thread/route.ts`                                                    |
| Agent, workflows, and tools   | `apps/web/lib/assistant/transaction-agent.ts`                                              |
| Structured UI spec generation | `apps/web/lib/assistant/response-parts.ts`                                                 |
| JSON UI builders              | `apps/web/lib/assistant/chat-ui.ts`                                                        |
| Structured UI renderer        | `apps/web/components/assistant-structured-ui.tsx`                                          |
| Form-to-prompt translation    | `apps/web/lib/assistant/form-submissions.ts`                                               |
| Chat persistence              | `apps/web/lib/chat/service.ts`                                                             |
| Admin-triggered replies       | `apps/web/lib/chat/assistant.ts`                                                           |
| Provider configuration        | `apps/web/lib/platform-ai/service.ts`                                                      |
| Suggestions                   | `apps/web/lib/ai-suggestions/service.ts`                                                   |
| Plans API/service/math        | `apps/web/app/api/v1/plans/*`, `apps/web/lib/plan-service.ts`, `apps/web/lib/plan-math.ts` |

## Replication Guide

### Minimum Setup

To reproduce the current assistant behavior locally, you need:

1. The web app running.
2. A valid authenticated user session.
3. A valid Google AI Studio key saved through the Google AI provider modal.
4. At least one test user with data for transactions, assets, budgets, goals, or suggestions.

### Replicate A Basic Chat Round Trip

1. Open `/chat`.
2. Start a new thread or reuse the existing local thread id.
3. Send a simple inspection prompt such as `Show my accounts`.
4. Watch the flow:
   * frontend submits `messages + threadId`
   * `/api/chat` persists the latest user message
   * recent thread context is loaded
   * the agent runs
   * tool results are converted into a deterministic UI spec
   * the assistant reply is stored on finish

### Replicate A Transaction Flow

Use a prompt such as `KFC 150k`.

Expected current behavior:

1. Workflow routing detects a spending/create intent.
2. The model typically calls `listAssets`.
3. If assets exist, the response-parts layer may emit an asset selector.
4. Clicking an asset emits a browser event.
5. The chat page converts that event back into a follow-up prompt containing `asset_id`.
6. The model can then call `createManualTransaction`.

### Replicate In-Chat Goal Creation

Use a prompt such as `I want to buy a house in 10 years`.

Expected current behavior:

1. Workflow routing classifies the prompt as `coach`.
2. The model is guided toward `showGoalPlanningForm`.
3. The frontend renders a structured form.
4. Submitting the form generates a deterministic JSON prompt.
5. The model is instructed to treat that JSON as authoritative and call `createGoal`.
6. The goal record is created.
7. Event-driven suggestion refresh runs as a follow-up.

### Replicate The Plans System Separately

The monthly plans system should be tested through the plans UI or `/api/v1/plans`, not through assistant tools.

Recommended manual path:

1. Create a goal first if you want a linked `primaryGoalId`.
2. Create a draft monthly plan.
3. Activate it.
4. Patch spending snapshots to trigger warnings.
5. Call rebalance.
6. Run cron/admin plan jobs for reminders and month-end behavior.

### How To Trace Problems Quickly

| Symptom                                     | First place to inspect                                             |
| ------------------------------------------- | ------------------------------------------------------------------ |
| No assistant reply                          | `/api/chat` and provider configuration                             |
| Wrong tool behavior                         | `transaction-agent.ts` tool schema + execute function              |
| Wrong structured UI                         | `response-parts.ts`                                                |
| Button/form click does nothing              | `assistant-structured-ui.tsx` and chat page event listeners        |
| Missing persisted messages                  | `chat/service.ts` and `chat_messages`                              |
| Goal creation works but plans do not update | `ai-suggestions/events.ts`, `plan-service.ts`, and `/api/v1/plans` |
| Monthly plan issue                          | `plan-service.ts` and `plan-math.ts`, not the assistant model      |

## Current Limitations

The most important limitations to keep in mind are:

* The assistant has **no direct monthly-plan mutation tool**.
* `monthly_plans` behavior is currently **deterministic service/API logic**, not model-generated planning logic.
* Suggestion CTAs can route users into chat or plan surfaces, but that is separate from the assistant's direct write capabilities.
* The assistant context window is intentionally small even though the product stores the full chat transcript.
* The assistant is currently focused on the web app. It is not the same system as the older Flutter local-first architecture documented elsewhere.

## Related Docs

* [Architecture](/getting-started/architecture)
* [Plans](/concepts/plans)
* [Plans MVP](/concepts/plans-mvp)
* [Plans MVP BVT](/concepts/plans-bvt)
