# Agent Hook Library & Permission Feed

> The vendor-agnostic hook adapter layer for Codex, Grok, OpenCode, Gemini, Cursor, Rovo Dev, Hermes Agent, and other agents, plus the feed path for approval and permission events.

- Repository: manaflow-ai/cmux
- GitHub: https://github.com/manaflow-ai/cmux
- Human wiki: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a
- Complete Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/llms-full.txt

## Source Files

- `CLI/CMUXCLI+AgentHookDefinitions.swift`
- `CLI/CMUXCLI+HermesAgentHooks.swift`
- `Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift`
- `Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift`
- `Sources/Feed/FeedCoordinator.swift`
- `Sources/Feed/FeedPermissionActionPolicy.swift`
- `cmuxTests/CLIGenericHookPersistenceTests.swift`
- `cmuxTests/FeedCoordinatorTests.swift`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [CLI/CMUXCLI+AgentHookDefinitions.swift](CLI/CMUXCLI+AgentHookDefinitions.swift)
- [CLI/CMUXCLI+HermesAgentHooks.swift](CLI/CMUXCLI+HermesAgentHooks.swift)
- [CLI/cmux.swift](CLI/cmux.swift)
- [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift](Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift)
- [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift](Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift)
- [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift](Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift)
- [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamStore.swift](Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamStore.swift)
- [Sources/Feed/FeedCoordinator.swift](Sources/Feed/FeedCoordinator.swift)
- [Sources/Feed/FeedPermissionActionPolicy.swift](Sources/Feed/FeedPermissionActionPolicy.swift)
- [Sources/Feed/FeedPanelView.swift](Sources/Feed/FeedPanelView.swift)
- [Sources/TerminalController.swift](Sources/TerminalController.swift)
- [cmuxTests/CLIGenericHookPersistenceTests.swift](cmuxTests/CLIGenericHookPersistenceTests.swift)
- [cmuxTests/FeedCoordinatorTests.swift](cmuxTests/FeedCoordinatorTests.swift)
- [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/HermesAgentHookConfigTests.swift](Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/HermesAgentHookConfigTests.swift)
- [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/RovoDevHookConfigTests.swift](Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/RovoDevHookConfigTests.swift)
</details>

# Agent Hook Library & Permission Feed

cmux has a vendor-agnostic hook adapter layer that installs small agent-native hook commands into each supported CLI’s own config format. Those hooks report session lifecycle, prompt, stop, notification, and selected tool events back to cmux without assuming a specific model provider or hosted control plane.

The permission feed is the second half of the design: hook payloads are normalized into `WorkstreamEvent`, sent over the cmux socket as `feed.push`, displayed as Feed items, and sometimes held open until the user approves, denies, answers a question, or times out. This keeps BYOC/BYOK compatibility because agents keep their own binaries, config homes, and keys; cmux only bridges local hook events and decisions.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:6-30](), [CLI/cmux.swift:28181-28217](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:3-10]()

## Adapter Model

The central abstraction is `AgentHookDef`. It records each agent’s CLI name, display name, status key, config directory/file, optional config-home environment override, session-store suffix, disable environment variable, binary name, hook config format, lifecycle events, aliases, and feed-hook events. This makes agent support mostly data-driven: adding an agent usually means adding a definition plus a serializer only if its config format is not already covered.

The supported format enum is deliberately small: flat JSON, nested JSON with timeouts, Antigravity JSON, Rovo Dev YAML, and Hermes Agent YAML. The table includes Codex, Grok, OpenCode, Pi, Amp, Cursor, Gemini, Antigravity, Rovo Dev, Hermes Agent, Copilot, CodeBuddy, Factory, and Qoder.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:6-47](), [CLI/CMUXCLI+AgentHookDefinitions.swift:122-245](), [CLI/CMUXCLI+AgentHookDefinitions.swift:246-297]()

| Agent | Config path basis | Format | Feed hook events |
|---|---:|---|---|
| Codex | `.codex/hooks.json`, or `CODEX_HOME` | nested JSON | `PreToolUse`, `PermissionRequest` |
| Grok | `.grok/hooks/cmux-session.json`, or `GROK_HOME/hooks` | nested JSON | `PreToolUse` |
| Cursor | `.cursor/hooks.json` | flat JSON | `beforeShellExecution` |
| Gemini | `.gemini/settings.json` | nested JSON | `PreToolUse` |
| Antigravity | `.gemini/config/hooks.json` | named JSON group | `PreToolUse`, `PostToolUse` |
| Rovo Dev | `.rovodev/config.yml` | YAML | none in `feedHookEvents`; `on_tool_permission` maps to generic prompt-submit telemetry |
| Hermes Agent | `.hermes/config.yaml`, or `HERMES_HOME` | YAML plus allowlist | `pre_tool_call`, `post_tool_call`, `pre_approval_request`, `post_approval_response` |
| Copilot / CodeBuddy / Factory / Qoder | agent-specific config homes | nested JSON | `PreToolUse` |

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:123-228](), [CLI/CMUXCLI+AgentHookDefinitions.swift:230-295]()

