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

# Personal Assistant API

> Internal reference for the Personal Assistant API — route architecture, plan gating, rate limiting, streaming implementation, routine engine, and session management.

> **Internal documentation for developers.** This page covers the Personal Assistant API internals: auth patterns, streaming SSE implementation, rate limiting, routine scheduling, and the runtime architecture. Not intended for end users.

***

## Route Map

| Method                  | Endpoint                                                         | Description                  |
| ----------------------- | ---------------------------------------------------------------- | ---------------------------- |
| `POST`                  | `/api/personal-assistant/chat`                                   | Streaming/non-streaming chat |
| `GET`                   | `/api/personal-assistant/state`                                  | Current assistant state      |
| `GET`                   | `/api/personal-assistant/capabilities`                           | Plan-based capability list   |
| `PATCH`                 | `/api/personal-assistant/settings`                               | Update settings              |
| `GET`                   | `/api/personal-assistant/usage`                                  | Usage statistics             |
| `POST`                  | `/api/personal-assistant/feedback`                               | Answer feedback              |
| `POST`                  | `/api/personal-assistant/context-help`                           | Contextual help              |
| `GET/POST/PATCH/DELETE` | `/api/personal-assistant/conversations/...`                      | Conversation CRUD            |
| `GET/POST/PATCH/DELETE` | `/api/personal-assistant/routines/...`                           | Routine CRUD + execution     |
| `POST`                  | `/api/personal-assistant/routines/[id]/toggle`                   | Enable/disable routine       |
| `GET`                   | `/api/personal-assistant/routines/[id]/executions`               | Execution history            |
| `POST`                  | `/api/personal-assistant/routines/run`                           | Manual trigger               |
| `GET`                   | `/api/personal-assistant/approvals`                              | Pending action approvals     |
| `POST`                  | `/api/personal-assistant/actions/[id]/approve`                   | Approve action               |
| `POST`                  | `/api/personal-assistant/actions/[id]/reject`                    | Reject action                |
| `GET/PATCH`             | `/api/personal-assistant/opportunities/...`                      | Opportunities                |
| `GET/DELETE`            | `/api/personal-assistant/privacy`                                | Privacy settings/deletion    |
| `GET/PATCH`             | `/api/personal-assistant/privacy/retention`                      | Retention policy             |
| `GET`                   | `/api/personal-assistant/audit`                                  | Audit log                    |
| `GET`                   | `/api/personal-assistant/audit/export`                           | Export audit log             |
| `GET`                   | `/api/personal-assistant/integrations/[integration]/diagnostics` | Integration diagnostics      |

***

## Plan Gating

Every PA route starts with plan validation:

```ts theme={null}
import { assertPersonalAssistantPlan } from '@zappway/lib/personal-assistant';

// Inside handler:
assertPersonalAssistantPlan(req.session);
// Throws 403 if plan doesn't include personal_assistant feature
```

***

## Auth Pattern

```ts theme={null}
withPermissionRoute(req, {
  permission: 'personal_assistant.read',    // all GETs
  permission: 'personal_assistant.execute', // chat, conversations
  permission: 'personal_assistant.manage',  // settings, routines
  permission: 'personal_assistant.audit',   // audit log
  authMode: 'lightweight',                  // for reads
}, handler)
```

PA is **user-bound** at the conversation level, but **org-bound** at the capabilities level. User ID comes from `req.session.user.id`.

***

## Runtime Architecture

```ts theme={null}
import { PersonalAssistantRuntime } from '@zappway/lib/personal-assistant';

const runtime = new PersonalAssistantRuntime({ db: prisma });

// Get capabilities
const capabilities = runtime.capabilities(req.session);

// Chat (non-streaming)
const result = await runtime.chat({
  session: effectiveSession,
  input: {
    message,
    requestedInteractionLanguage,
    conversationId,
    context,
  },
  signal: request.signal,
});

// Chat (streaming)
const result = await runtime.chat({
  session: effectiveSession,
  input: { message, conversationId },
  signal: abortController.signal,
  onDelta: (delta) => enqueue('chunk', { delta }),
});
```

### Language, intent, and verified knowledge pipeline

`LanguageAndLocaleResolver` resolves the response language separately from the
interface locale and persists durable conversation language metadata with a
compare-and-swap update. A short ambiguous message never overwrites the stored
language.

Human-language intent routing is model-classified into canonical intent IDs.
Only exact internal command IDs bypass that classifier. Invalid or
low-confidence classifier output fails closed to `GENERAL_ANSWER`.

