TokenDanceCode Rust is the active rewrite branch for the local coding-agent runtime. The TypeScript workspace remains in the tree as a contract reference until the Rust crates cover the same public behavior.
Keep the product boundary narrow:
- local CLI coding-agent runtime;
- deterministic session and transcript storage;
- provider adapters for OpenAI Responses, OpenAI-compatible Chat Completions / TokenDance Gateway, and Anthropic-compatible Messages;
- permission modes and subject-level safety evidence;
- AgentHub-consumable SDK facade,
agent.streammapping, approval bridge, TokenDanceID login helper, and same-session run guard; - Rust-first npm binary wrapper for the CLI and SDK bridge;
- MCP client for tool extensibility;
- subagent spawning for multi-agent orchestration;
- context compaction for long-running sessions;
- memory system for persistent knowledge;
- hooks system for lifecycle customization.
Do not build a hosted service, AgentHub replacement, plugin marketplace, full-screen IDE, or long-lived cloud daemon in this repo.
| Crate | Role | Current baseline |
|---|---|---|
tokendance-core |
Runtime, provider trait, session state, permissions, transcript JSONL, config, MCP, subagent, compaction, memory, hooks, streaming, context | 185 tests, 7 built-in tools, 3 provider transports, MCP client, subagent system |
tokendance-sdk |
AgentHub-facing facade and event mapping | 7 tests, runner facade, schema constants, same-session guard |
tokendance-cli |
tokendance binary |
12 tests, run, doctor, config validate, sessions list/show, transcript search, quality, gateway init, auth tokendanceid login-url |
The tool system is built around ToolDefinition, ToolRegistry, and a permission-aware execution pipeline.
Each tool declares:
- name: unique identifier (e.g.
echo,read_file,glob) - description: human-readable summary
- risk:
ToolRiskenum (Read,Write,Shell,Network,Dangerous) - concurrency:
ToolConcurrencyenum (Serial,ParallelSafe,Exclusive) - safety_notes: free-text safety annotations included in permission decisions
- subject_metadata: describes the input field that carries the subject (e.g.
path,command) - subject_guard: pre-permission hook that can deny or require approval based on input content
- executor: the function that runs when the tool is allowed
ToolExposure controls whether a tool appears in the catalog for permission evaluation. Default exposure includes all registered tools.
Subject guards inspect tool input before the permission engine evaluates the tool policy. They can:
- Extract a subject string (e.g. the target file path or command)
- Block execution with
ToolSafetyEvidence(hard-deny regardless of mode) - Require approval for specific subjects (e.g. secret-like paths)
Two built-in guard patterns:
workspace_path_subject_guard: normalizes and validates file paths under the session workspace. Rejects path traversal, absolute paths, and secret-like paths.directory_path_subject_guard: like the workspace guard but accepts directory paths (e.g..). Used byglobandgrepwhich search directories.
The PermissionEngine evaluates tool policies against the session's PermissionMode:
| Mode | Read | Write | Shell | Dangerous |
|---|---|---|---|---|
| Default | Allowed | RequiresApproval | RequiresApproval | RequiresApproval |
| Safe | Allowed | Denied | Denied | Denied |
| Auto | Allowed | Allowed | Allowed | RequiresApproval |
| Yolo | Allowed | Allowed | Allowed | Allowed |
Subject guards run before the permission engine and can override the engine (hard-deny destructive commands even in Yolo mode).
- Look up
ToolDefinitionby name (fail-closed for unknown tools) - Run subject guard (if present) and check for hard-deny evidence
- Run permission engine decision
- If allowed, execute the tool function
- Return
ToolExecutionResultwith optionalsafety_evidence
Provider adapters translate session state into protocol-specific HTTP requests. The transport layer uses gated HTTP with credential redaction.
| Protocol | Request mapping | Key fields |
|---|---|---|
| OpenAI Responses | input array with message items and function_call_output for tool results |
model, tool_choice, parallel_tool_calls |
| OpenAI Chat Completions | messages array with tool role for results |
model, tool_choice, tools schema |
| Anthropic Messages | Split system prompt, tool_result content blocks |
model, max_tokens, system |
The ProviderError type redacts secret-like values in error messages:
- Values starting with
sk-ortd-are replaced with[redacted] - Long alphanumeric strings (32+ chars) are treated as secrets
- Key-value pairs like
token=xxxhave the value redacted
HTTP transport uses reqwest with rustls-tls. API keys are resolved from TOKENDANCE_GATEWAY_API_KEY or per-provider env vars and are never printed to stdout or included in error context.
The MCP (Model Context Protocol) client enables tool extensibility through external server processes.
- Protocol: JSON-RPC 2.0 over stdio
- Server lifecycle: the client spawns a server process, initializes the connection, and shuts it down on drop
- Configuration:
McpServerConfigdefinescommand,args, andenvfor the server process
- On initialization, the client calls
tools/listto discover available tools - Each tool is represented as
McpToolInfowithname,description, andinputSchema - Discovered tools are registered in the
ToolRegistrywith namespaced names (e.g.mcp__{server}__{tool})
- Tools are called via
tools/callwith the tool name and arguments - Results are returned as
McpToolResultwith content and error fields - The client handles request/response correlation with JSON-RPC message IDs
- Resources are listed via
resources/listand read viaresources/read - Each resource has a URI, name, description, and optional MIME type
Subagents enable multi-agent orchestration by spawning isolated agent sessions.
SubagentConfig declares:
- name: descriptive name for the subagent type
- prompt: system prompt/instructions for the subagent
- allowed_tools / disallowed_tools: subset of parent's tools (empty means all available)
- max_turns: maximum turns the subagent can take (default: 10)
- permission_mode: can be more restrictive than parent
- model: optional override (None = inherit from parent)
- working_directory: optional override (None = inherit from parent)
- Each subagent gets an isolated
SessionStatewith its own message history - Tool access is filtered through the
allowed_tools/disallowed_toolslists - The subagent's permission mode can be stricter than the parent's
A hardcoded list of tool names (subagent, run_subagent, agent) are blocked from subagent tool lists to prevent unbounded recursion.
SubagentResult captures:
subagent_id: unique identifiersuccess: whether the subagent completed successfullyresponse: the final response textturns_completed: number of turns executedtools_used: list of tools invoked
Context compaction reduces token usage in long-running sessions by summarizing older messages.
CompactConfig controls compaction behavior:
- max_messages: threshold to trigger compaction (default: 100)
- keep_recent: number of recent messages to preserve unsummarized (default: 10)
- enabled: whether compaction is active (default: true)
When the message count exceeds max_messages:
- Messages older than
keep_recentare selected for compaction - A summary is produced replacing the older messages
- The compacted messages are removed and replaced with a single summary message
CompactResultreports the summary, messages compacted, messages kept, and estimated tokens saved
The memory system provides persistent knowledge storage backed by markdown files.
Each memory entry is stored as a .md file with YAML-like frontmatter:
---
name: user-preferences
description: User's coding preferences
metadata:
type: feedback
updated: "2026-06-10"
---
User prefers kebab-case for directory names...| Type | Scope |
|---|---|
user |
Global user-level preferences |
feedback |
User feedback and corrections |
project |
Project-specific knowledge |
reference |
Reference material and documentation |
MemoryStore provides:
- Create: write a new memory entry as a markdown file
- Read: load and parse a memory entry from file
- Update: modify an existing memory entry in place
- Delete: remove a memory entry file
- List: enumerate all memory entries in the store
The context builder discovers instruction files that guide agent behavior.
Instructions are loaded in order (later overrides earlier):
- Global:
{HOME}/.tokendance/AGENTS.md - Project:
{project_root}/AGENTS.md - Project (alt):
{project_root}/CLAUDE.md - Local:
{project_root}/.tokendance/AGENTS.md
InstructionFile captures:
path: resolved file pathscope:InstructionScopeenum (Global, Project, Local)content: file contents
WorkingContext provides environmental context:
project_root: resolved project directoryproject_type: detected type ("rust", "typescript", "mixed", "unknown")top_level_files: files and directories in the project rootinstruction_count: number of instruction files found
The hooks system provides lifecycle callbacks for customizing agent behavior.
| Hook | Timing | Use case |
|---|---|---|
PreToolUse |
Before tool execution | Validate inputs, block dangerous operations |
PostToolUse |
After tool execution | Audit, logging, post-processing |
TurnCompleted |
After a turn finishes | Progress tracking, notification |
TurnFailed |
After a turn fails | Error reporting, cleanup |
HookContext provides:
session_id: current session identifierturn_id: current turn identifiertool_call: tool call details (for tool hooks)tool_result: tool result (for post hooks)decision: permission decision (for tool hooks)
- Continue: proceed normally
- Block: prevent the action with a reason
- Modify: alter the tool input (PreToolUse only)
HookRegistry manages named hook collections per hook point. Hooks are registered at startup and called synchronously in registration order.
The streaming subsystem handles real-time output from provider responses.
parse_sse_buffer processes Server-Sent Events text into structured SseEvent objects:
- event_type: the SSE event type field
- data: concatenated data lines
- id: optional event ID
The parser handles SSE edge cases: comment lines (starting with :), concatenated data fields, and missing fields.
The streaming layer converts SSE events into StreamEvent variants for the runtime to process.
Provider responses flow through tokio::sync::mpsc channels, enabling:
- Non-blocking streaming from provider HTTP responses
- Buffered event processing in the runtime loop
- Clean shutdown when the channel closes
The runtime provides platform-specific safety policies:
- Windows: PowerShell destructive-command hard-deny (e.g.
Remove-Item -Recurse -Forceon system paths) - Path validation: workspace-relative path enforcement, traversal rejection
- Secret-like paths: paths matching common secret patterns require approval or are denied in safe mode
- Command classification: shell commands are classified for risk before execution
Settings are loaded from settings.json files and merged:
- User config:
~/.tokendance/settings.json - Project config:
<project>/.tokendance/settings.json - Merge: project values override user values
Settings include:
provider.kind:openai_chat_completions|openai_responses|anthropic_messagesprovider.model: model identifierprovider.baseUrl: custom API endpointpermissionMode:default|safe|auto|yoloallowedTools/disallowedTools: tool-level overrides
Validation catches unknown provider kinds, unknown permission modes, and tools appearing in both allow and disallow lists.
- Preserve TS public contracts in tests and docs.
- Implement Rust runtime primitives: session, transcript, events, provider trait, permission profiles.
- Port provider adapters with explicit protocol errors and no project
.envloading. - Port CLI commands and structured
run --json/--stream-jsonoutput. - Port AgentHub SDK facade: event envelope schema, approval bridge, context preview, OIDC helper, same-session concurrency.
- Add Rust-first npm binary wrapper and SDK bridge.
- Remove or archive TypeScript implementation only after Rust release gates prove parity.
Use repo-local worktrees under .worktrees/ and keep ownership disjoint:
- CLI worker:
crates/tokendance-cli/**, CLI docs/tests. - Core runtime worker:
crates/tokendance-core/src/runtime.rs,transcript.rs, runtime tests. - Provider worker:
crates/tokendance-core/src/provider.rs, provider adapter modules and protocol tests. - Permission/tools worker:
permissions.rs, tool registry and shell/file tools. - SDK/AgentHub worker:
crates/tokendance-sdk/**, AgentHub docs/tests. - Release worker: npm wrapper, package metadata, privacy scan, release docs.
The Rust branch is not releasable until these pass:
cargo fmt --all -- --check
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo run -p tokendance-cli -- --version
cargo run -p tokendance-cli -- doctor --json
pnpm verify
pnpm release:rust:plan:check
pnpm smoke:rust-wrapper
node scripts/smoke-rust-release.mjs
node scripts/smoke-providers.mjspnpm verify intentionally remains the short Rust verification gate for now:
cargo fmt --all -- --check && cargo test --workspaceThe Rust rewrite must preserve these TS contracts before a public release:
- CLI:
tokendance run --json,tokendance run --stream-json,doctor --json,config validate --json,gateway init, TokenDanceID login URL helper, sessions, transcript, quality, tasks/todos, worktree and subagent commands. - Structured run JSON: aggregate result with
threadId/sessionId, success flag, final response, events, and structured error; stream JSONL ends withrun.result. - AgentHub:
agenthub-sdk.v1,agent.streamschema version2, required envelope fields, terminalrun.agent.resultfor success and failure, approval request schema version1. - Session safety: AgentHub
sessionIdis the TokenDanceCode thread id. Same resolved storage root plus session id must reject concurrent runs before transcript mutation. - Provider boundary: project
.envis ignored by default; TokenDance Gateway API keys are model API credentials and are never TokenDanceID/OIDC login tokens. - Packaging: npm release starts with a native binary wrapper. Do not publish root workspace, private examples, legacy Python package, or Rust crates to crates.io without a separate API decision.
The Rust-first npm binary wrapper is a distribution layer, not a second CLI implementation.
packages/cli/bin/tokendance.jsis the npmbinentry fortokendance.- The JavaScript shim resolves a local built Rust binary first, then a reviewed platform-native binary package placeholder, forwards argv and stdio unchanged, and shows a clear unsupported-platform error when no binary is available.
packages/cli/package.jsonuses Rust-alignedbuildandtestscripts forcrates/tokendance-cli; it no longer points the npm bin at legacy TypeScriptdist.pnpm smoke:rust-wrapperpacks and installs the wrapper locally without publishing, locates or builds the current-platform Rust binary for the temp install, runstokendance --versionandtokendance doctor --json, and scans the packed wrapper for source/test/build-only files, local paths, npm auth config, and token-like secret material.- Platform binaries should come from CI artifacts built from
crates/tokendance-cli; package scripts must not compile ad hoc release binaries on user machines. - Optional native packages may be listed through
optionalDependenciesonly after their manifests, CI artifact names, target triples, and smoke tests are defined. - Planned native package names include
@tokendance/code-cli-win32-x64-msvc,@tokendance/code-cli-darwin-arm64,@tokendance/code-cli-darwin-x64,@tokendance/code-cli-linux-x64-gnu, and@tokendance/code-cli-linux-arm64-gnu. - Do not add publish scripts. Publishing remains a manual release-owner action from reviewed tarballs.