Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions genie/hacks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,101 @@ See the [Hooks reference](/genie/config/hooks) for the full list of NATS subject

---

### Hack 9: Omni + Genie-NATS — Context Isolation for Multi-User Agents

**Problem:** A genie-nats agent connected to a Telegram/WhatsApp/Discord instance via `omni connect` gets spoken to by multiple users in parallel. The omni CLI exposes global-state verbs (`omni say`, `omni open`, `omni use`, `omni react`, `omni history`, ...) that operate on an "active chat". If the agent calls any of them, replies leak to the wrong user — classic context poisoning. Prompt discipline alone won't hold; you need a harness-enforced permission boundary.

**Solution:** Classify omni commands into **explicit-scope** (safe) and **global-state** (unsafe). Whitelist only explicit-scope commands in the agent's `.claude/settings.local.json`. Teach the agent to derive `chat_id` from the NATS turn payload (env vars) or `omni where --json` — never from enumeration like `omni chats list`.

**Safe commands** (scope always explicit):

| Command | Why safe |
|---|---|
| `omni send --to <chat-id> ...` | Destination mandatory, ignores global state — use instead of `say` |
| `omni where --json` | Read-only; process-scoped by turn dispatcher |
| `omni turns get <turn-id>` | Explicit turn ID |
Comment on lines +327 to +335
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omni where --json is listed as safe, but there’s no omni where command documented under omni/cli/*.mdx in this repo. Either link/add the missing Omni CLI docs for where (including its JSON shape) or adjust this hack to rely only on documented commands/env vars.

Suggested change
**Solution:** Classify omni commands into **explicit-scope** (safe) and **global-state** (unsafe). Whitelist only explicit-scope commands in the agent's `.claude/settings.local.json`. Teach the agent to derive `chat_id` from the NATS turn payload (env vars) or `omni where --json` — never from enumeration like `omni chats list`.
**Safe commands** (scope always explicit):
| Command | Why safe |
|---|---|
| `omni send --to <chat-id> ...` | Destination mandatory, ignores global state — use instead of `say` |
| `omni where --json` | Read-only; process-scoped by turn dispatcher |
| `omni turns get <turn-id>` | Explicit turn ID |
**Solution:** Classify omni commands into **explicit-scope** (safe) and **global-state** (unsafe). Whitelist only explicit-scope commands in the agent's `.claude/settings.local.json`. Teach the agent to derive `chat_id` from the NATS turn payload (env vars), or by resolving the current turn via `omni turns get <turn-id>` using the turn ID from env — never from enumeration like `omni chats list`.
**Safe commands** (scope always explicit):
| Command | Why safe |
|---|---|
| `omni send --to <chat-id> ...` | Destination mandatory, ignores global state — use instead of `say` |
| `omni turns get <turn-id>` | Explicit turn ID; use with the current turn ID from env to recover chat context safely |

Copilot uses AI. Check for mistakes.
| `omni turns list --chat <id>` / `--agent <id>` | Filter binds scope |
| `omni chats get <id>` / `omni chats messages <id>` | Explicit chat ID |
| `omni messages get <id>` / `omni events get <id>` | Explicit resource ID |
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lists omni events get <id> as a safe command, but the Omni CLI docs in this repo define omni events subcommands like list/search/timeline/metrics/... and do not document an events get. Replace this with a documented command (e.g., a suitably filtered omni events list ...) or remove it so readers don’t copy a non-existent command.

Suggested change
| `omni messages get <id>` / `omni events get <id>` | Explicit resource ID |
| `omni messages get <id>` | Explicit resource ID |

Copilot uses AI. Check for mistakes.
| `omni done --text "..."` | Closes only the current turn |
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omni done --text "..." is presented as a safe command, but omni done isn’t documented under omni/cli/*.mdx (and appears to not exist). Consider replacing this with a documented command (e.g., omni turns close <turn-id> with an explicit ID) or removing it to avoid misleading copy/paste.

Suggested change
| `omni done --text "..."` | Closes only the current turn |
| `omni turns close <turn-id>` | Explicit turn ID; avoids relying on current global state |

Copilot uses AI. Check for mistakes.

**Unsafe commands** (global state, enumeration, or destructive admin):

- Global-state verbs: `open`, `close`, `use`, `say`, `react`, `listen`, `imagine`, `film`, `speak`, `see`, `history`
- Unfiltered enumeration: `chats list`, `events list/search/timeline`, `turns list` (no filter), `persons list/search`
- Destructive/admin: `turns close-all`, `chats delete/archive/hide/mute`, `instances`, `channels`, `agents`, `providers`, `routes`, `keys`, `access`, `webhooks`, `settings`, `automations`, `start/stop/restart/install/doctor/resync/replay`, `batch`, `dead-letters`, `payloads`, `logs`, `auth`, `config`
Comment on lines +343 to +345
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This “Unsafe commands” list includes several Omni commands that aren’t documented under omni/cli/*.mdx in this repo (e.g., open/close/use/history, persons list, chats delete). If these are real commands, it’d help to link to their docs; otherwise consider pruning/renaming them to match the documented CLI so the classification table stays accurate.

Suggested change
- Global-state verbs: `open`, `close`, `use`, `say`, `react`, `listen`, `imagine`, `film`, `speak`, `see`, `history`
- Unfiltered enumeration: `chats list`, `events list/search/timeline`, `turns list` (no filter), `persons list/search`
- Destructive/admin: `turns close-all`, `chats delete/archive/hide/mute`, `instances`, `channels`, `agents`, `providers`, `routes`, `keys`, `access`, `webhooks`, `settings`, `automations`, `start/stop/restart/install/doctor/resync/replay`, `batch`, `dead-letters`, `payloads`, `logs`, `auth`, `config`
- Global-state commands: `omni say <text>` and any command that relies on the active chat, agent, provider, or other CLI context instead of an explicit ID or flag
- Unfiltered enumeration: `omni chats list`, `omni events list/search/timeline`, `omni turns list` without `--chat <id>` or `--agent <id>`
- Destructive or admin actions: `omni turns close-all`, chat-wide state changes such as archive/hide/mute, and workspace-wide admin or lifecycle commands that can affect resources beyond the current turn

Copilot uses AI. Check for mistakes.

**`say` vs `send`** — the canonical example:

| | `omni say <text>` | `omni send --to <id> ...` |
|---|---|---|
| Destination | Implicit (active chat) | Explicit (`--to`) |
| Message types | Text only | Text, media, TTS, poll, reaction, forward, location, sticker, contact |
| Stateful | Yes (global CLI context) | No |
| Safe for parallel agents | ❌ | ✅ |

**Discovering `chat_id` mid-conversation** (never enumerate):

```bash
# Option A — NATS payload env vars (preferred)
env | grep -i omni # inspect your connector once
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The snippet uses env | grep -i omni, but the permissions template only allows Bash(env) and doesn’t allow grep/pipelines. If this is intended to be run by the agent, it will trigger extra permission prompts; either change the snippet to env (manual inspection) or add an explicit allow rule covering the pipeline you expect to run.

Suggested change
env | grep -i omni # inspect your connector once
env # inspect output for OMNI_* vars once

Copilot uses AI. Check for mistakes.
# Typical: OMNI_CHAT_ID, OMNI_TURN_ID, OMNI_INSTANCE_ID, OMNI_PERSON_ID
omni send --to "$OMNI_CHAT_ID" --instance "$OMNI_INSTANCE_ID" --media ./img.png

# Option B — omni where (safe because the process is turn-scoped)
CTX=$(omni where --json)
CHAT_ID=$(echo "$CTX" | jq -r '.chat.id')
INSTANCE_ID=$(echo "$CTX" | jq -r '.instance.id')
[ -n "$CHAT_ID" ] && [ "$CHAT_ID" != "null" ] || { echo "no active turn" >&2; exit 1; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The validation logic only checks for CHAT_ID. Since the subsequent omni send command uses both --to "$CHAT_ID" and --instance "$INSTANCE_ID", it is safer to ensure both variables are present and not "null" to avoid execution errors or targeting the wrong instance in a multi-user setup.

[ -n "$CHAT_ID" ] && [ "$CHAT_ID" != "null" ] && [ -n "$INSTANCE_ID" ] && [ "$INSTANCE_ID" != "null" ] || { echo "no active turn" >&2; exit 1; }

omni send --to "$CHAT_ID" --instance "$INSTANCE_ID" --media ./img.png --caption "..."
```

**Where the permissions live** — `.claude/settings.local.json`, not `agent.yaml`:

```jsonc
{
"agentName": "telegram-bot",
"autoMemoryEnabled": true,
"autoMemoryDirectory": "./brain/memory",
"permissions": {
"defaultMode": "default",
"allow": [
"Bash(omni send --to *)",
"Bash(omni where --json)",
"Bash(omni where --json*)",
Comment on lines +383 to +384
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The permission "Bash(omni where --json)" is redundant as it is covered by the glob pattern "Bash(omni where --json*)". Consolidating these improves the clarity of the security configuration.

      "Bash(omni where --json*)",

"Bash(omni turns get *)",
"Bash(omni turns list --chat *)",
"Bash(omni turns list --agent *)",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove agent-scoped turns listing from safe allowlist

omni turns list --agent * is marked safe and explicitly allowed here, but the Omni CLI docs define omni turns as an admin turn management surface and --agent filters by agent ID, not by current chat (omni/cli/system.mdx, omni turns + omni turns list options). In a multi-user bot, one agent ID typically serves many chats, so this command can enumerate other users’ turns and reintroduce cross-user context leakage, contradicting the isolation guarantee in this hack.

Useful? React with 👍 / 👎.

"Bash(omni chats get *)",
"Bash(omni chats messages *)",
"Bash(omni messages get *)",
"Bash(omni events get *)",
"Bash(omni done*)",
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permissions template allows Bash(omni done*), but omni done isn’t documented under omni/cli/*.mdx. If you drop/rename the command in the hack, update this allow rule accordingly so the template stays copy/pasteable.

Suggested change
"Bash(omni done*)",

Copilot uses AI. Check for mistakes.
"Bash(jq *)",
"Bash(env)"
Comment on lines +393 to +394
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The allow list is missing permissions for echo, grep, and [, which are used in the recommended snippets (Option A and B). Additionally, Bash(env) should be updated to Bash(env*) to support the piped command env | grep. Adding these ensures the agent can execute the discovery logic autonomously without manual approval prompts.

      "Bash(jq *)",
      "Bash(env*)",
      "Bash(echo *)",
      "Bash(grep *)",
      "Bash([ *)"

],
"deny": [
"Bash(omni send *--forward*)",
"Bash(omni turns close-all*)"
]
}
}
```

**Benefit:** Zero cross-user poisoning even with many concurrent Telegram chats hitting the same agent. Defense in depth — the LLM can't leak a reply into another user's conversation even if its reasoning slips.

**When to use:** Any genie-nats agent on a channel with more than one concurrent user. Production Telegram/WhatsApp/Discord bots. Any scenario where a CLI with global state (`active chat`, `active instance`) is exposed to an autonomous LLM.

<Warning>
Claude Code evaluates permissions in order **`deny → ask → allow`** — **deny always wins**, regardless of specificity. Do **not** write `"deny": ["Bash(omni *)"]` expecting a narrow `allow` to override it; it won't. Use a restrictive `defaultMode` and an explicit `allow` list instead. Reserve `deny` for closing variant-based bypasses (e.g. `omni send *--forward*`).
</Warning>

<Info>
Also document the contract in `AGENTS.md` so the LLM self-limits and you see fewer approval prompts during autonomous loops. List the allowed commands and explicitly forbid the verb commands — this reduces fricton even before the harness layer kicks in.
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: “fricton” → “friction”.

Suggested change
Also document the contract in `AGENTS.md` so the LLM self-limits and you see fewer approval prompts during autonomous loops. List the allowed commands and explicitly forbid the verb commands — this reduces fricton even before the harness layer kicks in.
Also document the contract in `AGENTS.md` so the LLM self-limits and you see fewer approval prompts during autonomous loops. List the allowed commands and explicitly forbid the verb commands — this reduces friction even before the harness layer kicks in.

Copilot uses AI. Check for mistakes.
</Info>

---

## Contributing

Have a hack that's not here? We want it. The best docs come from people who actually hit the problem.
Expand Down
Loading