콘텐츠로 이동

상태·기억·재개

멀티턴 업무에서는 무엇을 기억할지보다 어디에 저장할지를 먼저 정합니다. 대화, 앱 상태, 작업 결과와 실행 재개 지점은 수명과 신뢰 범위가 다릅니다.

저장 영역담는 값용도
대화 기록사용자·assistant text, 축약 DataPart, 파일 참조최근 문맥
WorkflowState작은 구조화 값마지막 route, 진행 플래그, 누계
WorkHistory검토된 summary·facts·keywords관련 작업 결과 회수
durable checkpoint단계, target snapshot, relay artifact재개와 중복 호출 방지

파일 원본, OCR 전문, base64와 signed URL은 장기 state에 넣지 마세요. 현재 요청의 원본을 자식에게 전달하는 일과 다음 턴을 위한 안전한 참조·요약을 저장하는 일은 별개입니다.

AgenticProgram에서는 최근 대화와 WorkMemory 회수가 자동입니다. context_turns만큼의 대화와 context_roles로 허용한 역할, 관련 기억의 제한된 projection이 controller 첫 관측에 들어갑니다. work_memory_context = "none"은 자동 주입만 끄며 read-only work_recall 도구는 남습니다.

원본 FilePart, OCR과 가공 전 payload는 이 projection에 들어가지 않습니다. 직접 작성한 run_turn(ctx)와 code-first Rules hook은 대화나 WorkHistory를 자동으로 자식 프롬프트에 넣지 않습니다. 필요한 값만 접어 전달하세요.

Controller는 이 bounded 대화를 항상 보지만, Agentic child의 text는 기본적으로 현재 요청을 유지합니다. 대명사형 후속 질문을 독립 요청으로 해소해야 하는 read-only 도구에만 input = "conversation"을 명시하세요. 직전 assistant 답변까지 참조하려면 context_roles = ["user", "assistant"]가 필요합니다. 기본 input = "context"인 기존 도구의 입력은 바뀌지 않습니다.

# code-first 경로에서만 최근 사용자 입력을 직접 합칩니다.
text = ctx.fold_prior(
3,
prior_header="[최근 요청]",
current_header="[현재 요청]",
)
result = await ctx.call(
"verification",
text=text,
data=ctx.data,
files=ctx.files,
)

fold_prior()는 턴 시작 snapshot을 읽어 현재 요청을 중복하지 않습니다. fold_files()는 파일 참조만 합치며 과거 bytes를 복구하거나 다시 다운로드하지 않습니다.

일반 서버는 auto_record_request=True, auto_record_request_policy="compact"를 기본으로 사용합니다. 성공한 run_turn 뒤에 현재 요청을 기록하면서 큰 DataPart와 파일을 축약합니다. 공통 정책으로 표현할 수 없는 마스킹이 있을 때만 ctx.record_request()를 직접 호출하세요. 인자 없는 수동 호출은 현재 DataPart와 FilePart를 그대로 저장할 수 있습니다.

다음 턴의 분기에 필요한 작은 값은 WorkflowState에 둡니다.

# 최신 route 하나만 남깁니다.
ctx.remember("last_route", "verification")
# 처리 ID는 중복을 제거하고 최근 100개로 제한합니다.
ctx.reduce(
"processed_ids",
["ORD-1002"],
lambda old, new: [*dict.fromkeys([*(old or []), *new])][-100:],
)

현재 턴에서만 쓰는 값은 지역 변수로 두세요. WorkflowState는 업무 데이터베이스가 아닙니다.

WorkHistory는 원본 응답 저장소가 아니라 업무 결과 요약 저장소입니다. summary, 공개 가능한 factskeywords만 남깁니다.

[work_memory.policy]는 summary Agent에 보낼 근거를 정합니다.

mode근거
none요약하지 않음
requestchild에게 보낸 DataPart·FilePart
responsechild가 반환한 DataPart·FilePart
bothrequest와 response를 분리된 segment로 전달

