feat(llmfs): async Write + per-session streaming for live token delivery
Enable lucibridge to stream LLM tokens into the Lucifer conversation
zone as they are generated, without waiting for the full response.
Changes:
- session.go: add BeginGeneration/EndGeneration/SendChunk/GetStreamCh/
WaitDone methods on Session; streamCh (cap 256) carries raw text chunks
during generation; doneCh signals completion; EndGeneration closes
channels but does NOT nil streamCh (late readers still see closed chan)
- session_ask.go: Write() is now async — calls BeginGeneration(), spawns
goroutine, returns immediately; Read() calls WaitDone() before accessing
LastResponse so pread blocks until generation completes
- session_stream.go (new): /n/llm/N/stream file; Read() blocks on <-ch
returning each text chunk as it arrives; returns EOF when generation
is done or no generation is active (channel nil or closed)
- session_dir.go: register stream file in Children() and Lookup()
- client.go: AskWithRequest() branches on req.StreamFunc != nil to use
SSE Messages.NewStreaming() path; text_delta events forwarded to
StreamFunc; session.Ask() sets StreamFunc=session.SendChunk when
GetStreamCh() is non-nil
- server.go: add detailed walk debug logging (names, types, failures)
controlled by existing -debug flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(llmfs): native Anthropic tool_use protocol support
Add structured tool_use protocol to llm9p, enabling Veltro to use
Claude's native JSON tool invocation instead of text-based parsing.
New types (backend.go):
- AskResponse: carries Response, StructuredJSON, and Tokens — replaces
the old (string, int, error) return from AskWithRequest
- ToolDef: tool definition passed to Anthropic tools API
- ToolResult: tool execution result for submission to the LLM
- Backend.AskWithRequest() now returns (AskResponse, error)
client.go:
- Message.StructuredContent: stores JSON content blocks for correct
history replay of tool_use and tool_result turns
- AskWithRequest(): when ToolDefs non-nil, passes tools to API and
returns STOP:/TOOL: formatted response for Limbo parsing
Format: "STOP:tool_use\nTOOL:<id>:<name>:<args>\n<text>" or
"STOP:end_turn\n<text>" or plain text (no tools)
- AskWithToolResults(): submits tool results as a new user turn
- Helpers: buildMessageParam(), buildToolParams(), extractToolArgs(),
jsonEscapeString()
session.go:
- Session.tools field + SetTools/Tools methods
- Session.AddStructuredMessage() for storing structured content blocks
- AskRequest extended with ToolDefs and ToolResults fields
- SessionManager.Ask(): includes tools, stores structured JSON in history
- SessionManager.AskWithToolResults(): new method for tool result turns
- Fix Compact() for new AskResponse return type
- Helpers: extractTextContent(), buildToolResultsJSON()
cli_client.go: update AskWithRequest() to return AskResponse (no tools
support; StructuredJSON always empty)
session_tools.go (new): /n/llm/{id}/tools write-only file
- Write JSON array of ToolDef to enable native tool_use protocol
- Empty write clears tools (returns session to text-only mode)
session_ask.go:
- Detect TOOL_RESULTS\n prefix in Write() → parseToolResults() → AskWithToolResults()
- TOOL_RESULTS format: "TOOL_RESULTS\n<id>\n<content>\n---\n..."
session_dir.go: add tools file to Children() and Lookup()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(llm9p): per-session compact and usage files for context window management
Add automatic context window compaction support to the per-session 9P API:
- session.go: Add Session.EstimatedContextTokens() (4 chars/token heuristic
over current messages — more accurate than cumulative totalTokens for
threshold decisions). Add SessionManager.Compact(ctx, id) which summarises
the conversation via AskWithRequest then replaces session.messages with a
compact 2-message exchange. Add SessionManager.EstimatedContextTokens(id)
and SessionManager.ContextLimit() (200K for all Claude models).
- session_compact.go: New /n/llm/N/compact file. Write any content to
trigger Compact() for that session. Follows the SessionModelFile pattern.
- session_usage.go: New /n/llm/N/usage file. Read returns
"estimated_tokens/200000\n". Follows the SessionModelFile pattern.
- session_dir.go: Wire compact and usage into Children() and Lookup().
Also land two pre-existing uncommitted fixes:
- cli_client.go: Accept result messages with empty Result field
- protocol.go: Increase MaxMessageSize 8192→65536 for large system prompts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(llm9p): Implement clone-based session architecture
Replace per-fid session model with Plan 9 clone pattern:
- Reading /n/llm/new creates a session and returns its ID
- Each session gets its own directory: /n/llm/<id>/
- Per-session files: ask, ctl, model, system, thinking, context, metrics
- AskWithRequest method for stateless CSP-style LLM calls
- Session settings (model, temperature, thinking) are per-session
- Remove old ask.go, context.go in favor of session-scoped files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>