> ## Documentation Index
> Fetch the complete documentation index at: https://gigi-f9937525.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Bot Commands

> Current Discord commands and command documentation rules.

# Bot Commands

This page is the canonical guide for commands Gigi can run today.

## Documentation Rule

Every new or changed Discord command must update this page in the same change.

Each command entry must include:

* current status: live, planned, or not implemented
* surface: slash command, DM, guild mention, prefix, button, or select menu
* required Gigi capability
* Discord bot permission requirements
* options and inputs
* example usage
* expected response
* failure cases
* privacy or audit behavior

Do not document planned behavior as shipped behavior. If a command is planned, mark it planned and keep it out of the live command list.

## Live Commands

### `/ping`

Status: live for current-channel retained `full` memory messages.

Surface: slash command.

Required Gigi capability: none.

Discord bot permissions: normal slash command access.

Options: none.

Example:

```text theme={null}
/ping
```

Expected response:

```text theme={null}
pong
```

Failure cases:

* command is missing if slash command sync has not run
* bot is offline if no response appears

Privacy and audit: no privileged action; no permission audit required.

### DM `ping`

Status: live.

Surface: direct message.

Required Gigi capability: none.

Discord bot permissions: bot must be able to receive/send DMs for that user.

Example:

```text theme={null}
ping
```

Expected response:

```text theme={null}
pong
```

Failure cases:

* user blocks bot DMs
* bot is offline

### Guild Mention `ping`

Status: live.

Surface: guild message that mentions Gigi.

Required Gigi capability: none.

Discord bot permissions: read/send messages in the channel.

Example:

```text theme={null}
@Gigi ping
```

Expected response:

```text theme={null}
pong
```

Failure cases:

* bot cannot read/send in the channel
* message does not mention Gigi

Ordinary guild messages without a Gigi mention are ignored.

## Permission Commands

All `/permissions` commands are live.

Surface: slash command.

Required Gigi capability: `capability.manage`.

Admin bootstrap: Discord guild owner or a member with Discord `ADMINISTRATOR` permission can pass through admin override.

Responses: ephemeral.

Audit: permission checks and permission changes are audited.

Discord bot permissions:

* `role create` requires Gigi's bot role to have `Manage Roles`
* `role assign` and `role unassign` require `Manage Roles`
* Gigi's bot role must be higher than any role it assigns, removes, or manages
* channel membership or channel admin status is not enough

### `/permissions role create`

Creates a Discord role and grants a preset capability set to that role.

Syntax:

```text theme={null}
/permissions role create name:<role-name> preset:<preset>
```

Example:

```text theme={null}
/permissions role create name:Gigi Plugin Managers preset:plugin-manager
```

Expected response:

```text theme={null}
Created role @Gigi Plugin Managers and granted preset `plugin-manager`.
```

Failure cases:

* requester lacks `capability.manage` and has no admin override
* Gigi lacks `Manage Roles`
* role name is missing or too long
* preset is unknown

### `/permissions role assign`

Assigns a Discord role to a user.

Syntax:

```text theme={null}
/permissions role assign role:<role> user:<user>
```

Example:

```text theme={null}
/permissions role assign role:@Gigi Plugin Managers user:@person
```

Expected response:

```text theme={null}
Assigned role @Gigi Plugin Managers to user @person.
```

Failure cases:

* requester lacks `capability.manage` and has no admin override
* Gigi lacks `Manage Roles`
* Gigi's bot role is not higher than target role
* role or user is missing

### `/permissions role unassign`

Removes a Discord role from a user.

Syntax:

```text theme={null}
/permissions role unassign role:<role> user:<user>
```

Expected response:

```text theme={null}
Unassigned role @Gigi Plugin Managers from user @person.
```

Failure cases match `/permissions role assign`.

### `/permissions role grant`

Grants one capability to an existing Discord role.

Syntax:

```text theme={null}
/permissions role grant role:<role> capability:<capability>
```

Example:

```text theme={null}
/permissions role grant role:@Gigi Plugin Managers capability:plugin.install
```

Expected response:

```text theme={null}
Granted `plugin.install` for role @Gigi Plugin Managers.
```

Failure cases:

* requester lacks `capability.manage` and has no admin override
* capability is unknown or invalid
* role is missing

### `/permissions role revoke`

Revokes one capability from an existing Discord role.

Syntax:

```text theme={null}
/permissions role revoke role:<role> capability:<capability>
```

Expected response:

```text theme={null}
Revoked `plugin.install` for role @Gigi Plugin Managers.
```

Failure cases match `/permissions role grant`.

### `/permissions role grant-preset`

Grants a preset capability set to an existing Discord role.

Syntax:

```text theme={null}
/permissions role grant-preset role:<role> preset:<preset>
```

Example:

```text theme={null}
/permissions role grant-preset role:@Gigi Relay preset:relay-user
```

Expected response:

```text theme={null}
Granted preset `relay-user` for role @Gigi Relay.
```

Failure cases:

* requester lacks `capability.manage` and has no admin override
* preset is unknown
* role is missing

### `/permissions role revoke-preset`

Revokes a preset capability set from an existing Discord role.

Syntax:

```text theme={null}
/permissions role revoke-preset role:<role> preset:<preset>
```

Expected response:

```text theme={null}
Revoked preset `relay-user` for role @Gigi Relay.
```

Failure cases match `/permissions role grant-preset`.

### `/permissions user grant`

Grants one direct user capability exception.

Syntax:

```text theme={null}
/permissions user grant user:<user> capability:<capability>
```

Example:

```text theme={null}
/permissions user grant user:@person capability:job.admin
```

Expected response:

```text theme={null}
Granted `job.admin` for user @person.
```

Use direct user grants only for rare audited exceptions. Prefer Discord role grants for normal access control.

### `/permissions user revoke`

Revokes one direct user capability exception.

Syntax:

```text theme={null}
/permissions user revoke user:<user> capability:<capability>
```

Expected response:

```text theme={null}
Revoked `job.admin` for user @person.
```

## Presets

