Ch11. Schedules, State, Hooks
Eve의 cron schedule, durable state, stream event hook을 운영 자동화와 감사 체계로 연결한다.
핵심 요약
- Schedules, state, hooks는 대화 밖에서도 에이전트를 운영 시스템으로 굴리게 해주는 표면입니다.
- durable state는 작은 session-scoped 기억에 쓰고, 데이터베이스 대신 쓰면 안 됩니다.
- hook은 stream event 뒤에 실행되는데, 여기서 실패하면 session 전체가 실패할 수 있습니다. 그래서 관측/감사 코드도 운영 품질로 봐야 합니다.
프로덕션 에이전트는 사용자가 말을 걸 때만 움직이는 게 아닙니다. 정기적으로 점검하고, 세션 도중 상태를 기억하고, 실행을 빠짐없이 감사해야 합니다. Eve의 schedule, state, hook이 이 운영면을 맡습니다.
Schedules
Schedule은 agent/schedules/ 아래에서 정의합니다. root-only 슬롯이며 subagent에는 없습니다.
형태는 두 가지입니다.
| 형태 | 사용 |
|---|---|
markdown | fire-and-forget task-mode prompt |
run handler | channel handoff, branching, waitUntil이 필요한 작업 |
Markdown schedule
import { defineSchedule } from "eve/schedules";
export default defineSchedule({
cron: "*/5 * * * *",
markdown: "Check the system heartbeat and summarize anomalies.",
});Task-mode schedule은 output을 버릴 수 있고 사람 입력이나 OAuth sign-in을 기다리지 못합니다. write action이나 approval이 필요한 작업이라면 handler form을 쓰는 편이 낫습니다.
Handler schedule
import { defineSchedule } from "eve/schedules";
import slack from "../channels/slack.js";
export default defineSchedule({
cron: "0 9 * * 1-5",
async run({ receive, waitUntil, appAuth }) {
waitUntil(
receive(slack, {
message: "Summarize yesterday's incidents and post a digest.",
target: { channelId: "C0123ABC" },
auth: appAuth,
}),
);
},
});Vercel에서는 cron을 UTC 기준으로 평가합니다. self-host라면 eve start가 Nitro schedule runner를 띄워야 합니다. custom hosting에서 HTTP server만 올리면 schedule이 안 돌 수 있습니다.
Schedule 운영 기준
| 항목 | 기준 |
|---|---|
| cron timezone | UTC 기준 문서화 |
| idempotency | 동일 cron 중복 실행 대비 |
| output | channel handoff 또는 external store |
| auth | app principal과 service principal 구분 |
| eval | schedule prompt가 task-mode에서 완료되는지 |
| observability | run logs와 failure alert |
Durable state
defineState는 session-scoped durable memory입니다.
import { defineState } from "eve/context";
export const budget = defineState("ops-agent.budget", () => ({
count: 0,
cap: 25,
}));tool/hook 안에서 읽고 씁니다.
const current = budget.get();
budget.update((state) => ({ ...state, count: state.count + 1 }));주의:
- active Eve runtime context 밖에서 호출하면 throw합니다.
- subagent와는 state를 공유하지 않습니다.
- session이 끝나면 사라지므로 장기 비즈니스 저장소로 쓰기 어렵습니다.
State 사용 기준
| 사용 | 적합 여부 |
|---|---|
| 세션 내 query budget | 적합 |
| 현재 plan/checklist | 적합 |
| user profile 영구 저장 | 부적합 |
| tenant shared memory | 부적합 |
| OAuth token cache | 직접 구현 부적합, connection auth 사용 |
| 컴팩션 후 보존할 working memory | 적합 |
영구적이거나 공유하거나 분석해야 하는 데이터는 external DB나 connection으로 옮깁니다.
Hooks
Hook은 stream event 이후 실행되는 extension point입니다.
import { defineHook } from "eve/hooks";
export default defineHook({
events: {
async "action.result"(event, ctx) {
await writeAuditLog({
sessionId: ctx.session.id,
result: event.data.result,
});
},
},
});Hook은 모델 컨텍스트를 주입하는 기능이 아닙니다. 모델에 동적 컨텍스트를 주려면 dynamic instructions/skills/tools를 씁니다.
Hook failure는 실제 실패다
Eve 문서와 구현을 보면 hook throw가 turn failure로 번질 수 있습니다. failure cascade event에서 또 throw하면 session failure까지 커집니다.
그래서 감사/메트릭 hook은 안에서 실패를 격리해 둬야 합니다.
export default defineHook({
events: {
async "action.result"(event, ctx) {
try {
await writeAuditLog({ sessionId: ctx.session.id, result: event.data.result });
} catch (error) {
console.error("audit hook failed", error);
}
},
},
});감사 로그가 반드시 남아야 하는 regulated action이라면 반대로 hook 실패를 turn failure로 남기는 게 맞습니다. 이 정책은 명시해 둡니다.
Hook vs channel event handler
| 필요 | 선택 |
|---|---|
| 모든 채널 공통 감사 | hook |
| Slack message update | channel event handler |
| metric/alert export | hook |
| channel state mutation | channel event handler |
| model context 추가 | dynamic resolver |
Event order상 channel handler가 먼저 돌고 hook이 뒤따릅니다. hook은 durable stream에 event가 기록된 다음 실행됩니다.
Tool result narrowing
toolResultFrom으로 특정 tool/connection result를 type-safe하게 좁힙니다.
import { defineHook } from "eve/hooks";
import { toolResultFrom } from "eve/tools";
import refundCharge from "../tools/refund_charge";
export default defineHook({
events: {
"action.result"(event) {
const refund = toolResultFrom(event.data.result, refundCharge);
if (!refund) return;
console.log(refund.output);
},
},
});감사/비용/보안 이벤트를 tool별로 나누는 데 유용합니다.
운영 패턴
| 패턴 | 구성 |
|---|---|
| 세션 예산 | defineState + query tool budget check + hook metric |
| 일일 digest | schedule handler + Slack channel + eval |
| 승인 감사 | approval-gated tool + action.result hook |
| incident watcher | custom webhook channel + state + schedule heartbeat |
| drift monitor | schedule + eval-like tool call + alert hook |
체크리스트
| 항목 | 기준 |
|---|---|
| schedules | UTC, idempotency, failure alert |
| state | session-scoped인지 확인 |
| hooks | 실패 격리 또는 의도적 failure 정책 |
| channel state | metadata projection에 민감값 없음 |
| eval | schedule/hook/state behavior를 실제 session으로 검증 |
| observability | hook 결과와 Eve stream event를 상호 참조 가능 |
Schedules, state, hooks는 에이전트를 “대화형 데모”에서 “운영 자동화 시스템”으로 끌어올리는 표면입니다.