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

# Plugin 훅

Plugin 훅은 OpenClaw Plugin을 위한 인프로세스 확장 지점입니다. Plugin이 에이전트 실행, 도구 호출, 메시지 흐름, 세션 수명 주기, 하위 에이전트 라우팅, 설치 또는 Gateway 시작을 검사하거나 변경해야 할 때 사용하세요.

`/new`, `/reset`, `/stop`, `agent:bootstrap`, `gateway:startup` 같은 명령 및 Gateway 이벤트에 대해 운영자가 설치하는 작은 `HOOK.md` 스크립트를 원한다면 대신 [내부 훅](/ko/automation/hooks)을 사용하세요.

## 빠른 시작

Plugin 엔트리에서 `api.on(...)`으로 타입이 지정된 Plugin 훅을 등록하세요.

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";

export default definePluginEntry({
  id: "tool-preflight",
  name: "Tool Preflight",
  register(api) {
    api.on(
      "before_tool_call",
      async (event) => {
        if (event.toolName !== "web_search") {
          return;
        }

        return {
          requireApproval: {
            title: "Run web search",
            description: `Allow search query: ${String(event.params.query ?? "")}`,
            severity: "info",
            timeoutMs: 60_000,
            timeoutBehavior: "deny",
          },
        };
      },
      { priority: 50 },
    );
  },
});
```

훅 핸들러는 `priority`가 높은 순서대로 순차 실행됩니다. 우선순위가 같은 훅은 등록 순서를 유지합니다.

`api.on(name, handler, opts?)`는 다음을 받습니다.

* `priority` - 핸들러 순서 지정(높을수록 먼저 실행).
* `timeoutMs` - 선택적 훅별 예산입니다. 설정하면 훅 실행기는 예산이 경과한 뒤 해당 핸들러를 중단하고 다음 핸들러로 계속 진행합니다. 느린 설정이나 회상 작업이 호출자가 구성한 모델 타임아웃을 소비하게 두지 않습니다. 생략하면 훅 실행기가 일반적으로 적용하는 기본 관찰/결정 타임아웃을 사용합니다.

운영자는 Plugin 코드를 패치하지 않고도 훅 예산을 설정할 수 있습니다.

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "plugins": {
    "entries": {
      "my-plugin": {
        "hooks": {
          "timeoutMs": 30000,
          "timeouts": {
            "before_prompt_build": 90000,
            "agent_end": 60000
          }
        }
      }
    }
  }
}
```

`hooks.timeouts.<hookName>`은 `hooks.timeoutMs`를 재정의하며, 이는 Plugin 작성자가 설정한 `api.on(..., { timeoutMs })` 값을 재정의합니다. 구성된 각 값은 600000밀리초 이하의 양의 정수여야 합니다. 느린 것으로 알려진 훅에는 훅별 재정의를 선호하여 하나의 Plugin이 모든 곳에서 더 긴 예산을 갖지 않도록 하세요.

각 훅은 해당 핸들러를 등록한 Plugin의 확인된 구성인 `event.context.pluginConfig`를 받습니다. 현재 Plugin 옵션이 필요한 훅 결정에 사용하세요. OpenClaw는 다른 Plugin이 보는 공유 이벤트 객체를 변경하지 않고 핸들러별로 이를 주입합니다.

## 훅 카탈로그

훅은 확장하는 표면별로 그룹화됩니다. **굵게** 표시된 이름은 결정 결과(차단, 취소, 재정의 또는 승인 요구)를 받을 수 있으며, 나머지는 모두 관찰 전용입니다.

**에이전트 턴**

* `before_model_resolve` - 세션 메시지가 로드되기 전에 공급자 또는 모델 재정의
* `agent_turn_prepare` - 대기 중인 Plugin 턴 주입을 소비하고 프롬프트 훅 전에 동일 턴 컨텍스트 추가
* `before_prompt_build` - 모델 호출 전에 동적 컨텍스트 또는 시스템 프롬프트 텍스트 추가
* `before_agent_start` - 호환성 전용 결합 단계입니다. 위의 두 훅을 선호하세요
* **`before_agent_run`** - 모델 제출 전에 최종 프롬프트와 세션 메시지를 검사하고 선택적으로 실행 차단
* **`before_agent_reply`** - 합성 응답 또는 침묵으로 모델 턴 단락
* **`before_agent_finalize`** - 자연스러운 최종 답변을 검사하고 모델 패스 한 번 더 요청
* `agent_end` - 최종 메시지, 성공 상태, 실행 시간 관찰
* `heartbeat_prompt_contribution` - 백그라운드 모니터 및 수명 주기 Plugin을 위한 Heartbeat 전용 컨텍스트 추가

