---
name: iblai-vibe-agent-privacy
description: Add the agent Privacy tab with two sub-tabs, PII Filtering (detect personal information in messages and allow, redact, mask or block it, choose the entity types, filter AI responses too) and Incognito (whether conversations with this agent are kept out of chat history and memory; Users Decide, Always Incognito, or Never Incognito). Use when the user mentions PII, personal data, redaction, incognito for an agent, or "don't save conversations with this agent". For the in-chat Incognito toggle and how the org, agent, user and session tiers resolve see /iblai-vibe-incognito; for the user's own default see /iblai-vibe-profile; for the REST endpoints see /iblai-api-agent-privacy
globs:
alwaysApply: false
metadata:
kind: ui
---
# /iblai-vibe-agent-privacy
Add the agent **Privacy tab** -- "Control what personal information the
agent sees and how Incognito works for its conversations." **Scope: per
agent.** Every control saves at once to the agent's settings, so it applies
to everyone who chats with the agent. Two sub-tabs behind one pill switcher:
- **PII Filtering** — an inline **PII filtering** toggle; when on,
what to do when PII is detected (**Allow**, **Redact**, **Mask**,
**Block**), the **Block Message**, the **Entity Types** chips
(Person, Email, Phone, SSN, Credit Card, Location, Date / Time,
Passport, Driver's License, IP Address, IBAN, Medical License, Bank
Number) and **Also filter AI responses**.
- **Incognito** — one choice over how Incognito works for this agent:
**Users Decide**, **Always Incognito**, or **Never Incognito**. This
replaces the old Private Mode switch that lived on the Settings tab.
This is one tab in the agent-settings family indexed by `/iblai-vibe-agent`;
it shares the `AgentSettingsProvider` wrapper with every other tab (optional
for this tab — identity can be passed as props).
**PII filtering off** -- the switch at the top turns the Privacy Router on.
While it is off, the controls below stay visible but grayed out, so admins
can see what they would get.

**On, with Redact** -- **When PII is detected** picks the action (the line
under it explains it), **Entity Types** picks what to detect (none selected
means the defaults), and **Also filter AI responses** applies the same filter
to the agent's replies.

**Actions** -- **Allow** (detect and log, leave the message unchanged),
**Redact** (replace with the type, e.g. `[EMAIL_ADDRESS]`), **Mask** (replace
with asterisks), **Block** (reject the message).

**Block** -- adds **Block Message**, the text the user sees instead of an
answer. It saves when the field loses focus.

**Incognito** -- the three cards, offered only when the organization allows
it (Prerequisites).

