Akan.js
Docs
DocsConventionsReferencesCheatsheet
Akan.js
DocsConventionsReferencesCheatsheet
Akan.js

Released under the MIT License

  • Official Akan.js Consulting onAkansoftCopyright © 2026 Akan.js All rights reserved.System managed bybassman
    YouBothAgent▾
    You — Business rules and flows you own. Read these yourself.
    Both — Know the idea; your agent follows the details.
    Agent — Conventions and references your agent follows. Look up as needed.
    CLI Reference▾
    CommandsWorkspaceApplicationLibraryModuleScalarPackagePagePrimitiveWorkflowQualityContextAgentGuideline
    AkanJS Reference▾
    akanjs/baseakanjs/commonakanjs/constantakanjs/fetchakanjs/signalakanjs/serverakanjs/clientakanjs/webkit
    UI Reference▾
    OverviewCoreDisplayFormsOverlaysSystemAgentCustomization
    YouBothAgent▾
    You — Business rules and flows you own. Read these yourself.
    Both — Know the idea; your agent follows the details.
    Agent — Conventions and references your agent follows. Look up as needed.
    CLI Reference▾
    CommandsWorkspaceApplicationLibraryModuleScalarPackagePagePrimitiveWorkflowQualityContextAgentGuideline
    AkanJS Reference▾
    akanjs/baseakanjs/commonakanjs/constantakanjs/fetchakanjs/signalakanjs/serverakanjs/clientakanjs/webkit
    UI Reference▾
    OverviewCoreDisplayFormsOverlaysSystemAgentCustomization
    PreviousSystemNextCustomization

    Agent UI

    An assistant that presses the buttons already on the screen, under the same guards, while the person watches. It is not a separate API for robots.
    The Agent namespace is that UI. One <Agent.Chat /> in a layout is the whole integration; the other members narrow it or show what it sees.import { Agent } from "akanjs/ui";
    The relay endpoint never runs a tool. Every call runs in the user's own browser session, through the app's guards and the approval card. So a tool exists only where a component declared one, and what the screen does not offer the user, the agent cannot do either.
    Words used on this page
    TermDescription
    tool
    One action a component publishes with st.tool, usually the handler its button calls.
    surface
    Everything the agent can do and read on the current screen: the mounted tools and keys.
    session
    One conversation with its loop and options. Agent.Chat and Agent.Zone each build one.
    transcript
    The conversation so far. It is sent to the model again on every turn.
    built-ins
    Runtime tools every screen gets: navigate, goBack, readScreen, readState, highlight.
    resource
    A value a component publishes with st.expose or st.useState for the agent to read.
    Members
    Member
    Own UI
    Own chat
    Name prefix
    What the user talks to
    Agent.Chat
    ✓
    ✓
    The chat panel: launcher, transcript, composer and approval card. Mount it once.
    Agent.Zone
    ✓
    ✓
    A section with its own conversation over a narrowed view of the same screen.
    Guidance and scope
    Agent.Guide
    Standing instructions for a route subtree. Renders nothing.
    Agent.History
    Connects the enclosing zone's transcript to storage the app owns. Renders nothing.
    Agent.Skip
    A region the default screen read leaves out, named so it can still be asked for.
    Agent.Scope
    ✓
    Prefixes the tools and resources below it, without opening a conversation.
    Development
    Agent.Dock
    ✓
    The development inspector: tools, readable state, withheld keys and the transcript.
    Dock parts
    ✓
    Agent.Context, Agent.Section, Agent.StateKey, Agent.Tool, Agent.Transcript: the dock's pieces, for an inspector of your own.
    ✓YesNo
    Where the concepts live
    This page lists every member and its props. The ideas behind them are explained here:
    In-Page Agent→
    How the loop, the surface, the approval gate and compaction fit together.
    Agent Chat Cheatsheet→
    The short version: mount, configure, declare a tool, ship.

    Chat

    The chat people see: a floating panel wired to the tools and state this screen declared. The loop and every tool call run in this browser. Mount it once, in a layout.
    Props / API
    Session Options
    How the conversation runs, read once at mount. Inside an Agent.Zone or AgentProvider the chat joins that session, so these belong to whoever built it.
    instructionsstring
    App-wide framing. Route guidance from mounted Agent.Guides layers on top of it.
    runnerAgentRunnerdefault fetchRunner()
    Swaps the transport. The default posts to runAgentTurn; httpRunner({ url }) posts elsewhere.
    maxTurnsnumberdefault 12
    Model round trips one ask may spend. At the limit, the chat asks the user whether to keep going.
    compactCompactOptionsdefault { at: 24_000, keep: 6, buffer: 13_000 }
    Summarizes past at estimated tokens or buffer short of the known window. { at: 0 } turns it off.
    builtinsBuiltinOptiondefault true
    true gives all five built-ins, false none, an array only those named. askUser always stays.
    persistPersistOption | SessionHistory
    Transcript survives reloads in sessionStorage; { storage: "local" } or SessionHistory moves it.
    onCompact(replaced, summary) => void
    Runs after a compaction replaced messages with one summary; a host syncs its own watermark here.
    visualboolean | AgentVisualOptiondefault true
    Rings the calling control (reveal) and a pointer presses it (cursor). false turns both off.
    Opening And Placement
    defaultOpenbooleandefault false
    Starts the panel open. The chat keeps its own open state after that.
    open / onOpenChangeboolean / (open) => void
    Open state the app controls. open alone draws no close button.
    launcherbooleandefault true
    false draws no launcher, for an app that opens the panel from a control of its own.
    inlinebooleandefault false
    Renders in the page flow instead of floating, for a zone chat inside its own section.
    shortcutbooleandefault true
    Cmd/Ctrl+L opens the panel. false gives the chord back, for a shell that already uses it.
    Look
    classNamestring
    Reaches whichever surface is showing: the launcher while closed, the panel while open.
    launcherClassName / panelClassNamestring
    Styles one surface each, where className reaches both.
    titlestring
    Panel heading. Left out, it reads "Agent" in the user's language.
    introReactNode
    Replaces the intro line while the transcript is empty. Starter questions go here.
    header / chromeReactNode / booleandefault chrome = true
    Controls left of clear and close. chrome={false} drops the whole bar, leaving /new to clear.
    Composer Input
    defaultDraftstring
    Composer text read once at mount, e.g. a ?prompt= value to prefill without sending it.
    attachAttachReader
    Turns a file into an attachment; null falls back to the built-in reader for images and text.
    attachLimits{ perFileBytes?, perMessageBytes?, perMessageCount? }
    Size and count caps. Defaults: 4 MB per file, 8 MB and five files per message.
    reference / mentionsReferenceSource[] / boolean
    @ menu sources, each with a search. mentions draws pointers as names, on when sources exist.
    voiceVoiceEngine
    Press-to-talk into the composer. Replies are read aloud only when the question was spoken.
    • Closing the panel keeps the conversation. The session lives in a ref, so it survives reopening; without persist it ends with the page.
    • Function props need a small client component. attach, voice, reference, runner, onCompact and onOpenChange carry functions, which cannot cross the RSC boundary from a server layout. Wrap the chat in ui/ the way apps/akan/ui/DocsAgentChat.tsx does with useSpeech() from @libs/util/webkit.
    • A controlled open alone stays server-safe. Without onOpenChange no function is passed, so a server component can still render it.
    • The launcher appears after hydration. The panel is a lazy(…, { ssr: false }) boundary, so its chunk loads once the page has hydrated. That is normal.
    • Server settings live in lib/option.ts. option.setLlm({ apiKey, model, host }) and option.setAgentAccess(SignedIn) configure it, never environment variables.
    • Answer data when the provider cannot reach a url. The provider fetches an attach result's url itself. Answering both sends the bytes to the model and keeps the address for the thumbnail.
    A layout mounts it once, with a translated title and a transcript that survives reloads:
    apps/koyo/page/(user)/_layout.tsx

    Zone

    A section with its own conversation over a narrowed view of the same screen. An Agent.Chat inside binds to it automatically, so two zones on one screen run two conversations side by side, each seeing only its own subtree.
    Props / API
    idstring
    Required. Sets the name prefix and data-agent-zone; characters outside A-Za-z0-9_- become -.
    childrenReactNode
    The section itself. Everything mounted here is part of the zone.
    classNamestring
    Goes on the wrapper div that carries data-agent-zone.
    labelstring
    Readable name for the scope, sent to the model with the screen context.
    instructionsstring
    Zone guidance, mounted as an Agent.Guide: the root agent reads it too, a sibling zone never.
    runner / maxTurns / compact / builtins / persist / onCompact / visualsame as Chat
    Same contracts as the chat's, applied to this zone's session and read once at mount.
    sessionAgentSession
    Runs the zone on a session the app built and owns; unmounting the zone leaves it running.
    onSession(session) => void
    Hands the session out once it exists, for a page or store that sends into it or watches it.
    • Zones are views, never walls. Tools, st.use subscriptions and guides mounted inside belong to this zone's session and to the root agent both.
    • Everything a zone publishes is named <id>.<name>. Instructions that name a tool must carry the prefix; a bare name is a tool that does not exist, and the model spends a turn on Unknown tool. Build the name from the id, and check the published list with Agent.Context's Assemble.
    • A zone that must stay on its screen withholds the rest. builtins={["readScreen", "readState"]} takes navigate, goBack and highlight away rather than discouraging them, so no prompt can talk the model past it.
    • Each zone persists on its own. persist is keyed by the zone's scope path, so two zones never share a transcript.
    apps/koyo/ui/CommentZone.tsx

    Guide

    Standing guidance for a route subtree. Render it from a _layout.tsx or a page, and its text joins every turn's instructions while that subtree is mounted. It draws nothing.
    Props / API
    instructionsstring
    The text, always in English: the model reads it, so the l() rule does not apply.
    • The render tree is the cascade. Each mounted Guide adds its own block, and navigating away withdraws it.
    • It is a component, not a route stage. Neither page() nor pageConfig has an instructions field, and *.abstract.md is never served to agents.
    apps/koyo/page/(user)/plan/_layout.tsx

    History

    Connects the enclosing zone's transcript to storage the app owns. It does what persist does, as a mounted leaf instead of a prop, and draws nothing.
    Props / API
    load / save / clearSessionHistory["load" | "save" | "clear"]
    The three sides of the store. Inline closures are fine, since they are read through a ref.
    onCompact(replaced, summary) => void
    Where a host with its own server-side summary moves its watermark.
    • Why a component rather than persist. A function cannot cross the server/client boundary as a prop, so passing persist to whoever builds the session makes every ancestor up to it a client component. With this leaf as the only client module, the zone and its chat stay in a server component.
    • It needs an enclosing session. Mount it inside Agent.Zone or AgentProvider, as in <Agent.Zone id="thread"><ThreadHistory … /></Agent.Zone>. The root Agent.Chat hands no session down, so use its persist there.
    • Restoring happens only on a fresh conversation. Mounted with the zone, it restores; mounted after something has happened, it only saves from then on.
    • The store is attached only while this is mounted. A zone's own session ends with the zone anyway, but one the app passed in outlives this and stops saving on unmount. To keep saving, call session.setHistory yourself; that takes the slot, so a later unmount here leaves it alone.
    apps/koyo/ui/ThreadHistory.tsx

    Skip

    A region the default screen read leaves out: chrome that costs tokens and answers nothing, such as a footer, a cookie banner or a repeated nav.
    Props / API
    labelstring
    Printed in place of the region, and the name section takes to read it anyway. Required.
    childrenReactNode
    The region itself.
    classNamestring
    Goes on the wrapper div.
    • The agent knows what it skipped. [skipped: <label>] stands in its place, so asked about the footer it says it did not read one instead of saying there is none. section: "<label>" reads it on request.
    • It hides text, not behaviour. Tools and state keys are declarations, not markup: an st.tool inside is published as before, and highlight still reaches a control in here.
    • Where a wrapper would move the layout, use the attribute. Between a flex container and its children, put it on the element you already render: <footer data-agent-skip="site footer">.
    apps/koyo/ui/SiteFooter.tsx

    Scope

    Prefixes every tool and resource registered below it, so repeated list items can reuse local names. It opens no conversation and holds no session.
    Props / API
    idstring
    The prefix: everything below is published as <id>.<name>, nested scopes joined with dots.
    childrenReactNode
    The subtree the prefix applies to.
    labelstring
    Readable name for the scope, sent to the model with the screen context.
    kindstring
    What sort of scope this is. Agent.Zone opens its own with kind="zone".
    • Scope or Zone? Use Agent.Scope when a repeated subtree needs distinct tool names but shares the screen's one agent. Agent.Zone wraps a scope and adds a conversation of its own.
    apps/koyo/ui/WaypointRow.tsx

    Development Dock

    Each component declares its own agent surface, so no single file tells you what the whole screen published. Agent.Dock shows it. Mount it next to the chat during development:
    apps/koyo/page/(user)/_layout.tsx
    • Production draws nothing. Agent.Dock and Agent.Context render nothing when AKAN_PUBLIC_ENV=main, so leaving them mounted costs a visitor nothing.
    • The other parts do not check the environment. An inspector you assemble from Agent.Section, Agent.StateKey, Agent.Tool or Agent.Transcript has to hide itself in production.
    • open expands Tools. Transcript always starts expanded; the other sections start folded.
    What each section answers
    SectionDescription
    Tools
    Did this screen publish what its author meant, under the names the instructions use?
    State
    Which keys are readable right now, and what does one actually return when read?
    Context
    What would the next turn carry? Assemble prints the tool names, guides and context blocks.
    Withheld
    Which keys were refused, and for what reason.
    Transcript
    What has the agent already done to this page?
    The parts
    Each part is exported, so an app that wants a dock of its own shape composes them instead of re-reading the surface.
    Agent.Dock{ className?, bridge?, surface?, open? }
    The whole panel: bridge supplies the state keys, surface the tools, open expands Tools.
    Agent.Context{ className? }
    An Assemble button that prints what a turn would carry: tool names, guides, context blocks.
    Agent.Section{ className?, title, count, children, open? }
    One collapsible <details> group with a count beside its title. The dock draws five.
    Agent.StateKey{ className?, bridge, name, entry, live? }
    One readable key, read and masked on click, so an object no model claims is refused here.
    Agent.Tool{ className?, surface, tool, onRun }
    One declared tool with its arguments as JSON, and a Run button that calls it in the running app.
    Agent.Transcript{ className?, calls }
    What the agent did, oldest first, to check against what the page did. There is no undo.
    Restyling the chat
    Eleven of the chat's own parts are override slots, so an app re-skins the transcript or the composer without re-implementing the loop. AgentChat replaces the whole panel.
    AgentLauncherAgentBubbleAgentStepsAgentComposerAgentApprovalAgentQuestionAgentQueuedAgentMenuAgentMarkdownAgentToolCardAgentCode
    Overridable Slots→
    The full slot list, and how a _overrides.tsx binds one.

    On this page

    Agent UI
    Chat
    Zone
    Guide
    History
    Skip
    Scope
    Development Dock