**대화 관찰**

* `model_call_started` / `model_call_ended` - 프롬프트 또는 응답 콘텐츠 없이 정리된 공급자/모델 호출 메타데이터, 타이밍, 결과, 제한된 요청 ID 해시 관찰
* `llm_input` - 공급자 입력(시스템 프롬프트, 프롬프트, 기록) 관찰
* `llm_output` - 공급자 출력, 사용량, 사용 가능한 경우 확인된 `contextTokenBudget` 관찰

**도구**

* **`before_tool_call`** - 도구 매개변수 재작성, 실행 차단 또는 승인 요구
* `after_tool_call` - 도구 결과, 오류, 기간 관찰
* `resolve_exec_env` - Plugin 소유 환경 변수를 `exec`에 제공
* **`tool_result_persist`** - 도구 결과에서 생성된 어시스턴트 메시지 재작성
* **`before_message_write`** - 진행 중인 메시지 쓰기 검사 또는 차단(드문 경우)

**메시지 및 전달**

* **`inbound_claim`** - 에이전트 라우팅 전 인바운드 메시지 클레임(합성 응답)
* `message_received` — 인바운드 콘텐츠, 발신자, 스레드, 메타데이터 관찰
* **`message_sending`** — 아웃바운드 콘텐츠 재작성 또는 전달 취소
* **`reply_payload_sending`** — 전달 전 정규화된 응답 페이로드 변경 또는 취소
* `message_sent` — 아웃바운드 전달 성공 또는 실패 관찰
* **`before_dispatch`** - 채널 인계 전 아웃바운드 디스패치 검사 또는 재작성
* **`reply_dispatch`** - 최종 응답 디스패치 파이프라인에 참여

**세션 및 Compaction**

* `session_start` / `session_end` - 세션 수명 주기 경계 추적. 이벤트의 `reason`은 `new`, `reset`, `idle`, `daily`, `compaction`, `deleted`, `shutdown`, `restart`, `unknown` 중 하나입니다. `shutdown` 및 `restart` 값은 세션이 아직 활성 상태인 동안 프로세스가 중지되거나 재시작될 때 Gateway 종료 파이널라이저에서 발생하므로, 메모리 또는 트랜스크립트 저장소 같은 다운스트림 Plugin이 재시작 후에도 열린 상태로 남을 수 있는 고스트 행을 마무리할 수 있습니다. 파이널라이저는 제한되어 있어 느린 Plugin이 SIGTERM/SIGINT를 차단할 수 없습니다.
* `before_compaction` / `after_compaction` - Compaction 주기 관찰 또는 주석 추가
* `before_reset` - 세션 재설정 이벤트(`/reset`, 프로그래밍 방식 재설정) 관찰

**하위 에이전트**

* `subagent_spawned` / `subagent_ended` - 하위 에이전트 시작 및 완료 관찰.
* `subagent_delivery_target` - 코어 세션 바인딩이 경로를 투영할 수 없을 때 완료 전달을 위한 호환성 훅.
* `subagent_spawning` - 사용 중단된 호환성 훅입니다. 이제 코어는 `subagent_spawned`가 발생하기 전에 채널 세션 바인딩 어댑터를 통해 `thread: true` 하위 에이전트 바인딩을 준비합니다.
* `subagent_spawned`에는 OpenClaw가 시작 전에 자식 세션의 네이티브 모델을 확인한 경우 `resolvedModel` 및 `resolvedProvider`가 포함됩니다.
* `subagent_ended`는 `targetSessionKey`(ID — 이는 `subagent_spawned.childSessionKey`와 일치), `targetKind`(`"subagent"` 또는 `"acp"`), `reason`, 선택적 `outcome`(`"ok"`, `"error"`, `"timeout"`, `"killed"`, `"reset"` 또는 `"deleted"`), 선택적 `error`, `runId`, `endedAt`, `accountId`, `sendFarewell`을 전달합니다. `agentId` 또는 `childSessionKey`는 포함하지 **않습니다**. 해당 `subagent_spawned` 이벤트와 연결하려면 `targetSessionKey`를 사용하세요.

**수명 주기**

* `gateway_start` / `gateway_stop` - Gateway와 함께 Plugin 소유 서비스 시작 또는 중지
* `deactivate` - `gateway_stop`의 사용 중단된 호환성 별칭입니다. 새 Plugin에서는 `gateway_stop`을 사용하세요
* `cron_changed` - Gateway 소유 Cron 수명 주기 변경(추가, 업데이트, 제거, 시작, 완료, 예약) 관찰
* **`before_install`** - 로드된 Plugin 런타임에서 준비된 Skill 또는 Plugin 설치 자료 검사