both는 두 배열을 섞지 않습니다. 파일에서는 안정된 ID, 이름, MIME, 확장자와 크기만 남기고 bytes·base64·content·OCR·URL은 제거합니다. input_required, 도메인 오류와 pending unavailable은 성공 결과가 아니므로 저장하지 않습니다.

timing동작
before_finalize성공한 child 직후 provisional summary 준비
turn_end턴 끝에 호출 순서대로 요약하는 기본값
explicitctx.work.summarize_result()를 호출한 결과만 요약
write_behind성공한 child 직후 계약 facts와 결정적 core summary를 저장하고, 문장 품질 개선은 응답 commit 뒤 background worker에서 수행

write_behind의 응답 경로는 LLM을 호출하지 않습니다. Orchestrator는 summaryPhase="core"로 summary Agent에 deterministic projection을 요청하며, ResponseContractSummaryAdapter는 wrapped model을 우회하고 자신이 소유한 ResponseContractCache의 단일 snapshot으로 facts를 만듭니다. 따라서 summary Agent의 okf-contracts/·원격 contract overlay와 다른 로컬 계약을 Orchestrator가 임의로 읽지 않습니다. 이 authoritative facts와 core summary는 같은 턴의 finalizer·WorkMemoryFactsProvider·recall에 즉시 보이고, background enrichment는 facts·ID·순서·lifecycle을 바꾸지 않은 채 summary 문장만 개선합니다.

동기 core snapshot을 만들거나 저장할 수 없으면 facts 계약을 지키기 위해 해당 턴을 실패시키지만, worker 시작이나 enqueue, enrichment child 호출이 실패해도 저장된 core 응답은 정상적으로 계속됩니다. PostgreSQL의 pending generation은 만료되는 claim lease로 replica 하나만 LLM을 호출하고, 검증된 LLM 결과는 새 attempt를 만들지 않은 채 write-back만 backoff 재시도합니다. enrichment LLM 호출은 최대 세 번이며 store claim·write-back lock을 각각 짧게 점유하므로, 이 timing을 쓰는 custom WorkflowStateStore는 keyword-only lock_timeout을 지원해야 합니다. all뿐 아니라 latest·relevant controller projection도 write-back 전후 facts와 byte budget을 보존합니다.

문장 개선이 실제로 반영됐는지 확인할 때는 턴 trace가 아니라 orchestrator.work-memory-enrichment trace를 이름으로 찾으세요. 응답을 보낸 뒤에 도는 작업이라 턴 trace 안에는 나타나지 않고, 세션 id 없이 기록되므로 세션 필터로도 걸리지 않습니다. 요약 대상 facts는 다른 child 호출과 마찬가지로 이 trace에 남으니, 민감한 값이 섞이는 도메인이라면 관측 마스킹 정책을 함께 검토하세요. 개선이 반영되지 않아도 대화는 그대로 이어집니다 — 저장된 core 요약과 facts는 유지되고, 반영되지 않은 이유는 orchestrator 로그에 경고 한 줄로 남습니다.

자동 summary는 provisional로 시작합니다. FinalResponse와 output guardrail을 통과해야 committed로 이동해 일반 recall에 보입니다. 담당자 반려나 출력 차단은 해당 항목을 quarantine으로 옮깁니다. state 저장이나 commit이 실패하면 메시지, 기억 bucket과 checkpoint를 턴 시작 상태로 함께 복원합니다.

ctx.work.remember()remember_result()로 앱이 직접 넣은 항목은 곧바로 committed입니다. 공개 가능한 facts인지 애플리케이션이 먼저 확인하세요.

orchestrator.toml
[agents]
verification = "registry:verification"
summary = "registry:data-summary"
[work_memory]
enabled = true
summary_agent = "summary"
summary_schema = "llamon.work-summary.v1"
timing = "before_finalize"
store_states = ["completed"]
failure_mode = "fail_soft"
[work_memory.policy]
# 요청과 결과를 출처가 다른 segment로 요약합니다.
verification = "both"
[work_memory.history]
state_key = "work_history"
max_items = 10
select_limit = 3
max_item_bytes = 16384
max_summary_chars = 2000
max_text_chars = 2000
기본값·제약
enabledfalse. 자동 WorkMemory 활성화
summary_agentsummary. [agents]의 alias
summary_schemallamon.work-summary.v1. 빈 문자열 금지
timingturn_end; before_finalize·turn_end·explicit·write_behind
store_states["completed"]. terminal 상태는 넣어도 저장되지 않음
failure_modefail_soft 또는 fail_closed
policyalias별 none·request·response·both
state_keywork_history
max_items10, 1~100
select_limit3, 0 이상
max_item_bytes16384, 양의 정수
max_summary_chars·max_text_chars2000, 0 이상

