polyc-tools 2026.9.0

The in-process tool core for polychrome agents: local executors (coding, web fetch, wallet, ...), the tool registry, and MCP composition. The networked connectors live in polyc-connectors.
//! Spec for the agent-evaluable admin `invite` tool (#698).
//!
//! An admin asking in natural language — "create an invite for @someone",
//! "give them access", "onboard @someone" — should be handled as an admin
//! invite, not mis-answered by the model. This tool lets the agent recognize
//! that intent and hand it to the control plane, which enforces the admin gate,
//! mints the single-use code, and routes it to the edge for **private**
//! delivery to the target's direct message.
//!
//! Like the wallet and history families it has no in-process implementation:
//! the conversation sandbox can't reach the persona store, and — crucially —
//! it can't privately message the target. The harness advertises it via the
//! same control-plane proxy; the control plane runs it (admin-gated) and
//! attaches the minted code to the turn result for the edge to deliver. The
//! agent only ever sees a non-secret acknowledgement: the code is never in the
//! tool result, the agent's reply, or a channel.
//!
//! This is onboarding a PERSON to Polychrome — distinct from `wallet_link`,
//! which connects the caller's own spending wallet. Redeeming an invite stays
//! edge-only (identity comes from the signed event, never the model); only the
//! admin INVITE is agent-evaluable.

use polyc_llm::ToolSpec;
use serde_json::json;

/// The `invite` tool name.
pub const TOOL_NAME: &str = "invite";

/// Every invite tool name, for allowlist checks and dispatch (one, today).
pub const ALL: &[&str] = &[TOOL_NAME];

/// The required argument: the target's provider-native user id, taken from the
/// mention markup already in the agent's input.
pub const ARG_TARGET_USER_ID: &str = "target_user_id";

/// Every invite tool spec.
#[must_use]
pub fn all_specs() -> Vec<ToolSpec> {
    vec![invite_spec()]
}

/// `invite` spec — an admin onboards a person to Polychrome.
///
/// Admin-only: the control plane refuses a non-admin caller and mints nothing.
/// Not read-only (it provisions the target's persona and mints a code) and not
/// egress. The code is delivered privately by the edge, never returned to the
/// agent.
#[must_use]
pub fn invite_spec() -> ToolSpec {
    ToolSpec::new(
        TOOL_NAME,
        "For an admin only: invite a specific person to Polychrome. Use it when \
         an admin asks to invite, onboard, or give someone access — for example \
         \"create an invite for @Vitor\" or \"give @sam access\". Pass the target's \
         user id EXACTLY as it appears in the mention markup in the message (the \
         id inside `<@...>`), never a typed-out name. The invite goes only to \
         that person, privately, as a single-use code sent to their direct \
         message; you never see the code and must not ask for or repeat it — just \
         confirm the invite is on its way. This is for onboarding a PERSON, which \
         is different from wallet_link (connecting someone's own spending \
         wallet). If the person asking isn't an admin, it returns a refusal \
         rather than an invite.",
        json!({
            "type": "object",
            "properties": {
                ARG_TARGET_USER_ID: {
                    "type": "string",
                    "description": "The target's provider-native user id, taken \
                        verbatim from the mention markup (`<@U...>`) in the \
                        message — not a display name or handle."
                }
            },
            "required": [ARG_TARGET_USER_ID],
            "additionalProperties": false
        }),
    )
    .titled("Invite someone to Polychrome (admin)")
}