## 런타임 훅 디버그

Plugin이 에이전트 턴의 공급자 또는 모델을 전환해야 할 때 `before_model_resolve`를 사용하세요. 이는 모델 확인 전에 실행됩니다. `llm_output`은 모델 시도가 어시스턴트 출력을 생성한 뒤에만 실행됩니다.

유효한 세션 모델을 증명하려면 런타임 등록을 검사한 다음 `openclaw sessions` 또는 Gateway 세션/상태 표면을 사용하세요. 공급자 페이로드를 디버그할 때는 `--raw-stream` 및 `--raw-stream-path <path>`로 Gateway를 시작하세요. 이 플래그는 원시 모델 스트림 이벤트를 jsonl 파일에 기록합니다.

## 도구 호출 정책

`before_tool_call`은 다음을 받습니다.

* `event.toolName`
* `event.params`
* 선택적 `event.toolKind` 및 `event.toolInputKind`, 의도적으로 이름을 공유하는 도구를 위한 호스트 권한 판별자입니다. 예를 들어 외부 코드 모드 `exec` 호출은 `toolKind: "code_mode_exec"`를 사용하며, 입력 언어를 알 수 있으면 `toolInputKind: "javascript" | "typescript"`를 포함합니다
* 선택적 `event.derivedPaths`, `apply_patch` 같은 잘 알려진 도구 봉투에 대해 호스트가 최선 노력으로 파생한 대상 경로 힌트를 포함합니다. 존재하는 경우 이러한 경로는 불완전하거나 도구가 실제로 건드릴 범위를 과대 추정할 수 있습니다(예: 잘못되었거나 부분적인 입력)
* 선택적 `event.runId`
* 선택적 `event.toolCallId`
* `ctx.agentId`, `ctx.sessionKey`, `ctx.sessionId`, `ctx.runId`, `ctx.jobId`(Cron 기반 실행에서 설정), `ctx.toolKind`, `ctx.toolInputKind`, 진단용 `ctx.trace` 같은 컨텍스트 필드

