Last updated

/iblai-vibe-agent-safety

Mirrored from iblai/vibe · skills/agents/iblai-vibe-agent-safety/SKILL.md. This page is generated — edit it in the repository, not here.

Add the agent Safety tab -- the two guardrails on an agent's chats. Scope: per agent. The Moderation Prompt checks each user message before the agent sees it; the Safety Prompt checks each reply before the user sees it. Each has an Active switch, and each has a response sent instead when it trips. This is one tab in the agent-settings family indexed by /iblai-vibe-agent; it shares the AgentSettingsProvider wrapper with every other tab.

Safety -- four cards (Moderation Prompt, Safety Prompt, Moderation Response, Safety Response), each with Edit and Copy; the two prompt cards have the Active switch. View Flagged Prompts shows only when you pass a FlaggedPromptsModal and showFlaggedPrompts is true (Step 2).

Safety tab -- overview
Safety tab -- overview

Edit -- a rich-text editor for the prompt or response; Save writes it to the agent.

Safety tab -- Edit Safety Prompt
Safety tab -- Edit Safety Prompt

Flagged prompts -- the modal your app supplies. This one is the minimal modal from Step 2: the user messages the Moderation Prompt stopped, with who sent each, when, the message, and the reason.

Safety tab -- flagged prompts
Safety tab -- flagged prompts

Common setup (brand, conventions, env files, verification): see docs/skill-setup.md.

Prerequisites

  • Auth set up (/iblai-vibe-auth, or vibe-starter).
  • AgentSettingsProvider wraps the route (/iblai-vibe-agent §1).
  • @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.
  • To list flagged prompts, the user needs the agent's #view_moderation_logs grant (org admins have it).

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 AgentSafetyTab

The four cards need nothing but the provider: <AgentSafetyTab />. The flagged-prompts list is not part of the SDK tab -- the host injects it. These are the pieces a full mount wires:

  • FlaggedPromptsModal -- your modal; it gets isOpen, onClose, mentorId, tenantKey, username.
  • showFlaggedPrompts -- whether to show the button. Gate it on the /mentors/<agent db id>/#view_moderation_logs grant.
  • renderPromptContent -- how the cards render prompt text (plain text by default, so line breaks and Markdown are lost). Pass a Markdown renderer to keep them.
  • executeGatedAction on the provider -- wraps the two Active switches, for a paywall or free-trial check. Prompt edits are not gated.

A minimal flagged-prompts modal, built on the starter's Dialog:

// app/(app)/agents/[mentorId]/safety/flagged-prompts-modal.tsx
"use client";

import { useGetModerationLogsQuery } from "@iblai/iblai-js/data-layer";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";

type FlaggedPromptsModalProps = {
  isOpen: boolean;
  onClose: () => void;
  mentorId: string;
  tenantKey: string;
  username: string;
};

type ModerationLog = {
  id: number;
  username: string;
  email: string | null;
  prompt: string;
  reason: string;
  target_system: string;
  date_created: string;
};

export function FlaggedPromptsModal({
  isOpen,
  onClose,
  mentorId,
  tenantKey,
  username,
}: FlaggedPromptsModalProps) {
  const { data, isLoading } = useGetModerationLogsQuery(
    {
      org: tenantKey,
      // @ts-expect-error userId (the path's user) is read at runtime but missing from the type
      userId: username,
      mentor: mentorId,
      page: 1,
      pageSize: 20,
      ordering: "-date_created",
    },
    { skip: !isOpen },
  );
  const logs = (data?.results ?? []) as unknown as ModerationLog[];

  return (
    <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
      <DialogContent className="sm:max-w-2xl">
        <DialogHeader>
          <DialogTitle>Flagged prompts</DialogTitle>
          <DialogDescription>
            {data?.count ?? 0} messages stopped by the Moderation Prompt.
          </DialogDescription>
        </DialogHeader>
        <div className="max-h-[60vh] space-y-3 overflow-y-auto">
          {isLoading && <p className="text-sm text-gray-500">Loading…</p>}
          {!isLoading && logs.length === 0 && (
            <p className="py-8 text-center text-sm text-gray-500">No flagged prompts for this agent.</p>
          )}
          {logs.map((log) => (
            <div key={log.id} className="rounded-md border border-gray-200 p-3">
              <div className="flex justify-between gap-4 text-xs text-gray-500">
                <span>{log.email ?? log.username}</span>
                <span>
                  {log.target_system} · {new Date(log.date_created).toLocaleString()}
                </span>
              </div>
              <p className="mt-2 text-sm text-gray-900">{log.prompt}</p>
              <p className="mt-1 text-xs text-gray-600">{log.reason}</p>
            </div>
          ))}
        </div>
      </DialogContent>
    </Dialog>
  );
}