| Preset           | Capabilities                                                                                                                                                                                                                                                                          |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gigi-admin`     | `capability.manage`, `plugin.install`, `job.admin`, `job.read`, `job.schedule`, `job.write`, `web.search`, `web.fetch`, `agent.analytics`, `agent.reply_latency.manage`, `llm.provider.write`, `llm.provider.test`, `llm.provider.select`, `memory.read.guild`, `memory.manage.guild` |
| `plugin-manager` | `plugin.install`                                                                                                                                                                                                                                                                      |
| `job-operator`   | `job.read`, `job.schedule`, `job.write`                                                                                                                                                                                                                                               |
| `web-reader`     | `web.search`, `web.fetch`                                                                                                                                                                                                                                                             |
| `llm-manager`    | `llm.provider.write`, `llm.provider.test`, `llm.provider.select`                                                                                                                                                                                                                      |
| `memory-reader`  | `memory.read.guild`                                                                                                                                                                                                                                                                   |
| `memory-manager` | `memory.read.guild`, `memory.manage.guild`                                                                                                                                                                                                                                            |
| `relay-user`     | `relay.dispatch`, `relay.receive`                                                                                                                                                                                                                                                     |

## Capabilities

| Capability                   | What it allows                                                                  |
| ---------------------------- | ------------------------------------------------------------------------------- |
| `capability.manage`          | Grant or revoke role/user capabilities                                          |
| `plugin.install`             | Install or configure guild external app integrations                            |
| `job.admin`                  | Inspect, retry, or cancel durable jobs                                          |
| `job.read`                   | List durable jobs through approved agent tooling                                |
| `job.schedule`               | Schedule allowed durable jobs through confirmation-gated agent tooling          |
| `job.write`                  | Cancel queued or failed durable jobs through confirmation-gated agent tooling   |
| `web.search`                 | Run capped public web searches through approved agent tooling                   |
| `web.fetch`                  | Fetch capped public text or HTML content through SSRF-filtered agent tooling    |
| `agent.analytics`            | View aggregate guild agent-runtime stats                                        |
| `agent.reply_latency.manage` | Enable or disable the guild reply latency footer                                |
| `memory.read.guild`          | Read permitted guild memory scope                                               |
| `memory.manage.guild`        | Enable guild memory channels, configure retention, queue sync, and purge memory |
| `relay.dispatch`             | Ask Gigi to send a mediated message                                             |
| `relay.receive`              | Receive mediated messages from Gigi                                             |
| `llm.provider.write`         | Set, rotate, or delete guild-scoped provider credentials                        |
| `llm.provider.test`          | Test a guild-scoped provider credential without viewing it                      |
| `llm.provider.select`        | Choose active guild provider/model and view guild usage                         |

Dynamic external app capabilities such as `plugin.run.<id>` are future work and should appear only after restricted plugin authorization exists.

## LLM Commands

The guild-scoped provider, model, and usage commands below are live. Personal BYOK and policy commands remain planned.

Surface: slash command.

Responses: ephemeral.

Credential entry uses a private modal flow, not a normal slash command string option. Gigi never echoes raw provider keys. Audits for provider mutation, provider tests, and model selection include safe metadata only.

### `/llm provider list`

Status: live.

Required Gigi capability: `llm.provider.write`.

Syntax:

```text theme={null}
/llm provider list
```

Expected response:

```text theme={null}
LLM provider credentials:
- `main` - `openai` (active, test: succeeded)
```

Failure cases:

* requester lacks `llm.provider.write`
* provider store is unavailable

Privacy and audit: lists metadata only; does not show secrets, ciphertext, nonce, or fingerprints.

### `/llm provider add`

Status: live when `GIGI_LLM_SECRET_KEY_BASE64` is configured.

Required Gigi capability: `llm.provider.write`.

Syntax:

```text theme={null}
/llm provider add provider:<openai|anthropic|gemini|custom> label:<label>
```

Expected response: private modal titled `Add LLM Credential`.

Failure cases:

* requester lacks `llm.provider.write`
* credential entry is not configured
* provider is unknown
* label is missing, too long, or looks sensitive

Privacy and audit: submitted credential value is sealed before storage. Audit metadata records action and provider only.

### `/llm provider test`

Status: live.

Required Gigi capability: `llm.provider.test`.

Syntax:

```text theme={null}
/llm provider test label:<label>
```

Expected response:

```text theme={null}
Tested `openai` credential `main`: succeeded.
```

Failure cases:

* requester lacks `llm.provider.test`
* credential label is missing or unknown
* provider probe fails

Privacy and audit: response and audit omit raw secrets and provider responses. Last test status is stored on credential metadata.

### `/llm provider rotate`

Status: live when `GIGI_LLM_SECRET_KEY_BASE64` is configured.

Required Gigi capability: `llm.provider.write`.

Syntax:

```text theme={null}
/llm provider rotate label:<label>
```

Expected response: private modal titled `Rotate LLM Credential`.

Failure cases:

* requester lacks `llm.provider.write`
* credential entry is not configured
* active credential label is unknown

Privacy and audit: same private modal and safe audit rules as `/llm provider add`.

### `/llm provider delete`

Status: live.

Required Gigi capability: `llm.provider.write`.

Syntax:

```text theme={null}
/llm provider delete label:<label> confirm:true
```

Expected response:

```text theme={null}
Revoked LLM credential `main`.
```

Failure cases:

* requester lacks `llm.provider.write`
* `confirm` is not true
* credential label is unknown

Privacy and audit: audits safe deletion metadata only.

### `/llm model show`

Status: live.

Required Gigi capability: `llm.provider.select`.

Syntax:

```text theme={null}
/llm model show purpose:<chat|reasoning|embedding|routing>
```

Expected response:

```text theme={null}
Active `chat` model: `gpt-4o-mini` via `openai` (`credential-id`).
```

Failure cases:

* requester lacks `llm.provider.select`
* purpose is unknown
* no active profile exists for that purpose

Privacy and audit: metadata only; no raw secret access is shown.

### `/llm model set`

Status: live.

Required Gigi capability: `llm.provider.select`.

Syntax:

```text theme={null}
/llm model set purpose:<chat|reasoning|embedding|routing> label:<label> model:<auto|model-id>
```

Expected response:

```text theme={null}
Selected `gemini-3.5-flash` for `chat` using credential `main` (auto default for Gemini).
```

`model` is optional. If omitted or set to `auto`, Gigi selects a static default based on the credential provider and purpose. Pass a provider model ID to override the default.

Failure cases:

* requester lacks `llm.provider.select`
* purpose, label, or model is invalid
* no default exists for that provider and purpose
* selected provider does not support that purpose

Privacy and audit: audits provider, purpose, and model metadata only.

### `/llm routing show`

Status: live.

Required Gigi capability: `llm.provider.select`.

Syntax:

```text theme={null}
/llm routing show
```

Expected response:

```text theme={null}
LLM tool routing mode: `off`.
```

### `/llm routing set`

Status: live.

Required Gigi capability: `llm.provider.write`.

Syntax:

```text theme={null}
/llm routing set mode:<off|dry-run|enabled>
```

Modes:

* `off`: deterministic mention routing only; no LLM tool planner calls.
* `dry-run`: LLM may propose memory or external-app tool calls, but Gigi does not execute read-only memory tools or semantic public dispatch.
* `enabled`: LLM may route to allowed native memory tools and public external app dispatch after validation and capability checks.

Privacy and audit: audits routing mode metadata only.

### `/llm usage guild`

Status: live.

Required Gigi capability: `llm.provider.select`.

Syntax:

```text theme={null}
/llm usage guild
```

Expected response:

```text theme={null}
LLM usage for this server: 46 tokens (12 input, 34 output) across 3 requests; 1 failed.
```

Failure cases:

* requester lacks `llm.provider.select`
* usage store is unavailable

Privacy and audit: reads aggregate token and request counts only. Usage events do not store raw prompts, completions, provider responses, or secrets.

This command remains the aggregate token/provider usage view. It is separate from the optional reply latency footer controlled by `/agent stats guild reply-latency:on|off`.

## Agent Commands

### Guild mention boundary

Guild mentions are conversational agent input, not a broad heuristic command parser. Current deterministic pre-planner behavior should stay narrow: liveness `ping`, enabled external app prefix matching, and exact current-channel memory count questions documented below. Flexible mention text should route through LLM planning, registered tools, policy checks, and answer composition.

For explicit behavior, prefer exact slash commands such as `/ask`, `/memory count`, `/plugins dry-run`, `/llm routing show`, and `/agent trace last`. Slash commands are the deterministic source for stable syntax, required capabilities, response shape, and failure cases.

### `/ask`

Status: live.

Required Gigi capability: none for `context:none`; dynamic channel context and channel memory tools require `memory.read.guild` when `context:channel` and tool routing are enabled.

Syntax:

```text theme={null}
/ask question:<text> context:<none|channel> visibility:<public|private>
```

`context:none` routes through agent chat without memory fetches. `context:channel` lets the agent load permitted retained messages from this channel into a bounded context pack and allows the planner to choose current-channel memory tools such as `memory.count`, `memory.search`, and `memory.recent` when `/llm routing` allows it and the requester has `memory.read.guild`.

For guild mentions, if the planner determines no tool or fetched/prior agent context is needed, the agent path yields to the normal chat handler instead of composing an empty tool-result answer. Evidence labels such as `[S1]` are validation markers for agent answer composition and are removed before Discord delivery.

If `visibility` is omitted, `context:none` defaults to public and `context:channel` defaults to private. A private agent response also forces an ephemeral slash response even if the request asked for public visibility.

Examples:

```text theme={null}
/ask question:explain recursion in one paragraph context:none visibility:private
/ask question:how often did we mention postgres here context:channel
```

Privacy and audit: dynamic context fetches are scoped to the current guild channel and require enabled memory plus `memory.read.guild`. The agent path records scoped metadata such as run IDs, step indexes, context scope, source type, result count, truncation flag, tool names, status, capabilities, source IDs, message IDs, citation labels, restore handles, context-pack status markers, run status, and termination reason. It does not store raw prompt text, raw memory snippets, provider responses, embeddings, planned commands, or secrets in audit metadata or durable step observations. Follow-up snapshots omit memory evidence summaries and provenance from `memory.search` and `memory.recent` tool data.

Failure cases and notes:

* `context:channel` but channel memory is off may leave Gigi with no fetched snippets
* requester lacks `memory.read.guild`
* `/llm routing` is off or tool routing is unavailable
* context packs may be truncated because of budget; Gigi should still answer from the bounded pack

When a durable run needs confirmation, Gigi includes the run ID in the response and points to `/agent pending`, `/agent confirm`, and `/agent reject`. Confirming or rejecting updates the pending confirmation record; confirmed actions are not resumed automatically in this slice.

### `/agent trace last`

Status: live.

Required Gigi capability: none. The command only shows your own last agent run in the current channel.

Syntax:

```text theme={null}
/agent trace last visibility:<private|public?>
```

Expected response:

```text theme={null}
Last agent run `agentrun_...`:
status: `succeeded`
- `plan` succeeded routing=`enabled`
- `1` `tool` succeeded tool=`memory.recent` kind=`read`
- `2` `answer` succeeded
```

Use this after a guild mention or `/ask` request to see which planner/tool/answer path Gigi used. The default response is private.

Failure cases:

* no prior agent run exists for you in this channel
* trace storage is unavailable in the current process

Privacy and audit: trace output is safe metadata only. It does not include raw prompts, raw memory snippets, provider responses, secrets, or raw external commands.

### `/agent stats guild`

Status: live.

Surface: slash command.

Required Gigi capability: `agent.analytics` for aggregate stats. `reply-latency:on|off` requires `agent.reply_latency.manage`.

Discord bot permissions: normal slash command access.

Options:

* `period`: optional fixed window, one of `24h`, `7d`, `30d`, or `all`; defaults to `7d`.
* `reply-latency`: optional guild setting toggle, either `on` or `off`; enables or disables the reply latency footer for future guild replies.

Syntax:

```text theme={null}
/agent stats guild
/agent stats guild period:7d
/agent stats guild reply-latency:on
/agent stats guild reply-latency:off
```

Example:

```text theme={null}
/agent stats guild period:7d
/agent stats guild reply-latency:on
```

Expected response:

```text theme={null}
Agent stats for this server (last 7 days):
runs: `9` total (`6` succeeded, `2` failed, `1` canceled)
latency: avg `1.5s`, p50 `1.0s`, p95 `3.0s`, max `4.0s`
steps: `21` total, `14` tool, `5` llm
top reasons: `planner_failed` `2`; `tool_failed` `1`
top tools: `memory.search` `3` ok / `1` failed; `permissions.check` `2` ok / `0` failed
```

When `reply-latency` is provided, Gigi updates the guild setting and reports the new state. `reply-latency:on` enables a footer on future guild replies; `reply-latency:off` disables it.

Use this to inspect aggregate agent-runtime health for the current server. The stats response is ephemeral. If no period is provided, Gigi uses `7d`. If no runs match the selected period, Gigi returns a zero-run aggregate summary for that window. The reply latency footer is not token usage, provider usage, or billing data; it is response elapsed wall time.

Failure cases:

* requester lacks `agent.analytics` for aggregate stats, or `agent.reply_latency.manage` for the reply latency setting
* command is used outside a guild
* stats permission is not configured
* stats storage is unavailable

Privacy and audit: reads aggregate guild counters only. It may show aggregate latency, status counts, termination reasons, and top tool names, but it does not show user IDs, per-user prompts, raw prompt text, memory snippets, provider responses, tool arguments, planned commands, secrets, or individual run details.

Reply latency footer privacy: when enabled, the footer exposes only response elapsed time. It does not expose token counts, provider usage, raw prompts, snippets, tool arguments, provider responses, or per-user details.

### `/agent cancel`

Status: live.

Required Gigi capability: none for the run owner; Discord administrators can cancel another user's run in the same server.

Syntax:

```text theme={null}
/agent cancel run:<agent-run-id>
```

Expected response:

```text theme={null}
Cancellation requested for agent run `agentrun_...`.
```

### `/agent pending`

Status: live.

Required Gigi capability: none for the run owner; Discord administrators can inspect another user's pending confirmation in the same server.

Syntax:

```text theme={null}
/agent pending run:<agent-run-id>
```

Expected response:

```text theme={null}
Pending confirmation for agent run `agentrun_...`.
Tool: `plugin.dispatch`.
Step: `1`.
Confirmation: `agentconfirm_...`.
```

### `/agent confirm`

Status: live.

Required Gigi capability: none for the run owner; Discord administrators can resolve another user's pending confirmation in the same server.

Syntax:

```text theme={null}
/agent confirm run:<agent-run-id>
```

Expected response:

```text theme={null}
Confirmed pending agent action `plugin.dispatch` for run `agentrun_...`.
```

### `/agent reject`

Status: live.

Required Gigi capability: none for the run owner; Discord administrators can resolve another user's pending confirmation in the same server.

Syntax:

```text theme={null}
/agent reject run:<agent-run-id>
```

Expected response:

```text theme={null}
Rejected pending agent action `plugin.dispatch` for run `agentrun_...`.
```

Agent command privacy and audit: run management and stats responses are ephemeral. Run-management audit metadata may include action names, run IDs, confirmation IDs, tool names, and step indexes; stats audit metadata records the stats scope, period, and required capability. Audit events do not include raw prompts or raw tool arguments.

## Planned LLM Commands

These commands are not implemented yet.

### `/llm policy set`

Status: planned.

Required Gigi capability: `llm.provider.write`.

Planned syntax:

```text theme={null}
/llm policy set personal-keys:<off|dm-only|guild-allowed>
```

V1 personal BYOK command direction:

```text theme={null}
/llm key add provider:<openai|anthropic|gemini|custom> label:<label>
/llm key test label:<label>
/llm key prefer surface:<dm|guild> label:<label>
/llm key remove label:<label>
```

Credential entry should use a private modal or private flow, not a normal slash command string option. Gigi should never echo, log, or audit raw provider keys. Personal BYOK pays for reasoning but does not grant Gigi action capabilities.

## Memory Commands

Guild memory starts off, requires explicit per-channel opt-in, and runs live ingestion through a bounded async queue so Discord message handling stays responsive.

### Guild mention memory questions

Status: live for exact current-channel count questions matching `how many times did @user mention "text"?` and, when LLM tool routing is enabled, flexible current-channel count/search phrasing.

Required Gigi capability: `memory.read.guild`.

Example:

```text theme={null}
@Gigi how many times did @sam mention "postgres"?
@Gigi how many times has postgres been mentioned in this channel?
```

Example response:

```text theme={null}
@sam mentioned `postgres` 14 times in this channel.
Messages mentioned `postgres` 19 times in this channel.
```

Count answers use deterministic SQL over indexed messages. When LLM tool routing is enabled, Gigi may use the routing model to choose the memory tool, but it does not ask an LLM to guess aggregate counts.

Failure cases:

* requester lacks `memory.read.guild`
* memory is not enabled for the guild or channel
* target user or text is ambiguous
* indexed data is not ready yet
* requester cannot view channels that contain matching messages

### `/memory count`

Status: live for this-channel exact counts over retained `full` memory messages.

Required Gigi capability: `memory.read.guild`.

Syntax:

```text theme={null}
/memory count text:<string> user:<user?> scope:<this-channel|server>
```

Use this for exact normalized aggregate questions. The response is ephemeral. `server` scope is reserved until channel-visibility filtering lands.

### `/memory search`

Status: live.

Required Gigi capability: `memory.read.guild`.

Syntax:

```text theme={null}
/memory search query:<string> user:<user?> limit:<number?>
```

Returns matching normalized evidence for the current channel with safe author metadata, message IDs, citation labels, source IDs, restore handles, retrieval time, and retention proof. Cross-channel search waits for channel-visibility filtering.

### `/memory ask`

Status: planned.

Required Gigi capability: `memory.read.guild`.

Syntax:

```text theme={null}
/memory ask question:<string> scope:<this-channel|server> since:<date?> include-sources:<true|false>
```

Uses retrieval plus a configured guild `chat` model to answer with citations. Semantic retrieval also needs a guild `embedding` model profile.

### `/memory status`

Status: planned.

Required Gigi capability: `memory.read.guild`.

Syntax:

```text theme={null}
/memory status
```

Shows configured channel modes, retention, default storage mode, and embedding policy. Response is ephemeral.

### `/memory sync`

Status: live for `show` and channel `set`.

Required Gigi capability: `memory.manage.guild`.

Syntax:

```text theme={null}
/memory sync channel:<channel?> since:<date?> confirm:true
```

Queues a resumable backfill job and returns immediately. Workers should fetch Discord history page by page, checkpoint progress, batch inserts, and batch embeddings without blocking the bot.

### `/memory settings`

Status: planned.

Required Gigi capability: `memory.manage.guild`.

Syntax:

```text theme={null}
/memory settings show
/memory settings set channel:<channel> mode:<off|metadata|full> retention-days:<number?>
```

`metadata` mode stores message hashes only in this slice. `full` mode stores normalized text for current-channel counts and future search/retrieval. Retention is required before ingest.

## External App Mention Actions

External app matching is live for guild mentions. Deterministic prefix matching runs first against manifest actions. If no prefix trigger matches and `/llm routing` is `dry-run` or `enabled`, semantic routing can call the configured `routing` model, validate the proposal against enabled manifests, and apply capability checks. `dry-run` returns the planned action only. `enabled` may dispatch public `send_message` actions only when the approved version has stored public-dispatch consent; restricted actions still dry-run. It does not call LangChain, run harnesses, or use LLM output as dispatch authority.

Dispatch is opt-in per approved version and per action. If a matched action has `dispatch: "send_message"`, `safety: "public"`, empty `permissions`, and stored public-dispatch consent from import, Gigi sends the planned prefix command into the channel as Gigi. If the action omits that dispatch mode, lacks stored consent, or has non-empty `permissions`, Gigi returns a dry-run response instead.

Prefix trigger `value` is the command Gigi sends. Trigger `aliases` are optional user-facing phrases Gigi can match from the mention. For example, a manifest can match `@Gigi play never gonna give you up` and dispatch `m!play never gonna give you up`.

External apps may ignore bot-authored messages. Gigi can send `!play ...`; the external app decides whether to act.

Surface: guild mention message.

Required Gigi capability: the matched action's `permissions` entry. If an action has no permissions, it is public for guild members after the plugin is enabled.

Audit: matched, denied, failed dry-run, and sent dispatch attempts are audited without storing raw message text or the planned command.

Syntax:

```text theme={null}
@Gigi play never gonna give you up
@Gigi !play never gonna give you up
```

Expected response:

```text theme={null}
Matched external app: `Jockie Music`.
Planned command: `!play never gonna give you up`.
Dry-run only; no command sent.
```

Expected response when the enabled action opts into public `send_message` dispatch:

```text theme={null}
!play never gonna give you up
```

Failure cases:

* no enabled external app manifest matches the text and semantic routing cannot produce a valid dry-run plan, so Gigi falls back to normal mention behavior
* requester lacks the matched action permission and has no Discord administrator override; public actions with empty `permissions` do not require this check
* database is unavailable
* action trigger or surface does not match the message
* routing model profile or sealed guild credential is missing for semantic routing
* external app ignores bot-authored messages after Gigi sends the command

If mention routing is silent, use `/plugins dry-run` to test the same enabled manifest matcher through slash commands. This avoids Discord message-content delivery issues and proves whether import, enable, and trigger matching work.

## Plugin Commands

All `/plugins` commands are live admin commands for the external app manifest catalog. They never dispatch external app commands; only guild mention routing can send an opted-in public command.

Surface: slash command.

Required Gigi capability: `plugin.install`.

Admin bootstrap: Discord guild owner or a member with Discord `ADMINISTRATOR` permission can pass through admin override.

Responses: ephemeral.

Audit: import, enable, and disable changes are audited.

Discord bot permissions: normal slash command access.

### `/plugins list`

Lists approved external app manifests in the catalog.

Syntax:

```text theme={null}
/plugins list
```

Expected response:

```text theme={null}
Approved plugins:
- `example-tool@1.0.0` - Example Tool
```

Failure cases:

* requester lacks `plugin.install` and has no admin override
* database is unavailable

### `/plugins import-manifest`

Fetches `gigi-plugin.json` from HTTPS, validates it, and stores it as an approved external app manifest.

Syntax:

```text theme={null}
/plugins import-manifest url:<https-url> allow-public-dispatch:<true|false?>
```

Expected response:

```text theme={null}
Imported plugin `Example Tool` (`example-tool@1.0.0`).
```

Failure cases:

* requester lacks `plugin.install` and has no admin override
* URL is missing, is not HTTPS, or includes user info, query, or fragment data
* manifest is too large
* manifest JSON is invalid
* manifest lacks exact Discord application ID or bot user ID
* manifest fails capability, trigger, surface, audit, or attribution validation

If any action declares `dispatch: "send_message"` with `safety: "public"` and empty `permissions`, `allow-public-dispatch:true` is required and stored on the approved version. URL imports reject unsafe literal hosts, DNS-resolved unsafe addresses, unsafe redirects, and non-JSON manifest responses.

Privacy and audit: raw URLs are not written to audit metadata. Manifests must not contain secrets, private Discord content, private URLs, signed URLs, or guessed behavior.

### `/plugins import-file`

Imports an uploaded `gigi-plugin.json` attachment, validates it, and stores it as an approved external app manifest.

Syntax:

```text theme={null}
/plugins import-file attachment:<json-file> allow-public-dispatch:<true|false?>
```

Expected response:

```text theme={null}
Imported plugin `Example Tool` (`example-tool@1.0.0`) from uploaded file.
```

Failure cases:

* requester lacks `plugin.install` and has no admin override
* attachment is missing or cannot be resolved by Discord
* attachment filename does not end in `.json`
* manifest is too large
* manifest JSON is invalid
* manifest lacks exact Discord application ID or bot user ID
* manifest fails capability, trigger, surface, audit, or attribution validation

If any action declares `dispatch: "send_message"` with `safety: "public"` and empty `permissions`, `allow-public-dispatch:true` is required and stored on the approved version. Attachment imports reject non-JSON attachment content types when Discord provides a content type and reject unsafe redirects.

Privacy and audit: Discord attachment URLs are not stored in audit metadata or manifest source metadata. Uploaded manifests are normalized to `source_kind: "uploaded_file"` and `manifest_url` is cleared.

### `/plugins enable`

Enables an approved external app version for the current Discord server. This permits matching. Dispatch still depends on the matched action declaring `dispatch: "send_message"`, `safety: "public"`, empty `permissions`, and stored public-dispatch consent from import.

Syntax:

```text theme={null}
/plugins enable plugin:<plugin-id> version:<version>
```

Expected response:

```text theme={null}
Enabled plugin `example-tool@1.0.0` for this server.
```

Failure cases:

* requester lacks `plugin.install` and has no admin override
* plugin id or version is missing
* approved plugin version does not exist
* database is unavailable

### `/plugins disable`

Disables an external app integration for the current Discord server.

Syntax:

```text theme={null}
/plugins disable plugin:<plugin-id>
```

Expected response:

```text theme={null}
Disabled plugin `example-tool` for this server.
```

Failure cases:

* requester lacks `plugin.install` and has no admin override
* plugin id is missing
* enabled plugin is not found
* database is unavailable

### `/plugins enabled`

Lists external app integrations enabled for the current Discord server.

Syntax:

```text theme={null}
/plugins enabled
```

Expected response:

```text theme={null}
Enabled plugins:
- `example-tool@1.0.0` - Example Tool
```

Failure cases:

* requester lacks `plugin.install` and has no admin override
* database is unavailable

### `/plugins dry-run`

Tests enabled external app trigger matching without using message content events and without sending commands to the external app. This command always dry-runs, even if the matched action has `dispatch: "send_message"`.

Syntax:

```text theme={null}
/plugins dry-run text:play never gonna give you up
```

Expected response:

```text theme={null}
Matched external app: `Jockie Music`.
Planned command: `!play never gonna give you up`.
Dry-run only; no command sent.
```

Failure cases:

* requester lacks `plugin.install` and has no admin override
* no enabled manifest matches the text
* database is unavailable

## Not Implemented Yet

* rich DM chat
* semantic `/memory ask`, cross-channel retrieval, and citation-rich retrieval
* tasks
* relay actions
* LLM-driven external app dispatch
* restricted external app command dispatch
* confirmed per-message dispatch approval
