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
✓
✓
Where the concepts live
This page lists every member and its props. The ideas behind them are explained here:

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.
compactCompactOptions

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

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.

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">.

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.

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?

On this page

Agent UI
Chat
Zone
Guide
History
Skip
Scope
Development Dock
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.
default { 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 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
apps/koyo/ui/ThreadHistory.tsx
apps/koyo/ui/SiteFooter.tsx
apps/koyo/ui/WaypointRow.tsx
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.
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