Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions AGENTS-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ await api.invoke('your_command', { request: { ... } });
- 不要把硬编码限制或模式判断作为处理 agent loop 循环问题的第一反应,例如仅按字符串或次数阻止重复工具调用。
- 过多硬编码会把 agent loop 变成脆弱的 workflow。应先定位根因:工具行为、模型交互、会话上下文封装、prompt/tool schema 设计,或状态同步问题。

### Agent Hooks

- BitFun 实现的是 Codex Hook 契约,因此 <https://learn.chatgpt.com/docs/hooks> 是事件、载荷字段与决策结构的参考来源,不要另起炉灶。[`docs/features/agent-hooks.zh-CN.md`](docs/features/agent-hooks.zh-CN.md)([English](docs/features/agent-hooks.md))只覆盖 BitFun 特有部分 —— 文件位置、`app.hooks` 开关和差异表 —— 新增或消除差异时必须同步更新。
- 可移植引擎(配置解析、载荷构造、进程执行、决策合并)位于 `bitfun-agent-runtime::native_hooks`。`bitfun-core::native_hooks` 负责配置发现、开关门控和按事件的分发辅助函数;各分发点调用这些辅助函数,不要就地执行 Hook。
- 有三类不同的东西共用 "hook" 一词:本文所述的原生用户 Hooks、内部编译期 `post_call_hooks`,以及其他 AI 应用的只读外部 Hook 目录(`external_hooks`)。三者必须保持区分。

## 架构

### 产品架构护栏
Expand Down
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ await api.invoke('your_command', { request: { ... } });
- Do not add hard-coded limits or pattern checks to the agent loop as a first response to looping behavior, such as blocking repeated tool calls by string or count alone.
- Excessive hard-coding turns the agent loop into a brittle workflow engine. Investigate the root cause first: tool behavior, model interaction, session context packaging, prompt/tool schema design, or state synchronization issues.

### Agent hooks

- BitFun implements the Codex hook contract, so <https://learn.chatgpt.com/docs/hooks> is the reference for events, payload fields, and the decision schema. Do not fork that contract. [`docs/features/agent-hooks.md`](docs/features/agent-hooks.md) ([中文](docs/features/agent-hooks.zh-CN.md)) covers only the BitFun-specific parts — file locations, the `app.hooks` gates, and the deviations table — and must be updated whenever a deviation is added or closed.
- The portable engine (settings parsing, payload construction, process execution, decision merging) lives in `bitfun-agent-runtime::native_hooks`. `bitfun-core::native_hooks` owns config discovery, gating, and per-event dispatch helpers; dispatch sites call those helpers instead of executing hooks inline.
- Three separate things share the word "hook": these native user hooks, the internal compiled-in `post_call_hooks`, and the read-only external hook catalog of other AI applications (`external_hooks`). Keep them separate.

## Architecture

### Product architecture guardrails
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ BitFun's extension paths progress continuously from light to deep customization:
| Tier | Path | Best for |
| --- | --- | --- |
| **L1** | Custom Agent | Defining roles, flows, constraints, and tool bundles. |
| **L2** | MCP / Skills | Connecting external tools, professional capabilities, and workflows. |
| **L2** | MCP / Skills / [Hooks](docs/features/agent-hooks.md) | Connecting external tools and professional capabilities, and running your own commands at Agent lifecycle points — fully Codex-hook compatible, so existing hook scripts work as-is. |
| **L3** | Mini App | Generating dedicated interfaces, forms, panels, or visualizations for tasks. |
| **L4** | Source-level customization | Changing tools, adapters, UI, Runtime, or product shape. |

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ BitFun 的扩展路径从轻到重连续展开:
| 层级 | 方式 | 适合场景 |
| --- | --- | --- |
| **L1** | Agent 自定义 | 定义角色、流程、约束和工具组合。 |
| **L2** | MCP / Skills | 接入外部工具、专业能力和工作流。 |
| **L2** | MCP / Skills / [Hooks](docs/features/agent-hooks.zh-CN.md) | 接入外部工具和专业能力,并在 Agent 生命周期节点运行你自己的命令 —— 完全兼容 Codex Hooks,已有脚本无需适配。 |
| **L3** | Mini App | 为任务生成专属界面、表单、面板或可视化。 |
| **L4** | 源码级改造 | 修改工具、适配器、UI、Runtime 或产品形态。 |

