Skip to content

docs: Document 3 new features from PraisonAI PR #1504 (clarify tool, tool availability gating, unknown-user pairing) #223

@MervinPraison

Description

@MervinPraison

Context

PraisonAI PR #1504 (link) merged 2026-04-22 adds three user-facing features that have no current documentation. This issue tracks the docs work needed.

Features shipped in PR #1504

  1. Tool availability gating via @tool(availability=...) — hide tools from the LLM when they can't physically run (missing API key / dep / service).
  2. Unknown-user pairing flow — bot unknown_user_policy + praisonai pairing CLI to securely onboard new bot users.
  3. clarify tool — first-class tool agents call to ask a single clarifying question mid-task.

Each is opt-in and backward compatible.


Required Documentation Work

Two NEW pages + ONE UPDATE to an existing page.

Important

Per repo AGENTS.md: All new pages go under docs/features/. Do NOT add anything under docs/concepts/. Add entries to docs.json under the Features group, not Concepts.

✅ Task 1 — NEW page: docs/features/clarify-tool.mdx

Status in docs today: Does not exist (search confirms no matches in docs/ for clarify as a tool).

SDK source of truth (read these before writing):

  • src/praisonai-agents/praisonaiagents/tools/clarify.py (198 lines, new)
  • src/praisonai-agents/praisonaiagents/bots/config.py (adds "clarify" to bot default auto-approve list)
  • src/praisonai-agents/tests/unit/tools/test_clarify_tool.py (247 lines — usage patterns)

What it does: Lets the agent pause mid-run and ask the user a focused question with optional predefined choices, instead of guessing.

Public API (verbatim from SDK):

from praisonaiagents.tools.clarify import (
    clarify,                       # default ClarifyTool() instance
    ClarifyTool,                   # class
    ClarifyHandler,                # handler wrapper around an ask callback
    create_cli_clarify_handler,    # factory for CLI (stdin)
    create_bot_clarify_handler,    # factory for bots (send_message_fn + wait_for_reply_fn)
)

Tool metadata:

  • name = "clarify"
  • description = "Ask the user a focused clarifying question when genuinely ambiguous. Use sparingly - only when you cannot proceed without their input."

Tool signature (what the LLM calls):

  • clarify(question: str, choices: Optional[List[str]] = None) -> str

Handler resolution order (inside ClarifyTool.run):

  1. kwargs["ctx"]["clarify_handler"] (either a ClarifyHandler or a callable returning sync/async str)
  2. The tool's own self.handler (defaults to ClarifyHandler() with no callback)
  3. Fallback string: "No interactive channel available. Please proceed with your best judgment for: {question}"

CLI handler behavior (create_cli_clarify_handler):

  • Prints 🤔 {question}, then numbered choices if provided
  • Accepts number input or raw text
  • Returns "skip" on KeyboardInterrupt / EOF

Bot handler behavior (create_bot_clarify_handler(send_message_fn, wait_for_reply_fn)):

  • Sends formatted question (with numbered choices)
  • Awaits reply
  • If choices present and reply is a valid digit, returns that choice
  • Returns "no answer provided" on empty reply