userId is the signed-in user (the path segment). SDK 2.36.2 types the hook's argument without it, but the request needs it -- hence the @ts-expect-error; username in that type filters to the flagged user instead. mentor filters to this agent. Add search, a date range and paging the same way. This list holds Moderation System rows only: replies the Safety Prompt blocked are served by …/safety-logs/ (same query and row shape), which has no data-layer hook in SDK 2.36.2 -- call it from a server route (/iblai-api-agent-safety).

The page loads the agent's grants, gates the button, and passes the paywall gate through a nested provider:

// app/(app)/agents/[mentorId]/safety/page.tsx
"use client";

import { useEffect, useState } from "react";
import {
  AgentSafetyTab,
  AgentSettingsProvider,
  useAgentSettings,
} from "@iblai/iblai-js/web-containers/next";
import {
  useGetMentorSettingsQuery,
  useGetRbacPermissionsMutation,
} from "@iblai/iblai-js/data-layer";
import { checkRbacPermission } from "@iblai/iblai-js/web-utils";
import { FlaggedPromptsModal } from "./flagged-prompts-modal";

// Module scope: the provider memoizes on this function, so an inline arrow
// would rebuild the context on every render.
const executeGatedAction = (action: () => unknown) => action();

export default function AgentSafetyPage() {
  const settings = useAgentSettings();
  const { tenantKey, mentorId, username } = settings;
  const [grants, setGrants] = useState<object>({});
  const [getRbacPermissions] = useGetRbacPermissionsMutation();
  const { data } = useGetMentorSettingsQuery({
    mentor: mentorId,
    org: tenantKey,
    // @ts-expect-error userId is accepted at runtime
    userId: username,
  });
  const mentorDbId = (data as { mentor_id?: number } | undefined)?.mentor_id;

  useEffect(() => {
    if (!mentorDbId) return;
    getRbacPermissions({
      requestBody: { platform_key: tenantKey, resources: [`/mentors/${mentorDbId}/`] },
    })
      .unwrap()
      .then((permissions) => setGrants({ ...permissions }))
      .catch(() => {});
  }, [mentorDbId, tenantKey, getRbacPermissions]);

  return (
    <div className="flex h-full flex-col bg-white">
      <AgentSettingsProvider
        {...settings}
        rbacPermissions={grants}
        executeGatedAction={executeGatedAction}
      >
        <AgentSafetyTab
          FlaggedPromptsModal={FlaggedPromptsModal}
          showFlaggedPrompts={checkRbacPermission(
            grants,
            `/mentors/${mentorDbId}/#view_moderation_logs`,
          )}
          renderPromptContent={(content) => (
            <p className="whitespace-pre-wrap text-sm text-gray-700">{content}</p>
          )}
        />
      </AgentSettingsProvider>
    </div>
  );
}

Replace executeGatedAction with your paywall or trial check (run action once the user may proceed), defined outside the component or memoized. The grant path uses the agent's numeric mentor_id, not the UUID. Without a FlaggedPromptsModal the button never shows, whatever showFlaggedPrompts says (it defaults to true).

