·15 min read

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 Task class manages the recursive tool-calling lifecycle. It creates ResponseGroup objects 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 DiffSystem class orchestrates three subsystems: DiffStore (unified storage for response groups, file changes, and hunks), FileSnapshotStorage (per-message snapshots), and HunkApplier/HunkReverter (apply and rollback individual changes). The InlineDiffRenderer creates reactive View Zones in the editor for Accept/Reject buttons.
  • Indexing — The EmbeddingWorkerManager runs transformers.js in a dedicated worker thread. Tree-sitter parses files into AST-aware chunks. SQLite stores vectors and metadata. The FileWatcher triggers 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 .yaml workflows (e.g., /deploy) are parsed and executed step-by-step. Each step runs a modified agent loop that exits on stepCompleted. 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.

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 .rar archive. 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:

  1. Task creation. When you send a message, the Controller creates a Task with a unique ID. The Task creates a Session (which wraps the API provider) and a ResponseGroup (which tracks the message’s edits).

  2. Streaming. The Session calls attemptApiRequest(), 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).

  3. Tool execution. When a tool call arrives, the Task.executeTool() method dispatches to the appropriate handler. File edits go through the DiffSystem, terminal commands go through the TerminalManager, and search queries go through the Indexer.

  4. 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_completion or the user provides feedback.

  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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

NL

Written by Nivant Labs Team

Engineer at Nivant Labs

Share this post