다음을 반환할 수 있습니다.

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type BeforeToolCallResult = {
  params?: Record<string, unknown>;
  block?: boolean;
  blockReason?: string;
  requireApproval?: {
    title: string;
    description: string;
    severity?: "info" | "warning" | "critical";
    timeoutMs?: number;
    timeoutBehavior?: "allow" | "deny";
    allowedDecisions?: Array<"allow-once" | "allow-always" | "deny">;
    pluginId?: string;
    onResolution?: (
      decision: "allow-once" | "allow-always" | "deny" | "timeout" | "cancelled",
    ) => Promise<void> | void;
  };
};
```

타입이 지정된 수명 주기 훅의 훅 가드 동작:

* `block: true`는 터미널이며 더 낮은 우선순위 핸들러를 건너뜁니다.
* `block: false`는 결정 없음으로 처리됩니다.
* `params`는 실행을 위해 도구 매개변수를 재작성합니다.
* `requireApproval`은 에이전트 실행을 일시 중지하고 Plugin 승인을 통해 사용자에게 묻습니다. `/approve` 명령은 exec 승인과 Plugin 승인을 모두 승인할 수 있습니다. Codex 앱 서버 보고 모드 네이티브 `PreToolUse` 릴레이에서는 일치하는 앱 서버 승인 요청으로 지연됩니다. [Codex 하네스 런타임](/ko/plugins/codex-harness-runtime#hook-boundaries)을 참조하세요.
* 더 낮은 우선순위의 `block: true`는 더 높은 우선순위 훅이 승인을 요청한 뒤에도 여전히 차단할 수 있습니다.
* `onResolution`은 확인된 승인 결정(`allow-once`, `allow-always`, `deny`, `timeout` 또는 `cancelled`)을 받습니다.

승인 라우팅, 결정 동작, 선택적 도구 또는 exec 승인 대신 `requireApproval`을 사용해야 하는 경우는 [Plugin 권한 요청](/ko/plugins/plugin-permission-requests)을 참조하세요.

호스트 수준 정책이 필요한 Plugin은 `api.registerTrustedToolPolicy(...)`로 신뢰할 수 있는 도구 정책을 등록할 수 있습니다. 이러한 정책은 일반 `before_tool_call` 훅과 일반 훅 결정보다 먼저 실행됩니다. 번들된 신뢰 정책이 먼저 실행되고, 설치된 Plugin의 신뢰 정책이 Plugin 로드 순서대로 다음에 실행되며, 일반 `before_tool_call` 훅은 그 뒤에 실행됩니다. 번들된 Plugin은 기존 신뢰 정책 경로를 유지합니다. 설치된 Plugin은 명시적으로 활성화되어야 하며 `contracts.trustedToolPolicies`에 모든 정책 ID를 선언해야 합니다. 선언되지 않은 ID는 등록 전에 거부됩니다. 정책 ID는 등록 Plugin 범위에 속하므로 서로 다른 Plugin이 같은 로컬 ID를 재사용할 수 있습니다. 이 계층은 작업공간 정책, 예산 적용 또는 예약된 워크플로 안전성 같은 호스트 신뢰 게이트에만 사용하세요.

### Exec 환경 훅

`resolve_exec_env`를 사용하면 Plugin이 기본 exec 환경이 만들어진 뒤 명령이 실행되기 전에 `exec` 도구 호출에 환경 변수를 제공할 수 있습니다. 이는 다음을 받습니다.

* `event.sessionKey`
* `event.toolName`, 현재 항상 `"exec"`
* `event.host`, `"gateway"`, `"sandbox"` 또는 `"node"` 중 하나
* `ctx.agentId`, `ctx.sessionKey`, `ctx.messageProvider`, `ctx.channelId` 같은 컨텍스트 필드

exec 환경에 병합하려면 `Record<string, string>`을 반환하세요. 핸들러는 우선순위 순서로 실행되며, 이후 훅 결과는 같은 키에 대해 이전 훅 결과를 재정의합니다.

Hook 출력은 병합되기 전에 호스트 exec 환경 키 정책을 통해 필터링됩니다. 유효하지 않은 키, `PATH`, 그리고 `LD_*`, `DYLD_*`, `NODE_OPTIONS`, 프록시 변수, TLS 재정의 변수 같은 위험한 호스트 재정의 키는 삭제됩니다. 필터링된 plugin env는 gateway 승인/감사 메타데이터에 포함되고 node-host 실행 요청으로 전달됩니다.

### 도구 결과 지속성

도구 결과에는 UI 렌더링, 진단, 미디어 라우팅 또는 plugin 소유 메타데이터를 위한 구조화된 `details`가 포함될 수 있습니다. `details`는 프롬프트 콘텐츠가 아니라 런타임 메타데이터로 취급하세요.

* OpenClaw는 제공자 리플레이와 compaction 입력 전에 `toolResult.details`를 제거하여 메타데이터가 모델 컨텍스트가 되지 않게 합니다.
* 지속된 세션 항목은 제한된 `details`만 유지합니다. 너무 큰 details는 간결한 요약과 `persistedDetailsTruncated: true`로 대체됩니다.
* `tool_result_persist`와 `before_message_write`는 최종 지속성 한도 전에 실행됩니다. Hooks는 여전히 반환되는 `details`를 작게 유지해야 하며, 프롬프트 관련 텍스트를 `details`에만 넣지 않아야 합니다. 모델에 보이는 도구 출력은 `content`에 넣으세요.

## 프롬프트 및 모델 hooks

새 plugins에는 단계별 hooks를 사용하세요.

* `before_model_resolve`: 현재 프롬프트와 첨부 파일 메타데이터만 받습니다. `providerOverride` 또는 `modelOverride`를 반환합니다.
* `agent_turn_prepare`: 현재 프롬프트, 준비된 세션 메시지, 그리고 이 세션에 대해 비워진 정확히 한 번 큐 주입을 받습니다. `prependContext` 또는 `appendContext`를 반환합니다.
* `before_prompt_build`: 현재 프롬프트와 세션 메시지를 받습니다. `prependContext`, `appendContext`, `systemPrompt`, `prependSystemContext` 또는 `appendSystemContext`를 반환합니다.
* `heartbeat_prompt_contribution`: Heartbeat 턴에서만 실행되며 `prependContext` 또는 `appendContext`를 반환합니다. 사용자 시작 턴을 변경하지 않고 현재 상태를 요약해야 하는 백그라운드 모니터를 위한 것입니다.

`before_agent_start`는 호환성을 위해 남아 있습니다. plugin이 레거시 결합 단계에 의존하지 않도록 위의 명시적 hooks를 선호하세요.

`before_agent_run`은 프롬프트 구성 후, 그리고 프롬프트 로컬 이미지 로딩과 `llm_input` 관찰을 포함한 모든 모델 입력 전에 실행됩니다. 현재 사용자 입력을 `prompt`로 받고, 로드된 세션 기록을 `messages`로 받으며, 활성 시스템 프롬프트도 받습니다. 모델이 프롬프트를 읽기 전에 실행을 중지하려면 `{ outcome: "block", reason, message? }`를 반환하세요. `reason`은 내부용이고, `message`는 사용자에게 표시되는 대체 문구입니다. 지원되는 outcome은 `pass`와 `block`뿐이며, 지원되지 않는 결정 형태는 fail closed됩니다.

실행이 차단되면 OpenClaw는 `message.content`에 대체 텍스트만 저장하고 차단 plugin id와 타임스탬프 같은 민감하지 않은 차단 메타데이터를 함께 저장합니다. 원래 사용자 텍스트는 transcript나 향후 컨텍스트에 보존되지 않습니다. 내부 차단 reason은 민감한 정보로 취급되며 transcript, 기록, 브로드캐스트, 로그, 진단 페이로드에서 제외됩니다. 관찰성에는 blocker id, outcome, 타임스탬프 또는 안전한 category 같은 정제된 필드를 사용해야 합니다.

`before_agent_start`와 `agent_end`에는 OpenClaw가 활성 실행을 식별할 수 있을 때 `event.runId`가 포함됩니다. 같은 값은 `ctx.runId`에서도 사용할 수 있습니다. Cron 기반 실행은 `ctx.jobId`(원본 cron 작업 id)도 노출하므로 plugin hooks가 메트릭, 부작용 또는 상태를 특정 예약 작업으로 범위 지정할 수 있습니다.

채널에서 시작된 실행의 경우 `ctx.channel`과 `ctx.messageProvider`는 `discord` 또는 `telegram` 같은 제공자 표면을 식별하고, `ctx.channelId`는 OpenClaw가 세션 키나 전달 메타데이터에서 도출할 수 있을 때의 대화 대상 식별자입니다.

발신자 ID를 사용할 수 있는 경우 agent hook 컨텍스트에는 다음도 포함됩니다.

* `ctx.senderId` — 채널 범위 발신자 ID(예: Feishu `open_id`, Discord 사용자 ID). 실행이 알려진 발신자 메타데이터가 있는 사용자 메시지에서 시작될 때 채워집니다.
* `ctx.chatId` — 전송 네이티브 대화 식별자(예: Feishu `chat_id`, Telegram `chat_id`). 원본 채널이 네이티브 대화 ID를 제공할 때 채워집니다.
* `ctx.channelContext.sender.id` — `ctx.senderId`와 같은 발신자 ID이며, plugins가 채널별 필드로 확장할 수 있는 채널 소유 객체 아래에 있습니다.
* `ctx.channelContext.chat.id` — `ctx.chatId`와 같은 대화 ID이며, plugins가 채널별 필드로 확장할 수 있는 채널 소유 객체 아래에 있습니다.

Core는 중첩된 `id` 필드만 정의합니다. 인바운드 helper를 통해 더 풍부한 발신자 또는 채팅 메타데이터를 전달하는 channel plugins는 `openclaw/plugin-sdk/channel-inbound`의 `PluginHookChannelSenderContext` 또는 `PluginHookChannelChatContext`를 확장할 수 있습니다.

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
declare module "openclaw/plugin-sdk/channel-inbound" {
  interface PluginHookChannelSenderContext {
    unionId?: string;
    userId?: string;
  }
}
```

