Add the agent Privacy tab -- "Control what personal information the agent sees and how Incognito works for its conversations." 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 wider agent-settings family. All tabs share the
same AgentSettingsProvider wrapper (optional for this tab — identity
can be passed as props).


Common setup (brand, conventions, env files, verification): see 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 must be set up first (
/iblai-vibe-auth) - MCP server + skills configured (
@iblai/mcpin.mcp.json) AgentSettingsProviderwrapping the route (see/iblai-vibe-agent-settingStep 2), or passtenantKey/mentorId/usernameas props.- Ask the user for a real
mentorId(agent UUID). Do NOT 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
Before proceeding, check for an iblai.env in the project root. Look for
PLATFORM, DOMAIN, and TOKEN variables. If the file does not exist or
is missing these variables, 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
// app/(app)/agents/[mentorId]/privacy/page.tsx
"use client";
import { AgentPrivacyTab } from "@iblai/iblai-js/web-containers/next";
export default function AgentPrivacyPage() {
return (
<div className="flex h-full flex-col bg-white">
<AgentPrivacyTab />
</div>
);
}
The tab reads tenantKey, mentorId, and username from the nearest
<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).
With identity overrides
When the tab is not rendered inside an <AgentSettingsProvider>, pass
identity explicitly:
<AgentPrivacyTab
tenantKey="my-tenant"
mentorId="00000000-0000-0000-0000-000000000000"
username="learner@example.com"
/>;
With RBAC and gated mutations
<AgentPrivacyTab
enableRBAC
executeGatedAction={(fn) => withConfirm(fn)}
/>;
enableRBAC honors field-level permissions from agent settings — each
field (enable_privacy_router, privacy_action, privacy_response,
privacy_entities, enable_privacy_output_filter,
disable_chathistory, disable_privacy_mode) can be independently
read-only. executeGatedAction wraps every save so the host app can
interpose a confirmation or upgrade gate.
Step 3: Customize Labels (Optional)
import { AgentPrivacyTab } from "@iblai/iblai-js/web-containers/next";
<AgentPrivacyTab
labels={{
header: { title: "Data privacy", description: "Filter PII; decide how Incognito works." },
subTabs: { piiFiltering: "Personal data", incognito: "Incognito" },
incognito: {
options: { always: { title: "Always private", description: "Nothing is ever saved." } },
},
}}
/>;
Step 4: Use MCP Tools for Customization
get_component_info("AgentPrivacyTab")
get_component_info("AgentSettingsProvider")
get_component_info("ChatPrivacyToggle")
<AgentPrivacyTab> Props
Import from @iblai/iblai-js/web-containers/next.
| Prop | Type | Required | Description |
|---|---|---|---|
labels |
DeepPartial<PrivacyTabLabels> |
No | Override user-visible strings (header, capability card, field labels, tooltips, action descriptions, entity labels, sub-tab names, the three Incognito cards, toasts) |
tenantKey |
string |
No | Identity override. Defaults to the nearest <AgentSettingsProvider> |
mentorId |
string |
No | Agent UUID override. Defaults to the provider |
username |
string |
No | Username override. Defaults to the provider |
enableRBAC |
boolean |
No | Honor per-field permissions from agent settings. Defaults to the provider value or false |
executeGatedAction |
(fn: () => unknown) => unknown |
No | Wrap each save mutation (e.g. confirmation/upgrade gate). Defaults to the provider value |
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 allowDetected but left untouched — the message passes through unchanged. For monitoring or auditing without altering messages Redact (default) redactReplaced with its type — e.g. Email [EMAIL_ADDRESS]Mask maskHidden behind asterisks — e.g. Email ***********Block blockThe 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:
AgentPrivacyTab,AgentPrivacyTabProps— this tab.AGENT_PRIVACY_TAB_LABELS,PrivacyTabLabels— the default label bundle and its type (now includingsubTabsandincognito).
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,DEFAULT_PRIVACY_ACTION,DEFAULT_PRIVACY_RESPONSE, and thePrivacyAction/PrivacyEntityTypetypes.isPrivacyBlockedError,PRIVACY_BLOCKED_ERROR_KIND— recognise theprivacy_blockederror a Block action sends over the chat stream, to style it apart from a moderation block.useGetMentorSettingsQuery,useEditMentorJsonMutation— the read and the JSON PUT the tab uses.useGetTenantChatPrivacyConfigQuery— the organization gate the Incognito sub-tab depends on.MentorChatPrivacyPublicSettings—disable_privacy_modeas members read it from the agent's public settings.
The policy ↔ flags mapping helpers (INCOGNITO_POLICIES,
incognitoPolicyFrom, incognitoFlags) live in the tab's source but
are not re-exported from the package entry — custom UI should
mirror the table above.
SDK source: packages/web-containers/src/components/modals/edit-mentor-modal/tabs/privacy-tab.tsx
(with privacy-tab/incognito-policy.ts and privacy-tab/labels.ts).
Step 5: Verify
Run /iblai-vibe-ops-test before telling the user the work is ready:
pnpm build-- must pass with zero errorspnpm test-- vitest must pass- Start dev server and touch test:
pnpm dev & npx playwright screenshot http://localhost:3000/agents/<id>/privacy /tmp/agent-privacy.png
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
- Redux store: Must include
mentorReducerandmentorMiddleware initializeDataLayer(): 5 args (v1.2+)@reduxjs/toolkit: Deduplicated via webpack aliases innext.config.ts- Peer deps:
sonnerand@iblai/iblai-web-mentormust be installed (pnpm add sonner @iblai/iblai-web-mentor) - Provider optional: this tab uses
useAgentSettingsOptional, so it works insideAgentSettingsProvideror with explicit identity props. - JSON settings endpoint: all fields are persisted via the JSON
/settings/mutation (useEditMentorJsonMutation), not the multipart variant, soprivacy_entitiesround-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
ChatPrivacyEffectiveand the agent'smentorPublicSettingscaches, 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.
- Brand guidelines: BRAND.md