> ## 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.

# 커스텀 정책

> 에이전트에 특화된 장애 패턴에 대응하는 JavaScript 또는 TypeScript 정책을 작성, 테스트, 배포하세요.

커스텀 정책은 트레이스나 감사 내역에서 발견된 장애 패턴을 에이전트가 동작하는 동안 실시간으로 실행되는 결정으로 전환합니다. 정책은 특정 동작을 허용하거나, 에이전트에게 가이던스를 제공하거나, 동작이 또 다른 문제를 일으키기 전에 차단할 수 있습니다.

동작이 사용하는 도구, 경로, 명령어, 환경, 또는 운영 규칙에 따라 달라지는 경우 커스텀 정책을 사용하세요. 기존 제어를 재구현하지 않도록 [내장 정책 카탈로그](/ko/policies/builtin-catalog)를 먼저 확인하세요.

## 커스텀 정책 작성하기

<Tabs>
  <Tab title="대시보드">
    1. **Admin → 정책 편집기**로 이동하여 **새 정책**을 선택하고, 방지하려는 장애를 설명합니다.
    2. 정책 소스를 추가한 후, 편집기에서 예상 매칭 케이스와 안전한 비매칭 케이스를 테스트합니다. 유효성 검사 오류를 모두 해결하세요.
    3. 초안을 저장하고 **버전 게시**를 선택하여 변경 불가능한 버전을 생성합니다.
    4. **Admin → 적용**으로 이동하여 **관찰** 모드로 테스트 머신에 버전을 배포하고, 적용하기 전에 **Observe → policy**에서 결정 내역을 확인합니다.

           <img src="https://mintcdn.com/exosphere/WgPwQzedeDNwJBTy/images/dashboard/policy-editor.png?fit=max&auto=format&n=WgPwQzedeDNwJBTy&q=85&s=7c01c862f4ec601d0535a6969eb619ce" alt="커스텀 정책을 작성하고 게시하는 데 사용되는 정책 편집기." width="2938" height="1608" data-path="images/dashboard/policy-editor.png" />
  </Tab>

  <Tab title="CLI">
    1. `.failproofai/policies/checkout-policies.ts`를 생성합니다. 파일명은 반드시 `policies.js`, `policies.mjs`, 또는 `policies.ts`로 끝나야 합니다.
    2. `customPolicies.add()`로 하나 이상의 정책을 등록합니다.
    3. `failproofai policies --install --custom ./.failproofai/policies/checkout-policies.ts --scope project` 명령으로 파일을 유효성 검사하고 설치합니다.
    4. 매칭 동작 하나와 안전한 동작 하나를 트리거합니다. `failproofai policies`를 실행한 후 **Observe → policy**에서 해당 결정 내역을 확인합니다.
  </Tab>
</Tabs>

## 좁은 규칙으로 시작하기

아래 정책은 명령어가 프로덕션을 대상으로 할 때만 파괴적인 Kubernetes 명령어를 차단합니다. 해당 장애 모드 외의 모든 경우는 `allow()`를 반환합니다.

```ts theme={null}
import { customPolicies, allow, deny } from "failproofai";

const DESTRUCTIVE_KUBECTL = /\bkubectl\s+(delete|replace)\b/i;
const PRODUCTION_TARGET = /(?:--context|--namespace|-n)\s+(prod|production)\b/i;

customPolicies.add({
  name: "block-destructive-production-kubectl",
  description: "Block destructive Kubernetes commands against production",
  match: { events: ["PreToolUse"] },
  fn: async ({ toolName, toolInput }) => {
    if (toolName !== "Bash") return allow();

    const command = String(toolInput?.command ?? "");
    if (!DESTRUCTIVE_KUBECTL.test(command)) return allow();
    if (!PRODUCTION_TARGET.test(command)) return allow();

    return deny(
      "Destructive production Kubernetes commands require the approved deployment workflow.",
    );
  },
});
```

좋은 정책은 한 문장으로 설명할 수 있을 만큼 좁아야 합니다. 에이전트의 의도가 아닌 관찰 가능한 동작을 매칭하고, 규칙이 적용되지 않는 즉시 `allow()`를 반환하세요.

## 결정 선택하기