## Runtime Shape

The adapter layer separates installation from runtime handling. Installation writes each agent’s native hook config. Runtime hooks execute `cmux hooks <agent> <subcommand>` for lifecycle/status work or `cmux hooks feed --source <agent> --event <event>` for feed bridge work.

```mermaid
flowchart LR
  subgraph AgentConfig["Agent-owned config"]
    Hooks["hooks.json / settings.json / config.yml / config.yaml"]
  end

  subgraph CLI["cmux CLI hook commands"]
    Lifecycle["cmux hooks <agent> <event>"]
    FeedHook["cmux hooks feed --source <agent> --event <event>"]
  end

  subgraph Socket["cmux socket API"]
    FeedPush["feed.push"]
    Reply["feed.permission.reply / feed.question.reply / feed.exit_plan.reply"]
  end

  subgraph App["cmux app Feed"]
    Coordinator["FeedCoordinator"]
    Store["WorkstreamStore"]
    Panel["FeedPanelView actions"]
  end

  Hooks --> Lifecycle
  Hooks --> FeedHook
  FeedHook --> FeedPush
  FeedPush --> Coordinator --> Store
  Panel --> Reply --> Coordinator
```

Sources: [CLI/cmux.swift:23140-23207](), [CLI/cmux.swift:28197-28326](), [Sources/TerminalController.swift:10722-10747](), [Sources/Feed/FeedPanelView.swift:928-953]()

## Installing Hooks Without Owning the Agent

For JSON-style agents, `installAgentHooks` reads the agent config, builds cmux hook entries, removes prior cmux-owned entries, preserves non-cmux hooks, then writes the new config after an optional preview prompt. Flat configs get `version = 1`; nested configs preserve groups and only remove commands recognized as cmux-owned. This lets users keep their own agent hooks alongside cmux.

Rovo Dev and Hermes Agent have dedicated YAML paths. Rovo Dev writes under `eventHooks.events` and marks inserted blocks with `# cmux hooks rovodev begin/end`. Hermes Agent inserts under `hooks`, can add missing event sections, restores inline-empty YAML forms during uninstall, and writes a separate `shell-hooks-allowlist.json` approval entry for the cmux shell hooks.

Sources: [CLI/cmux.swift:24115-24280](), [CLI/cmux.swift:24280-24327](), [CLI/cmux.swift:23857-23937](), [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift:17-45](), [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift:22-89](), [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift:267-314]()

```swift
// CLI/CMUXCLI+AgentHookDefinitions.swift
static func feedHookCommandString(for def: AgentHookDef, agentEvent: String) -> String {
    agentHookShellCommand("cmux hooks feed --source \(def.name) --event \(agentEvent)", for: def)
}
```

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:304-310]()

## Feed Bridge Classification

`cmux hooks feed` reads hook JSON from stdin, derives the raw event from `hook_event_name`, `event`, or `--event`, extracts tool name/input/session/cwd/workspace, and builds a normalized `WorkstreamEvent` dictionary. Non-actionable telemetry uses `wait_timeout_seconds = 0`; actionable permission, plan, and question events wait up to 120 seconds for the user.

The classification is provider-aware but not provider-locked. Claude’s native `PermissionRequest` carries decisions. Hermes maps side-effecting `pre_tool_call` events to `PermissionRequest`; `post_tool_call` stays telemetry. Other agents use `PreToolUse` or `beforeShellExecution`, and only side-effecting tools become permission requests. Read-only tools intentionally stay telemetry to avoid flooding the actionable feed.

Sources: [CLI/cmux.swift:28216-28245](), [CLI/cmux.swift:28246-28312](), [CLI/cmux.swift:28357-28461](), [CLI/cmux.swift:28463-28488]()

## Workstream Event Contract

`WorkstreamEvent` is the wire contract between hook adapters and the app. Its field names mirror existing hook payload names: `session_id`, `hook_event_name`, `workspace_id`, `cwd`, `tool_name`, `tool_input`, `_source`, `_ppid`, and `_opencode_request_id`. Unknown extra fields are retained as JSON, and arbitrary `tool_input` shapes are normalized into stable JSON strings.

`WorkstreamStore` then converts event kinds into UI payloads: `PermissionRequest` becomes a permission card, `AskUserQuestion` becomes a question card, `ExitPlanMode` becomes a plan card, and lifecycle/tool/prompt events become telemetry or contextual items.

Sources: [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:3-23](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:52-82](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:84-139](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamStore.swift:263-320]()

## Blocking Approval Path

The app-side socket handler validates `feed.push`, decodes the nested event, publishes receipt/completion events, and delegates to `FeedCoordinator.ingestBlocking`. The coordinator registers a waiter by request id before inserting the item, hops to the main actor to update `WorkstreamStore`, arms a PID watcher when `_ppid` is present, posts a native notification only if the request is still awaiting a decision, and blocks the socket worker until resolution or timeout.