enabled=true이면 summary alias를 뺀 모든 static child에 policy를 적습니다. summary alias 자체를 다시 요약하도록 지정하면 시작 단계에서 거부합니다. 기존 timing에서 fail_soft는 경고와 trace를 남기고 summary 없이 계속하며 fail_closed는 계약을 지킬 수 없을 때 현재 경계를 실패시킵니다. write_behind는 동기 core 실패만 fail-closed이고 background worker 시작·enrichment는 항상 fail-soft이므로, worker가 비활성이어도 저장된 core facts 계약은 유지됩니다.

필요API
검토한 summary 저장ctx.work.remember(...)
정책에 따라 즉시 요약await ctx.work.summarize_result(result)
현재 execution의 factsctx.work.summary_for(result)
LLM용 제한된 projectionctx.work.recall(query, ...)
신뢰된 앱의 내부 관리history()·latest()·select()

recall()은 관련 항목을 결정적으로 고르고 items, parts와 크기를 제한한 JSON text를 반환합니다. 저장 ID, 내부 metadata와 원본 DataPart·FilePart는 공개 projection에서 빠집니다. history()select()의 가공 전 반환값을 프롬프트에 넣지 마세요.

recalled = ctx.work.recall(ctx.user_text, limit=3)
if not recalled.items:
# 안전한 기억이 없으면 권한 있는 자식이 원문을 다시 조회합니다.
return await ctx.call("verification", data=ctx.data, files=ctx.files)
return await ctx.generate(
"answer_writer",
text=ctx.user_text,
facts={"work_history": list(recalled.parts)},
)

요약에 실패해도 원본 DataPartFilePart를 generator facts로 대신 넘기지 마세요. 안전한 projection이 없다면 생성을 멈추거나 원문을 다시 조회합니다.

AgenticProgram에서는 WorkMemory 결합이 자동입니다. code-first 흐름은 recall() 결과를 직접 전달합니다. data-summary Agent의 ResponseContractSummaryAdapter와 custom summary_schema에이전트 구성, projection 계약은 응답 계약을 참고하세요.

Agentic과 내장 TurnWorkflow는 controller 판단, 도구 승인, 담당자 검수와 child의 input_requiredDurableExecutionStore에 checkpoint합니다. 같은 conversation으로 새 입력이 오면 완료한 단계를 다시 호출하지 않고 중단 지점부터 이어 갑니다.

workflow checkpoint수동 resume
단계와 target snapshotSDK가 저장앱이 관리
완료 호출 중복 방지있음자체로는 없음
새 턴 입력 병합workflow가 관리앱이 전달
주된 용도Agentic·내장 workflow@managed_run_turn 분기

ctx.clear_resume()은 workflow checkpoint를 지우지 않으며 ctx.get_resume()도 pending workflow 상태를 보여 주지 않습니다. 수동 재진입은 Deterministic 워크플로우call_key 패턴을 따르세요.

state_backend = "in_memory"는 프로세스를 다시 시작하면 state와 checkpoint가 사라집니다. 재시작과 여러 worker를 견뎌야 한다면 state_backend = "postgres"를 사용합니다.

DSN은 TOML이 아니라 POSTGRES_ORCH_DSN에 두며, 값이 없으면 POSTGRES_MEMORY_DSN을 사용합니다. 배포 전에는 같은 conversation의 동시 처리가 직렬화되는지, 저장 실패 때 응답과 WorkMemory가 함께 롤백되는지 확인하세요. 장애와 재시도 의미는 운영과 실패 복구에 있습니다.