> ## Documentation Index
> Fetch the complete documentation index at: https://docs.befailproof.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

title: Kiến trúc
description: "Cách hook handler, config loading, và policy evaluation hoạt động nội bộ"
icon: sitemap
-------------

Tài liệu này giải thích cách failproofai hoạt động nội bộ: cách hệ thống hook chặn các lệnh gọi công cụ của agent, cách cấu hình được tải và hợp nhất, cách các chính sách được đánh giá, và cách dashboard giám sát hoạt động của agent.

***

## Tổng quan

failproofai có hai hệ thống con độc lập:

1. **Hook handler** - Một quy trình CLI nhanh mà Claude Code gọi trên mỗi lệnh gọi công cụ của agent. Đánh giá các chính sách và trả về một quyết định.
2. **Agent Monitor (Dashboard)** - Một ứng dụng web Next.js để giám sát các phiên làm việc của agent và quản lý các chính sách.

Cả hai hệ thống con đều chia sẻ các tệp cấu hình trong `~/.failproofai/` và thư mục `.failproofai/` của dự án, nhưng chúng chạy dưới dạng các quy trình riêng biệt và chỉ giao tiếp qua hệ thống tệp.

***

## Hook handler

### Tích hợp với Claude Code

Khi bạn chạy `failproofai policies --install`, nó sẽ ghi các mục như thế này vào `~/.claude/settings.json`:

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
```

Claude Code sau đó gọi `failproofai --hook PreToolUse` như một quy trình con trước mỗi lệnh gọi công cụ, truyền một payload JSON vào stdin.

### Định dạng payload

```json theme={null}
{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
```

Đối với các sự kiện `PostToolUse`, payload cũng chứa `tool_result` với đầu ra của công cụ.

Handler áp dụng giới hạn stdin 1 MB. Các payload vượt quá giới hạn này bị loại bỏ và tất cả các chính sách ngầm cho phép.

### Định dạng phản hồi

**Từ chối (PreToolUse):**

```json theme={null}
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
```

**Từ chối (PostToolUse):**

```json theme={null}
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
```

**Hướng dẫn (bất kỳ sự kiện nào trừ Stop):**

```json theme={null}
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
```

**Hướng dẫn sự kiện Stop:**

* Mã thoát: `2`
* Lý do được ghi vào stderr (không phải stdout)

**Cho phép:**

* Mã thoát: `0`
* stdout rỗng

**Cho phép với tin nhắn:**

`allow(message)` cho phép một chính sách gửi bối cảnh thông tin quay lại Claude ngay cả khi hoạt động được phép. Hook handler ghi JSON sau đây vào **stdout** (không phải tệp cấu hình — đây là phản hồi của handler đối với Claude Code, giống như các phản hồi từ chối và hướng dẫn ở trên):

```json theme={null}
// Written to stdout by the hook handler process
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
```

* Mã thoát: `0` (hoạt động được phép)
* Khi nhiều chính sách trả về `allow` với một tin nhắn, các tin nhắn của chúng được nối với các dòng mới thành một chuỗi `additionalContext` duy nhất
* Nếu không có chính sách nào cung cấp tin nhắn, stdout trống (giống như trước đây)

### Đường ống xử lý

`src/hooks/handler.ts` triển khai đầy đủ đường ống:

```text theme={null}
stdin JSON
  → parse payload (max 1 MB)
  → extract session metadata (session_id, cwd, tool_name, tool_input, etc.)
  → readMergedHooksConfig(cwd)    ← merges project + local + global config
  → register enabled builtin policies with resolved params
  → load custom policies from customPoliciesPath (if set)
  → register custom policies into policy registry
  → evaluate all policies (builtins first, then custom)
      → first deny short-circuits
      → instruct decisions accumulate
      → allow messages accumulate
  → write JSON decision to stdout
  → persist event to ~/.failproofai/hook-activity.jsonl
  → exit
```

Toàn bộ quá trình chạy dưới 100ms cho các payload điển hình không có lệnh gọi LLM.

***

## Tải cấu hình

`src/hooks/hooks-config.ts` triển khai tải cấu hình ba phạm vi.

```text theme={null}
[1] {cwd}/.failproofai/policies-config.json        ← project  (highest priority)
[2] {cwd}/.failproofai/policies-config.local.json  ← local
[3] ~/.failproofai/policies-config.json             ← global   (lowest priority)
```

Logic hợp nhất:

* `enabledPolicies` - liên kết không trùng lặp trên cả ba tệp
* `policyParams` - mỗi khóa chính sách, tệp đầu tiên xác định nó sẽ thắng hoàn toàn
* `customPoliciesPath` - tệp đầu tiên xác định nó sẽ thắng
* `llm` - tệp đầu tiên xác định nó sẽ thắng

Dashboard web sử dụng `readHooksConfig()` (toàn cầu duy nhất) để đọc và ghi, vì nó không được gọi với cwd của dự án.

***

## Đánh giá chính sách

`src/hooks/policy-evaluator.ts` chạy các chính sách theo thứ tự.

Đối với mỗi chính sách:

1. Tra cứu lược đồ `params` của chính sách (nếu nó có).
2. Đọc `policyParams[policy.name]` từ cấu hình đã hợp nhất.
3. Hợp nhất các giá trị do người dùng cung cấp trên các giá trị mặc định của lược đồ để tạo ra `ctx.params`.
4. Gọi `policy.fn(ctx)` với bối cảnh đã được giải quyết.
5. Nếu kết quả là `deny`, dừng ngay lập tức và trả về quyết định đó.
6. Nếu kết quả là `instruct`, tích lũy thông báo và tiếp tục.
7. Nếu kết quả là `allow`, tiếp tục đến chính sách tiếp theo.

Sau khi tất cả các chính sách chạy:

* Nếu bất kỳ `deny` nào được trả về, phát ra phản hồi từ chối.
* Nếu có bất kỳ phản hồi `instruct` nào được thu thập, phát ra một phản hồi hướng dẫn duy nhất với tất cả các thông báo được nối lại.
* Nếu không, phát ra phản hồi cho phép (stdout trống, thoát 0).

***

## Chính sách tích hợp sẵn

`src/hooks/builtin-policies.ts` định nghĩa tất cả 39 chính sách tích hợp sẵn dưới dạng các đối tượng `BuiltinPolicyDefinition`:

```typescript theme={null}
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
```

Các chính sách chấp nhận `params` khai báo một `PolicyParamsSchema` với các kiểu và giá trị mặc định cho mỗi tham số. Người đánh giá chính sách tiêm các giá trị được giải quyết vào `ctx.params` trước khi gọi `fn`. Các hàm chính sách đọc `ctx.params` mà không cần bảo vệ null vì các giá trị mặc định luôn được áp dụng trước.

So khớp mẫu bên trong các chính sách sử dụng các token lệnh được phân tích cú pháp (argv), không phải so khớp chuỗi thô. Điều này ngăn chặn việc vượt qua thông qua tiêm toán tử shell (ví dụ: một mẫu cho `sudo systemctl status *` không thể bị vượt qua bằng cách nối thêm `; rm -rf /` vào lệnh).

***

## Chính sách tùy chỉnh

`src/hooks/custom-hooks-registry.ts` triển khai một sổ đăng ký được hỗ trợ bởi `globalThis`:

```typescript theme={null}
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // used in tests
```

`src/hooks/custom-hooks-loader.ts` tải tệp chính sách của người dùng:

1. Đọc `customPoliciesPath` từ cấu hình; bỏ qua nếu không có.
2. Giải quyết đến đường dẫn tuyệt đối; kiểm tra tệp tồn tại.
3. Viết lại tất cả các mục nhập `from "failproofai"` thành đường dẫn dist thực tế để `customPolicies` phân giải thành cùng một sổ đăng ký `globalThis`.
4. Viết lại một cách đệ quy các mục nhập cục bộ chuyên qua để đảm bảo khả năng tương thích ESM.
5. Ghi các tệp `.mjs` tạm thời và `import()` tệp entry.
6. Gọi `getCustomHooks()` để lấy các hook đã đăng ký.
7. Dọn sạch tất cả các tệp tạm thời trong khối `finally`.

Trong trường hợp có lỗi (tệp không tìm thấy, lỗi cú pháp, lỗi nhập), lỗi được ghi vào `~/.failproofai/hook.log` và loader trả về một mảng trống. Các chính sách tích hợp sẵn không bị ảnh hưởng.

Các chính sách tùy chỉnh được đánh giá sau tất cả các chính sách tích hợp sẵn. Một chính sách tùy chỉnh `deny` vẫn làm ngắn mạch các chính sách tùy chỉnh tiếp theo (nhưng tất cả các chính sách tích hợp sẵn đã chạy tại thời điểm đó).

***

## Ghi nhật ký hoạt động

Sau mỗi sự kiện hook, handler nối thêm một dòng JSONL vào `~/.failproofai/hook-activity.jsonl`:

```json theme={null}
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
```

Một dòng cho mỗi chính sách đã đưa ra quyết định không cho phép. Các quyết định cho phép không được ghi nhật ký (để giữ tệp nhỏ).

***

## Kiến trúc dashboard

Dashboard là một ứng dụng **Next.js 16** sử dụng App Router với React Server Components và Server Actions.

```text theme={null}
app/
  layout.tsx                  ← Root layout (theme, telemetry, nav)
  projects/page.tsx           ← Server component: list all Claude projects
  project/[name]/page.tsx     ← Server component: list sessions in a project
  project/[name]/session/
    [sessionId]/page.tsx      ← Server component: render session viewer
  policies/page.tsx           ← Client component: policy management + activity log
  actions/
    get-hooks-config.ts       ← Read config + policy list
    update-hooks-config.ts    ← Toggle policy on/off
    update-policy-params.ts   ← Update policy parameters
    get-hook-activity.ts      ← Paginate/search activity log
    install-hooks-web.ts      ← Install/remove hooks from the browser
  api/
    download/[project]/[session]/route.ts   ← Per-CLI session export (JSONL or JSON)
```

**Luồng dữ liệu:**

* Các thành phần trang gọi `lib/projects.ts` và `lib/log-entries.ts` để đọc dữ liệu dự án/phiên làm việc trực tiếp từ hệ thống tệp (không có lớp API cho các lần đọc).
* Trang Policies sử dụng Server Actions cho tất cả các thay đổi (bật/tắt, cập nhật tham số, cài đặt/xóa).
* Trình xem phiên làm việc phân tích định dạng bảng điểm JSONL của Claude và hiển thị dòng thời gian của các thông báo và lệnh gọi công cụ.

**Các quyết định thiết kế chính:**

* Không có cơ sở dữ liệu - tất cả trạng thái liên tục là trong các tệp thô (`~/.failproofai/`, `~/.claude/projects/`).
* Server Actions cho các thay đổi - không cần REST API cho các hoạt động CRUD.
* React Server Components cho các trang đọc - tải ban đầu nhanh hơn, không có gói máy khách cho tìm nạp dữ liệu.
* Các thành phần máy khách chỉ khi cần tương tác (bật/tắt chính sách, tìm kiếm hoạt động, trình xem nhật ký).

***

## Bố cục tệp

```text theme={null}
failproofai/
├── bin/
│   └── failproofai.mjs           # CLI router (hook / dashboard / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Hook event pipeline
│   ├── builtin-policies.ts       # 39 policy definitions
│   ├── policy-evaluator.ts       # Policy execution engine
│   ├── policy-registry.ts        # Policy registration and lookup
│   ├── policy-types.ts           # TypeScript interfaces
│   ├── hooks-config.ts           # Multi-scope config loading
│   ├── custom-hooks-registry.ts  # globalThis-backed hook registry
│   ├── custom-hooks-loader.ts    # ESM loader for user JS hooks
│   ├── manager.ts                # install / remove / list operations
│   ├── install-prompt.ts         # Interactive policy selection prompt
│   ├── hook-logger.ts            # Logging to hook.log
│   ├── hook-activity-store.ts    # Persist activity to hook-activity.jsonl
│   └── llm-client.ts             # LLM API client (for AI-powered policies)
├── app/                          # Next.js dashboard (pages + server actions)
├── lib/                          # Shared utilities
│   ├── projects.ts               # Enumerate Claude projects from filesystem
│   ├── log-entries.ts            # Parse Claude transcript JSONL format
│   ├── paths.ts                  # Resolve system paths
│   └── ...
├── components/                   # Shared React UI components
├── contexts/                     # React context providers (theme, auto-refresh, telemetry)
├── examples/                     # Example custom hook files
└── __tests__/                    # Unit and E2E tests
```