Replies enter through `feed.permission.reply`, `feed.question.reply`, or `feed.exit_plan.reply`. All three call `FeedCoordinator.deliverReply`, which stores the decision, signals the semaphore, marks the matching Feed item resolved, and cancels any notification. If the timeout wins, the item is marked expired and the hook receives `timed_out`.

Sources: [Sources/TerminalController.swift:10680-10747](), [Sources/TerminalController.swift:10784-10852](), [Sources/Feed/FeedCoordinator.swift:81-151](), [Sources/Feed/FeedCoordinator.swift:154-180](), [Sources/Feed/FeedCoordinator.swift:222-226]()

## Decision Encoding Back to Agents

Once `feed.push` returns a resolved decision, the CLI renders the result in the agent’s expected stdout shape. Claude and Codex use `hookSpecificOutput` for `PermissionRequest`. Generic PreToolUse-style agents receive allow/deny decisions and optional context. Hermes Agent receives `{ "action": "block" }` for denies and `{}` for allows. Antigravity receives its own `decision`/`reason` shape. Exit-plan and question replies are also translated into provider-appropriate hook output instead of simulating keystrokes.

Sources: [CLI/cmux.swift:28332-28355](), [CLI/cmux.swift:28492-28637](), [CLI/cmux.swift:28638-28720](), [CLI/cmux.swift:28722-28783]()

## Permission Mode Policy

The Feed UI does not expose every permission mode for every source. `FeedPermissionActionPolicy` disables persistent permission modes for Codex and Hermes Agent, and disables bypass permissions for Codex, Claude, and Hermes Agent. The tests assert that Claude still supports persistent modes but keeps bypass user-owned, while OpenCode supports both persistent and bypass modes.

Sources: [Sources/Feed/FeedPermissionActionPolicy.swift:3-10](), [cmuxTests/FeedCoordinatorTests.swift:10-23]()

## Persistence, Focus, and Recovery

Hook session stores let Feed items map back to a cmux workspace and surface. `FeedJumpResolver` parses workstream ids like `<agent>-<sessionId>`, reads `~/.cmuxterm/<agent>-hook-sessions.json`, and can dispatch focus or send-text intents without coupling Feed directly to terminal controllers.

Tests cover sanitized launch command persistence across multiple agent families. The scenarios strip resume IDs, old prompts, and secret API-key-like environment variables while preserving useful config-home variables such as `GEMINI_CLI_HOME`, `GROK_HOME`, `COPILOT_HOME`, `CODEBUDDY_CONFIG_DIR`, and `QODER_CONFIG_DIR`. This is the BYOK-friendly part of persistence: the restored route remembers how to resume an agent surface without storing provider secrets.

Sources: [Sources/Feed/FeedCoordinator.swift:278-326](), [Sources/Feed/FeedCoordinator.swift:329-400](), [cmuxTests/CLIGenericHookPersistenceTests.swift:16-73](), [cmuxTests/CLIGenericHookPersistenceTests.swift:102-160](), [cmuxTests/CLIGenericHookPersistenceTests.swift:161-249]()

## Verification Signals

The test suite checks several important invariants. Hermes YAML tests verify installation into empty and existing configs, preservation of user hook commands, clean uninstall, inline-empty YAML restoration, and allowlist changes that only remove cmux-owned commands. Rovo Dev tests verify insertion under the direct `eventHooks.events` child and protect unrelated YAML when markers are dangling. Feed coordinator tests verify timeout expiry and that already-resolved blocking requests do not post stale native notifications. Generic hook tests verify Antigravity feed timeout seconds, stable fallback session ids, and `_ppid` propagation.

Sources: [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/HermesAgentHookConfigTests.swift:7-45](), [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/HermesAgentHookConfigTests.swift:113-143](), [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/RovoDevHookConfigTests.swift:6-29](), [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/RovoDevHookConfigTests.swift:31-53](), [cmuxTests/FeedCoordinatorTests.swift:25-66](), [cmuxTests/FeedCoordinatorTests.swift:68-136](), [cmuxTests/CLIGenericHookPersistenceTests.swift:585-697]()

## Product Notes for Grok-Wiki Integration

A Grok-Wiki integration should present this as a local adapter architecture, not as a model-provider feature. The portable unit is the hook definition plus serializer plus feed classifier: those can come from checked-in files, a repository catalog, or a skill-pack source, as long as generated docs cite repository code as source of truth. The UI flow should describe “agent hook setup,” “feed event normalization,” and “approval reply rendering” before naming provider-specific commands, so the same mental model works for Codex, Grok, OpenCode, Gemini, Cursor, Rovo Dev, Hermes Agent, and future BYOC agents.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:75-101](), [CLI/cmux.swift:28191-28196](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:3-10]()

## Summary

The hook library is a small adapter table plus targeted serializers for divergent agent config formats. The permission feed is a blocking socket workflow around `WorkstreamEvent`, `FeedCoordinator`, `WorkstreamStore`, and reply-specific decision rendering. Together they let cmux support multiple local agents while preserving user-owned configs, provider keys, and native hook semantics.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:299-310](), [Sources/Feed/FeedCoordinator.swift:10-14](), [Sources/Feed/FeedCoordinator.swift:762-794]()
