Akan.js
Docs
문서컨벤션레퍼런스Cheatsheet
Akan.js
문서컨벤션레퍼런스Cheatsheet
Akan.js

MIT 라이선스 하에 배포되었습니다.

  • Akan.js 공식 컨설팅 서비스AkansoftCopyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman
    사람함께에이전트▾
    사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
    함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
    에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
    일반▾
    인증과 권한에이전트를 위한 OAuth스키마 설계텍스트 검색엣지 컴퓨팅파일 관리Single Sign-OnDataList & Enum
    인터페이스▾
    CRUDEndpointMCP 서버에이전트 채팅Form
    관측성▾
    로깅의존성 주입에러 처리메트릭
    성능▾
    캐싱이미지 최적화지연 로딩쿼리변경큐실시간
    모바일▾
    설정Push NotificationsDeep LinksUI & Keyboard데스크톱 배포
    개발▾
    문서화스키마 문서스크립트콘솔도커쿠버네티스PWA테스트
    사람함께에이전트▾
    사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
    함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
    에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
    일반▾
    인증과 권한에이전트를 위한 OAuth스키마 설계텍스트 검색엣지 컴퓨팅파일 관리Single Sign-OnDataList & Enum
    인터페이스▾
    CRUDEndpointMCP 서버에이전트 채팅Form
    관측성▾
    로깅의존성 주입에러 처리메트릭
    성능▾
    캐싱이미지 최적화지연 로딩쿼리변경큐실시간
    모바일▾
    설정Push NotificationsDeep LinksUI & Keyboard데스크톱 배포
    개발▾
    문서화스키마 문서스크립트콘솔도커쿠버네티스PWA테스트
    이전MCP 서버다음Form

    턴은 어디서 실행되나

    Agent.Chat은 화면에 채팅을 붙이고, 그 채팅이 사용자 대신 화면을 조작합니다. 서버는 메시지를 전달만 하고, 모든 툴은 사용자 자신의 브라우저 탭에서 실행됩니다.
    이 페이지에서 쓰는 말
    용어설명
    턴 (turn)
    사용자 메시지 하나와 다음 메시지 사이에 에이전트가 말하고 한 일 전부입니다.
    대화 (transcript)
    브라우저 탭에 있고 매 턴 모델에 다시 전송되는, 지금까지의 대화입니다.
    릴레이 (relay)
    대화를 LLM에 전달할 뿐 툴은 실행하지 않는 runAgentTurn 엔드포인트입니다.
    툴 (tool)
    컴포넌트가 st.tool로 공개한 동작 하나로, 보통 버튼이 이미 부르는 핸들러입니다.
    승인 카드
    사용자가 승인할 때까지 호출을 붙잡아 두는 카드입니다.
    슬롯 (slot)
    _overrides.tsx에서 갈아 끼울 수 있는 채팅의 한 부분입니다.
    참조 (reference)
    사용자가 @로 가리켜 자기 메시지에 실어 보내는 데이터입니다.
    zone
    Agent.Zone으로 감싸 자기만의 대화를 가진 구획입니다.
    환불 한 번을 따라가 보기
    주문 화면의 고객이 “마지막 주문 환불해줘”라고 쓰자, 잠시 뒤 주문이 환불됩니다. 보안 검토자가 가장 먼저 묻는 질문과 답은 이렇습니다:
    질문
    ↳ 답
    환불은 어느 기계에서 실행됐나요?
    고객의 브라우저 탭입니다. 환불 버튼이 부르는 바로 그 핸들러로 실행됐습니다.
    누구의 자격증명으로 실행됐나요?
    로그인한 사용자 자신의 것입니다. 그 페이지에서 버튼을 누른 것과 똑같습니다.
    남의 주문에 손대지 못하게 막은 것은?
    모든 호출이 지나는 가드, 그리고 툴이 요구하면 승인 카드입니다.
    서버는 무엇을 했나요?
    runAgentTurn이 대화와 툴 설명을 프로바이더에 넘기고 응답 하나를 돌려줬습니다.
    서버에 남은 것은?
    없습니다. 세션도 대화도 서버에 남지 않습니다.
    턴 하나의 처음과 끝
    세션 없음, 툴 실행 없음
    화면st.tool 선언 · 구독한 키
    Agent.Chat대화는 이 탭에 있음
    POST runAgentTurnAgentRelayAccess 가드
    LLM 프로바이더option.setLlm에서 지정
    모델이 요청한 툴 호출
    승인 카드확인과 가드
    툴은 이 브라우저에서 실행버튼이 부르는 그 핸들러
    변경 보고화면에서 바뀐 것
    서버에는 아무것도 저장하지 않음
    화면st.tool 선언 · 구독한 키
    Agent.Chat대화는 이 탭에 있음
    POST runAgentTurnAgentRelayAccess 가드
    세션 없음, 툴 실행 없음
    LLM 프로바이더option.setLlm에서 지정
    서버에는 아무것도 저장하지 않음
    모델이 요청한 툴 호출
    승인 카드확인과 가드
    툴은 이 브라우저에서 실행버튼이 부르는 그 핸들러
    변경 보고화면에서 바뀐 것
    가드를 지정하지 않으면 채팅은 누구에게도 답하지 않습니다. AgentRelayAccess는 lib/option.ts에서 앱의 가드로 option.setAgentAccess(SignedIn)을 호출하기 전까지 모든 호출을 거절합니다. 다른 엔드포인트에 적는 것과 같은 가드를 받고, 여러 개면 모두 통과해야 합니다(AND).

    채팅 마운트하기

    Agent.Chat은 에이전트가 닿아야 할 모든 화면을 감싸는 레이아웃에 한 번만 마운트합니다. 이 요소 하나에 런처, 대화창, 승인 카드, 스트리밍 루프가 모두 들어 있습니다:
    apps/koyo/page/(shop)/_layout.tsx
    • instructions는 항상 영어입니다. 가게가 어느 언어로 장사하든 모델이 읽는 글이라서, 모든 .desc()와 Agent.Guide도 마찬가지입니다.
    • title과 intro는 l()을 거칩니다. l()은 사람이 읽는 문자열용이라서 instructions에는 쓰지 않습니다.
    • persist는 새로고침해도 대화를 남깁니다. 어디에 보관되는지는 마지막 섹션에서 다룹니다.
    닫으려고 조건부로 마운트하지 마세요. 언마운트하면 세션이 중단되고 대화가 버려집니다. 앱의 컨트롤로 채팅을 열고 닫으려면 open과 onOpenChange를 함께 넘겨 바깥에서 제어하세요.
    Props
    titlestring기본값 l("base.agent")
    헤더 제목이자 패널의 접근성 이름입니다.
    instructionsstring
    영어로 쓰는 앱 전역 모델 지침이며, 라우트별 지침은 Agent.Guide가 그 위에 더합니다.
    defaultOpenboolean기본값 false
    패널이 상태를 스스로 관리할 때, 처음부터 열린 채로 시작합니다.
    openboolean
    onOpenChange와 함께 넘겨 바깥에서 제어하는 열림 상태이며, 빼면 패널이 스스로 관리합니다.
    onOpenChange(open: boolean) => void
    열고 닫을 때 호출되며, 없으면 바깥에서 제어하는 패널은 닫기 버튼을 그리지 않습니다.
    launcherboolean기본값 true
    이미 진입점이 있는 셸이라면 false로 떠 있는 버튼을 그리지 않습니다.
    introReactNode
    대화가 비어 있는 동안 기본 안내 문구를 대신하는, 예시 질문을 두는 자리입니다.
    headerReactNode
    기본 비우기·닫기 버튼 왼쪽, 헤더 바에 더할 컨트롤입니다.
    chromeboolean기본값 true
    false면 inline 채팅용으로 헤더 바와 header를 빼며, 비우기는 /new로 합니다.
    defaultDraftstring기본값 ""
    마운트 때 한 번 읽고 전송하지 않는 작성창 초기 텍스트로, ?prompt= 값을 넣는 자리입니다.
    inlineboolean기본값 false
    자기 구획 안의 zone 채팅처럼, 떠 있지 않고 페이지 흐름 안에 그립니다.
    shortcutboolean기본값 true
    Apple 플랫폼은 ⌘L, 그 외는 Ctrl+L이며, false면 이 단축키를 브라우저에 돌려줍니다.
    launcherClassNamestring
    닫힌 버튼에만 적용되는 클래스이며, className은 두 곳 모두에 적용됩니다.
    panelClassNamestring
    열린 패널에만 적용되는 클래스입니다.
    builtinsboolean | AgentBuiltin[]기본값 true
    이 채팅의 에이전트가 받을 기본 툴을 전부, 없음, 또는 나열한 것만으로 고릅니다.
    persistPersistOption | SessionHistory
    새로고침해도 대화를 보존하며, 마지막 섹션에서 다룹니다.
    attach, voice, visual, maxTurns, compact 등 나머지 prop은 Agent UI 레퍼런스에 있습니다.
    기본 툴 다섯 개
    화면이 선언한 툴 외에도 런타임은 모든 채팅에 이 다섯 툴을 줍니다. builtins로 이 채팅의 에이전트가 받을 툴을 고릅니다.
    툴설명
    navigate
    Link와 같은 라우터로 내부 경로를 엽니다.
    goBack
    이 세션 히스토리의 이전 페이지로 돌아갑니다.
    readScreen
    렌더된 화면을 압축한 텍스트로 읽습니다.
    readState
    화면이 구독한 스토어 키 하나를 모델로 마스킹해 읽습니다.
    highlight
    대상을 화면으로 스크롤해 깜빡이며, 사용자에게 위치를 보여 줍니다.
    • 화면을 떠나면 안 되는 채팅은 builtins={["readScreen", "readState", "highlight"]}로 둡니다. navigate와 goBack이 없으면 떠날 수 없습니다.
    • 뺀 툴은 모델에게 없는 툴입니다. 이름을 짐작해 호출해도, 등록된 적 없는 이름과 똑같이 "unknown tool"로 답합니다.
    • 화면이 선언한 툴은 빠지지 않습니다. 컴포넌트가 기본 툴과 같은 이름으로 선언한 툴은 화면의 것이라 builtins가 건드리지 않습니다.
    • askUser는 이 목록에 없습니다. 세션의 툴이라 builtins로 빠지지 않습니다.
    • 범용 대기 툴은 없습니다. 만들었다가, 키의 뜻을 모른 채 그럴듯한 키마다 턴을 세워 두는 바람에 제거했습니다. 대신 그 작업을 시작하는 컨트롤 옆에 기다리는 툴을 직접 선언하세요.

    모든 부분이 슬롯

    브랜드가 프레임워크의 말풍선을 그대로 원하는 경우는 드물지만, 승인 관문은 언제나 그대로 쓰고 싶어 합니다. 그래서 채팅 전체가 아니라 부분을 바꿉니다. 슬롯 열두 개를 page/**/_overrides.tsx 매니페스트에 묶으면 레이아웃처럼 라우트 트리를 따라 내려갑니다.
    슬롯설명과 기본 구현 export
    AgentChat
    런처, 대화, 카드, 작성창까지 패널 전체라서 가장 마지막에 꺼냅니다.
    AgentLauncher
    label, hotkey, 새 메시지 뱃지용 unread를 받는 닫힌 상태의 버튼입니다.
    AgentBubble
    메시지 하나이며, 대화가 스트리밍 조각마다 다시 그려지므로 memo()로 감싸세요.
    AgentSteps
    에이전트 턴 하나 전체와 isRunning을 받으며, 기본 구현은 요소를 더하지 않습니다.
    AgentComposer
    Send와 Stop이 있는 입력 줄이며, 입력 필드 모양은 input recipe 슬롯에서 옵니다.
    AgentApproval
    remove* 툴이 기본으로 거치는, 작성창 위의 승인 관문입니다.
    AgentQuestion
    보기만 보여 주는 askUser 카드이며, 직접 쓰는 답은 작성창에 입력합니다.
    AgentQueued
    턴이 도는 동안 대기 중인 메시지로, 되돌리기와 버리기 버튼을 함께 그립니다.
    AgentMenu
    / 커맨드와 @ 참조를 보여 주는, 작성창 위의 자동완성 목록입니다.
    AgentMarkdown
    어시스턴트 텍스트이며, 기본 렌더러는 dangerouslySetInnerHTML 없이 React 요소만 만듭니다.
    AgentCode
    신택스 하이라이터를 붙이는 코드 블록 자리이며, lang은 펜스에 적힌 언어입니다.
    AgentToolCard
    card 툴이 그리는 앱 컴포넌트를 감싸는 틀입니다.
    • 기본 구현을 겹쳐 쓰세요. 슬롯 열한 개는 기본 구현을 함께 내보내므로, 교체본은 원본을 다시 쓰지 않고 감싸서 쓸 수 있습니다.
    • AgentChat은 최후의 수단입니다. 패널 전체를 바꾸고, 겹쳐 쓸 기본 구현을 내보내지 않습니다.
    AgentSteps로 턴 접기
    AgentSteps는 겉모습만 바꾸는 슬롯이 아닙니다. 턴 전체를 받으므로, 중간 단계는 <details>로 접고 최종 답만 밖에 둘 수 있습니다:
    apps/koyo/ui/KoyoTurn.tsx
    그리고 다른 슬롯과 함께 라우트의 매니페스트에 묶습니다:
    apps/koyo/page/(shop)/_overrides.tsx
    • 턴은 이 슬롯만 보는 단위입니다. 턴 경계 양쪽의 메시지는 자기가 끝에 있다는 것을 모르므로, 메시지 단위 슬롯으로는 턴을 접을 수 없습니다.
    • isRunning은 세션이 작업 중인 마지막 턴에서만 true입니다. 이것이 없으면 진행 중인 줄과 끝난 턴의 머리글을 구별할 수 없습니다.
    • 기본 구현은 아무것도 더하지 않습니다. DefaultSteps는 같은 말풍선을 Fragment 안에 평평하게 그리므로 className을 받지 않고, 기존 레이아웃도 차이를 느끼지 못합니다.

    사용자가 답하는 툴

    어떤 인자는 사용자가 줘야 합니다. 배달 주소, 전화번호, 누군가 확인해야 아는 날짜 같은 것입니다. 모델이 이런 값을 채우면 자기 질문에 스스로 답한 셈이고, 프롬프트로는 이를 확실히 막을 수 없습니다.
    1. st.tool(name)으로 툴을 선언하고 .desc()와 모델이 넘길 .arg()를 적습니다.
    2. 체인을 .exec(fn) 대신 .card(render)로 끝냅니다. 호출은 채팅에 멈춰 서고, 그 자리에 앱의 폼이 그려집니다.
    3. 폼에서 submit(value)로 답하거나 cancel(reason)으로 거절합니다.
    아래에서는 모델이 고객에게 주소를 묻고, 주소가 생기기 전까지 배달 버튼은 꺼져 있습니다:
    apps/koyo/lib/icecreamOrder/IcecreamOrder.Zone.tsx
    card가 exec과 다른 점
    submit(value)
    폼이 제출한 값이 곧 호출 결과이고, 모델이 그것을 읽습니다. cancel(reason)은 오류로 전달되므로, 닫힌 카드도 에이전트가 반응할 수 있는 답이 됩니다.
    인자는 먼저 검사합니다
    카드를 세우기 전에 검사하고, 렌더 중에는 하지 않습니다. 잘못된 인자는 모델이 고칠 수 있는 거절로 돌아가고, 컴포넌트 안의 예외는 채팅 패널까지 무너뜨리기 때문입니다.
    툴 대기열 밖에서 기다립니다
    사람 앞에 놓인 폼은 작업이 아닙니다. 그동안 실행 락을 쥐고 있으면 답하지 않은 카드 하나 때문에 페이지의 다른 에이전트가 모두 멈춥니다.
    confirm
    .card()에서는 읽지 않습니다. 사용자 앞에 놓인 카드가 이미 묻는 행위이기 때문입니다.
    • 틀은 항상 닫기 버튼을 그립니다. 폼에 취소 버튼이 없어도 그리므로, 사용자가 빠져나갈 수 없는 카드에 턴이 멈춰 서는 일은 없습니다.
    • 스토어에 쓴 값도 보고됩니다. 기다리는 앞뒤로 화면을 스냅샷하므로, 모은 값을 스토어에 쓰는 카드는 다른 호출처럼 무엇이 바뀌었는지 보고합니다.

    데이터를 가리키기

    “이건 왜 환불됐어요?”에 답하려면 채팅이 ‘이건’이 무엇인지 알아야 합니다. @ 메뉴를 쓰면 사용자가 id를 입력하거나 에이전트가 턴을 들여 검색할 필요 없이 대상을 바로 가리킬 수 있습니다.
    문서 하나 전체
    채팅의 reference prop에 선언합니다. @ 메뉴가 앱의 검색으로 행을 찾습니다.
    <Agent.Chat reference={[…]} />
    화면의 필드 하나
    그 필드를 그리는 컴포넌트에서 호출합니다. 이미 쥔 값을 왕복 없이 그대로 건넵니다.
    useAgentReference()
    @ 메뉴로 문서 가리키기
    사용자가 어떤 문서를 가리킬 수 있는지는 프레임워크가 아니라 앱이 정할 문제입니다. 그래서 source마다 자기 검색을 가져옵니다:
    apps/koyo/ui/KoyoAgentChat.tsx
    source 하나는 다섯 필드로 된 ReferenceSource 객체입니다:
    refNamestring
    앱이 공개한 툴이 이미 쓰는 이름 그대로의 모델 이름입니다.
    labelstring
    @ 메뉴에서 이 묶음을 부르는 이름이라 l()을 거칩니다.
    typeAgentFieldType
    값이 브라우저를 떠나기 전에 마스킹할 모델 클래스입니다.
    search(query, signal) => Promise<ReferenceCandidate[]>
    메뉴 행을 찾는 앱의 쿼리이며, 사용자가 계속 입력하면 signal이 중단됩니다.
    resolve(refId) => Promise<unknown>
    사용자가 행을 고를 때 한 번 호출되어 문서를 불러옵니다.
    • type이 브라우저 밖으로 나가는 것을 정합니다. st.expose와 같은 방식으로, 지정한 모델 클래스가 값을 마스킹하므로 hidden, secret, visual 필드는 나가지 않습니다.
    • 가리킬 필드를 가진 클래스를 지정하세요. Light 클래스에는 대개 그 필드가 없고, 그것으로 마스킹된 참조는 정작 가리킨 이유였던 필드 없이 도착합니다.
    • search에는 브라우저가 부를 수 있는 쿼리가 필요합니다. 여기서는 q.search() 필터를 감싼 slice이며, 이런 slice는 목록으로 훑어도 괜찮은 데이터에만 둡니다.
    • 이 채팅은 클라이언트 컴포넌트에서 마운트합니다. reference에는 함수가 들어 있어 서버 레이아웃이 넘길 수 없으므로, ui/ 아래 작은 컴포넌트에 둡니다.
    useAgentReference로 필드 하나 가리키기
    필드를 그리는 컴포넌트는 이미 그 값을 쥐고 있습니다. 또한 field(Any)로 저장된 리치 텍스트가 에디터 문서가 아니라 한 문단으로 읽혀야 한다는 것을 아는 것도 그 컴포넌트뿐입니다:
    apps/koyo/lib/icecreamOrder/IcecreamOrder.Util.tsx
    • 위에 세션이 있어야 하므로, 버튼을 Agent.Zone이나 AgentProvider안에 그리세요. children 옆의 루트 Agent.Chat은 세션을 내주지 않아서, zone 밖에서는 경고만 남기고 아무 일도 하지 않습니다.
    • path가 필드를 가리킵니다. 문서 안의 점 경로이므로, 한 문서의 필드 두 개는 서로 다른 참조 두 개입니다.
    작성창에서는
    • 포인터는 이름으로 보입니다. source를 선언하면 별도 청크로 로드되는 Lexical 에디터가 각 포인터를 원시 @[label](mention:…) 토큰 대신 가리키는 대상의 이름으로 보여 줍니다.
    • mentions={false}는 평범한 textarea를 유지합니다. 작성창을 교체했거나 보내는 토큰을 그대로 보고 싶을 때 쓰며, 보내는 문자열은 어느 쪽이든 같습니다.
    • 참조를 실어 나르는 것은 토큰입니다. 토큰을 지우면 칩을 지운 것과 똑같이 참조가 빠지고, 이전 메시지에서 복사해 붙인 토큰은 값 없는 포인터로만 갑니다.
    참조는 스냅샷이며 20,000자에서 잘립니다. 넘으면 JSON이 구조 중간에서 끊기고, 그 사실을 알리는 메모가 모델에게 붙습니다. 한 턴에만 답하는 툴 결과와 달리 참조는 실린 메시지 이후 모든 턴에 함께 가고, 압축에서도 가장 마지막에 접힙니다.

    대기열과 슬래시 메뉴

    턴 하나는 몇 초가 걸리는데, 그 사이 다음 할 말이 떠오른 사용자가 입력을 기다릴 이유는 없습니다. 턴 중에 Enter를 누르면 메시지를 대기시켰다가 턴이 끝나는 순간 보냅니다.
    • 자리는 하나입니다. 두 번째로 보낸 것은 첫 번째 아래 줄에 붙으므로, 모델은 메시지 두 개가 아니라 하나를 받습니다.
    • 조용히 들고 있지 않고 보여 줍니다. 작성창에서 사라졌는데 대화에도 없는 메시지는 잃어버린 것처럼 보이므로, 작성창 위 AgentQueued 카드에서 되돌리거나 버릴 수 있게 합니다.
    • Stop은 정말 멈춥니다. 대기 중인 메시지로 다음 턴을 열지 않고 작성창으로 돌려줍니다.
    슬래시 커맨드
    / 메뉴에는 이 여섯 커맨드만 나옵니다:
    커맨드설명
    /new/clear
    턴 중이거나 질문 카드가 떠 있어도 새 대화를 시작합니다.
    /retry
    마지막 사용자 메시지만 다시 보내고 그 위는 그대로 둡니다.
    /compact
    원문을 하나도 남기지 않고 지금 대화 전체를 요약합니다.
    /copy
    대화를 페이지 URL과 시각을 붙인 마크다운으로 복사하며, 로컬 메모는 빠집니다.
    /help
    커맨드 목록을 모델에게 전송되지 않는 로컬 메모로 보여 줍니다.
    /tools
    이 화면이 공개한 툴과 읽을 수 있는 키를 보여 주며, zone 채팅은 그 zone만 보여 줍니다.
    • 앱은 / 커맨드를 추가할 수 없습니다. 제품 고유의 재사용 요청은 page().prompt()로 만들며, 이는 MCP 클라이언트에만 나열되고 인페이지 채팅에는 나오지 않습니다.
    • 나머지는 그냥 메시지입니다. 여섯 개가 아닌 /단어는 평범한 텍스트로 모델에게 전송됩니다.
    • 커맨드는 턴 중에도 실행됩니다. 턴이 진행 중이거나 질문 카드가 떠 있어도 동작하고, /retry와 /compact는 에이전트가 작업 중이라고 알려 줍니다.
    작성창 단축키
    키설명
    Enter
    보내되, 턴 중에는 메시지를 대기시키고 메뉴가 열려 있으면 행을 고릅니다.
    Shift+Enter
    줄을 바꿉니다.
    ↑ / ↓
    커서가 첫 줄이나 마지막 줄에 있으면 보낸 메시지를 오가고, 메뉴가 열려 있으면 선택을 옮깁니다.
    Tab
    선택한 / 커맨드를 완성하거나, 선택한 @ 행을 고릅니다.
    Esc
    열린 메뉴를 숨기고, 메뉴가 없으면 패널을 닫습니다.
    ⌘L / Ctrl+L
    shortcut={false}가 아니면 채팅을 열고 작성창에 포커스합니다.
    메뉴 뒤의 세션 호출
    호출설명
    session.note(text)
    대화에는 보이지만 모델에게는 전송되지 않는 로컬 메모를 씁니다.
    session.report(error)
    예외를 던진 커맨드 같은 호스트 쪽 실패를 대화에 오류로 기록합니다.
    session.retry()
    마지막 사용자 메시지만 다시 보내고 그 위는 그대로 둡니다.
    • 일반 메시지가 아니라 메모인 이유. 대화가 곧 모델의 히스토리라서, /help 출력을 그냥 붙이면 다음 턴에 어시스턴트가 자기가 한 말로 받아들입니다.
    • 세션에 닿는 법. Agent.Zone의 onSession이 세션을 건네주고, zone 안에서는 useAgent()로 읽으며, 교체한 작성창은 session prop으로 받습니다.
    • persist를 켜면 새로고침 뒤에도 다시 부를 수 있습니다. ↑/↓ 목록은 복원된 대화에서 채워지고, 쓰다 만 초안은 목록 끝에서 다시 돌아옵니다.

    대화를 보관하기

    릴레이는 세션을 갖지 않으므로, 대화는 브라우저 탭 하나에만 있습니다. persist는 새로고침해도 대화를 남기고, Agent.History는 앱의 서버에 보관합니다.
    쓰는 법
    ↳ 보관 방식
    persist
    새로고침은 견디고 탭과 함께 사라져 공용 PC에 남지 않는 sessionStorage에 둡니다.
    persist={{ storage: "local" }}
    탭을 닫아도 대화가 남아야 할 때 localStorage에 둡니다.
    persist={{ key: "…" }}
    저장 키를 직접 정하며, 기본값은 akan.agent.<appName>에 zone 안이면 zone 경로가 붙습니다.
    <Agent.History />
    직접 작성한 함수 세 개로 앱의 서버에 보관합니다.
    서버에 보관하기: Agent.History
    서버 저장소는 함수 세 개로 된 SessionHistory입니다. 함수는 prop으로 RSC 경계를 넘지 못하므로, prop으로 넘기면 세션을 만드는 곳까지의 모든 조상이 클라이언트 컴포넌트가 됩니다. 그래서 Agent.Guide처럼 잎 컴포넌트로, 보관할 zone 안에 마운트합니다:
    apps/koyo/lib/icecreamOrder/IcecreamOrder.Zone.tsx
    • 감싸는 세션이 필요합니다. Agent.History는 Agent.Zone이나 AgentProvider 안에 둡니다. 그 밖에서는 렌더링 중에 오류를 던집니다.
    • 세션 옵션은 zone에 적습니다. Agent.Zone 안의 채팅은 zone의 세션에 붙으므로, 그 Agent.Chat에 적은 persist, builtins, instructions는 무시됩니다.
    • onCompact는 압축 뒤에 불립니다. 압축이 메시지들을 요약 하나로 바꾼 뒤 호출되며, 서버에 자체 요약을 두는 호스트가 기준점을 옮기는 자리입니다.
    저장소마다 보관하는 것
    규칙
    persist
    Agent.History
    모든 저장소
    손대지 않은 대화에만 복원
    ✓
    ✓
    zone과 함께 마운트되면 복원하고, 나중에 마운트되면 그때부터 저장만 합니다.
    바뀔 때마다 저장
    ✓
    ✓
    디바운스되고 한 번에 하나씩 저장하며, 실패한 저장은 조용히 넘어갑니다.
    웹 스토리지만
    최근 50개만 보관
    ✓
    파일 내용과 참조 값은 버림
    ✓
    파일은 이름·타입·url·ref만, 참조는 포인터와 “다시 읽으라”는 메모만 남깁니다.
    ✓적용됨적용되지 않음
    웹 스토리지는 몇 메가바이트뿐이고 스크린샷 하나가 그 상당 부분을 차지합니다. 실패한 저장은 조용히 넘어가므로, 파일 내용까지 담으면 대화 저장 자체가 조용히 멈춥니다. Agent.History는 내용까지 포함한 메시지를 그대로 받습니다.
    다음으로 읽을 것
    이 화면에서 채팅이 무엇을 할 수 있는지는 별개의 주제입니다. 컴포넌트가 st.tool로 동작 하나를 선언하고, 컴포넌트가 구독한 스토어 키만 에이전트도 읽을 수 있으며, 스토어 클래스에서 저절로 생기는 것은 없습니다.
    인페이지 에이전트→
    에이전트의 표면: st.tool 동작, 읽을 수 있는 키, zone, LLM 어댑터.
    Agent UI 레퍼런스→
    Agent.Chat, Agent.Zone, Agent.History 등의 모든 prop.

    이 페이지

    턴은 어디서 실행되나
    채팅 마운트하기
    모든 부분이 슬롯
    사용자가 답하는 툴
    데이터를 가리키기
    대기열과 슬래시 메뉴
    대화를 보관하기