initial pass

ober

76e2e5227aca1be8699113986fb73b259928157b

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..84914e7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+SCHEME = scheme
+JERBOA_HOME ?= $(HOME)/mine/jerboa
+LIBDIRS = --libdirs $(JERBOA_HOME)/lib:./lib
+
+.PHONY: all build run test clean repl
+
+all: build
+
+build:
+	$(SCHEME) $(LIBDIRS) --compile-imported-libraries --program main.ss
+
+run:
+	$(SCHEME) $(LIBDIRS) --script main.ss
+
+repl:
+	$(SCHEME) $(LIBDIRS)
+
+test:
+	$(SCHEME) $(LIBDIRS) --script test/run.ss
+
+clean:
+	find . -name "*.so" -delete
+	find . -name "*.wpo" -delete
+	rm -f jcode
+
+# Static binary (requires Chez Scheme static libs)
+static: build
+	@echo "Static build not yet implemented"
diff --git a/PLAN.md b/PLAN.md
new file mode 100644
index 0000000..be01292
--- /dev/null
+++ b/PLAN.md
@@ -0,0 +1,602 @@
+# Jerboa-Code Implementation Plan
+
+*A portable AI coding agent in Chez Scheme*
+
+## Project Goal
+
+Implement a fully-featured AI coding agent equivalent to OpenCode, but:
+- **Portable**: Runs on FreeBSD, Linux, macOS, Windows, anywhere Chez Scheme runs
+- **Simple**: Clean architecture without JavaScript ecosystem bloat
+- **Fast**: Single static binary, no runtime dependencies
+- **Maintainable**: ~5,000 lines of Scheme vs 294,000 lines of TypeScript
+
+## Available Jerboa Standard Library
+
+The following modules are already available in `~/mine/jerboa/lib/std/`:
+
+### Core Infrastructure ✓
+- `(std net request)` - HTTP client (GET, POST, PUT, DELETE)
+- `(std db sqlite)` - SQLite database with prepared statements
+- `(std misc process)` - Process execution, subprocess I/O
+- `(std text json)` - JSON reader/writer
+- `(std os shell)` - Shell utilities
+- `(std os path)` - Path manipulation
+- `(std os env)` - Environment variables
+- `(std os temp)` - Temporary files
+
+### Additional Useful Modules ✓
+- `(std text glob)` - Glob pattern matching
+- `(std config)` - Configuration file parsing
+- `(std cli getopt)` - Command-line argument parsing
+- `(std log)` - Structured logging
+- `(std misc uuid)` - UUID generation
+- `(std text base64)` - Base64 encoding
+- `(std crypto digest)` - Cryptographic hashing
+- `(std net uri)` - URI parsing
+- `(std text diff)` - Text diff/patch
+- `(std misc retry)` - Retry with backoff
+
+---
+
+## Architecture Overview
+
+```
+jerboa-code/
+├── lib/
+│   └── jerboa-code/
+│       ├── core/
+│       │   ├── agent.sls         # Main agent loop
+│       │   ├── message.sls       # Message types and handling
+│       │   ├── session.sls       # Session persistence
+│       │   └── config.sls        # Configuration management
+│       │
+│       ├── provider/
+│       │   ├── provider.sls      # Provider abstraction
+│       │   ├── openai.sls        # OpenAI/compatible APIs
+│       │   ├── anthropic.sls     # Anthropic Claude API
+│       │   ├── google.sls        # Google Gemini API
+│       │   └── models.sls        # Model definitions
+│       │
+│       ├── tool/
+│       │   ├── registry.sls      # Tool registration
+│       │   ├── file.sls          # read, write, edit tools
+│       │   ├── bash.sls          # Shell command execution
+│       │   ├── search.sls        # grep, glob, find
+│       │   ├── web.sls           # HTTP fetch, web search
+│       │   └── batch.sls         # Parallel tool execution
+│       │
+│       └── ui/
+│           ├── cli.sls           # Command-line interface
+│           ├── repl.sls          # Interactive REPL
+│           └── format.sls        # Output formatting
+│
+├── main.ss                       # Entry point
+├── Makefile                      # Build system
+├── jerboa.pkg                    # Package definition
+└── PLAN.md                       # This file
+```
+
+---
+
+## Implementation Phases
+
+### Phase 1: Foundation (Week 1-2)
+
+#### 1.1 Project Skeleton
+```scheme
+;; jerboa.pkg
+(package jerboa-code
+  :package "jerboa-code"
+  :version "0.1.0"
+  :depends
+    ((std net)
+     (std db)
+     (std text)
+     (std misc)
+     (std os)
+     (std cli)))
+```
+
+#### 1.2 Configuration System
+- [ ] Read `~/.config/jerboa-code/config.json` or `jerboa-code.json`
+- [ ] API key management (env vars + config file)
+- [ ] Model selection and provider configuration
+- [ ] Project-local configuration support
+
+```scheme
+;; lib/jerboa-code/core/config.sls
+(library (jerboa-code core config)
+  (export load-config
+          config-api-key
+          config-model
+          config-provider)
+  (import (chezscheme)
+          (std text json)
+          (std os path)
+          (std os env)))
+```
+
+#### 1.3 Session Storage
+- [ ] SQLite database for sessions
+- [ ] Message persistence
+- [ ] Session listing/resumption
+
+```scheme
+;; lib/jerboa-code/core/session.sls
+(library (jerboa-code core session)
+  (export session-create
+          session-load
+          session-save
+          session-list
+          session-add-message
+          session-get-messages)
+  (import (chezscheme)
+          (std db sqlite)
+          (std misc uuid)))
+```
+
+#### 1.4 Message Types
+- [ ] User messages
+- [ ] Assistant messages  
+- [ ] Tool calls and results
+- [ ] Streaming support
+
+```scheme
+;; lib/jerboa-code/core/message.sls
+(library (jerboa-code core message)
+  (export make-user-message
+          make-assistant-message
+          make-tool-call
+          make-tool-result
+          message->json
+          json->message)
+  (import (chezscheme)
+          (std text json)))
+```
+
+---
+
+### Phase 2: Provider Integration (Week 2-3)
+
+#### 2.1 Provider Abstraction
+- [ ] Generic provider interface
+- [ ] API key resolution
+- [ ] Model capabilities
+
+```scheme
+;; lib/jerboa-code/provider/provider.sls
+(library (jerboa-code provider provider)
+  (export make-provider
+          provider-chat
+          provider-stream
+          provider-available?)
+  (import (chezscheme)
+          (std net request)
+          (std text json)))
+```
+
+#### 2.2 OpenAI Provider
+- [ ] Chat completions API
+- [ ] Tool calling support
+- [ ] Streaming responses
+- [ ] o1/o3 reasoning models
+
+```scheme
+;; lib/jerboa-code/provider/openai.sls
+(library (jerboa-code provider openai)
+  (export openai-chat
+          openai-stream
+          openai-models)
+  (import (chezscheme)
+          (jerboa-code provider provider)
+          (std net request)
+          (std text json)))
+```
+
+#### 2.3 Anthropic Provider
+- [ ] Messages API
+- [ ] Extended thinking
+- [ ] Tool use protocol
+
+```scheme
+;; lib/jerboa-code/provider/anthropic.sls
+(library (jerboa-code provider anthropic)
+  (export anthropic-chat
+          anthropic-stream)
+  (import (chezscheme)
+          (jerboa-code provider provider)
+          (std net request)
+          (std text json)))
+```
+
+#### 2.4 Additional Providers
+- [ ] Google Gemini
+- [ ] OpenRouter (for model aggregation)
+- [ ] Local models (Ollama)
+
+---
+
+### Phase 3: Tool System (Week 3-4)
+
+#### 3.1 Tool Registry
+- [ ] Tool definition format
+- [ ] Schema generation for AI
+- [ ] Tool execution dispatch
+
+```scheme
+;; lib/jerboa-code/tool/registry.sls
+(library (jerboa-code tool registry)
+  (export define-tool
+          tool-schema
+          tool-execute
+          list-tools)
+  (import (chezscheme)
+          (std text json)))
+```
+
+#### 3.2 File Tools
+- [ ] `read` - Read file contents
+- [ ] `write` - Write file contents  
+- [ ] `edit` - String replacement editing
+- [ ] `glob` - Find files by pattern
+- [ ] `grep` - Search file contents
+
+```scheme
+;; lib/jerboa-code/tool/file.sls
+(library (jerboa-code tool file)
+  (export tool-read
+          tool-write
+          tool-edit
+          tool-glob
+          tool-grep)
+  (import (chezscheme)
+          (std os path)
+          (std text glob)
+          (std io)))
+```
+
+#### 3.3 Bash Tool
+- [ ] Command execution
+- [ ] Timeout handling
+- [ ] Output capture
+- [ ] Working directory support
+
+```scheme
+;; lib/jerboa-code/tool/bash.sls
+(library (jerboa-code tool bash)
+  (export tool-bash
+          tool-bash-with-timeout)
+  (import (chezscheme)
+          (std misc process)
+          (std os shell)))
+```
+
+#### 3.4 Web Tools
+- [ ] `fetch` - HTTP GET/POST
+- [ ] Basic HTML parsing
+
+```scheme
+;; lib/jerboa-code/tool/web.sls
+(library (jerboa-code tool web)
+  (export tool-fetch
+          tool-web-search)
+  (import (chezscheme)
+          (std net request)
+          (std markup html-parser)))
+```
+
+#### 3.5 Batch Execution
+- [ ] Parallel tool execution
+- [ ] Result aggregation
+
+---
+
+### Phase 4: Agent Core (Week 4-5)
+
+#### 4.1 Main Agent Loop
+```scheme
+;; lib/jerboa-code/core/agent.sls
+(library (jerboa-code core agent)
+  (export agent-run
+          agent-step
+          agent-process-response)
+  (import (chezscheme)
+          (jerboa-code core session)
+          (jerboa-code core message)
+          (jerboa-code provider provider)
+          (jerboa-code tool registry)))
+
+;; Core loop pseudocode:
+;; 1. Get user input
+;; 2. Add to session messages
+;; 3. Send to AI provider with tool schemas
+;; 4. If response has tool calls:
+;;    a. Execute each tool
+;;    b. Add results to messages
+;;    c. Loop back to step 3
+;; 5. Return final text response
+```
+
+#### 4.2 Streaming Support
+- [ ] Server-sent events parsing
+- [ ] Incremental output display
+- [ ] Tool call streaming
+
+#### 4.3 Error Handling
+- [ ] API error recovery
+- [ ] Tool execution errors
+- [ ] Network retry logic
+
+---
+
+### Phase 5: CLI Interface (Week 5-6)
+
+#### 5.1 Command-Line Interface
+```scheme
+;; lib/jerboa-code/ui/cli.sls
+(library (jerboa-code ui cli)
+  (export cli-main
+          cli-run
+          cli-chat
+          cli-session)
+  (import (chezscheme)
+          (std cli getopt)
+          (jerboa-code core agent)))
+```
+
+Commands:
+- `jcode` - Start interactive session
+- `jcode "prompt"` - One-shot query
+- `jcode --model claude-sonnet-4` - Specify model
+- `jcode --provider openai` - Specify provider
+- `jcode session list` - List sessions
+- `jcode session resume <id>` - Resume session
+- `jcode config` - Show/edit configuration
+
+#### 5.2 Interactive REPL
+- [ ] Readline support
+- [ ] History
+- [ ] Multi-line input
+- [ ] Slash commands (/help, /model, /clear)
+
+#### 5.3 Output Formatting
+- [ ] Markdown rendering (terminal)
+- [ ] Code block highlighting
+- [ ] Diff display
+- [ ] Progress indicators
+
+---
+
+### Phase 6: Advanced Features (Week 6-8)
+
+#### 6.1 MCP (Model Context Protocol)
+- [ ] MCP server support
+- [ ] Tool discovery via MCP
+- [ ] External tool integration
+
+#### 6.2 Git Integration
+- [ ] Workspace detection
+- [ ] Commit/diff tools
+- [ ] Branch management
+
+#### 6.3 LSP Integration (Optional)
+- [ ] Use existing `(std lsp)` module
+- [ ] Code intelligence tools
+
+#### 6.4 Plugin System
+- [ ] Load external tool definitions
+- [ ] Custom provider support
+
+---
+
+## Tool Specifications
+
+### Core Tools (Must Have)
+
+| Tool | Description | OpenCode Equivalent |
+|------|-------------|---------------------|
+| `read` | Read file contents | `ReadTool` |
+| `write` | Write file contents | `WriteTool` |
+| `edit` | Replace string in file | `EditTool` |
+| `bash` | Execute shell command | `BashTool` |
+| `glob` | Find files by pattern | `GlobTool` |
+| `grep` | Search file contents | `GrepTool` |
+| `fetch` | HTTP request | `WebFetchTool` |
+| `batch` | Parallel execution | `BatchTool` |
+
+### Extended Tools (Nice to Have)
+
+| Tool | Description | OpenCode Equivalent |
+|------|-------------|---------------------|
+| `ls` | List directory | `ListTool` |
+| `patch` | Apply unified diff | `ApplyPatchTool` |
+| `multi-edit` | Multiple edits | `MultiEditTool` |
+| `lsp` | Language server | `LspTool` |
+| `search` | Code search | `CodeSearchTool` |
+
+---
+
+## API Specifications
+
+### Message Format
+
+```scheme
+;; User message
+(make-message
+  :role "user"
+  :content "Read the file main.ss")
+
+;; Assistant message with tool call
+(make-message
+  :role "assistant"
+  :content nil
+  :tool-calls
+  [(:id "call_123"
+    :type "function"
+    :function (:name "read" :arguments "{\"path\": \"main.ss\"}"))])
+
+;; Tool result
+(make-message
+  :role "tool"
+  :tool-call-id "call_123"
+  :content "(library ...)")
+```
+
+### Provider Interface
+
+```scheme
+(define-interface provider
+  ;; Send messages, get response
+  (chat [messages tools] -> response)
+  
+  ;; Stream response chunks
+  (stream [messages tools callback] -> void)
+  
+  ;; List available models
+  (models [] -> model-list))
+```
+
+### Tool Interface
+
+```scheme
+(define-interface tool
+  ;; Tool name for AI
+  (name [] -> string)
+  
+  ;; JSON schema for parameters
+  (schema [] -> json)
+  
+  ;; Execute tool
+  (execute [params] -> result))
+```
+
+---
+
+## File Count Estimate
+
+| Component | Files | Lines (est) |
+|-----------|-------|-------------|
+| Core (agent, session, config, message) | 4 | 800 |
+| Providers (openai, anthropic, google, etc) | 5 | 1000 |
+| Tools (file, bash, search, web, batch) | 6 | 1200 |
+| CLI/UI | 3 | 600 |
+| Utilities | 2 | 400 |
+| **Total** | **20** | **4000** |
+
+---
+
+## Build System
+
+```makefile
+# Makefile
+SCHEME = scheme
+JERBOA_HOME = $(HOME)/mine/jerboa
+LIBDIRS = --libdirs $(JERBOA_HOME)/lib:./lib
+
+.PHONY: all build run test clean
+
+all: build
+
+build:
+	$(SCHEME) $(LIBDIRS) --compile-imported-libraries --program main.ss
+
+run:
+	$(SCHEME) $(LIBDIRS) --script main.ss
+
+static:
+	# Build single static binary
+	$(SCHEME) $(LIBDIRS) --compile-whole-program main.ss jcode.wpo
+	cc -o jcode jcode.wpo $(CHEZ_LIBS)
+
+test:
+	$(SCHEME) $(LIBDIRS) --script test/run.ss
+
+clean:
+	rm -f *.so *.wpo jcode
+```
+
+---
+
+## Testing Strategy
+
+1. **Unit Tests**: Each module has corresponding test file
+2. **Integration Tests**: Full agent loop with mock provider
+3. **E2E Tests**: Real API calls with test prompts
+
+```scheme
+;; test/tool-file-test.ss
+(import (std test)
+        (jerboa-code tool file))
+
+(test-suite "file tools"
+  (test-case "read existing file"
+    (let ([result (tool-read "test/fixtures/sample.txt")])
+      (assert-equal? result "hello world\n")))
+  
+  (test-case "glob finds files"
+    (let ([files (tool-glob "test/**/*.ss")])
+      (assert (> (length files) 0)))))
+```
+
+---
+
+## Timeline Summary
+
+| Week | Phase | Deliverable |
+|------|-------|-------------|
+| 1-2 | Foundation | Config, session, message types |
+| 2-3 | Providers | OpenAI, Anthropic working |
+| 3-4 | Tools | All core tools implemented |
+| 4-5 | Agent | Main loop, streaming |
+| 5-6 | CLI | Interactive interface |
+| 6-8 | Polish | MCP, git, testing, docs |
+
+**Total: 6-8 weeks for complete feature parity**
+
+---
+
+## Success Criteria
+
+1. **Functional**: Can have productive coding sessions equivalent to OpenCode
+2. **Portable**: Runs on FreeBSD, Linux, macOS without modification
+3. **Fast**: Sub-100ms startup, single static binary
+4. **Simple**: Easy to understand, modify, and extend
+5. **Reliable**: No SQLite locking issues, proper error handling
+
+---
+
+## Getting Started
+
+```bash
+# Clone and setup
+cd ~/mine
+mkdir jerboa-code
+cd jerboa-code
+
+# Create initial structure
+mkdir -p lib/jerboa-code/{core,provider,tool,ui}
+mkdir -p test
+
+# Start with main.ss
+cat > main.ss << 'EOF'
+#!/usr/bin/env scheme --script
+(import (chezscheme)
+        (jerboa-code core agent)
+        (jerboa-code ui cli))
+
+(cli-main (command-line-arguments))
+EOF
+
+# Build and run
+make run
+```
+
+---
+
+## References
+
+- OpenCode source: `~/mine/opencode/packages/opencode/src/`
+- Jerboa stdlib: `~/mine/jerboa/lib/std/`
+- Chez Scheme: `~/mine/ChezScheme/`
+- OpenAI API: https://platform.openai.com/docs/api-reference
+- Anthropic API: https://docs.anthropic.com/en/api
diff --git a/jerboa.pkg b/jerboa.pkg
new file mode 100644
index 0000000..a7ccb9a
--- /dev/null
+++ b/jerboa.pkg
@@ -0,0 +1,11 @@
+(package jerboa-code
+  :package "jerboa-code"
+  :version "0.1.0"
+  :description "Portable AI coding agent"
+  :depends
+    ((std net)
+     (std db)
+     (std text)
+     (std misc)
+     (std os)
+     (std cli)))
diff --git a/lib/jerboa-code/core/agent.sls b/lib/jerboa-code/core/agent.sls
new file mode 100644
index 0000000..000f145
--- /dev/null
+++ b/lib/jerboa-code/core/agent.sls
@@ -0,0 +1,116 @@
+#!chezscheme
+;;; jerboa-code agent core - main AI interaction loop
+
+(library (jerboa-code core agent)
+  (export agent-run
+          agent-chat
+          agent-step)
+  
+  (import (chezscheme)
+          (jerboa-code core config)
+          (jerboa-code core message)
+          (jerboa-code core session)
+          (jerboa-code provider provider)
+          (jerboa-code tool registry)
+          (std text json)
+          (std log))
+
+  (define log (make-logger "agent"))
+
+  ;; System prompt for the agent
+  (define (system-prompt)
+    "You are an expert AI coding assistant. You help users with software development tasks.
+
+You have access to tools that let you:
+- Read and write files
+- Execute shell commands
+- Search code and files
+- Make HTTP requests
+
+When the user asks you to do something:
+1. Think about what tools you need
+2. Use tools to gather information or make changes
+3. Report back with results
+
+Be concise and helpful. When editing files, make minimal changes.")
+
+  ;; Run a complete agent session
+  (define (agent-run session-id user-input)
+    (log-info log "agent-run" `((session . ,session-id) (input . ,user-input)))
+    
+    ;; Add user message to session
+    (let ([user-msg (make-user-message user-input)])
+      (session-add-message session-id user-msg))
+    
+    ;; Get all messages and run agent loop
+    (let ([messages (session-get-messages session-id)])
+      (agent-loop session-id messages)))
+
+  ;; Main agent loop - handles tool calls
+  (define (agent-loop session-id messages)
+    (let* ([provider (get-current-provider)]
+           [tools (get-tool-schemas)]
+           [response (provider-chat provider messages tools)])
+      
+      (log-debug log "got-response" `((role . ,(message-role response))))
+      
+      ;; Save assistant response
+      (session-add-message session-id response)
+      
+      ;; Check for tool calls
+      (if (message-tool-calls response)
+          ;; Execute tools and continue
+          (let ([results (execute-tool-calls (message-tool-calls response))])
+            ;; Save tool results
+            (for-each 
+              (lambda (result) (session-add-message session-id result))
+              results)
+            ;; Continue the loop with updated messages
+            (agent-loop session-id (session-get-messages session-id)))
+          ;; No tool calls - return final response
+          response)))
+
+  ;; Execute a list of tool calls
+  (define (execute-tool-calls tool-calls)
+    (log-info log "executing-tools" `((count . ,(length tool-calls))))
+    (map execute-single-tool tool-calls))
+
+  ;; Execute a single tool call
+  (define (execute-single-tool tc)
+    (let* ([name (tool-call-name tc)]
+           [args (string->json-object (tool-call-arguments tc))]
+           [result (tool-execute name args)])
+      (log-debug log "tool-result" `((tool . ,name) (result-length . ,(string-length result))))
+      (make-tool-result (tool-call-id tc) result)))
+
+  ;; Get the current provider based on config
+  (define (get-current-provider)
+    (let* ([provider-name (config-provider)]
+           [api-key (config-api-key)]
+           [model (config-model)])
+      (make-provider provider-name api-key model)))
+
+  ;; Simple one-shot chat (no session)
+  (define (agent-chat user-input)
+    (let* ([provider (get-current-provider)]
+           [tools (get-tool-schemas)]
+           [messages (list 
+                       (make-system-message (system-prompt))
+                       (make-user-message user-input))])
+      (agent-chat-loop provider messages tools)))
+
+  ;; Chat loop without session persistence
+  (define (agent-chat-loop provider messages tools)
+    (let ([response (provider-chat provider messages tools)])
+      (if (message-tool-calls response)
+          (let* ([results (execute-tool-calls (message-tool-calls response))]
+                 [new-messages (append messages (list response) results)])
+            (agent-chat-loop provider new-messages tools))
+          (message-content response))))
+
+  ;; Single step (for debugging)
+  (define (agent-step messages)
+    (let* ([provider (get-current-provider)]
+           [tools (get-tool-schemas)])
+      (provider-chat provider messages tools)))
+)
diff --git a/lib/jerboa-code/core/config.sls b/lib/jerboa-code/core/config.sls
new file mode 100644
index 0000000..1d762f2
--- /dev/null
+++ b/lib/jerboa-code/core/config.sls
@@ -0,0 +1,96 @@
+#!chezscheme
+;;; jerboa-code configuration management
+
+(library (jerboa-code core config)
+  (export load-config
+          config-ref
+          config-api-key
+          config-model
+          config-provider
+          config-get-provider-key
+          *config*)
+  
+  (import (chezscheme)
+          (std text json)
+          (std os path)
+          (std os env))
+
+  ;; Global config state
+  (define *config* (make-parameter #f))
+
+  ;; Configuration file locations (in priority order)
+  (define (config-paths)
+    (list
+      (path-join (current-directory) "jerboa-code.json")
+      (path-join (or (getenv "XDG_CONFIG_HOME")
+                     (path-join (getenv "HOME") ".config"))
+                 "jerboa-code" "config.json")
+      (path-join (getenv "HOME") ".jerboa-code.json")))
+
+  ;; Find first existing config file
+  (define (find-config-file)
+    (let loop ([paths (config-paths)])
+      (cond
+        [(null? paths) #f]
+        [(file-exists? (car paths)) (car paths)]
+        [else (loop (cdr paths))])))
+
+  ;; Load configuration from file and environment
+  (define (load-config)
+    (let* ([file (find-config-file)]
+           [file-config (if file
+                           (call-with-input-file file read-json)
+                           (make-hashtable string-hash string=?))]
+           [config (merge-env-config file-config)])
+      (*config* config)
+      config))
+
+  ;; Merge environment variables into config
+  (define (merge-env-config config)
+    (let ([providers (or (hashtable-ref config "providers" #f)
+                        (make-hashtable string-hash string=?))])
+      ;; Check common API key env vars
+      (for-each
+        (lambda (pair)
+          (let ([env-var (car pair)]
+                [provider (cdr pair)])
+            (let ([key (getenv env-var)])
+              (when key
+                (let ([p (or (hashtable-ref providers provider #f)
+                            (make-hashtable string-hash string=?))])
+                  (hashtable-set! p "api_key" key)
+                  (hashtable-set! providers provider p))))))
+        '(("OPENAI_API_KEY" . "openai")
+          ("ANTHROPIC_API_KEY" . "anthropic")
+          ("GOOGLE_API_KEY" . "google")
+          ("OPENROUTER_API_KEY" . "openrouter")))
+      (hashtable-set! config "providers" providers)
+      config))
+
+  ;; Get config value by key path
+  (define (config-ref . keys)
+    (let loop ([obj (*config*)] [keys keys])
+      (cond
+        [(null? keys) obj]
+        [(not (hashtable? obj)) #f]
+        [else
+          (loop (hashtable-ref obj (car keys) #f) (cdr keys))])))
+
+  ;; Get API key for a provider
+  (define (config-get-provider-key provider)
+    (config-ref "providers" provider "api_key"))
+
+  ;; Get default model
+  (define (config-model)
+    (or (config-ref "model")
+        "claude-sonnet-4-20250514"))
+
+  ;; Get default provider
+  (define (config-provider)
+    (or (config-ref "provider")
+        "anthropic"))
+
+  ;; Get API key for default provider
+  (define (config-api-key)
+    (config-get-provider-key (config-provider)))
+)
diff --git a/lib/jerboa-code/core/message.sls b/lib/jerboa-code/core/message.sls
new file mode 100644
index 0000000..112e835
--- /dev/null
+++ b/lib/jerboa-code/core/message.sls
@@ -0,0 +1,100 @@
+#!chezscheme
+;;; jerboa-code message types
+
+(library (jerboa-code core message)
+  (export make-user-message
+          make-assistant-message
+          make-tool-call
+          make-tool-result
+          make-system-message
+          message-role
+          message-content
+          message-tool-calls
+          message-tool-call-id
+          message->json
+          json->message
+          tool-call-id
+          tool-call-name
+          tool-call-arguments)
+  
+  (import (chezscheme)
+          (std text json)
+          (std misc uuid))
+
+  ;; Message record type
+  (define-record-type message
+    (fields role content tool-calls tool-call-id))
+
+  ;; Tool call record type
+  (define-record-type tool-call
+    (fields id name arguments))
+
+  ;; Create a user message
+  (define (make-user-message content)
+    (make-message "user" content #f #f))
+
+  ;; Create an assistant message (optionally with tool calls)
+  (define (make-assistant-message content . tool-calls)
+    (make-message "assistant" content 
+                  (if (null? tool-calls) #f (car tool-calls))
+                  #f))
+
+  ;; Create a tool call
+  (define (make-tool-call name arguments)
+    (make-tool-call (uuid-generate) name arguments))
+
+  ;; Create a tool result message
+  (define (make-tool-result call-id content)
+    (make-message "tool" content #f call-id))
+
+  ;; Create a system message
+  (define (make-system-message content)
+    (make-message "system" content #f #f))
+
+  ;; Convert message to JSON for API
+  (define (message->json msg)
+    (let ([ht (make-hashtable string-hash string=?)])
+      (hashtable-set! ht "role" (message-role msg))
+      (when (message-content msg)
+        (hashtable-set! ht "content" (message-content msg)))
+      (when (message-tool-calls msg)
+        (hashtable-set! ht "tool_calls"
+          (map tool-call->json (message-tool-calls msg))))
+      (when (message-tool-call-id msg)
+        (hashtable-set! ht "tool_call_id" (message-tool-call-id msg)))
+      ht))
+
+  ;; Convert tool call to JSON
+  (define (tool-call->json tc)
+    (let ([ht (make-hashtable string-hash string=?)]
+          [fn (make-hashtable string-hash string=?)])
+      (hashtable-set! ht "id" (tool-call-id tc))
+      (hashtable-set! ht "type" "function")
+      (hashtable-set! fn "name" (tool-call-name tc))
+      (hashtable-set! fn "arguments" 
+        (if (string? (tool-call-arguments tc))
+            (tool-call-arguments tc)
+            (json-object->string (tool-call-arguments tc))))
+      (hashtable-set! ht "function" fn)
+      ht))
+
+  ;; Parse message from JSON response
+  (define (json->message json)
+    (let ([role (hashtable-ref json "role" "assistant")]
+          [content (hashtable-ref json "content" #f)]
+          [tool-calls-json (hashtable-ref json "tool_calls" #f)]
+          [tool-call-id (hashtable-ref json "tool_call_id" #f)])
+      (make-message 
+        role 
+        content
+        (and tool-calls-json (map json->tool-call tool-calls-json))
+        tool-call-id)))
+
+  ;; Parse tool call from JSON
+  (define (json->tool-call json)
+    (let ([fn (hashtable-ref json "function" #f)])
+      (make-tool-call
+        (hashtable-ref json "id" "")
+        (hashtable-ref fn "name" "")
+        (hashtable-ref fn "arguments" "{}"))))
+)
diff --git a/lib/jerboa-code/core/session.sls b/lib/jerboa-code/core/session.sls
new file mode 100644
index 0000000..213e541
--- /dev/null
+++ b/lib/jerboa-code/core/session.sls
@@ -0,0 +1,196 @@
+#!chezscheme
+;;; jerboa-code session persistence
+
+(library (jerboa-code core session)
+  (export session-init-db
+          session-create
+          session-load
+          session-list
+          session-add-message
+          session-get-messages
+          session-update-title
+          session-delete
+          session-id
+          session-title
+          session-created
+          session-messages)
+  
+  (import (chezscheme)
+          (std db sqlite)
+          (std misc uuid)
+          (std os path)
+          (std os env)
+          (std text json)
+          (jerboa-code core message))
+
+  ;; Session record type
+  (define-record-type session
+    (fields id title created messages))
+
+  ;; Get database path
+  (define (db-path)
+    (let ([data-dir (or (getenv "XDG_DATA_HOME")