Bot auto-approve list now includes "clarify" (so the bot won't block the call on an approval prompt by default).

Required structure (per AGENTS.md template): frontmatter → one-sentence intro → hero Mermaid diagram → Quick Start <Steps> → How It Works (sequence diagram) → Configuration / Handler options → Common Patterns (CLI, bot, custom context handler) → Best Practices <AccordionGroup> → Related <CardGroup>.

Agent-centric Quick Start (the top example, per §1.1 rule 9):

from praisonaiagents import Agent
from praisonaiagents.tools.clarify import clarify

agent = Agent(
    name="Writer",
    instructions="Write code. If the requirement is genuinely ambiguous, call clarify once.",
    tools=[clarify],
)

agent.start("Build me a web scraper")
# Agent may emit: clarify(question="Which language?", choices=["python", "rust"])

User-interaction flow to show (per §1.1 rule 10):

  • CLI: prompt appears in terminal, user types answer/number, agent resumes
  • Bot (Telegram/Discord/Slack): bot DMs the question with numbered choices, user replies, agent resumes
  • UI: interactive component (describe conceptually — SDK does not ship a UI component yet)

Mermaid hero diagram — use the standard color palette from AGENTS.md (§3.1). Show: Agent → clarify() → Channel (CLI/Bot/UI) → User → back to Agent.

Icon suggestion: messages-question or circle-question (Mintlify icon; verify availability).

docs.json: Add under Features group, after the existing tools entries.


✅ Task 2 — NEW page: docs/features/tool-availability.mdx

Status in docs today: Not documented. The @tool decorator itself has no dedicated page and no mention of availability. Search for check_availability / availability in docs/ returns zero hits related to this feature.

SDK source of truth (read these before writing):

  • src/praisonai-agents/praisonaiagents/tools/decorator.py (adds availability kwarg + check_availability() method on FunctionTool)
  • src/praisonai-agents/praisonaiagents/tools/protocols/tool_protocol.py (new ToolAvailabilityProtocol)
  • src/praisonai-agents/praisonaiagents/tools/registry.py (new list_available_tools() method + module-level function)
  • src/praisonai-agents/praisonaiagents/tools/__init__.py (exports the new registry function)
  • src/praisonai-agents/tests/unit/tools/test_availability_gating.py (185 lines — usage patterns)

What it does: A tool author can provide a availability callable returning (is_available: bool, reason: str). At schema-build time, unavailable tools are filtered from the list sent to the LLM, so the model can't hallucinate a call to a tool whose env/API key/service isn't ready. Zero runtime cost for available tools — check only at schema build.

Public API (verbatim from SDK):

Decorator form:

from praisonaiagents.tools import tool
import os

@tool(availability=lambda: (bool(os.getenv("API_KEY")), "API_KEY missing"))
def search_web(query: str) -> str:
    return "results"

Class form (for BaseTool subclasses) — implement the protocol:

from praisonaiagents.tools.base import BaseTool

class MyTool(BaseTool):
    name = "mytool"
    description = "..."

    def check_availability(self) -> tuple[bool, str]:
        if not self.api_key:
            return False, "API key not configured"
        return True, ""

Protocol (new, exported from praisonaiagents.tools.protocols):

from praisonaiagents.tools.protocols import ToolAvailabilityProtocol

Signature: check_availability(self) -> tuple[bool, str]. Runtime-checkable. Called at schema-build time — must be fast, no I/O.

Registry / filtering (new):

from praisonaiagents.tools import list_available_tools, get_registry

# Only tools that pass their check
available = list_available_tools()

# All registered tools, regardless of availability
all_tools = get_registry().list_tools()

Behavior rules to document (from registry.py and decorator.py):

  • Tools without check_availability are treated as always-available.
  • If check_availability() raises, the tool is treated as unavailable and the exception message becomes the reason: f"Availability check failed: {e}" — a warning is logged.
  • Plain functions registered via register_function are always considered available (no protocol for them yet).

Agent-centric Quick Start (top of page):

import os
from praisonaiagents import Agent
from praisonaiagents.tools import tool

@tool(availability=lambda: (bool(os.getenv("SERP_API_KEY")), "SERP_API_KEY not set"))
def search_web(query: str) -> str:
    """Search the web."""
    ...

agent = Agent(
    name="Researcher",
    instructions="Research the topic the user asks about.",
    tools=[search_web],
)
# If SERP_API_KEY is missing, the LLM will never see search_web.
agent.start("Research quantum computing")

User-interaction flow to include:

  1. Dev defines availability on a tool that needs an API key / Docker / DB.
  2. Env var missing → tool hidden from LLM → agent doesn't hallucinate calls.
  3. Env var set → tool appears → agent uses it normally.

Hero Mermaid diagram: Show Tool Registry → check_availability → [pass|fail] → LLM sees / doesn't see.

Icon suggestion: toggle-on or shield-check.

docs.json: Add under Features group. If a "Tools" sub-group exists, nest it there.


✅ Task 3 — UPDATE existing page + NEW page: Bot pairing

Current state of docs/best-practices/bot-security.mdx:

The page already describes pairing but is littered with <Warning> blocks saying the feature is conceptual / planned / may not exist. As of PR #1504, it is real and shipping. These disclaimers must be removed and APIs aligned to the new SDK.

SDK source of truth (read these before writing):

  • src/praisonai-agents/praisonaiagents/bots/config.py — adds unknown_user_policy: Literal["deny", "allow", "pair"] = "deny" to BotConfig
  • src/praisonai/praisonai/bots/_auth.py (140 lines, new) — UnknownUserHandler
  • src/praisonai/praisonai/bots/telegram.py — wires handler into Telegram adapter (Discord/Slack/WhatsApp are NOT wired yet in this PR)
  • src/praisonai/praisonai/cli/commands/pairing.py (169 lines, new) — praisonai pairing Typer app
  • src/praisonai/praisonai/cli/app.py — registers pairing subcommand
  • src/praisonai/praisonai/gateway/pairing.pyPairingStore gains persistent per-install secret, generate_code(channel_id=...), verify_and_pair(channel_id=None, ...), list_pending(), persists pending codes to disk

Task 3a — UPDATE docs/best-practices/bot-security.mdx:

Remove / replace the following three warning blocks (they are now factually wrong):

  1. Around the "Gateway Pairing" section:

    "The pairing system described below represents planned functionality. Current SDK implementation may differ. Verify against actual SDK documentation."

    → Delete. Replace with a pointer/link to the new dedicated docs/features/bot-pairing.mdx page.

  2. Around "Doctor Security Check":

    "The doctor command shown may not be available in current SDK version. Verify implementation status."

    Verify against current SDK (praisonai/cli/features/doctor/checks/bot_checks.py exists in the synced tree). If available, drop the warning; if still gated, keep a scoped, accurate note.

  3. In "Quick Start" — remove the comment lines:

    # Note: Security features shown are conceptual
    # Actual implementation may vary
    # Security configuration would go here
    # when implemented in the SDK

    Replace the Quick Start with a real, runnable example using the new unknown_user_policy field on BotConfig:

    from praisonaiagents.bots import BotConfig
    
    config = BotConfig(
        allowed_users=["@alice", "123456789"],
        unknown_user_policy="pair",   # "deny" (default) | "allow" | "pair"
    )

Also update the "Gateway Pairing" section to reflect the real flow:

  • PRAISONAI_GATEWAY_SECRET env var is no longer required — if unset, a per-install secret is auto-generated at <store_dir>/.gateway_secret with 0600 perms and reused across restarts. Env var still takes precedence when set. (gateway/pairing.py::_load_or_create_secret)
  • generate_code now accepts a channel_id — binding a code to a channel at generation time.
  • verify_and_pair accepts channel_id=None when the code was generated with a bound channel.

Task 3b — NEW page docs/features/bot-pairing.mdx:

Dedicated agent-centric page covering the end-to-end pairing flow. Bot-security page should link here.

Key content:

  1. One-liner: "Let unknown users self-request access to your bot with a secure 8-char code that you approve from the CLI."

  2. Hero Mermaid diagram: Unknown User → Bot DM → UnknownUserHandler → PairingStore.generate_code → Bot replies with code → Owner runs "praisonai pairing approve ..." → User now allowed.

  3. Agent-centric Quick Start:

    from praisonaiagents import Agent
    from praisonaiagents.bots import BotConfig
    # ... bot adapter wiring (Telegram in this PR)
    
    config = BotConfig(
        allowed_users=["@owner"],
        unknown_user_policy="pair",  # new
    )

    Then from the owner's terminal:

    praisonai pairing list
    praisonai pairing approve telegram ABCD1234 --label "alice"
    praisonai pairing revoke telegram 987654321
    praisonai pairing clear --confirm
  4. Policy table (from config.py comments):

    Policy Behavior
    deny (default, backward compatible) silently drop messages from unknown
    allow allow everyone (overrides allowed_users)
    pair run pairing flow: generate code, DM user, wait for owner approval
  5. CLI command reference — document every flag in src/praisonai/praisonai/cli/commands/pairing.py:

    • praisonai pairing list [--store-dir PATH]
    • praisonai pairing approve PLATFORM CODE [CHANNEL_ID] [--label TEXT] [--store-dir PATH]
      • CHANNEL_ID is optional when the code was generated with a bound channel_id (the bot does this).
      • Platforms shipped with handler wiring: telegram (only). Document discord/slack/whatsapp as valid platform strings for PairingStore but note adapter wiring is pending.
    • praisonai pairing revoke PLATFORM CHANNEL_ID [--store-dir PATH]
    • praisonai pairing clear [--confirm] [--store-dir PATH]
  6. Security model to surface:

    • Codes are 8-char (from generate_code), HMAC-signed with per-install secret.
    • Codes have TTL (code_ttl, check PairingStore.__init__ default) — consumed on approve.
    • Per-install secret auto-persisted to <store_dir>/.gateway_secret mode 0600 when PRAISONAI_GATEWAY_SECRET is unset. A warning is logged if file perms drift from 0600.
    • Rate limiting on code generation: 600 s (10 min) window per channel_id (see UnknownUserHandler._rate_limit_window).
    • Codes and pairings both persist through restarts (atomic tempfile+rename write).
  7. User-interaction flow (step-by-step, per §1.1 rule 10):

    • Stranger DMs your Telegram bot.
    • UnknownUserHandler checks PairingStore.is_paired(channel_id, "telegram") — no.
    • Rate-limit OK → PairingStore.generate_code(channel_type="telegram", channel_id=<their chat id>).
    • Bot sends: "Your pairing code: `ABCD1234`\nOwner: `praisonai pairing approve telegram ABCD1234`"
    • Owner runs that command → channel marked paired → next DM is processed normally.
  8. Sequence Mermaid diagram of the above flow (per §3.4 template).

  9. Best Practices accordion group:

    • "Keep unknown_user_policy='deny' in dev / allow-less environments"
    • "Treat .gateway_secret like a secret — back it up, don't commit it"
    • "Use --label on approve so you can tell pairing list rows apart"
    • "Monitor the rate-limit warnings — they're a signal of probing"

Icon suggestion: handshake or user-plus.

docs.json: Add under Features group near other bot-related entries (bot-gateway, bot-routing, messaging-bots, bot-commands).


Cross-Cutting Checklist for All Three Pages

Per AGENTS.md (root of this repo):

  • Read SDK source before writing each page (§1.2 cycle)
  • Place every new file under docs/features/never docs/concepts/
  • Mintlify frontmatter: title, sidebarTitle, description, icon
  • Hero Mermaid diagram with the standard palette (#8B0000, #189AB4, #10B981, #F59E0B, #6366F1, white text, #7C90A0 strokes)
  • Quick Start uses <Steps>; Best Practices uses <AccordionGroup>; Related uses <CardGroup>
  • Top example is agent-centric (starts from Agent(...), not from the low-level class)
  • Shortest-possible imports: from praisonaiagents.tools import tool, from praisonaiagents.tools.clarify import clarify, from praisonaiagents.bots import BotConfig
  • No forbidden phrases ("In this section...", "As you can see...", "It's important to note...", etc.)
  • Active voice, one-sentence section intros
  • Configuration options documented against SDK ground truth (no guessing, no invented params)
  • Include a user-interaction flow section for each feature (§1.1 rule 10)
  • Update docs.json under the Features group (not Concepts). Validate JSON after edit.
  • Cross-link: clarify ↔ tools overview; tool-availability ↔ @tool decorator / tools overview; bot-pairing ↔ bot-security + messaging-bots.

Deliverables

  1. docs/features/clarify-tool.mdx (new)
  2. docs/features/tool-availability.mdx (new)
  3. docs/features/bot-pairing.mdx (new)
  4. docs/best-practices/bot-security.mdx (update — drop conceptual warnings, fix examples, link to bot-pairing)
  5. docs.json entries for the three new pages under Features
  6. Single PR targeting main with the 4 content edits + docs.json

Out of Scope

  • Auto-generated docs/js/ and docs/rust/ parity pages — handled by the parity generator, not this issue.
  • Any files under docs/concepts/ — human-approved only.
  • Discord/Slack/WhatsApp pairing adapters — wiring for non-Telegram adapters is not in PR #1504; document as "Telegram-only currently" where relevant.

Created automatically in response to webhook pull_request.closed on PraisonAI#1504.

Metadata

Metadata

Assignees

No one assigned

    Labels

    claudeTrigger Claude Code analysisdocumentationImprovements or additions to documentationquestionFurther information is requestedsecurity

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions