Implementation Plan: TinyClaw OpenClaw Parity via Terraphim Extensions
Status: Draft Research Doc: tinyclaw-openclaw-parity-research-2026-02-27.md Author: Terraphim AI Date: 2026-02-27 Related Issues: #590 (epic), #588, #589, #591, #592, #593, #560
Overview
Summary
This plan implements OpenClaw parity for TinyClaw through five sequenced tracks. Each track extends existing Terraphim functionality rather than duplicating it, following the core epic principle.
Approach
- Foundation First: Fix #588 before any feature work to avoid compounding instability
- Extension Over Duplication: Reuse
terraphim-markdown-parser,terraphim_spawner - Feature Gates: Heavy dependencies (voice) remain optional
- Safety First: Unified guard policy closes security gaps
Scope
In Scope:
- Track 1 (#588): Foundation hardening (reset, config wiring, guardrails, docs)
- Track 2 (#589): Provider-backed web search and config-driven web tooling
- Track 3 (#592): Markdown-defined commands and skills
- Track 4 (#593): Voice transcription pipeline with graceful fallback
- Track 5 (#591): Session tools and orchestration runway (extends #560)
Out of Scope:
- Matrix channel (disabled due to sqlite conflict)
- Complex workflow DAGs (skills remain sequential)
- Built-in vector search (use haystack middleware separately)
- Custom LLM provider registry (use existing patterns)
Avoid At All Cost (from 5/25 analysis):
- Creating a parallel markdown parser when
terraphim-markdown-parserexists - Duplicating spawner functionality instead of using
terraphim_spawner - Adding features before foundation hardening (#588)
- Breaking existing skill JSON format (additive only)
Architecture
Component Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ TinyClaw Architecture │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Telegram │ │ Discord │ │ CLI │ │ (Matrix) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ MessageBus │ │
│ └───────┬────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ ┌────────▼─────┐ ┌────▼────┐ ┌──────▼──────┐ │
│ │SessionManager│ │Tool │ │ HybridLlm │ │
│ │ (JSONL) │ │Registry │ │ Router │ │
│ └──────┬───────┘ └────┬────┘ └──────┬──────┘ │
│ │ │ │ │
│ │ ┌─────────┼─────────┐ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────────┐ ┌──────┐ ┌──────────┐ │
│ │ │Session │ │Web │ │Markdown │ <- NEW │
│ │ │Tools │ │Search│ │Commands │ │
│ │ └────────┘ └──────┘ └──────────┘ │
│ │ │ │ │ │
│ │ ▼ ▼ ▼ │
│ │ ┌────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ │Agent │ │ terraphim │ │ terraphim │ <- REUSED │
│ │ │Spawn │ │ _spawner │ │-markdown- │ │
│ │ │ │ │ │ │ parser │ │
│ │ └────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Unified │ <- MODIFIED (closes guard bypass) │
│ │ Execution │ │
│ │ Guard │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘Data Flow (New Components)
# Markdown Command Flow
[Markdown File] -> terraphim-markdown-parser -> CommandDefinition
|
[ToolCallingLoop]
|
handle_slash_command()
|
[Execution]
# Web Search Flow
[User Query] -> web_search tool -> WebSearchProvider (config-selected)
|
+-------------------+-------------------+
| | |
[Brave] [SearXNG] [Google]
| | |
+-------------------+-------------------+
|
[Unified Response]
|
[LLM Context]
# Session Tool Flow
[User] -> sessions_list/history/send -> SessionManager
|
[Query/Filter]
|
[JSONL Files]
|
[Return Results]Key Design Decisions
| Decision | Rationale | Alternatives Rejected |
|----------|-----------|----------------------|
| Use terraphim-markdown-parser for commands | Single parser, DRY | Custom TinyClaw-only parser |
| Feature gate voice deps | Binary size control | Always compile voice |
| Unified ExecutionGuard | Security: close bypass path | Separate guard for skills |
| Config-driven web provider | Runtime flexibility | Hardcoded provider |
| JSONL session format | Append-only, crash-safe | SQLite (conflict), single JSON |
Eliminated Options
| Option Rejected | Why Rejected | Risk of Including | |-----------------|--------------|-------------------| | Custom markdown parser | Duplicates terraphim-markdown-parser | Maintenance burden, drift | | Built-in whisper (no gate) | +50MB binary size | Deployment blocker | | Separate skill guard | Complexity, inconsistency | Security holes | | SQLite sessions | Dependency conflict with matrix-sdk | Build breakage |
Simplicity Check
What if this could be easy?
The simplest design would:
- Wire existing config fields directly (no schema changes)
- Add markdown command loader as thin wrapper over existing parser
- Reuse
terraphim_spawnerAPI without adaptation layer - Make voice a compile-time option (already done)
The current design achieves this simplicity for tracks 1, 3, 5. Tracks 2 and 4 require moderate complexity for provider abstraction and audio processing.
Track Specifications
Track 1: Foundation Hardening (#588)
Files Changed
| File | Change |
|------|--------|
| src/session.rs | Add clear() method, fix /reset |
| src/agent/agent_loop.rs | Wire max_session_messages, fix /reset, update slash handler |
| src/skills/executor.rs | Route shell steps through ExecutionGuard |
| src/tools/shell.rs | Export guard check for skills reuse |
| src/config.rs | Add validation for tool configs |
| README.md | Remove Matrix/voice claims until enabled |
API Changes
// session.rs - Add clear method
// agent_loop.rs - Wire max_session_messages
// skills/executor.rs - Use ExecutionGuard
async Test Strategy
| Test | Location | Purpose |
|------|----------|---------|
| test_session_clear | session.rs | Verify clear() removes messages + summary |
| test_reset_command_clears | agent_loop.rs | Integration: /reset -> clear() |
| test_skill_shell_guarded | skills/executor.rs | Verify dangerous commands blocked |
| test_max_session_compression | agent_loop.rs | Compression triggers at limit |
Track 2: Provider-Backed Web Search (#589)
Files Changed
| File | Change |
|------|--------|
| src/tools/web.rs | Refactor to provider trait, implement real search |
| src/tools/web_search/ | NEW Directory with provider implementations |
| src/tools/web_search/mod.rs | NEW Provider trait and dispatcher |
| src/tools/web_search/brave.rs | NEW Brave Search provider |
| src/tools/web_search/searxng.rs | NEW SearXNG provider |
| src/tools/web_search/exa.rs | NEW Exa AI Search provider |
| src/tools/web_search/kimi_search.rs | NEW Kimi Search provider |
| src/config.rs | Extend WebToolsConfig with provider credentials |
New Types
// tools/web_search/mod.rs
// tools/web_search/brave.rs
Config Extension
[tools.web]
search_provider = "brave" # or "searxng", "exa", "kimi_search"
fetch_mode = "readability" # or "raw"
[tools.web.brave]
api_key = "${BRAVE_API_KEY}"
[tools.web.searxng]
base_url = "https://search.example.com"
[tools.web.exa]
api_key = "${EXA_API_KEY}"
[tools.web.kimi_search]
api_key = "${KIMI_API_KEY}"
base_url = "https://search.moonshot.cn"Test Strategy
| Test | Location | Purpose |
|------|----------|---------|
| test_brave_provider_search | web_search/brave.rs | Mock API response parsing |
| test_provider_dispatcher | web_search/mod.rs | Route to correct provider |
| test_web_fetch_readability | web.rs | Extract main content |
| test_search_integration | tests/ | Live test with real API (ignored) |
Track 3: Markdown Commands and Skills (#592)
Files Changed
| File | Change |
|------|--------|
| src/commands/mod.rs | NEW Markdown command types and loader |
| src/commands/loader.rs | NEW Load commands from directory |
| src/commands/executor.rs | NEW Execute markdown-defined commands |
| src/skills/markdown.rs | NEW Markdown skill support |
| src/agent/agent_loop.rs | Integrate markdown commands into slash handler |
| Cargo.toml | Add terraphim-markdown-parser dependency |
New Types
// commands/mod.rs
// commands/loader.rs
Markdown Format
- -
Step 2: Analyze with LLM
prompt: |
Analyze this Rust codebase for {focus} issues:
{previous_output}Step 3: Respond
template: |
## Analysis Results
{previous_output}
#### Test Strategy
| Test | Location | Purpose |
|------|----------|---------|
| `test_parse_command_frontmatter` | `commands/loader.rs` | Extract metadata |
| `test_parse_command_steps` | `commands/loader.rs` | Parse tool/llm/respond blocks |
| `test_execute_markdown_command` | `commands/executor.rs` | End-to-end execution |
| `test_json_skill_still_works` | `skills/executor.rs` | Backward compatibility |
---
### Track 4: Voice Transcription (#593)
#### Files Changed
| File | Change |
|------|--------|
| `src/tools/voice_transcribe.rs` | Implement real transcription |
| `Cargo.toml` | Add `whisper-rs`, `symphonia` deps behind `voice` feature |
| `src/config.rs` | Add voice configuration section |
#### Implementation
```rust
// tools/voice_transcribe.rs (voice feature enabled)
#[cfg(feature = "voice")]
pub struct VoiceTranscribeTool {
temp_dir: PathBuf,
whisper: WhisperContext,
}
#[cfg(feature = "voice")]
#[async_trait]
impl Tool for VoiceTranscribeTool {
async fn execute(&self, args: serde_json::Value) -> Result<String, ToolError> {
let audio_url = extract_url(&args)?;
// 1. Download
let audio_path = self.download_audio(audio_url).await?;
// 2. Convert to WAV (16kHz mono)
let wav_path = self.convert_to_wav(&audio_path).await
.map_err(|e| ToolError::ExecutionFailed {
tool: "voice_transcribe".to_string(),
message: format!("Audio conversion failed: {}", e),
})?;
// 3. Transcribe with Whisper
let text = self.transcribe(&wav_path).await
.map_err(|e| ToolError::ExecutionFailed {
tool: "voice_transcribe".to_string(),
message: format!("Transcription failed: {}", e),
})?;
// 4. Cleanup
let _ = tokio::fs::remove_file(&audio_path).await;
let _ = tokio::fs::remove_file(&wav_path).await;
Ok(text)
}
}
#[cfg(feature = "voice")]
impl VoiceTranscribeTool {
async fn convert_to_wav(&self, input: &Path) -> Result<PathBuf, TranscribeError> {
use symphonia::default::get_probe;
use symphonia::core::codecs::DecoderOptions;
// Decode any format to raw samples
let probe = get_probe();
let source = File::open(input)?;
let mss = MediaSourceStream::new(Box::new(source), Default::default());
// Probe and decode
let result = probe.format(
&Default::default(),
mss,
&Default::default(),
&Default::default(),
)?;
// Resample to 16kHz mono if needed
// Write to WAV file
Ok(wav_path)
}
async fn transcribe(&self, wav_path: &Path) -> Result<String, TranscribeError> {
use whisper_rs::{FullParams, SamplingStrategy};
let params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });
let pcm_data = read_wav_16khz_mono(wav_path)?;
self.whisper.full(params, &pcm_data)?;
// Extract text from segments
let num_segments = self.whisper.full_n_segments()?;
let mut text = String::new();
for i in 0..num_segments {
text.push_str(&self.whisper.full_get_segment_text(i)?);
text.push(' ');
}
Ok(text.trim().to_string())
}
}
// Stub implementation when voice feature disabled
#[cfg(not(feature = "voice"))]
#[async_trait]
impl Tool for VoiceTranscribeTool {
async fn execute(&self, _args: serde_json::Value) -> Result<String, ToolError> {
Ok("[Voice transcription requires 'voice' feature enabled at compile time]".to_string())
}
}Error Handling
| Error Condition | User Message | |-----------------|--------------| | Feature not compiled | "Voice transcription not enabled. Recompile with --features voice" | | Download failed | "Could not download audio. Check URL and network." | | Invalid audio | "Audio file could not be decoded. Supported: OGG, MP3, WAV" | | Transcription timeout | "Transcription timed out. Try a shorter audio clip." | | Model not found | "Voice model not found. Run: tinyclaw download-model" |
Test Strategy
| Test | Location | Purpose |
|------|----------|---------|
| test_voice_feature_disabled | voice_transcribe.rs | Graceful fallback |
| test_voice_download_audio | voice_transcribe.rs | Mock HTTP download |
| test_voice_invalid_url | voice_transcribe.rs | Error handling |
| test_voice_transcribe_live | tests/ | Live test (ignored, requires feature) |
Track 5: Session Tools and Orchestration (#591)
Files Changed
| File | Change |
|------|--------|
| src/tools/session_tools.rs | NEW Session tool implementations |
| src/tools/mod.rs | Register session tools |
| src/tools/agent_spawn.rs | NEW Agent spawn tool (extends #560) |
New Types
// tools/session_tools.rs
;
;
;
// tools/agent_spawn.rs (extends #560)
Test Strategy
| Test | Location | Purpose |
|------|----------|---------|
| test_session_list_tool | session_tools.rs | List sessions |
| test_session_history_tool | session_tools.rs | Get history |
| test_session_send_tool | session_tools.rs | Cross-session message |
| test_agent_spawn_mock | agent_spawn.rs | Mock spawner integration |
| test_agent_spawn_live | tests/ | Live test (ignored, requires setup) |
Implementation Sequencing
Phase 1: Foundation (Track 1 - #588)
Goal: Stable base for feature work
Steps:
-
Session clear method (2 hours)
- Add
Session::clear() - Test: messages and summary cleared
- Add
-
Fix /reset command (1 hour)
- Call
session.clear()in handler - Test: integration with session manager
- Call
-
Wire max_session_messages (2 hours)
- Read config value in agent_loop
- Test: compression triggers at limit
-
Close guard bypass (4 hours)
- Export ExecutionGuard checks
- Integrate into skill shell execution
- Test: dangerous commands blocked in skills
-
Docs alignment (1 hour)
- Remove Matrix/voice claims
- Update feature status table
Phase 1 Exit Criteria:
- [ ]
/resetclears session messages and summary - [ ]
agent.max_session_messagestriggers compression - [ ] Skill shell steps go through ExecutionGuard
- [ ] Docs match shipped behavior
- [ ] All tests pass
Phase 2: Core Features (Tracks 2 & 3 - #589, #592)
Goal: Primary OpenClaw parity features
Track 2 Steps:
- Search provider trait (2 hours)
- Brave provider implementation (4 hours)
- SearXNG provider (2 hours)
- Provider dispatcher (2 hours)
- Web fetch improvements (2 hours)
Track 3 Steps:
- Add terraphim-markdown-parser dependency (1 hour)
- Markdown command types (2 hours)
- Command loader (4 hours)
- Command executor (4 hours)
- Slash command integration (2 hours)
- Markdown skill support (4 hours)
Phase 2 Exit Criteria:
- [ ]
web_searchreturns real results - [ ] Provider configurable in TOML
- [ ] Markdown commands load and execute
- [ ] JSON skills still work (backward compat)
- [ ] Integration tests pass
Phase 3: Extended Features (Tracks 4 & 5 - #593, #591)
Goal: Voice and orchestration capabilities
Track 4 Steps:
- Add voice deps (whisper-rs, symphonia) (1 hour)
- Implement audio conversion (4 hours)
- Implement whisper transcription (4 hours)
- Error handling polish (2 hours)
Track 5 Steps:
- Session list tool (2 hours)
- Session history tool (2 hours)
- Session send tool (2 hours)
- Agent spawn tool (extends #560) (4 hours)
- Tool registration (1 hour)
Phase 3 Exit Criteria:
- [ ] Voice transcription works (with feature)
- [ ] Graceful fallback without feature
- [ ] Session tools functional
- [ ] Agent spawn integrates with terraphim_spawner
- [ ] All tests pass
Test Strategy
Unit Tests
| Test | Location | Phase |
|------|----------|-------|
| test_session_clear | session.rs | 1 |
| test_skill_guard_integration | skills/executor.rs | 1 |
| test_brave_provider | web_search/brave.rs | 2 |
| test_command_loader | commands/loader.rs | 2 |
| test_voice_conversion | voice_transcribe.rs | 3 |
| test_session_tools | session_tools.rs | 3 |
Integration Tests
| Test | Location | Phase |
|------|----------|-------|
| test_reset_end_to_end | tests/agent_loop.rs | 1 |
| test_web_search_live | tests/web_search.rs | 2 |
| test_markdown_command_e2e | tests/commands.rs | 2 |
| test_voice_pipeline | tests/voice.rs | 3 |
| test_session_orchestration | tests/orchestration.rs | 3 |
Regression Tests
| Test | Purpose |
|------|---------|
| test_json_skills_still_work | Backward compatibility |
| test_config_loading | Config format unchanged |
| test_existing_tools | No tool behavior regression |
Dependencies
New Dependencies
| Crate | Version | Purpose | Feature | |-------|---------|---------|---------| | terraphim-markdown-parser | workspace | Parse markdown commands | default | | terraphim_spawner | workspace | Agent spawning | default | | readability | 0.3 | HTML content extraction | default | | whisper-rs | 0.11 | Voice transcription | voice | | symphonia | 0.5 | Audio format conversion | voice |
Dependency Updates
| File | Change |
|------|--------|
| Cargo.toml | Add deps above |
Rollback Plan
If issues discovered:
- Per-track rollback: Each track is independent; disable by reverting track-specific commits
- Feature gate disable: Voice can be disabled by not using
--features voice - Config fallback: New config fields have sensible defaults; removing them doesn't break
Feature flags:
voice: Disable if transcription issuestelegram/discord: Channel-specific, already gated
Migration
Config Migration
New config sections are additive. Old configs work without changes.
# New optional sections
[tools.web]
search_provider = "brave"
[tools.web.brave]
api_key = "..."
[commands]
load_path = "~/.config/terraphim/commands/"Session Format
JSONL format unchanged. New fields optional.
Open Items
| Item | Status | Owner | |------|--------|-------| | Coordinate with #560 | Pending | Shared | | Choose primary web search provider | Pending | Product | | Voice model distribution | Pending | DevOps |
Approval Checklist
- [ ] Technical review complete
- [ ] Test strategy approved
- [ ] Performance targets agreed
- [ ] Human approval received
- [ ] Sequencing approved (Foundation -> Core -> Extended)
Appendix
Track/Issue Mapping
| Track | Issue | Name | Priority | |-------|-------|------|----------| | 1 | #588 | Foundation Hardening | P0 (First) | | 2 | #589 | Provider Web Search | P1 | | 3 | #592 | Markdown Commands | P1 | | 4 | #593 | Voice Transcription | P2 | | 5 | #591 | Session Tools + Spawn | P2 |
File Change Summary
New Files (7):
src/tools/web_search/mod.rssrc/tools/web_search/brave.rssrc/tools/web_search/searxng.rssrc/commands/mod.rssrc/commands/loader.rssrc/commands/executor.rssrc/tools/session_tools.rssrc/tools/agent_spawn.rssrc/skills/markdown.rs
Modified Files (8):
src/session.rssrc/agent/agent_loop.rssrc/skills/executor.rssrc/tools/shell.rssrc/tools/web.rssrc/tools/mod.rssrc/config.rsCargo.tomlREADME.md