Channel plugins는 인바운드 SDK helper를 통해 해당 필드를 전달합니다.

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
buildChannelInboundEventContext({
  // ...
  channelContext: {
    sender: { id: senderOpenId, unionId, userId },
    chat: { id: chatId },
  },
});
```

이 필드는 선택 사항이며 시스템 시작 실행(heartbeat, cron, exec-event)에는 없습니다.

`ctx.senderExternalId`는 오래된 plugins를 위한 더 이상 권장되지 않는 소스 호환성 필드로 남아 있습니다. Core는 이를 채우지 않습니다. 새 채널별 발신자 ID는 모듈 확장을 통해 `ctx.channelContext.sender` 아래에 있어야 합니다.

`agent_end`는 관찰 hook입니다. Gateway와 지속성 harness 경로는 턴 이후 이를 fire-and-forget으로 실행하는 반면, 짧게 실행되는 일회성 CLI 경로는 프로세스 정리 전에 hook promise를 기다려 신뢰할 수 있는 plugins가 터미널 관찰성을 flush하거나 상태를 캡처할 수 있게 합니다. Hook runner는 30초 타임아웃을 적용하므로 멈춘 plugin이나 embedding 엔드포인트가 hook promise를 영원히 pending 상태로 남겨둘 수 없습니다. 타임아웃은 로그에 기록되고 OpenClaw는 계속 진행합니다. 다만 plugin도 자체 abort signal을 사용하지 않는 한 plugin 소유 네트워크 작업은 취소하지 않습니다.

원시 프롬프트, 기록, 응답, 헤더, 요청 본문 또는 제공자 요청 ID를 받아서는 안 되는 제공자 호출 telemetry에는 `model_call_started`와 `model_call_ended`를 사용하세요. 이 hooks에는 `runId`, `callId`, `provider`, `model`, 선택적 `api`/`transport`, 최종 `durationMs`/`outcome`, 그리고 OpenClaw가 제한된 제공자 request-id hash를 도출할 수 있을 때의 `upstreamRequestIdHash` 같은 안정적인 메타데이터가 포함됩니다. 런타임이 context-window 메타데이터를 해석한 경우 hook 이벤트와 컨텍스트에는 모델/config/agent 한도 이후의 유효 토큰 예산인 `contextTokenBudget`도 포함되며, 더 낮은 한도가 적용되었을 때 `contextWindowSource`와 `contextWindowReferenceTokens`도 포함됩니다.

`before_agent_finalize`는 harness가 자연스러운 최종 assistant 답변을 수락하려고 할 때만 실행됩니다. 이는 `/stop` 취소 경로가 아니며 사용자가 턴을 중단할 때는 실행되지 않습니다. 최종화 전에 harness에 모델 패스를 한 번 더 요청하려면 `{ action: "revise", reason }`을 반환하고, 최종화를 강제하려면 `{ action:
"finalize", reason? }`을 반환하거나, 계속하려면 결과를 생략하세요. Codex 네이티브 `Stop` hooks는 OpenClaw `before_agent_finalize` 결정으로 이 hook에 중계됩니다.

`action: "revise"`를 반환할 때 plugins는 추가 모델 패스를 제한적이고 replay-safe하게 만들기 위해 `retry` 메타데이터를 포함할 수 있습니다.

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type BeforeAgentFinalizeRetry = {
  instruction: string;
  idempotencyKey?: string;
  maxAttempts?: number;
};
```

