Skycode AI: An open-source alternative to Cursor
An open-source alternative to Cursor — a VS Code fork with a deeply integrated AI agent where the loop, diff, and indexing engine communicate via gRPC.
The Problem
Every AI coding tool on the market falls into one of two categories: extensions that bolt onto an existing editor, or standalone products that lock you into a proprietary platform. Extensions like GitHub Copilot and Continue run inside VS Code but cannot modify the editor itself — they work through the extension API, which means the AI is a guest, not a resident. Proprietary tools like Cursor fork VS Code and build deep integration, but you pay a subscription and your code passes through their servers.
The gap is clear: there is no open-source, self-hostable editor where the AI agent is a first-class citizen of the editor process itself. You either get shallow integration with full control (extensions) or deep integration with zero control (proprietary forks).
| Dimension | Extension-based (Copilot, Continue) | Proprietary Fork (Cursor) | Skycode AI |
|---|---|---|---|
| AI integration depth | Extension API only | Deep (fork) | Deep (fork) |
| Open source | Yes (some) | No | Yes (Apache 2.0) |
| Self-hostable | Yes | No | Yes |
| Model provider choice | Limited | 10-15 providers | 40+ providers |
| Offline semantic search | No | No | Yes (transformers.js + WASM) |
| Per-message diff snapshots | No | Yes | Yes (217 tests) |
| MCP support | Via extension | Yes | Yes |
| Voice input | No | No | Yes (offline Whisper) |
| Cost | Free tier + subscription | $20/month | Free (bring your own API key) |
Why this matters: If you care about data sovereignty, model choice, and editor-level AI integration, Skycode AI is the first open-source project that delivers all three. It is a VS Code fork where the agent loop, diff engine, and semantic index are core subsystems — not extensions loaded at runtime.
The Investigation
We cloned the repository, built from source, and ran the agent against a production Node.js codebase to understand how the architecture holds up under real use.
Finding 1: The agent loop is a recursive tool-calling pipeline, not a simple request-response cycle.
The core loop lives in extensions/skycode/src/core/task/index.ts. When you send a message, the Task class creates a ResponseGroup, streams the LLM response, and enters a recursive loop:
Controller.initTask()
-> new Task(taskId, ...)
-> Task.startTask()
-> say("text", task) -> startCheckpoint(messageTs)
-> recursivelyMakeSkycodeRequests() // main AI loop
-> attemptApiRequest() // streaming chunks
-> parseAssistantMessage()
-> executeTool() // write_to_file, bash, etc.
-> ask("completion_result") // wait for user feedback
-> Feedback -> startCheckpoint(feedbackTs) // new ResponseGroup
What this means: the agent does not stop after one LLM call. It loops — calling the model, executing tools, feeding results back, and calling again — until the model signals completion via attempt_completion. This is the same pattern used by Cursor’s agent mode and Claude Code’s agent loop, but implemented entirely in the editor’s extension host process.
Finding 2: The diff system v4 uses per-message snapshots, not per-hunk rollback.
Most inline diff implementations track individual hunks. If you reject hunk 3 of 10, the system reverses that one hunk. Skycode’s approach is different: each chat message gets a full file snapshot taken before the first edit. When you delete a message, the system restores the snapshot for that message and all subsequent messages, then re-applies only the hunks from messages before the deleted one.
Message 1 -> RG1 (snapshot A) -> hunks 1, 2, 3
Message 2 -> RG2 (snapshot B) -> hunks 4, 5
Message 3 -> RG3 (snapshot C) -> hunk 6
Deleting message 2:
-> rollbackFromMessage(ts2)
-> finds RG2 and RG3 (chatMessageTs >= ts2)
-> restores file from snapshot B
-> marks RG2 + RG3 hunks as rejected
-> leaves RG1 hunks pending
What this means: rollback is O(1) per message — restore a snapshot and mark hunks as rejected. There is no diff computation during rollback. The tradeoff is storage: each message with edits stores a full copy of every changed file. For a typical session with 20 messages touching 3 files each, that is roughly 60x the working set size in snapshot storage.
Finding 3: The semantic search index runs entirely in-process using transformers.js and SQLite.
Skycode does not send your code to a remote embedding API by default. It runs paraphrase-multilingual-MiniLM-L12-v2 (384 dimensions) in a Node.js worker thread using transformers.js compiled to WASM. Chunks are embedded in batches of 32 with a 50ms delay between batches to avoid blocking the event loop. The index lives in SQLite alongside chunk metadata (file path, content hash, embedding model version).
Indexing flow:
FileWatcher detects change (debounced 700ms)
-> Tree-sitter parses file into AST chunks
-> EmbeddingWorkerManager embeds chunks (batch of 32)
-> SQLite stores vectors + metadata
Search flow:
User types query
-> Query embedded with same model
-> Cosine similarity against stored vectors
-> Results reranked with keyword match (ripgrep)
What this means: the index is fully offline. No code leaves your machine. The tradeoff is index quality — a 384-dimension local model cannot match the retrieval quality of OpenAI’s text-embedding-3-large (3072 dimensions). Skycode supports a remote embedding API for users who want higher quality at the cost of sending code to a third party.
The Solution
Skycode AI is a VS Code fork where the AI agent, diff system, and indexing engine are compiled into the extension host process, not loaded as separate extensions. The webview UI communicates with the extension host via gRPC over Protobuf.
+------------------------------------------------------------------+
| VS Code Fork (Electron) |
| +------------------------------------------------------------+ |
| | Extension Host (Node.js) | |
| | +------------------+ +----------------+ +-------------+ | |
| | | Agent Loop | | Diff System v4 | | Indexing | | |
| | | Task lifecycle | | Snapshot store | | SQLite | | |
| | | Tool execution | | HunkApplier | | Embeddings | | |
| | | Mode switching | | HunkReverter | | Tree-sitter | | |
| | +--------+---------+ +-------+--------+ +------+------+ | |
| | | | | | |
| | +--------v--------------------v--------------------v------+ | |
| | | API Provider Layer (40+ providers) | | |
| | | OpenAI | Anthropic | Google | Ollama | OpenRouter... | | |
| | +-------------------------------------------------------+ | |
| | | | |
| | +--------v--------+ +-----------+ +-------------------+ | |
| | | MCP Hub | | Prompts | | Workflow | | |
| | | External tools | | Engine | | Orchestrator | | |
| | +-----------------+ +-----------+ +-------------------+ | |
| +----------------------------|-----------------------------------+ |
| | gRPC + Protobuf |
| +----------------------------v-----------------------------------+ |
| | Webview UI (React, 230+ components) | |
| | Chat interface | Settings | History | Pending changes bar | |
| +----------------------------------------------------------------+ |
+----------------------------------------------------------------------+
Here is what each piece does:
- Agent Loop — The
Taskclass manages the recursive tool-calling lifecycle. It createsResponseGroupobjects per message, streams LLM responses, executes tool calls, and loops until the model signals completion. Five operating modes (Act, Ask, Plan, Debug, Chat) each define their own toolset and system prompt. - Diff System v4 — The
DiffSystemclass orchestrates three subsystems:DiffStore(unified storage for response groups, file changes, and hunks),FileSnapshotStorage(per-message snapshots), andHunkApplier/HunkReverter(apply and rollback individual changes). TheInlineDiffRenderercreates reactive View Zones in the editor for Accept/Reject buttons. - Indexing — The
EmbeddingWorkerManagerruns transformers.js in a dedicated worker thread. Tree-sitter parses files into AST-aware chunks. SQLite stores vectors and metadata. TheFileWatchertriggers incremental re-indexing with a 700ms debounce. - API Provider Layer — A unified interface abstracts 40+ model providers. Each provider implements the same streaming chat completion contract. The layer handles retries, rate limiting, and token counting.
- MCP Hub — The Model Context Protocol integration allows the agent to connect external tools (databases, APIs, file systems) through a standardized interface. MCP servers are loaded as child processes and communicate via stdio JSON-RPC.
- Workflow Orchestrator — Multi-step
.yamlworkflows (e.g.,/deploy) are parsed and executed step-by-step. Each step runs a modified agent loop that exits onstepCompleted. Steps can be silent (invisible to the user) or visible with progress checklists.
Production-grade code walkthrough
The agent loop is the heart of the system. Here is the core recursive loop, simplified from the actual source:
// extensions/skycode/src/core/task/index.ts (simplified)
export class Task {
private taskState: TaskState;
private session: Session | null = null;
private responseGroups: ResponseGroup[] = [];
async startTask(userContent: UserContent): Promise<void> {
this.taskState = new TaskState({ abort: false });
this.session = new Session(this.provider);
// Create the first response group for this message
const messageTs = Date.now();
const rg = new ResponseGroup(messageTs);
this.responseGroups.push(rg);
// Emit the user message to the webview
await this.say("text", {
content: userContent.text,
images: userContent.images,
});
// Enter the recursive agent loop
await this.initiateTaskLoop(userContent);
}
private async initiateTaskLoop(
nextUserContent: UserContent
): Promise<void> {
while (!this.taskState.abort) {
this.session.pipeline.nextIteration();
const didEndLoop = await this.recursivelyMakeSkycodeRequests(
nextUserContent,
false // isOnlyTextResponse
);
if (didEndLoop) break;
// If the loop didn't end, wait for user feedback
const feedback = await this.ask("completion_result");
if (feedback) {
const feedbackTs = Date.now();
const rg = new ResponseGroup(feedbackTs);
this.responseGroups.push(rg);
nextUserContent = { text: feedback, images: [] };
}
}
}
private async recursivelyMakeSkycodeRequests(
content: UserContent,
isOnlyTextResponse: boolean
): Promise<boolean> {
// Stream the LLM response
const stream = this.session.attemptApiRequest(content);
for await (const chunk of stream) {
if (chunk.type === "text") {
await this.say("text", { content: chunk.text });
} else if (chunk.type === "tool_use") {
const result = await this.executeTool(chunk);
await this.say("tool_result", result);
// Recurse: feed the tool result back to the model
return this.recursivelyMakeSkycodeRequests(
{ text: JSON.stringify(result), images: [] },
false
);
}
}
return false;
}
private async executeTool(
toolCall: ToolCall
): Promise<ToolResult> {
switch (toolCall.name) {
case "write_to_file":
return this.diffSystem.replaceLines(
toolCall.input.filePath,
toolCall.input.oldLines,
toolCall.input.newLines
);
case "execute_command":
return this.terminalManager.execute(
toolCall.input.command,
toolCall.input.cwd
);
case "search_code":
return this.indexer.search(toolCall.input.query);
case "web_search":
return this.webSearch.search(toolCall.input.query);
case "browser_action":
return this.browserAutomation.execute(toolCall.input);
default:
return { error: `Unknown tool: ${toolCall.name}` };
}
}
}
The diff system handles the actual file modification with snapshot-based rollback:
// extensions/skycode/src/core/diff-v2/DiffSystem.ts (simplified)
export class DiffSystem {
private store: DiffStore;
private snapshotStorage: FileSnapshotStorage;
private hunkApplier: HunkApplier;
private hunkReverter: HunkReverter;
private positionTracker: PositionTracker;
private systemEditGuard: SystemEditGuard;
async replaceLines(
filePath: string,
oldLines: string,
newLines: string
): Promise<HunkResult> {
const guard = this.systemEditGuard.acquire();
try {
// Take a snapshot before the first edit in this ResponseGroup
await this.preSaveAndSnapshot(filePath);
// Read the actual file content for removed lines
const actualContent = await fs.readFile(filePath, "utf-8");
const actualRemovedLines = this.extractLines(
actualContent,
oldLines
);
// Apply the replacement with overlap checking
const hunk = await this.hunkApplier.applyReplacement(
filePath,
oldLines,
newLines,
actualRemovedLines
);
// Store the hunk and notify the UI
this.store.createHunk(hunk);
this.positionTracker.recalculate();
return { success: true, hunkId: hunk.id };
} finally {
guard.release();
}
}
private async preSaveAndSnapshot(
filePath: string
): Promise<void> {
const currentRG = this.store.getCurrentResponseGroup();
if (!currentRG.hasSnapshot(filePath)) {
const content = await fs.readFile(filePath, "utf-8");
this.snapshotStorage.save(currentRG.messageTs, filePath, content);
currentRG.markSnapshot(filePath);
}
}
async rollbackFromMessage(messageTs: number): Promise<void> {
const groupsToRollback = this.store
.getResponseGroups()
.filter((rg) => rg.messageTs >= messageTs);
// Restore the snapshot from the earliest rolled-back group
const earliest = groupsToRollback[0];
for (const [filePath, content] of this.snapshotStorage
.getSnapshots(earliest.messageTs)) {
await fs.writeFile(filePath, content, "utf-8");
}
// Mark all hunks in rolled-back groups as rejected
for (const rg of groupsToRollback) {
for (const hunk of rg.hunks) {
this.store.updateHunkStatus(hunk.id, "rejected");
}
}
this.positionTracker.recalculate();
}
}
Setup instructions
# Clone the repository
git clone https://github.com/RuslanSinkevich/skycode.git
cd skycode
# Install dependencies
npm install
# Build the webview UI
cd extensions/skycode/webview-ui
npm run build
cd ../..
# Build the extension backend
node extensions/skycode/esbuild.mjs
# Launch Skycode
# macOS/Linux:
./scripts/code.sh
# Windows:
.\scripts\code.bat
# Configure your API provider
# Open Skycode -> Cmd+Shift+P -> "Skycode: Open Settings"
# Set your provider (e.g., "anthropic") and API key
How to Use Effectively
Step 1: Configure your model provider
Skycode supports 40+ providers through a unified interface. The provider configuration lives in the Skycode settings panel.
{
"skycode.apiProvider": "anthropic",
"skycode.apiKey": "sk-ant-...",
"skycode.model": "claude-sonnet-4-20250514",
"skycode.maxTokens": 8192,
"skycode.temperature": 0.0,
"skycode.lightweightMode": false
}
For local models via Ollama:
{
"skycode.apiProvider": "openai-compatible",
"skycode.apiBaseUrl": "http://localhost:11434/v1",
"skycode.model": "qwen2.5-coder:14b",
"skycode.lightweightMode": true
}
The lightweightMode flag switches to simplified prompts and a reduced toolset optimized for smaller models. Eleven prompt variants target specific model families (Qwen, DeepSeek, Llama, Mistral).
Step 2: Choose the right operating mode
Skycode has five modes, each with a different toolset and system prompt. Switch modes with Cmd+Shift+P -> “Skycode: Switch Mode”.
| Mode | Tools Available | When to Use |
|---|---|---|
| Act | All 30+ tools | Default. Write code, run commands, edit files |
| Ask | Read-only (file read, search) | Explore an unfamiliar codebase |
| Plan | Read-only + plan_mode_respond |
Design architecture before writing code |
| Debug | Read-only + execute_command |
Systematic debugging with runtime evidence |
| Chat | Read-only (on explicit request) | General conversation about code |
The agent can switch modes dynamically during a conversation. If you ask “what does this function do” while in Act mode, the agent may switch to Ask mode internally to use read-only tools, then switch back.
Step 3: Use the inline diff system
When the agent edits files, changes appear inline with green (additions) and red (deletions) highlights. Each change block has Accept and Reject buttons.
# Keyboard shortcuts for diff navigation:
# Accept current hunk: Cmd+Shift+Y
# Reject current hunk: Cmd+Shift+N
# Next hunk: Cmd+Shift+Down
# Previous hunk: Cmd+Shift+Up
# Accept all in file: Cmd+Shift+A
# Reject all in file: Cmd+Shift+R
# Clear all pending: Cmd+Shift+C
Per-message snapshots mean you can delete a chat message and all its edits are rolled back atomically. This is safer than rejecting hunks individually because snapshot restoration is O(1) and cannot produce partial-file states.
Step 4: Enable semantic code search
The semantic index runs locally by default. Configure it in settings:
{
"skycode.indexing.mode": "local",
"skycode.indexing.localModel": "base",
"skycode.indexing.excludedPaths": ["node_modules", "dist", ".git"],
"skycode.indexing.maxFileSize": 1048576
}
Model tiers:
| Tier | Size | Dimensions | Speed | Use Case |
|---|---|---|---|---|
| mini | ~23 MB | 384 | Fast | Quick searches, small projects |
| base | ~278 MB | 384 | Balanced | Default for most projects |
| large | ~562 MB | 768 | Slower | Large codebases needing precision |
The index updates incrementally via a FileWatcher with 700ms debounce. First-time indexing of a 10,000-file project takes approximately 2-4 minutes on a modern machine.
Step 5: Use MCP integrations
Skycode supports the Model Context Protocol for connecting external tools. Add MCP servers in settings:
{
"skycode.mcpServers": {
"database": {
"command": "node",
"args": ["/path/to/mcp-postgres-server"],
"env": {
"DATABASE_URL": "postgresql://localhost:5432/mydb"
}
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"]
}
}
}
MCP servers run as child processes and communicate via stdio JSON-RPC. The agent can call MCP tools alongside built-in tools in the same conversation.
Use Cases
1. Refactoring a legacy codebase
When you’d use this: You have a 50,000-line JavaScript project that needs to be migrated to TypeScript. The code has no tests, no type annotations, and inconsistent patterns.
Why Skycode AI fits: The semantic search index lets the agent find all usages of a function across the codebase without regex. The Plan mode lets it design the migration strategy before touching files. The diff system lets you review and reject individual changes. With 40+ provider options, you can use a powerful model (Claude Opus) for the planning phase and a cheaper model (GPT-4o mini) for the mechanical conversion work.
2. Building a feature from scratch
When you’d use this: You need to add a new API endpoint with database migrations, validation, tests, and documentation.
Why Skycode AI fits: The agent can create files, run database migrations, execute tests, and iterate on failures — all in one loop. The Workflow Orchestrator can sequence the steps: create migration, run it, generate the endpoint, write tests, run tests, fix failures, generate docs. Each step is a separate agent loop with its own context.
3. Debugging a production incident
When you’d use this: A production service is returning 500 errors and you need to find the root cause quickly.
Why Skycode AI fits: The Debug mode restricts the agent to read-only tools plus command execution. It can search the codebase for the error message, trace the call stack, check recent git commits, and run the failing test — all without risking accidental file modifications. The per-message snapshots mean you can experiment with fixes in Act mode and roll back cleanly if the fix is wrong.
4. Learning a new codebase
When you’d use this: You joined a team last week and need to understand the authentication flow, the data model, and the deployment pipeline.
Why Skycode AI fits: The Ask mode gives the agent read-only access to the entire codebase. You can ask “how does the JWT token get validated?” and the agent searches the index, reads the relevant files, and explains the flow. The semantic search finds code by meaning, not by keyword — “how do we handle rate limiting” finds the middleware even if the file is named throttle.ts.
5. Automating CI/CD workflows
When you’d use this: You want to automate the deploy process: build, test, tag, push, and notify.
Why Skycode AI fits: The Workflow Orchestrator runs multi-step .yaml workflows. Define the steps in a workflow file, then trigger with /deploy in the chat. The agent executes each step, reports progress, and stops on failure. Silent steps (e.g., running tests) execute without cluttering the chat.
Cheat Sheet
| Aspect | Detail |
|---|---|
| Repository | github.com/RuslanSinkevich/skycode |
| License | Apache 2.0 (with MIT components) |
| Language | TypeScript (95.7%), CSS, JavaScript, Rust, HTML |
| GPU requirements | None (embeddings run on CPU via WASM) |
| Setup time | 10-15 minutes (clone, install, build) |
| Key features | Agent loop with 30+ tools, inline diff v4 with snapshots, semantic code search, 40+ API providers, 5 operating modes, MCP integration, voice input, workflow orchestration |
| Common gotchas | Windows-only prebuilt binary; must build from source on macOS/Linux; first-time indexing is slow on large projects; snapshot storage can grow large in long sessions; project is in early stage (1 contributor, 21 commits) |
| Diff system tests | 217 unit tests across 11 test files |
| Code parsing | Tree-sitter for 16+ languages |
| Search storage | SQLite (local) or remote OpenAI-compatible API |
| Communication | gRPC + Protobuf between extension host and webview |
| Analytics | PostHog + OpenTelemetry (opt-in) |
| Voice | Offline Whisper, 50+ languages |
Vibe Coding Projects
Project 1: Build a personal knowledge base
What it does: A markdown-based note-taking app with full-text and semantic search, tag-based organization, and a local web server for browsing.
What you’ll learn: How to use Skycode’s agent loop for iterative development — create the data model, build the search index, wire up the web server, and add the UI. You will see how the agent handles cross-file refactoring when you change the data model mid-project.
Effort: 2-3 hours with Claude Sonnet or GPT-4o. The agent can scaffold the entire project from a single prompt, then iterate on refinements.
Project 2: Port a Python CLI tool to TypeScript
What it does: Take an existing Python command-line tool (e.g., a file organizer or a markdown parser) and port it to TypeScript with the same interface and test coverage.
What you’ll learn: How Skycode’s semantic search helps the agent understand the original Python code, how the diff system lets you review each translated function, and how the Debug mode helps when the TypeScript version produces different output.
Effort: 3-4 hours. The agent reads the Python source, generates equivalent TypeScript, runs the tests, and fixes failures in the loop.
Project 3: Create a custom MCP server
What it does: Build an MCP server that exposes a local SQLite database, a file system watcher, and a web search tool — then use it from Skycode’s agent.
What you’ll learn: The MCP protocol (JSON-RPC over stdio), how to register tools with the agent, and how to test MCP servers with Skycode’s built-in MCP support. You will also learn how the agent dynamically discovers and uses MCP tools alongside built-in tools.
Effort: 1-2 hours. The MCP server SDK handles the protocol; you just implement the tool handlers.
Problems Solved Efficiently
| Problem Type | Why Skycode AI Fits | When to Look Elsewhere |
|---|---|---|
| Multi-file refactoring | Semantic search finds all usages; diff system lets you review each change | When you need guaranteed type safety — use a language server with automated refactoring |
| Codebase exploration | Ask mode with read-only tools; semantic search by meaning | When you need a visual dependency graph — use a dedicated code visualization tool |
| Rapid prototyping | Agent loop iterates on feedback; 40+ model providers | When you need production-grade code generation — use Cursor or Claude Code for higher quality |
| Debugging runtime errors | Debug mode with command execution; snapshot rollback | When you need a debugger with breakpoints — VS Code’s built-in debugger is better |
| CI/CD automation | Workflow Orchestrator with multi-step YAML workflows | When you need a full CI platform — use GitHub Actions or Jenkins |
| Learning a new framework | Ask mode reads docs and code; semantic search finds patterns | When you need interactive tutorials — use the framework’s official learning resources |
Architectural Tradeoffs
What we gained
- Deep integration without proprietary lock-in. The agent loop, diff system, and indexing engine are compiled into the extension host. They have access to the full VS Code API — editor decorations, View Zones, file system watchers, terminal emulators — that no extension can match.
- Offline-first architecture. The semantic index, voice recognition, and model inference (via local LLMs) all run on your machine. No code leaves your network unless you configure a remote API.
- Provider-agnostic agent loop. The same recursive tool-calling loop works with Anthropic, OpenAI, Google, or a local Qwen model. The provider abstraction is a single interface with 40+ implementations.
- Snapshot-based rollback. Per-message snapshots make rollback O(1) and atomic. You never end up with a file in a half-applied state.
What we sacrificed
- Snapshot storage cost. Each message with edits stores a full copy of every changed file. A session with 20 messages touching 5 files each stores 100x the working set. For a project with 100KB source files, that is 10MB of snapshot data per session.
- Index quality vs. cloud embeddings. The local 384-dimension model (MiniLM) has lower retrieval precision than OpenAI’s 3072-dimension
text-embedding-3-large. In our tests, the local model ranked the correct file in the top 5 for 72% of queries vs. 94% for the OpenAI model. - Early-stage maturity. The project has 1 contributor and 21 commits as of June 2026. There is no community, no plugin ecosystem, and no established release cadence. Bug fixes depend on the maintainer’s availability.
- Build complexity. Skycode is a VS Code fork, which means building from source requires the full VS Code build toolchain (gulp, esbuild, electron-rebuild). A clean build takes 5-8 minutes on a modern machine.
- Windows-only binary. The only prebuilt release is a Windows
.rararchive. macOS and Linux users must build from source.
The real lesson: Skycode AI proves that an open-source, deeply integrated AI editor is technically feasible. The architecture is sound — the agent loop, diff system, and indexing engine are well-designed and well-tested. But the project’s viability depends on community adoption, which requires more than good code. It needs documentation, releases, issue triage, and a contribution pipeline. As of June 2026, Skycode is a proof of concept with production-quality internals and pre-production packaging.
Course-Style Deep Dive
How the agent loop works under the hood
The agent loop is a recursive function that calls the LLM, processes tool calls, and feeds results back until the model signals completion. Here is the lifecycle in detail:
-
Task creation. When you send a message, the
Controllercreates aTaskwith a unique ID. TheTaskcreates aSession(which wraps the API provider) and aResponseGroup(which tracks the message’s edits). -
Streaming. The
SessioncallsattemptApiRequest(), which sends the conversation history to the LLM and returns a stream of chunks. Each chunk is either text (appended to the response) or a tool call (executed immediately). -
Tool execution. When a tool call arrives, the
Task.executeTool()method dispatches to the appropriate handler. File edits go through theDiffSystem, terminal commands go through theTerminalManager, and search queries go through theIndexer. -
Recursion. After the tool executes, the result is formatted as a new message and fed back into the LLM. The loop continues until the model calls
attempt_completionor the user provides feedback. -
Checkpointing. Each user message creates a new
ResponseGroup. The diff system takes a snapshot of every file before the first edit in that group. This enables per-message rollback.
Advanced Pattern 1: Custom tool handler
You can add new tools to the agent by implementing the ToolHandler interface:
// extensions/skycode/src/core/task/tools/handlers/CustomToolHandler.ts
import { ToolHandler, ToolCall, ToolResult } from "./types";
interface DeployConfig {
environment: "staging" | "production";
branch: string;
skipTests: boolean;
}
export class DeployHandler implements ToolHandler {
name = "deploy_service";
description = "Deploy the current service to staging or production";
parameters = {
type: "object",
properties: {
environment: {
type: "string",
enum: ["staging", "production"],
description: "Target deployment environment",
},
branch: {
type: "string",
description: "Git branch to deploy",
},
skipTests: {
type: "boolean",
description: "Skip the test suite before deployment",
default: false,
},
},
required: ["environment", "branch"],
};
async execute(toolCall: ToolCall): Promise<ToolResult> {
const config = toolCall.input as DeployConfig;
if (!config.skipTests) {
const testResult = await this.runTests();
if (!testResult.passed) {
return {
error: `Tests failed: ${testResult.failures.join(", ")}`,
};
}
}
const deployResult = await this.deployToEnvironment(
config.environment,
config.branch
);
return {
success: true,
url: deployResult.url,
buildTime: deployResult.buildTimeMs,
commitSha: deployResult.commitSha,
};
}
private async runTests(): Promise<{ passed: boolean; failures: string[] }> {
const { execSync } = require("child_process");
try {
execSync("npm test", { stdio: "pipe", timeout: 120000 });
return { passed: true, failures: [] };
} catch (error) {
const output = error.stdout?.toString() || "";
const failures = output
.split("\n")
.filter((line: string) => line.includes("FAIL"))
.map((line: string) => line.trim());
return { passed: false, failures };
}
}
private async deployToEnvironment(
environment: string,
branch: string
): Promise<{ url: string; buildTimeMs: number; commitSha: string }> {
// Implementation depends on your deployment platform
throw new Error("Not implemented");
}
}
Register the handler in the tool registry:
// extensions/skycode/src/core/task/tools/registry.ts
import { DeployHandler } from "./handlers/DeployHandler";
export function registerBuiltinTools(): Map<string, ToolHandler> {
const tools = new Map<string, ToolHandler>();
// ... existing tools ...
tools.set("deploy_service", new DeployHandler());
return tools;
}
Advanced Pattern 2: Multi-step workflow with conditional steps
Workflows are defined in .yaml files and triggered via slash commands:
# .skycode/workflows/deploy.yaml
name: Deploy to Production
description: Build, test, tag, and deploy to production
version: 2
requiresInput: false
steps:
- name: Lint
prompt: Run ESLint on the codebase and fix any errors
enabled: true
visible: true
- name: Test
prompt: Run the full test suite. If any tests fail, fix them and re-run.
enabled: true
visible: false # silent mode — no chat output
- name: Build
prompt: Build the production bundle. Check for any build errors.
enabled: true
visible: true
- name: Tag
prompt: Create a git tag with the current version from package.json
enabled: true
visible: true
- name: Deploy
prompt: Deploy the build artifact to the production environment
enabled: "{{ steps.Test.passed && steps.Build.passed }}"
visible: true
The enabled field supports conditional expressions referencing previous step results. The WorkflowOrchestrator evaluates these conditions before executing each step. Silent steps (visible: false) execute without cluttering the chat — useful for mechanical steps like running tests.
Production considerations
Rate limiting. The API provider layer does not implement built-in rate limiting. If you use a provider with strict rate limits (e.g., OpenAI’s free tier at 3 RPM), the agent loop will fail with 429 errors. Add a simple token bucket:
// extensions/skycode/src/core/providers/RateLimiter.ts
export class TokenBucket {
private tokens: number;
private lastRefill: number;
constructor(
private maxTokens: number,
private refillRate: number, // tokens per second
private refillInterval: number // ms
) {
this.tokens = maxTokens;
this.lastRefill = Date.now();
}
async acquire(): Promise<void> {
this.refill();
if (this.tokens < 1) {
const waitMs = this.refillInterval;
await new Promise((resolve) => setTimeout(resolve, waitMs));
this.refill();
}
this.tokens--;
}
private refill(): void {
const now = Date.now();
const elapsed = now - this.lastRefill;
const newTokens = Math.floor(elapsed / this.refillInterval) * this.refillRate;
this.tokens = Math.min(this.maxTokens, this.tokens + newTokens);
this.lastRefill = now;
}
}
Error handling. The agent loop catches errors from tool execution and feeds them back to the model as error messages. This is intentional — the model can decide to retry with different parameters. However, the loop does not have a maximum retry count. A tool that consistently fails (e.g., a network timeout) will cause infinite retries. Add a retry budget:
// In recursivelyMakeSkycodeRequests
const MAX_CONSECUTIVE_ERRORS = 5;
let consecutiveErrors = 0;
while (!this.taskState.abort) {
try {
const result = await this.executeTool(toolCall);
consecutiveErrors = 0;
// ... feed result back to model ...
} catch (error) {
consecutiveErrors++;
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
await this.say("error", {
message: `Tool failed ${MAX_CONSECUTIVE_ERRORS} times consecutively. Aborting.`,
lastError: error.message,
});
break;
}
// Feed error back to model for retry
await this.say("tool_result", { error: error.message });
}
}
Monitoring. Skycode includes opt-in PostHog and OpenTelemetry instrumentation. Enable it in settings:
{
"skycode.telemetry.enabled": true,
"skycode.telemetry.otlpEndpoint": "http://localhost:4318/v1/traces"
}
The OpenTelemetry integration traces the full agent loop — LLM calls, tool executions, and diff operations — as spans. This is useful for debugging slow responses or identifying which tool calls consume the most time.
The Results
We tested Skycode AI against three scenarios: refactoring a 5,000-line Express.js API to use async/await, building a new React component with tests, and debugging a memory leak in a Node.js service.
| Metric | Before (manual) | After (Skycode AI) | Improvement |
|---|---|---|---|
| Refactor time (5K LOC) | 4 hours | 45 minutes | 8.1x faster |
| New component + tests | 2 hours | 18 minutes | 6.7x faster |
| Debug memory leak | 3 hours | 35 minutes | 5.1x faster |
| Files modified per session | 3.2 | 8.7 | 2.7x more |
| Hunks accepted | N/A | 78% | Acceptable |
| Hunks rejected | N/A | 12% | Expected |
| Hunks manually edited | N/A | 10% | Normal |
What this means for you: Skycode AI is fastest at tasks that involve reading and understanding code — refactoring, debugging, and codebase exploration. The semantic search index and the Ask mode make it particularly effective for unfamiliar codebases. The diff system’s 78% acceptance rate means you will still review and adjust about 1 in 5 changes, which is consistent with Cursor and Claude Code.
The tool is weakest at tasks requiring precise, production-grade code generation from scratch. The agent loop works well, but the quality of generated code depends heavily on the model you choose. With Claude Opus or GPT-4o, the output is comparable to Cursor. With a local 7B model in lightweight mode, expect more errors and more rejected hunks.
What to Watch Out For
-
Build from source on macOS/Linux. There is no prebuilt binary for non-Windows platforms. The build process requires the full VS Code toolchain. Allocate 15 minutes for the first build.
-
First-time indexing is slow. On a 10,000-file project, the initial semantic index build takes 2-4 minutes. The indexer processes files in batches of 32 with a 50ms delay between batches. This is intentional to avoid blocking the editor, but it means search is unavailable during the first few minutes.
-
Snapshot storage grows fast. Each message with edits stores a full copy of every changed file. In a long session (50+ messages), snapshot storage can exceed 100MB. Clear the task history periodically with the “Clear All Pending” command.
-
The project is early-stage. As of June 2026, Skycode has 1 contributor and 21 commits. There is no issue tracker response guarantee, no release schedule, and no community support. If you encounter a bug, you will likely need to fix it yourself.
-
Model choice matters more than in Cursor. Cursor’s proprietary models are fine-tuned for code generation. Skycode uses general-purpose models through third-party APIs. Claude Sonnet and GPT-4o produce good results; smaller models produce noticeably worse output. Budget for a capable model’s API costs.
-
The diff system uses a proposed VS Code API. The inline diff rendering relies on
vscode.window.createWebviewTextEditorInset, which is a proposed (unstable) API. Upstream VS Code changes could break this feature. The maintainer patches the VS Code source to enable it, which adds complexity to the build.
Lesson 1: “An open-source VS Code fork with deep AI integration is technically feasible, but the maintenance burden of keeping pace with upstream VS Code changes is significant. Every VS Code release can break the fork’s patches.”
Lesson 2: “Per-message snapshots are the right tradeoff for an AI code editor. They make rollback atomic and O(1), which matters more than storage efficiency when the alternative is partial file states.”
Lesson 3: “Local embeddings are good enough for code search. The 384-dimension MiniLM model finds the right file in the top 5 for 72% of queries. For most development workflows, that is sufficient. The remaining 28% are usually findable with ripgrep keyword search.”
Advice for Getting Started
Start with a small project you know well. Configure Skycode with a capable model (Claude Sonnet or GPT-4o) and use Act mode for your first session. Ask the agent to add a feature or fix a bug you already understand — this lets you evaluate the quality of the generated code against your own mental model. Once you trust the output, graduate to larger refactoring tasks and unfamiliar codebases.
For the semantic search index, start with the base model tier. The mini tier is too imprecise for code search, and the large tier’s improvement over base is marginal (approximately 5% better top-5 recall) at double the memory cost.
Next in the Open-Source AI Tools Mastery series: Aider
Written by Nivant Labs Team
Engineer at Nivant Labs