Expand Down
215 changes: 215 additions & 0 deletions docs/features/agent-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# Agent hooks

Hooks let you run your own commands at fixed points in the BitFun Agent's
lifecycle: before and after a tool call, when a permission prompt would appear,
when a prompt is submitted, around context compaction, around subagents, and
when a session or turn starts or ends. A hook can observe what the Agent is
doing, add context the model will read, rewrite a tool call's arguments, or
block an action outright.

## BitFun hooks are Codex hooks

BitFun implements **the Codex hook contract**, not a BitFun dialect:

- the same `hooks.json` document — events, matcher groups, handler fields;
- the same event names (`PreToolUse`, `PostToolUse`, `PermissionRequest`,
`UserPromptSubmit`, `PreCompact`, `PostCompact`, `SessionStart`,
`SessionEnd`, `SubagentStart`, `SubagentStop`, `Stop`);
- the same JSON payload on stdin, with the same field names;
- the same exit-code meanings (`0` success, `2` block with stderr as the
reason, anything else a non-blocking error);
- the same JSON decision schema on stdout (`permissionDecision`,
`updatedInput`, `additionalContext`, `decision`/`reason`, …).

**A Codex hook script runs in BitFun unchanged, and vice versa — there is
nothing to port.**

So this page does not restate the reference. For event semantics, the exact
payload fields per event, and the decision schema, use Codex's own
documentation, which covers all of it well:

**→ <https://learn.chatgpt.com/docs/hooks>**

The rest of this page is only what is BitFun-specific: where the files live,
how to switch hooks on, and where BitFun currently differs.

## Where BitFun reads hooks

Codex reads `~/.codex/hooks.json`; BitFun reads its own config directory
instead. Everything inside the file is identical.

| Scope | Path |
| --- | --- |
| User | `<user config dir>/config/hooks.json` |
| Project | `<workspace>/.bitfun/config/hooks.json` |

The user config directory is `~/.config/bitfun` on Linux,
`~/Library/Application Support/bitfun` on macOS, and `%APPDATA%\bitfun` on
Windows.

Both layers are additive: every matching handler runs, user handlers first.
There is no override or shadowing between them. Changes are picked up without
restarting BitFun.

## Turning hooks on

**Settings → Agent Hooks**, or directly under the `app` section of
`<user config dir>/config/app.json`:

```json
{
"app": {
"hooks": {
"enabled": true,
"project_hooks_enabled": true
}
}
}
```

| Setting | Default | Meaning |
| --- | --- | --- |
| `app.hooks.enabled` | `true` | Master switch. `false` disables all hooks. |
| `app.hooks.project_hooks_enabled` | `false` | Whether the project hook file is honored. |

**Project hooks are off by default.** A project hook file executes commands
that live inside a checked-out repository, so anyone who can land a commit
could otherwise run code on your machine. Turn it on only for repositories
you trust, and re-check the file after pulling.

Codex's `[features] hooks = false` has no BitFun equivalent — use
`app.hooks.enabled` instead.

## Quick start

Create `<user config dir>/config/hooks.json`:

```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/bitfun-commands.log"
}
]
}
]
}
}
```

Start a new session and ask the Agent to run a shell command; each command it
runs is appended to `~/bitfun-commands.log`.

A hook that blocks — here, refusing edits under `migrations/`:

```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "python3 ~/hooks/protect.py" }]
}
]
}
}
```

```python
#!/usr/bin/env python3
import json, sys

payload = json.load(sys.stdin)
if "/migrations/" in payload.get("tool_input", {}).get("file_path", ""):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Migrations are generated; edit the schema instead.",
}
}))
sys.exit(0)
```