`instruction`은 harness로 전송되는 수정 reason에 추가됩니다. `idempotencyKey`를 사용하면 호스트가 동등한 finalize 결정 전반에서 같은 plugin 요청에 대한 retry 횟수를 셀 수 있고, `maxAttempts`는 자연스러운 최종 답변으로 계속 진행하기 전에 호스트가 허용할 추가 패스 수를 제한합니다.

원시 대화 hooks(`before_model_resolve`, `before_agent_reply`, `llm_input`, `llm_output`, `before_agent_finalize`, `agent_end` 또는 `before_agent_run`)가 필요한 비번들 plugins는 다음을 설정해야 합니다.

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "plugins": {
    "entries": {
      "my-plugin": {
        "hooks": {
          "allowConversationAccess": true
        }
      }
    }
  }
}
```

프롬프트를 변경하는 hooks와 지속성 있는 다음 턴 주입은 plugin별로 `plugins.entries.<id>.hooks.allowPromptInjection=false`를 사용해 비활성화할 수 있습니다.

### 세션 확장 및 다음 턴 주입

Workflow plugins는 `api.registerSessionExtension(...)`으로 작은 JSON 호환 세션 상태를 지속하고 Gateway `sessions.pluginPatch` 메서드를 통해 업데이트할 수 있습니다. 세션 행은 등록된 확장 상태를 `pluginExtensions`를 통해 투영하여 Control UI와 다른 클라이언트가 plugin 내부를 알지 않고도 plugin 소유 상태를 렌더링할 수 있게 합니다.

plugin이 다음 모델 턴에 정확히 한 번 도달해야 하는 지속성 컨텍스트가 필요할 때는 `api.enqueueNextTurnInjection(...)`을 사용하세요. OpenClaw는 프롬프트 hooks 전에 큐에 있는 주입을 비우고, 만료된 주입을 삭제하며, plugin별로 `idempotencyKey`를 기준으로 중복 제거합니다. 이는 approval resume, 정책 요약, 백그라운드 모니터 delta, 그리고 다음 턴에서 모델에 보여야 하지만 영구 시스템 프롬프트 텍스트가 되어서는 안 되는 명령 continuation에 적합한 seam입니다.

정리 의미론은 계약의 일부입니다. 세션 확장 정리와 런타임 수명 주기 정리 콜백은 `reset`, `delete`, `disable` 또는 `restart`를 받습니다. 호스트는 reset/delete/disable에 대해 소유 plugin의 지속성 세션 확장 상태와 pending 다음 턴 주입을 제거합니다. restart는 지속성 세션 상태를 유지하는 한편, 정리 콜백을 통해 plugins가 이전 런타임 generation의 scheduler jobs, 실행 컨텍스트 및 기타 out-of-band 리소스를 해제할 수 있게 합니다.

## 메시지 hooks

채널 수준 라우팅 및 전달 정책에는 message hooks를 사용하세요.

* `message_received`: 인바운드 콘텐츠, 발신자, `threadId`, `messageId`, `senderId`, 선택적 실행/세션 상관관계, 메타데이터를 관찰합니다.
* `message_sending`: `content`를 다시 작성하거나 `{ cancel: true }`를 반환합니다.
* `reply_payload_sending`: 정규화된 `ReplyPayload` 객체(`presentation`, `delivery`, 미디어 참조, 텍스트 포함)를 다시 작성하거나 `{ cancel: true }`를 반환합니다.
* `message_sent`: 최종 성공 또는 실패를 관찰합니다.

오디오 전용 TTS 답변의 경우 채널 페이로드에 보이는 텍스트/캡션이 없더라도 `content`에 숨겨진 음성 transcript가 포함될 수 있습니다. 해당 `content`를 다시 작성하면 hook에 보이는 transcript만 업데이트되며, 미디어 캡션으로 렌더링되지는 않습니다.

`reply_payload_sending` 이벤트에는 best-effort live 턴별 모델/사용량/컨텍스트 스냅샷인 `usageState`가 포함될 수 있습니다. 지속성 전달, 복구된 replay, 정확한 실행 상관관계가 없는 답변에는 이를 생략합니다.

Message hook 컨텍스트는 사용 가능한 경우 안정적인 상관관계 필드를 노출합니다. `ctx.sessionKey`, `ctx.runId`, `ctx.messageId`, `ctx.senderId`, `ctx.trace`, `ctx.traceId`, `ctx.spanId`, `ctx.parentSpanId`, `ctx.callDepth`. 인바운드 및 `before_dispatch` 컨텍스트는 채널에 visibility-filtered 인용 메시지 데이터가 있을 때 답장 메타데이터도 노출합니다. `replyToId`, `replyToIdFull`, `replyToBody`, `replyToSender`, `replyToIsQuote`. 레거시 메타데이터를 읽기 전에 이러한 일급 필드를 우선 사용하세요.

채널별 메타데이터를 사용하기 전에 typed `threadId` 및 `replyToId` 필드를 우선 사용하세요.

결정 규칙:

* `cancel: true`가 있는 `message_sending`은 종결 상태입니다.
* `cancel: false`가 있는 `message_sending`은 결정 없음으로 처리됩니다.
* 다시 작성된 `content`는 이후 후크가 전달을 취소하지 않는 한 더 낮은 우선순위의 후크로 계속 전달됩니다.
* `reply_payload_sending`은 페이로드 정규화 이후, 채널 전달 이전에 실행되며, 원래 채널로 다시 라우팅되는 답장도 포함됩니다. 핸들러는 순차적으로 실행되며 각 핸들러는 더 높은 우선순위의 핸들러가 생성한 최신 페이로드를 봅니다.
* `reply_payload_sending` 페이로드는 `trustedLocalMedia` 같은 런타임 신뢰 마커를 노출하지 않습니다. plugins는 페이로드 형태를 수정할 수 있지만 로컬 미디어 신뢰를 부여할 수는 없습니다.
* `message_sending`은 취소와 함께 `cancelReason` 및 제한된 `metadata`를 반환할 수 있습니다. 새 메시지 수명 주기 API는 이를 이유가 `cancelled_by_message_sending_hook`인 억제된 전달 결과로 노출하며, 레거시 직접 전달은 호환성을 위해 계속 빈 결과 배열을 반환합니다.
* `message_sent`는 관찰 전용입니다. 핸들러 실패는 로그에 기록되며 전달 결과를 변경하지 않습니다.

## 설치 후크

운영자가 소유한 허용/차단 결정에는 `security.installPolicy`를 사용하세요. 이 정책은 OpenClaw 구성에서 실행되고 CLI 설치 및 업데이트 경로를 포함하며, 활성화되었지만 사용할 수 없는 경우 실패 시 닫힘으로 동작합니다.

`before_install`은 plugin 런타임 수명 주기 후크입니다. Gateway 기반 설치 흐름처럼 plugin 후크가 이미 로드된 OpenClaw 프로세스에서만 `security.installPolicy` 이후에 실행됩니다. Plugin 소유의 관찰, 경고, 호환성 검사에 유용하지만 설치를 위한 기본 엔터프라이즈 또는 호스트 보안 경계는 아닙니다. `builtinScan` 필드는 호환성을 위해 이벤트 페이로드에 남아 있지만, OpenClaw는 더 이상 설치 시점의 기본 위험 코드 차단을 실행하지 않으므로 빈 `ok` 결과입니다. 해당 프로세스에서 설치를 중지하려면 추가 발견 사항 또는 `{ block: true, blockReason }`를 반환하세요.

`block: true`는 종결 상태입니다. `block: false`는 결정 없음으로 처리됩니다.
핸들러 실패는 설치를 실패 시 닫힘으로 차단합니다.

## Gateway 수명 주기

Gateway 소유 상태가 필요한 plugin 서비스에는 `gateway_start`를 사용하세요. 컨텍스트는 cron 검사 및 업데이트를 위해 `ctx.config`, `ctx.workspaceDir`, `ctx.getCron?.()`를 노출합니다. 장기 실행 리소스를 정리하려면 `gateway_stop`을 사용하세요.

Plugin 소유 런타임 서비스에 내부 `gateway:startup` 후크에 의존하지 마세요.

`cron_changed`는 `added`, `updated`, `removed`, `started`, `finished`, `scheduled` 이유를 포함하는 타입 지정 이벤트 페이로드와 함께 gateway 소유 cron 수명 주기 이벤트에 대해 발생합니다. 이벤트는 `PluginHookGatewayCronJob` 스냅샷(있는 경우 `state.nextRunAtMs`, `state.lastRunStatus`, `state.lastError` 포함)과 `not-requested` | `delivered` | `not-delivered` | `unknown` 중 하나인 `PluginHookGatewayCronDeliveryStatus`를 전달합니다. 삭제된 이벤트도 삭제된 작업 스냅샷을 계속 전달하므로 외부 스케줄러가 상태를 조정할 수 있습니다. 외부 깨우기 스케줄러를 동기화할 때 런타임 컨텍스트의 `ctx.getCron?.()` 및 `ctx.config`를 사용하고, 마감 확인 및 실행의 진실 공급원은 OpenClaw로 유지하세요.

## 예정된 사용 중단

몇 가지 후크 인접 표면은 사용 중단되었지만 여전히 지원됩니다. 다음 메이저 릴리스 전에 마이그레이션하세요.

* `inbound_claim` 및 `message_received` 핸들러의 **평문 채널 엔벨로프**. 평면 엔벨로프 텍스트를 파싱하는 대신 `BodyForAgent`와 구조화된 사용자 컨텍스트 블록을 읽으세요. [평문 채널 엔벨로프 → BodyForAgent](/ko/plugins/sdk-migration#active-deprecations)를 참조하세요.
* \*\*`before_agent_start`\*\*는 호환성을 위해 남아 있습니다. 새 plugins는 결합된 단계 대신 `before_model_resolve` 및 `before_prompt_build`를 사용해야 합니다.
* \*\*`subagent_spawning`\*\*은 이전 plugins와의 호환성을 위해 남아 있지만, 새 plugins는 여기서 스레드 라우팅을 반환해서는 안 됩니다. 코어는 `subagent_spawned`가 발생하기 전에 채널 세션 바인딩 어댑터를 통해 `thread: true` 하위 에이전트 바인딩을 준비합니다.
* \*\*`deactivate`\*\*는 2026-08-16 이후까지 사용 중단된 정리 호환성 별칭으로 남아 있습니다. 새 plugins는 `gateway_stop`을 사용해야 합니다.
* \*\*`before_tool_call`의 `onResolution`\*\*은 이제 자유 형식 `string` 대신 타입 지정된 `PluginApprovalResolution` 유니언(`allow-once` / `allow-always` / `deny` / `timeout` / `cancelled`)을 사용합니다.

전체 목록은 메모리 기능 등록, 공급자 thinking 프로필, 외부 인증 공급자, 공급자 검색 타입, 작업 런타임 접근자, `command-auth` → `command-status` 이름 변경을 포함하며, [Plugin SDK 마이그레이션 → 활성 사용 중단](/ko/plugins/sdk-migration#active-deprecations)을 참조하세요.

## 관련 항목

* [Plugin SDK 마이그레이션](/ko/plugins/sdk-migration) - 활성 사용 중단 및 제거 일정
* [plugins 빌드](/ko/plugins/building-plugins)
* [Plugin SDK 개요](/ko/plugins/sdk-overview)
* [Plugin 진입점](/ko/plugins/sdk-entrypoints)
* [내부 후크](/ko/automation/hooks)
* [Plugin 아키텍처 내부 구조](/ko/plugins/architecture-internals)