> **Common setup (brand, conventions, env files, verification):** see [docs/skill-setup.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/docs/skill-setup.md).
## Related Incognito surfaces
Incognito is one feature with four tiers; this tab is the **agent**
tier. `/iblai-vibe-incognito` explains how they resolve and mounts the
in-chat toggle.
- **`/iblai-vibe-incognito`** — the chat-header **Incognito** toggle and
its "Enable Incognito for this chat?" dialog, plus the tier order.
- **`/iblai-vibe-profile`** — the user's own **Privacy** tab: their
default (Normal / Anonymized / Incognito).
- **`/iblai-vibe-account`** — organization settings → **Advanced** →
"Allow users to control chat privacy", the gate that makes the
Incognito sub-tab (and the toggle) exist at all.
## Prerequisites
- Auth set up (`/iblai-vibe-auth`, or vibe-starter).
- `AgentSettingsProvider` wraps the route (`/iblai-vibe-agent` §1), **or** pass
`tenantKey` / `mentorId` / `username` as props.
- `@iblai/iblai-js@^2.62.1` (resolves `@iblai/web-containers` 1.62.1). Check with
`pnpm why @iblai/web-containers`.
- A real agent UUID. Ask the user; never invent one.
- The **Incognito** sub-tab appears only when the organization allows
user chat-privacy control (`allow_user_chat_privacy_control`). With
the gate off there is no Incognito to govern, and the tab shows PII
Filtering alone.
## Step 1: Check Environment
Look for `iblai.env` in the project root with `PLATFORM`, `DOMAIN`, and
`TOKEN`. If it is missing, tell the user:
"You need an `iblai.env` with your platform configuration. Download the
template and fill in your values:
`curl -o iblai.env https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/iblai.env`"
## Step 2: Mount `AgentPrivacyTab`
```tsx
// app/(app)/agents/[mentorId]/privacy/page.tsx
"use client";
import { AgentPrivacyTab } from "@iblai/iblai-js/web-containers/next";
export default function AgentPrivacyPage() {
return (
);
}
```
The tab reads `tenantKey`, `mentorId`, `username`, `enableRBAC` and
`executeGatedAction` from `AgentSettingsProvider`, loads the agent settings
and the organization's chat-privacy gate, and decides which sub-tabs to offer
(see **Which sub-tabs appear** below). Each can be passed as a prop instead,
for example to mount the tab outside the provider:
```tsx
import { AgentPrivacyTab } from "@iblai/iblai-js/web-containers/next";
export function PrivacyPanel(props: { org: string; agentId: string; username: string }) {
return (
);
}
```
With `enableRBAC` on, each control follows the agent's field permissions
(`enable_privacy_router`, `privacy_action`, `privacy_response`,
`privacy_entities`, `enable_privacy_output_filter`, `disable_chathistory`,
`disable_privacy_mode`) and is disabled when the user may not edit it.
`executeGatedAction` wraps every save, so the host can put a paywall or
confirmation in front of it.
## Step 3: Customize Labels (Optional)
The tab renders with the default agent-facing copy
(`AGENT_PRIVACY_TAB_LABELS`, localized through the SDK's i18n). Pass a
partial `labels` object to change any string:
```tsx
import { AgentPrivacyTab } from "@iblai/iblai-js/web-containers/next";
;
```
Label groups (`PrivacyTabLabels`): `header` (`title`, `description`),
`capability` (the switch: `title`, `description`, `offHint`), `fields` --
`action` (`label`, `tooltip`, `options` and `descriptions` per action),
`blockMessage` (`label`, `placeholder`), `entities` (`label`, `tooltip`,
`emptyHint`, `labels` per entity type), `outputFilter` (`label`, `tooltip`),
`enableRouter` -- `subTabs` (`piiFiltering`, `incognito`), `incognito`
(`description`, `groupAriaLabel`, `options` per card), `noAccess`, and `toasts`
(`updateSuccess`, `updateError`).
## Step 4: Use MCP Tools for Customization
```
get_component_info("AgentPrivacyTab")
get_component_info("AgentSettingsProvider")
get_component_info("ChatPrivacyToggle")
```
## `` Props
Import from `@iblai/iblai-js/web-containers/next`.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `labels` | `DeepPartial` | No | Override user-visible strings |
| `tenantKey` | `string` | No | Organization key; defaults to `AgentSettingsProvider` |
| `mentorId` | `string` | No | Agent UUID; defaults to `AgentSettingsProvider` |
| `username` | `string` | No | Signed-in username; defaults to `AgentSettingsProvider` |
| `enableRBAC` | `boolean` | No | Follow the agent's field permissions; defaults to the provider, else `false` |
| `executeGatedAction` | `(fn: () => unknown) => unknown` | No | Wrap every save (paywall, confirmation); defaults to the provider |
## What each sub-tab renders
### PII Filtering
- **PII filtering** capability card — the on/off switch sits inline at
the top of the sub-tab with its explanation. When it is **off** the
controls below stay visible but inert, so an admin can preview what
they will get before turning it on.
- **When PII is detected** — a select with an inline description of
the chosen action:
| Action | Wire value | What happens |
|---|---|---|
| Allow | `allow` | Detected but left untouched — the message passes through unchanged. For monitoring or auditing without altering messages |
| Redact (default) | `redact` | Replaced with its type — e.g. `Email [EMAIL_ADDRESS]` |
| Mask | `mask` | Hidden behind asterisks — e.g. `Email ***********` |
| Block | `block` | The message is rejected and your block message is shown |
- **Block Message** — a textarea shown only for **Block**; saves on
blur.
- **Entity Types** — the 13 chips, multi-select. With none selected the
hint reads *"Using defaults."*
- **Also filter AI responses** — scrubs PII from the agent's output as
well (may add slight latency to streamed responses).
### Incognito
*"Incognito keeps a conversation out of chat history and memory.
Choose how it works for this agent."* Three radio cards:
| Card | Flags saved | Effect in the chat header |
|---|---|---|
| **Users Decide** | `disable_chathistory: false`, `disable_privacy_mode: false` | Users can turn Incognito on for any conversation; otherwise chats are saved as usual |
| **Always Incognito** | `disable_chathistory: true` | Every conversation is incognito, for anyone. The Incognito pill shows **locked on** |
| **Never Incognito** | `disable_privacy_mode: true` | Incognito isn't available for this agent; the toggle disappears. Users whose profile default is Incognito are saved **anonymously** instead |
One control writes both flags in the same PUT, so the contradictory
"always *and* never" pair cannot be configured from here. A record that
still carries both (older data) **behaves as Always Incognito**: the
backend checks `disable_chathistory` first, answers `mode: disabled`,
`source: mentor`, and writes no history — even though the tab displays
it as Never Incognito. Choosing any card rewrites both flags and clears
the mismatch.
## Which sub-tabs appear
| Condition | Result |
|---|---|
| Organization gate `allow_user_chat_privacy_control` off | Incognito sub-tab not offered |
| `enableRBAC` and **no** PII field is readable (`permissions.field`) | PII Filtering not offered |
| `enableRBAC` and **either** Incognito flag is unreadable | Incognito not offered |
| Nothing readable | The tab shows a single notice: *"You don't have permission to view this agent's privacy settings."* |
Members typically see Incognito read-only and no PII Filtering. Writes
stay per control: a field the user may read but not write renders
disabled.
## Settings fields
Read with `GET` and written with `PUT` on
`${dmUrl}/api/ai-mentor/orgs/{org}/users/{username}/mentors/{mentor_unique_id}/settings/`
(a partial update — send only what changes):
| Field | Type | Sub-tab |
|---|---|---|
| `enable_privacy_router` | `boolean` | PII Filtering — the capability switch |
| `privacy_action` | `"allow" \| "redact" \| "mask" \| "block"` | PII Filtering |
| `privacy_response` | `string` | PII Filtering — the block message |
| `privacy_entities` | `string[]` (`PERSON`, `EMAIL_ADDRESS`, …) — `[]` means defaults | PII Filtering |
| `enable_privacy_output_filter` | `boolean` | PII Filtering |
| `disable_chathistory` | `boolean` | Incognito — Always |
| `disable_privacy_mode` | `boolean \| null` (`null` = off on older records) | Incognito — Never |
## Related Exports
From `@iblai/iblai-js/web-containers/next`:
- `AGENT_PRIVACY_TAB_LABELS` -- the default label bundle.
- `AgentPrivacyTabProps`, `PrivacyTabLabels` -- types.
From `@iblai/iblai-js/web-containers`: `ChatPrivacyToggle` -- the chat-header
toggle that reacts to the Incognito policy (`/iblai-vibe-incognito`).
From `@iblai/iblai-js/data-layer`:
- `PRIVACY_ACTIONS` (`allow`, `redact`, `mask`, `block`),
`PRIVACY_ENTITY_TYPES` (`PERSON`, `EMAIL_ADDRESS`, `PHONE_NUMBER`, `US_SSN`,
`CREDIT_CARD`, `LOCATION`, `DATE_TIME`, `US_PASSPORT`, `US_DRIVER_LICENSE`,
`IP_ADDRESS`, `IBAN_CODE`, `MEDICAL_LICENSE`, `US_BANK_NUMBER`),
`DEFAULT_PRIVACY_ACTION`, `DEFAULT_PRIVACY_RESPONSE`, and the
`PrivacyAction` / `PrivacyEntityType` types.
- `isPrivacyBlockedError`, `PRIVACY_BLOCKED_ERROR_KIND` -- recognise the
`privacy_blocked` error a **Block** action sends over the chat stream, to
style it apart from a moderation block.
- `MentorChatPrivacyPublicSettings` -- `disable_privacy_mode` as members read
it from the agent's public settings.
## Platform data
| Hook | Purpose |
|---|---|
| `useGetMentorSettingsQuery` | Current privacy fields and field permissions |
| `useEditMentorJsonMutation` | Save a change (JSON, so `privacy_entities` stays an array) |
| `useGetTenantChatPrivacyConfigQuery` | The organization gate the Incognito sub-tab depends on |
REST twin: [`/iblai-api-agent-privacy`](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/agents/iblai-api-agent-privacy/SKILL.md)
(also the `privacy-flags` audit log of every detection, which this tab does
not show).
## Step 5: Verify
Run `/iblai-vibe-ops-test` before telling the user the work is ready, and give
every page, control and state this skill added its own checkpoint — one Playwright
test titled ` · …` in `e2e/journeys/`, listed in `e2e/coverage.json`;
`pnpm e2e:coverage` must pass:
1. `pnpm build` -- must pass with zero errors.
2. `pnpm test` -- vitest must pass.
3. Touch test `/agents//privacy` in the browser as `/iblai-vibe-ops-test` says,
signed in as an org admin: the controls render grayed out; turn PII filtering on,
pick **Block** -- the Block Message field appears; pick each Incognito card; read
the console; check the phone width.
Stable selectors: `privacy-sub-tab-pii`, `privacy-sub-tab-incognito`,
`privacy-capability-toggle`, `privacy-incognito-{users|always|never}`
(with `aria-pressed`), `privacy-no-access`.
## Important Notes
- **Saves on every click**: there is no Save button. Try settings on a test
agent.
- **Allow still detects**: detections are logged to the `privacy-flags`
audit log; only the message is left alone.
- **JSON settings endpoint**: all fields are persisted via the JSON
`/settings/` mutation (`useEditMentorJsonMutation`), not the
multipart variant, so `privacy_entities` round-trips cleanly. The tab
refetches agent settings after each save because that mutation does
not invalidate the settings cache tag.
- **Incognito saves refresh the chat header**: choosing a card also
invalidates `ChatPrivacyEffective` and the agent's
`mentorPublicSettings` caches, so an open chat's Incognito pill locks,
unlocks or disappears without a reload. Custom UI writing the two
flags must do the same.
- **Private Mode moved**: the switch that used to sit on the Settings
tab's capabilities is gone; the Incognito sub-tab here is its
replacement, and the user-facing name is now **Incognito** everywhere.
- **Shared provider**: mount `AgentSettingsProvider` once at the layout
level (`/iblai-vibe-agent` §1); do not wrap each tab.
- **Peer deps**: `sonner` and `@iblai/iblai-web-mentor`
(`pnpm add sonner @iblai/iblai-web-mentor`).
- **Brand guidelines**: [BRAND.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/BRAND.md)