## Where BitFun differs from Codex

Everything not listed here behaves as the Codex documentation describes.

### Not supported

| Codex feature | BitFun |
| --- | --- |
| `config.toml` `[hooks]` table | not read — put hooks in `hooks.json` |
| `[features] hooks = false` | use `app.hooks.enabled` |
| Plugin-bundled and managed hooks (`PLUGIN_ROOT`, `managed_dir`) | not supported |
| `prompt` and `agent` handler types | parsed so shared files stay valid, but skipped — only `type: "command"` executes |
| Remote workspaces | hooks are skipped entirely: a local hook process and a remote workspace path do not describe the same filesystem |

### Fields not populated yet

| Field or event | Current behavior |
| --- | --- |
| `transcript_path`, `agent_transcript_path` | always `null` |
| `permission_mode` | only `default` or `bypassPermissions` |
| `SessionStart.source` | only `startup`; `resume`, `clear`, `compact` are not dispatched |
| `SessionEnd.reason` | always `other` |
| `SubagentStop.stop_hook_active` | always `false` |
| `SubagentStop` | dispatched when a subagent settles successfully, not on failure, cancellation, or timeout |
| `Stop` | top-level turns only; subagent turns report through `SubagentStop` |

### Behavior worth knowing

- **A hook can narrow the permission policy, never widen it.** A `PreToolUse`
`permissionDecision: "allow"` waives the interactive prompt, but a tool call
denied by a permission rule stays denied.
- `suppressOutput` is parsed and currently ignored.
- `continue: false` is honored for `PreToolUse` and `UserPromptSubmit`; for
other events use `decision: "block"`.
- `PostToolUse` fires for error results too, not only successes.
- Limits: 1 MiB per `hooks.json`, 2048 handlers inspected across all layers
(invalid and non-`command` handlers count toward it), and 10,000 bytes of
model-visible text per hook before truncation.

## Security

A hook is arbitrary code that runs with your user account's full privileges,
every time its event fires. Treat `hooks.json` like a shell profile:

- Review any hook you did not write before enabling it.
- Keep project hooks off unless you trust everyone who can commit to the
repository.
- Payload values (prompts, tool arguments, file paths) are model- and
user-supplied text. Parse them as JSON and never interpolate them into a
shell command — that is why the examples above read fields with
`jq`/`json.load`.
- Do not print secrets to stdout for `SessionStart`, `UserPromptSubmit`, or
`SubagentStart`, where plain stdout becomes context the model reads.

## Troubleshooting

| Symptom | Cause |
| --- | --- |
| No hook runs at all | `app.hooks.enabled` is `false`, the file is not at the documented path, or the workspace is remote. |
| Project hooks do not run | `app.hooks.project_hooks_enabled` is `false` (the default). |
| The whole file is ignored | Invalid JSON, or a root key other than `description`/`hooks`. |
| One event is ignored | Misspelled event name — the names are case-sensitive. |
| A handler never runs | Its matcher does not match, or the matcher is not a valid pattern. Matchers are regular expressions anchored to the whole value, so `Bash` matches `Bash` but not `BashOutput`. |
| A `prompt`/`agent` handler never runs | Only `type: "command"` handlers execute. |
| Blocking has no effect | Blocking needs exit code 2 (reason on stderr), or a `decision`/`permissionDecision` field on stdout with exit code 0. |
| Plain `echo` output is not visible to the model | Only `SessionStart`, `UserPromptSubmit`, and `SubagentStart` turn plain stdout into context; elsewhere use `hookSpecificOutput.additionalContext`. |

Configuration problems, non-zero exits, timeouts, and hook decisions are
written to the BitFun backend log. See
[`src/crates/LOGGING.md`](../../src/crates/LOGGING.md) for how to raise the
log level.

## Related

- CLI `/hooks` inspects hooks configured for *other* AI applications (Claude
Code, Codex, OpenCode). That view is read-only and never executes anything;
the hooks described here are BitFun's own and do execute.
Loading