Step 3: Customize Labels (Optional)

The tab renders with the default agent-facing copy (AGENT_SAFETY_TAB_LABELS, localized through the SDK's i18n). Pass a partial labels object to change any string:

import { AgentSafetyTab } from "@iblai/iblai-js/web-containers/next";

<AgentSafetyTab
  labels={{
    header: { title: "Guardrails" },
    actions: { viewFlaggedPrompts: "Blocked messages" },
  }}
/>;

Label groups (SafetyTabLabels): header (title, description), prompts -- moderation and safety (title, tooltip, activeLabel, inactiveLabel), moderationResponse and safetyResponse (title) -- actions (edit, viewFlaggedPrompts), and toasts (updateSuccess, updateError).

Step 4: Use MCP Tools for Customization

get_component_info("AgentSafetyTab")
get_component_info("AgentSettingsProvider")

<AgentSafetyTab> Props

Import from @iblai/iblai-js/web-containers/next.

PropTypeRequiredDescription
FlaggedPromptsModalComponentType<{ isOpen, onClose, mentorId, tenantKey, username }>NoYour flagged-prompts modal. Without it there is no button
showFlaggedPromptsbooleanNoShow View Flagged Prompts. Defaults to true; gate it on #view_moderation_logs
renderPromptContent(content: string) => ReactNodeNoRender card text (e.g. Markdown). Defaults to plain text
labelsDeepPartial<SafetyTabLabels>NoOverride user-visible strings

Identity, enableRBAC (per-field permissions on the cards) and executeGatedAction come from AgentSettingsProvider.

From @iblai/iblai-js/web-containers/next:

  • AGENT_SAFETY_TAB_LABELS -- the default label bundle.
  • SafetySelectedPrompt, SafetyEditFormValues -- the edit dialog's prompt and form types.
  • AgentSafetyTabProps, SafetyTabLabels -- types.

How it saves

Every change is one field on PUT /api/ai-mentor/orgs/{org}/users/{username}/mentors/{uuid}/settings/:

ControlField
Moderation Prompt switch / Saveenable_moderation / moderation_system_prompt
Safety Prompt switch / Saveenable_safety_system / safety_system_prompt
Moderation Response Savemoderation_response
Safety Response Savesafety_response

The flagged-prompts modal above reads GET /api/ai-mentor/orgs/{org}/users/{username}/moderation-logs/?mentor=<uuid> (rows: prompt, reason, target_system, date_created, and who sent it). Safety Prompt catches are at …/safety-logs/?mentor=<uuid> instead.

Platform data

HookPurpose
useGetMentorSettingsQuery / useEditMentorMutationRead and save the prompts, responses and switches
useGetModerationLogsQuery / useDeleteModerationLogMutationFlagged prompts, for your modal
useGetRbacPermissionsMutationLoad the #view_moderation_logs grant (Step 2)

All from @iblai/iblai-js/data-layer. REST twin: /iblai-api-agent-safety.

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 <ID> · … 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/<uuid>/safety in the browser as /iblai-vibe-ops-test says, signed in as an org admin: the four cards and View Flagged Prompts render; with moderation Active, send the agent a clearly harmful request in chat -- it shows up in the modal; read the console; check the phone width.

Important Notes

  • Switches save at once: flipping Active changes the agent for everyone who chats with it. Try changes on a test agent.
  • Moderation runs before the agent, safety after: a moderated message never reaches the agent; a reply that fails the safety check is replaced by the Safety Response.
  • Shared provider: mount AgentSettingsProvider once at the layout level (/iblai-vibe-agent §1); the page above nests a second one only to add grants and the gate.
  • Peer deps: sonner and @iblai/iblai-web-mentor (pnpm add sonner @iblai/iblai-web-mentor).
  • Brand guidelines: BRAND.md

Copyright © ibl.ai | support@iblai.zendesk.com