| 헬퍼                 | 결과                                  | 사용 시점                                    |
| ------------------ | ----------------------------------- | ---------------------------------------- |
| `allow(reason?)`   | 작업이 계속 진행됩니다.                       | 정책이 적용되지 않거나 동작이 안전한 경우.                 |
| `instruct(reason)` | 하네스가 지원하는 경우 가이던스와 함께 작업이 계속 진행됩니다. | 불변 조건을 강제하지 않고 에이전트를 더 나은 방향으로 유도하려는 경우. |
| `deny(reason)`     | 이벤트와 하네스가 차단을 지원하는 경우 작업이 차단됩니다.    | 동작이 진행되어서는 안 되는 경우.                      |

복구해야 하는 에이전트를 위한 이유를 작성하세요. 감지된 내용과 대신 해야 할 행동을 설명하세요.

<Warning>
  안전 경계에는 `instruct()`를 사용하지 마세요. 가이던스 전달 방식은 에이전트 하네스에 따라 다릅니다. 동작을 반드시 막아야 할 때는 `deny()`를 사용하세요.
</Warning>

## 정책 객체

```ts theme={null}
customPolicies.add({
  name: "policy-name",
  description: "What this policy prevents",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => allow(),
});
```

| 필드             | 필수 여부 | 설명                                                      |
| -------------- | ----- | ------------------------------------------------------- |
| `name`         | 예     | 정책의 안정적인 식별자. 파일 전체에서 고유한 이름을 유지하세요.                    |
| `description`  | 아니오   | 정책 목록과 결정 내역에 표시되는 사람이 읽을 수 있는 목적.                      |
| `match.events` | 아니오   | 정책을 호출하는 이벤트 타입. `match`를 생략하면 사용 가능한 모든 이벤트에 대해 호출됩니다. |
| `fn`           | 예     | `allow`, `instruct`, 또는 `deny` 결과를 반환하는 동기 또는 비동기 함수.   |

`fn` 내부에서 도구를 필터링하세요. `match.toolNames`는 공개 커스텀 정책 타입에 포함되지 않습니다.

## 정책 컨텍스트

모든 정책은 `PolicyContext`를 받습니다.

| 필드          | 타입                                     | 포함 내용                                                      |
| ----------- | -------------------------------------- | ---------------------------------------------------------- |
| `eventType` | `HookEventType`                        | 현재 평가 중인 정규화된 이벤트.                                         |
| `toolName`  | `string \| undefined`                  | `Bash`, `Read`, `Write`, `Edit` 등 표준 도구 이름.                |
| `toolInput` | `Record<string, unknown> \| undefined` | 현재 도구 호출에 대한 표준 입력.                                        |
| `payload`   | `Record<string, unknown>`              | 완전히 정규화된 이벤트 페이로드.                                         |
| `session`   | `SessionMetadata \| undefined`         | 세션 ID, 작업 디렉터리, 트랜스크립트 경로, 권한 모드, 그리고 사용 가능한 경우 하네스 메타데이터. |
| `cli`       | `string \| undefined`                  | 소스 에이전트 하네스 (예: `claude`, `codex`, `cursor`).              |
| `params`    | `Record<string, unknown>`              | 내장 정책 파라미터. 커스텀 정책은 현재 빈 객체를 받습니다.                         |

모든 선택적 값을 실제로 선택적인 것으로 처리하세요. 에이전트 버전과 이벤트 타입에 따라 제공되는 필드가 다를 수 있습니다.

### 일반적인 도구 입력

Failproof AI는 지원되는 하네스 전반에 걸쳐 공통 도구를 정규화하므로 정책은 일반적으로 하나의 입력 형태를 사용할 수 있습니다.

| 도구      | 공통 필드                                   |
| ------- | --------------------------------------- |
| `Bash`  | `command`                               |
| `Read`  | `file_path`                             |
| `Write` | `file_path`, `content`                  |
| `Edit`  | `file_path`, `old_string`, `new_string` |
| `Grep`  | `pattern`, `path`                       |

도구 입력 값은 `unknown` 타입이므로 방어적 강제 변환을 사용하세요:

```ts theme={null}
const command = String(ctx.toolInput?.command ?? "");
const filePath = String(ctx.toolInput?.file_path ?? "");
```

## 이벤트 선택하기

