- Remove deprecated GenerateWithLoopAndStreaming and TreeManager
AppendFantasyMessage / AddFantasyMessages / GetFantasyMessages to
close the SDK leakage caused by the kit.TreeManager type alias
- Switch extensionAPI method signatures to local Extension* aliases so
pkg.go.dev signatures no longer expose internal package names
- Bundle runNormalMode dependencies into a runModeDeps struct, shrinking
the runNonInteractive and runInteractive call sites from 40+ positional
args to (ctx, deps)
- Add generic subscribeTyped[E Event] helper and collapse ~30 typed OnXxx
wrappers in pkg/kit/events.go onto it (public signatures unchanged)
- Extract setupBashPipes / interpretBashExit in internal/core/bash.go to
deduplicate the buffered and streaming execution paths
- Extract resolveAutoRouteAPIKey and wrapProviderErr helpers in
internal/models/providers.go and uniformly apply them across every
createXxxProvider site
- Reimplement internal/extensions/watcher.go as a thin wrapper over the
general-purpose internal/watcher.ContentWatcher, eliminating ~130 LOC
of duplicated fsnotify logic while preserving the existing test API
- Add ctx.Err() pre-flight checks in executeRead / Write / Edit / Ls so
cancellation actually short-circuits pure file-IO tools
Open the /resume session picker faster by extracting per-file metadata
across a GOMAXPROCS-sized worker pool instead of sequentially. Each
extractSessionInfo call is I/O + JSON-parse bound and independent, so
wall time drops roughly proportionally to core count — meaningful for
users with many sessions, where ListSessions + ListAllSessions ran
back-to-back on the UI goroutine before the picker rendered.
After /compact, BuildContext emitted [summary, post-compact, kept]
which placed an older kept user/assistant turn after the latest
post-compaction turn. This broke user/assistant alternation and caused
the model to respond as if the post-compaction turn never happened on
the next user message.
- Emit kept messages chronologically before post-compaction messages
- Mirror the same order in GetContextEntryIDs so cut-point to entry-ID
mapping stays aligned across repeat compactions
- Update TestCompactionWithNewMessagesAfterCompaction to assert the
correct chronological order
- Stale comment showed ~/.kit/sessions/--<cwd-path>--/ which does not
match the actual encoding (no leading/trailing dashes)
- Update to reflect the real format and point to encodeCwdForDir for
full rules
- Encode cwd via new encodeCwdForDir helper that handles both `/`
and `\` separators and strips characters illegal in Windows
directory names (`: < > " | ? *`)
- Fixes session creation on Windows where the drive-letter colon
produced names like `C:--test` and caused mkdir to fail
- Add regression tests covering Unix paths, Windows drive roots,
secondary drives, mixed separators, and other illegal chars
Fixes#18
- Buffer session JSONL writes with bufio.Writer, flush at sync points;
ForkToNewSession and AddLLMMessages now batch N entries into ~1 syscall
- Cache lipgloss styles in style.CachedStyles, lazily built and
invalidated on SetTheme; eliminates ~15 NewStyle() calls per frame in
hot render paths (reasoning blocks, spinner, tool headers, margins)
- Cache git ls-files results for @file suggestions with 3s TTL; typing
@filename no longer spawns 3 subprocesses per keystroke
- Use strings.Builder for StreamingMessageItem.content; eliminates O(n²)
string copying during LLM response streaming
Add defensive validation to detect and prevent cycles in the session tree
parent chain that could occur after compaction or file corruption.
- Add tree_validation.go with cycle detection and parent chain validation
- Validate parent chain before appending messages (AppendMessage)
- Validate firstKeptEntryID exists in AppendCompaction
- Add depth limit and cycle detection to buildTreeNode to prevent infinite recursion
- Log diagnostics on session open to detect existing cycles
- Add tests for cycle detection and graceful handling
Change compaction behavior so the compaction entry has no parent (empty
ParentID), creating a new root for post-compaction history. This ensures
old compacted messages are not traversed when building LLM context.
- Modify AppendCompaction to create entries with empty ParentID
- Update BuildContext to collect kept messages via FirstKeptEntryID
- Update GetContextEntryIDs with same logic
- Add comprehensive tests for compaction behavior
- Add web viewer support for displaying compaction entries
Add SystemPromptEntry type to capture system prompt, model, and provider
when sharing sessions via /share command. The entry is inserted into the
JSONL after the header and displayed in the web viewer as a collapsible
section with a model badge.
- Add SystemPromptEntry with Content, Model, and Provider fields
- Capture current system prompt and model at share time
- Display in web viewer with collapsible UI and model badge
- Update documentation for /share command
- Add ForkToNewSession method to create new session with history up to target
- Add NewTreeSelectorForFork showing only user messages (flat list)
- Update performFork to create and switch to new session file
- Update /fork command description in docs and help text
Previously /fork just branched within the same session file like /tree.
Now /fork creates a completely new session file with parent_session reference,
matching Pi's behavior exactly.
- Fix gofmt formatting issues in 7 files
- Replace atomic.AddUint64 with atomic.Uint64 type (modernize)
- Replace for i := 0; i < count; i++ with for i := range count (modernize)
- Replace strings.Split with strings.SplitSeq (modernize)
- Replace deprecated GetFantasyProviders with GetLLMProviders
- Replace deprecated GetFantasyMessages with GetLLMMessages
- Replace deprecated ConvertFromFantasyMessage with ConvertFromLLMMessage
- Replace deprecated FromFantasyMessage with FromLLMMessage
- Replace deprecated ToFantasyMessages with ToLLMMessages
- Remove 2 unused formatToolArgs functions
Rename public SDK symbols to use generic LLM terminology instead of
exposing the internal dependency name (charm.land/fantasy):
Public API renames (with deprecated wrappers for backward compat):
- ConvertToFantasyMessages() → ConvertToLLMMessages()
- ConvertFromFantasyMessage() → ConvertFromLLMMessage()
- GetFantasyProviders() → GetLLMProviders()
New type alias:
- LLMFilePart = fantasy.FilePart (eliminates need for direct fantasy import)
- PromptResultWithFiles() signature now uses LLMFilePart
Internal renames (with deprecated wrappers):
- ModelsRegistry.GetFantasyProviders() → GetLLMProviders()
- TreeManager.GetFantasyMessages() → GetLLMMessages()
- TreeManager.AppendFantasyMessage() → AppendLLMMessage()
- TreeManager.AddFantasyMessages() → AddLLMMessages()
- Message.ToFantasyMessages() → ToLLMMessages()
- FromFantasyMessage() → FromLLMMessage()
- npmToFantasyProvider → npmToLLMProvider
- isProviderFantasySupported() → isProviderLLMSupported()
All internal callers migrated to new names. ~30 comments updated
to remove Fantasy references across pkg/kit/, internal/agent/,
internal/models/, internal/message/, internal/session/.
Documentation updates:
- AGENTS.md: added Public SDK rules section (no dependency leakage,
naming conventions, deprecation pattern)
- README.md: removed Fantasy references
- pkg/kit/README.md: full rewrite with current API surface
- skills/kit-sdk/SKILL.md: updated examples and type references
- www/pages/providers.md, www/pages/cli/commands.md: updated
kit.go
- Extract iterBranchMessages helper to eliminate ~15 lines of duplicated
branch-fetch/type-assert boilerplate between GetSessionMessages and
GetStructuredMessages
- Move skillCache from package-level global to per-Kit field; avoids
cross-contamination when multiple Kit instances exist in same process
skills.go
- Remove globalSkillCache var and skillCache type definition
- Update DiscoverSkillsForExtension and ClearSkillCache to use m.skillCache
- Remove unused sync import
sessions.go
- Use m.treeSession.EntryID instead of local getEntryID duplicate
- Remove local getEntryID function (was missing LabelEntry, SessionInfoEntry,
CompactionEntry types that internal/session.TreeManager.EntryID handles)
internal/session/tree_manager.go
- Export entryID -> EntryID so pkg/kit can use it directly
- Update all internal callers to use EntryID
config.go
- Add sync comment for defaultSystemPrompt noting it should be kept in sync
with CLI default in cmd/root.go
hooks_test.go
- Add newEmptyHookedTool helper for tests that need hookedTool with empty
hook registries
- Update TestHookedTool_Passthrough and TestHookedTool_InfoDelegates to use
helper (saves ~6 lines of boilerplate each)
- Merge TestHookRegistry_HasHooks into TestHookRegistry_Unregister (was
testing same behavior, now just one initial state assertion added)
All changes tested with opencode/kimi-k2.5 exploring the repo in tmux.
6 files changed, 69 insertions(+), 98 deletions(-)
Empty sessions (no messages) are now automatically cleaned up:
1. On shutdown: When kit exits cleanly, if the current session has no
messages, the session file is deleted.
2. On /resume: When listing sessions for the resume picker, any empty
session files are deleted and not shown in the list.
This prevents accumulation of orphaned empty session files when users
start sessions but don't send any messages.
Changes:
- internal/session/tree_manager.go: add IsEmpty() helper
- internal/app/app.go: delete empty session on Close()
- internal/session/store.go: filter and delete empty sessions in listSessionsInDir()
Replace bufio.Scanner with bufio.Reader in OpenTreeSession to avoid
64KB line length limit. Scanner silently truncates long lines which can
corrupt session data. Reader handles arbitrary line lengths properly.
Rework the compaction system with several improvements modelled after
pi's approach:
Compaction engine (internal/compaction):
- Tool result truncation: cap tool result text at 2000 chars during
serialisation to keep summarisation requests within token budgets
- Serialize tool calls and reasoning parts (previously only text)
- Split turn handling: when a single turn exceeds the keep budget,
summarise the turn prefix separately and merge with history summary
- Cumulative file tracking: extract read/modified files from tool calls
(read, write, edit, grep, find, ls) and carry forward across
compactions via PreviousCompaction parameter
- Add IsSplitTurn, findTurnStart helpers and CutPoint, ReadFiles,
ModifiedFiles fields to CompactionResult
Session tree (internal/session):
- New CompactionEntry type records summary, first-kept-entry-id, token
stats, and file lists without deleting old messages
- BuildContext skips entries before the compaction boundary and injects
the summary as a system message
- GetContextEntryIDs maps fantasy message indices to entry IDs for
cut-point resolution
- GetLastCompaction retrieves prior file tracking state
Non-destructive compaction (pkg/kit):
- Compact now appends a CompactionEntry instead of clearing and
rewriting the session — old messages remain on disk for history
- Extension hook (BeforeCompact) can now provide a custom Summary that
replaces the LLM-generated one, in addition to cancelling
UI (internal/ui):
- Tree selector renders CompactionEntry nodes with info styling
Events & hooks (pkg/kit):
- CompactionEvent includes ReadFiles and ModifiedFiles
- BeforeCompactResult gains Summary field for extension-provided summaries
- Bridge updated to forward custom summaries from extensions
Implement 4-phase subagent system enabling LLM and extensions to spawn,
manage, and orchestrate child Kit instances for parallel task execution.
- Phase 1: SDK API with SpawnSubagent() for extensions
- Phase 2: spawn_subagent core tool for LLM usage
- Phase 3: Session hierarchy with ParentSessionID tracking
- Phase 4: Provider pooling for concurrent model access
New files:
- internal/extensions/subagent.go: SpawnSubagent implementation
- internal/core/subagent.go: Core tool definition
- internal/models/pool.go: Provider pool for concurrency
- examples/extensions/subagent-test.go: Test extension
- openspec/subagent-support.md: Design specification
Implement Phase 1 extension API gaps identified in the pi-mono gap analysis:
- Gap 1: Session Management API (GetMessages, GetSessionPath) — read-only
access to conversation history from extensions
- Gap 2: Session Persistence (AppendEntry, GetEntries) — custom extension
data survives across session restarts via new ExtensionDataEntry type
- Gap 10: SetEditorText — extensions can pre-fill the input editor
- Gap M3: Keyed Status Bar (SetStatus, RemoveStatus) — multiple extensions
can place independent entries in the TUI status bar, ordered by priority
Delete legacy session files (manager.go, session.go) and unused
ParseModelName() — all orphaned after Plans 00-09. Add Plan 10 to
close all deferred items: app uses kit.New(), executeStep() delegates
to kit.PromptResult(), extension observation events route through SDK
EventBus.
SendMessage lets extensions inject messages into the conversation and
trigger new agent turns, enabling async patterns like background
subagent execution. It delegates to app.Run() which handles queueing.
CommandDef.Execute now receives Context so commands can access
SendMessage, Print*, and session metadata. The UI layer wraps the
call via runner.GetContext() at the boundary.
Also fixes all 20+ golangci-lint issues across the codebase:
errcheck, modernize (min/max/slices.Contains/SplitSeq/range-over-int),
staticcheck (error string casing), and unused code removal.
Implement pi-style JSONL append-only session management with tree branching:
- TreeManager with id/parent_id tree structure, leaf pointer, and context
building that walks leaf-to-root for LLM messages
- Auto-discovery by cwd in ~/.kit/sessions/ with session listing
- /tree TUI overlay with ASCII art rendering, filter modes, and navigation
- /fork, /new, /name, /session slash commands for tree operations
- --continue, --resume, --no-session CLI flags
- Default auto-creates a tree session per working directory
Remove the entire internal/builtin package (bash, fetch, todo, http, fs
servers) and all inprocess/builtin transport support from config and
connection pool.
Add internal/core package with 7 direct fantasy.AgentTool implementations
matching pi's coding agent: bash, read, write, edit, grep, find, ls.
These execute in-process with zero MCP/JSON serialization overhead.
Add internal/message package with crush-inspired custom content blocks:
ContentPart interface with TextContent, ReasoningContent, ToolCall,
ToolResult, and Finish types. Messages carry heterogeneous Parts slices
with type-tagged JSON serialization for persistence and a ToFantasyMessages
bridge for LLM provider integration.
Core tools are always registered on the agent. External MCP servers remain
supported for additional tools, but MCP loading failures are now non-fatal
since core tools guarantee a working baseline.
Replace catwalk dependency with direct models.dev integration (97 providers,
3039 models vs catwalk's 22/679). Auto-route @ai-sdk/openai-compatible
providers through fantasy's openaicompat using the api URL from models.dev,
eliminating the need for --provider-url. Add --all flag to 'mcphost models'
to show all providers vs just fantasy-compatible ones.
Fix all 74 golangci-lint issues: errcheck (53), staticcheck SA4006 (24),
SA9003 (2), ST1005 (5), ineffassign (3). Restructure styles.go color
handling into a colorScheme struct to eliminate SA4006 false positives
from new(x) syntax.
Each spinner created a new tea.NewProgram which sent DECRQM queries for
synchronized output mode 2026. When the program exited and restored
cooked terminal mode, the terminal's DECRPM response leaked as visible
^[[?2026;2$y characters. Replace Bubble Tea spinner with a simple
goroutine animation loop writing directly to stderr via lipgloss.