The `CANONICAL_CONTEXT_PIPELINE_V1` flow preserves the original route, query,
hash, active tabs, and safe transient states. It then resolves
`VERIFIED_TEACHING_V1` or `INTEGRATION_GUIDANCE_V1`, adds provenance nodes to
the ContextGraph, and sends that verified context to the answer model.
Build-time coverage checks validate all 446 inventory surfaces; the generated
catalog is not imported into the request path.

Do not restore translated keyword arrays or substring matching for intents.
Do not expose an external navigation target unless its exact destination has a
verified source. Integration guidance is descriptive or links to verified
configuration only; it is never promoted to an executable action.

***

## Streaming Implementation

The chat endpoint uses `ReadableStream` with SSE format:

```ts theme={null}
const stream = new ReadableStream({
  async start(controller) {
    const encoder = new TextEncoder();
    
    const enqueue = (event: string, data: unknown) => {
      controller.enqueue(encoder.encode(
        `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
      ));
    };

    // Heartbeat every 15s
    const heartbeat = setInterval(() => {
      controller.enqueue(encoder.encode(': heartbeat\n\n'));
    }, 15_000);

    // Timeout at 90s
    const timeout = setTimeout(
      () => abortController.abort('personal_assistant_stream_timeout'),
      90_000
    );

    try {
      const result = await runtime.chat({ ..., onDelta: (delta) => enqueue('chunk', { delta }) });
      enqueue('done', result);
    } catch (error) {
      enqueue('error', { code: 'CHAT_FAILED', retryable: true });
    } finally {
      clearTimeout(timeout);
      clearInterval(heartbeat);
    }
  }
});

return new Response(stream, {
  headers: {
    'Content-Type': 'text/event-stream; charset=utf-8',
    'Cache-Control': 'private, no-store, no-transform',
    Connection: 'keep-alive',
  },
});
```

***

## Routine Engine

```ts theme={null}
import { PersonalAssistantRoutineEngine } from '@zappway/lib/personal-assistant';

const engine = new PersonalAssistantRoutineEngine(prisma);

// List routines
await engine.listRoutines(userId, organizationId, locale);

// Create/update routine (schema: PersonalAssistantRoutineCreateSchema)
await engine.updateRoutine({ session, routineId, input });
```

Routines are stored in DB with a cron schedule. A background job (`/api/crons/personal-assistant-routines` or similar) triggers them on schedule.

***

## Rate Limiting

The chat endpoint enforces rate limits from `@zappway/lib/rate-limit`:

```ts theme={null}
const { limited, headers: rateLimitHeaders } = await checkRateLimit({
  session: req.session,
  feature: 'personal_assistant_chat',
});
if (limited) return 429;
```

Rate limit headers are included in ALL responses (streaming + non-streaming):

* `X-RateLimit-Limit`
* `X-RateLimit-Remaining`
* `X-RateLimit-Reset`

***

## i18n Context Injection

The chat route injects request context for i18n:

```ts theme={null}
import { detectLocale, injectRequestContext } from '@zappway/lib/i18n-server';
const locale = detectLocale(request, req.session);
const cleanupReqCtx = injectRequestContext({ locale });
try {
  // ... handle chat
} finally {
  cleanupReqCtx?.(); // CRITICAL: cleanup to avoid memory leaks between requests
}
```

> **Critical:** Always call `cleanupReqCtx()` in the `finally` block. Missing this causes locale context to leak between concurrent requests.

***

## Privacy — Data Deletion

```ts theme={null}
// DELETE /api/personal-assistant/privacy
// Deletes ALL user-specific PA data:
// - Conversations + messages
// - Routines + execution history
// - Opportunities
// - Audit log entries
// - Action approvals
// Uses prisma.$transaction for atomicity
```

***

## Known Issues / Gotchas

* **`sourceIdentity`** is stripped from the input before passing to `runtime.chat()` — the dashboard always uses the authenticated user's identity, never a client-supplied actor label.
* **Streaming abort handling:** Both client abort (`client_disconnected`) and timeout (`personal_assistant_stream_timeout`) trigger `abortController.abort()`. The error code is preserved in the SSE `error` event for client-side differentiation.
* **State endpoint** (`GET /state`) uses `PersonalAssistantStateQuerySchema` validation + i18n injection — same pattern as chat.
* **Conversation isolation:** PA conversations are scoped to `userId` (not just `organizationId`). Users cannot see each other's PA conversations even within the same org.