| 이벤트                           | 실행 시점               | 일반적인 사용                                                  |
| ----------------------------- | ------------------- | -------------------------------------------------------- |
| `PreToolUse`                  | 도구가 실행되기 전.         | 명령어, 쓰기, 읽기, 외부 동작을 차단하거나 가이드.                           |
| `PostToolUse`                 | 도구가 반환된 후.          | 에이전트에게 전달되기 전에 결과를 검사. deny는 전체 결과를 차단하며 특정 필드를 삭제하지 않음. |
| `PermissionRequest`           | 에이전트가 권한을 요청할 때.    | 조직별 권한 규칙 적용.                                            |
| `UserPromptSubmit`            | 제출된 프롬프트가 계속되기 전.   | 금지된 지시를 거부하거나 워크플로 가이던스 추가.                              |
| `Stop`                        | 에이전트가 종료를 시도할 때.    | 로컬 검증 단계와 같은 도달 가능한 완료 조건 요구.                            |
| `SubagentStop`                | 하위 에이전트가 종료를 시도할 때. | 부모로 반환되기 전에 위임된 작업을 게이트.                                 |
| `SessionStart` / `SessionEnd` | 세션 경계에서.            | 세션 수준 상태를 기록하거나 확인.                                      |

이벤트 가용성과 차단 동작은 에이전트 하네스에 따라 다릅니다. 혼합 플릿에서 이벤트에 의존하기 전에 [에이전트 하네스](/ko/reference/harnesses)를 확인하세요.

<Accordion title="모든 정책 이벤트 이름">
  `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `PreToolUse`, `PermissionRequest`, `PermissionDenied`, `PostToolUse`, `PostToolUseFailure`, `Notification`, `SubagentStart`, `SubagentStop`, `TaskCreated`, `TaskCompleted`, `Stop`, `StopFailure`, `TeammateIdle`, `InstructionsLoaded`, `ConfigChange`, `CwdChanged`, `FileChanged`, `WorktreeCreate`, `WorktreeRemove`, `PreCompact`, `PostCompact`, `Elicitation`, `ElicitationResult`, `UserPromptExpansion`, `PostToolBatch`, `Setup`.
</Accordion>

## 일반적인 정책 패턴 작성하기

### 보호된 경로에 대한 쓰기 차단

```ts theme={null}
import { customPolicies, allow, deny } from "failproofai";

customPolicies.add({
  name: "block-generated-file-edits",
  description: "Require generated files to be changed through their generator",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (!["Write", "Edit"].includes(ctx.toolName ?? "")) return allow();

    const filePath = String(ctx.toolInput?.file_path ?? "");
    if (!/(^|\/)(dist|generated)\//.test(filePath)) return allow();

    return deny("Edit the source and run the generator instead of changing generated output.");
  },
});
```

### 비차단 가이던스 제공

```ts theme={null}
import { customPolicies, allow, instruct } from "failproofai";

customPolicies.add({
  name: "prefer-reviewed-deploy-command",
  description: "Guide agents toward the reviewed deployment wrapper",
  match: { events: ["PreToolUse"] },
  fn: async (ctx) => {
    if (ctx.toolName !== "Bash") return allow();

    const command = String(ctx.toolInput?.command ?? "");
    if (!/^kubectl\s+apply\b/.test(command.trim())) return allow();

    return instruct("Use ./scripts/deploy-reviewed instead of invoking kubectl directly.");
  },
});
```

### 세션 완료 게이팅

```ts theme={null}
import { execFileSync } from "node:child_process";
import { customPolicies, allow, deny } from "failproofai";

customPolicies.add({
  name: "require-clean-typecheck",
  description: "Require the project typecheck to pass before the agent finishes",
  match: { events: ["Stop"] },
  fn: async (ctx) => {
    const cwd = ctx.session?.cwd;
    if (!cwd) return allow();

    try {
      execFileSync("bunx", ["tsc", "--noEmit"], {
        cwd,
        stdio: "ignore",
        timeout: 8_000,
      });
      return allow();
    } catch {
      return deny("Fix the typecheck errors before finishing the task.");
    }
  },
});
```

<Warning>
  거부된 `Stop` 이벤트는 에이전트가 재시도하게 만들 수 있습니다. 현재 환경에서 에이전트가 충족할 수 있는 조건에만 게이팅하고, 모든 서브프로세스 또는 네트워크 호출에 제한을 설정하세요.
</Warning>

## 정책 파일 로드하기

### 컨벤션 파일

컨벤션 파일은 자동으로 로드됩니다:

```text theme={null}
<project>/.failproofai/policies/security-policies.ts
~/.failproofai/policies/personal-policies.mjs
```

* 프로젝트 및 사용자 정책 디렉터리가 모두 로드됩니다.
* 각 디렉터리 내에서 파일은 알파벳 순서로 로드됩니다.
* 파일은 반드시 `policies.js`, `policies.mjs`, 또는 `policies.ts`로 끝나야 합니다.
* 하나의 파일에서 여러 번 `customPolicies.add()`를 호출하는 것이 지원됩니다.
* 로컬 모듈의 상대 임포트가 지원됩니다.
* 프로젝트 정책은 커밋할 수 있어 동일한 규칙이 저장소를 따라다닐 수 있습니다.

### 명시적 파일

유효성 검사나 설정에서 진입 파일을 직접 지정해야 할 때 명시적 경로를 사용하세요:

```bash theme={null}
failproofai policies --install \
  --custom ./security.policies.ts \
  --custom ./workflow.policies.ts \
  --scope project
```

명시적 파일이 먼저 로드되고, 이후 프로젝트 컨벤션 파일, 그 다음 사용자 컨벤션 파일이 로드됩니다. 두 경로를 통해 모두 발견된 파일은 한 번만 로드됩니다.

## 유효성 검사 및 테스트

유효성 검사는 프로덕션 로더를 통해 모듈을 실행하고 최소 하나의 정책이 등록되었는지 확인합니다.

```bash theme={null}
failproofai policies --install \
  --custom ./.failproofai/policies/checkout-policies.ts \
  --scope project
failproofai policies
```

유효성 검사는 파일 누락, 구문 오류, 해결되지 않은 임포트, 최상위 예외, 모듈 로드 타임아웃을 감지합니다. 매칭 로직이 올바른지는 증명하지 않습니다.

최소한 다음 케이스를 테스트하세요:

* 반드시 매칭되어 의도한 정책 이유를 생성해야 하는 동작 하나.
* 반드시 `allow()`를 반환해야 하는 인접하지만 안전한 동작 하나.
* 누락되거나 잘못 형성된 도구 필드.
* 대체 명령어 구문, 경로, 인용, 대소문자, 공백.
* 사용 불가능한 서브프로세스 또는 네트워크 의존성.

**Observe → policy**에서 결과가 커스텀 정책에 귀속되는지 확인하세요. 다른 내장 정책이 결정을 내렸다면 차단된 테스트만으로는 충분하지 않습니다.

## 런타임 동작

* 내장 정책이 커스텀 정책보다 먼저 평가됩니다.
* 첫 번째 `deny`가 이후 정책 평가를 중단합니다.
* 어떤 정책도 이벤트를 거부하지 않으면 여러 `instruct` 결과가 결합될 수 있습니다.
* 정책 함수의 실행 제한 시간은 10초입니다.
* 예외가 발생하거나 타임아웃이 되면 로그에 기록되고 `allow()`로 처리됩니다.
* 로드에 실패한 컨벤션 파일은 건너뛰며, 다른 커스텀 파일과 내장 정책은 계속 실행됩니다.
* 최상위 모듈 로딩에도 10초 제한 시간이 있습니다.
* 클라우드 관찰 모드는 정책을 실행하지만 비허용 결정을 적용하지 않고 기록만 합니다.

정책 모듈을 결정론적이고 빠르게 유지하세요. 최상위 수준의 네트워크 호출이나 서버 시작을 피하세요. `fn` 내부의 작업에 제한을 두고, 의존성 오류를 처리하며, 해당 오류가 작업을 허용해야 할지 거부해야 할지 신중하게 결정하세요.

## API 내보내기

| 내보내기                         | 목적                                 |
| ---------------------------- | ---------------------------------- |
| `customPolicies.add(policy)` | 모듈이 로드될 때 커스텀 정책을 등록합니다.           |
| `allow(reason?)`             | 작업을 허용합니다.                         |
| `instruct(reason)`           | 작업을 허용하고 지원되는 경우 가이던스를 제공합니다.      |
| `deny(reason)`               | 지원되는 경우 작업을 차단합니다.                 |
| `getCustomHooks()`           | 모듈 레지스트리에 현재 등록된 정책을 반환합니다.        |
| `clearCustomHooks()`         | 해당 레지스트리를 지우며, 주로 테스트 및 로더에 사용됩니다. |

TypeScript는 `PolicyContext`, `PolicyResult`, `CustomHook`, `PolicyDecision`, `PolicyFunction`을 내보냅니다.

<Card title="커스텀 정책 배포" icon="server-cog" href="/ko/policies/deploy">
  버전을 게시하고, 관찰 모드로 배포하고, 결정 내역을 확인한 후 적용으로 전환하세요.
</Card>
