#whatispossible

# KISS Sorcar: A Radically Simple AI Coding IDE

![KISS Sorcar Screenshot](../../../../assets/KISSSorcar.png)

## Overview

KISS Sorcar is an open-source, browser-based AI coding IDE built on a single, elegant premise: an AI coding agent should be a tight loop between a human, an LLM, and a handful of tools -- nothing more. While commercial tools like Cursor and Claude Code have grown into multi-layered architectures with subagents, virtual file systems, and elaborate coordination protocols, Sorcar achieves comparable capability through a transparent, self-contained design that a single developer can read and understand in an afternoon.

The entire IDE ships as a Python package. Run `sorcar` on any directory, and a browser tab opens with a VS Code editor on the left and a chat panel on the right. Behind the scenes, the system is powered by [`RelentlessAgent`](../../core/RELENTLESS_AGENT.md) -- a ~300-line class that implements the only orchestration pattern you actually need for long-running agentic tasks.

______________________________________________________________________

## Architecture at a Glance

Sorcar's architecture can be described in one sentence: **a Starlette web server streams SSE events from a `RelentlessAgent` that runs in a background thread, using `KISSAgent` for each sub-session of tool-calling against any LLM**. Claude Opus 4.6 is highly recommended.

```
Browser UI (HTML/JS)
     |
     | SSE / REST
     v
sorcar.py  (Starlette server, ~1500 lines)
     |
     | agent_factory(name) -> RelentlessAgent
     v
RelentlessAgent.perform_task()   (~300 lines total)
     |
     | for session in range(max_sub_sessions):
     |     KISSAgent(tools).run()
     v
KISSAgent._run_agentic_loop()   (~465 lines total)
     |
     | model.generate_and_process_with_tools()
     v
Any LLM  (Claude, GPT, Gemini, OpenRouter, Together AI)
```

There are no subagent hierarchies. No shadow file systems. No planner/worker/judge roles. No lock files. No MCP servers. The class hierarchy is `SorcarAgent` → `RelentlessAgent` → `Base`, where `RelentlessAgent` creates fresh `KISSAgent` instances for each sub-session.

______________________________________________________________________

## The IDE: A Unified Browser Experience

The Sorcar IDE is a single HTML page served by a Starlette application. The left panel embeds code-server (VS Code in the browser); the right panel is the chat interface. A draggable divider lets the user control the split. The two panels are integrated through:

- **Merge view.** After the agent completes a task, Sorcar computes a diff of all changes, opens a merge view in the editor, and presents accept/reject controls in a floating toolbar. The user reviews changes file-by-file before they land.
- **Keyboard shortcuts.** `Cmd/Ctrl-K` toggles focus between editor and chat. `Cmd/Ctrl-L` runs selected editor text as an agent task.

This is a meaningfully different experience from both Cursor and Claude Code:

- **Cursor** tightly couples the AI agent to a proprietary VS Code fork. The agent runs server-side, and the editor is the only interface. There is no standalone chatbot; everything happens through inline suggestions, the command palette, or the sidebar chat.
- **Claude Code** is a CLI tool. There is no integrated editor. The user switches between their terminal and their editor, with the agent operating on files directly.
- **Sorcar** gives you both in one browser tab, with no IDE installation required. It works on any machine with a browser, including remote servers accessed via SSH tunneling.

______________________________________________________________________

## What Sorcar Gets Right

**Simplicity as a feature, not a limitation.** The entire agent stack (`KISSAgent` + `RelentlessAgent` + `SorcarAgent`) is around 1,000 lines of Python. The server is another ~1,500 lines. The UI is a single HTML page with embedded CSS and JavaScript. A developer who wants to understand how their AI coding assistant works can read the entire system in a few hours. This is not true of Cursor (proprietary, closed-source, multi-service architecture) or Claude Code (open-source but complex, with Bun compilation, React Ink rendering, and elaborate permission systems).

**Transparent execution.** Every step of the agent's reasoning is streamed to the UI in real time: thinking blocks, tool calls with full arguments, tool results, usage stats (tokens, cost, session count). There is no hidden planning phase. The user is always in the loop.

**Structured continuation over lossy compaction.** The `RelentlessAgent` continuation mechanism is the single most important architectural advantage. It lets Sorcar handle tasks that take millions of tool calls across multiple context windows without the "context rot" that plagues single-session approaches. The summary checkpoint between sessions is an auditable artifact, not a black-box heuristic.

**Self-improvement loop.** The system prompt instructs the agent to maintain a `LESSONS.md` file with learned behaviors and mistakes, and to review it at the start of each task. Combined with a `task_history.jsonl` file that logs completed work, this gives Sorcar a form of persistent memory across sessions that is fully transparent and user-editable.

**No vendor lock-in.** Sorcar works with any LLM that supports function calling. It does not depend on Anthropic's context caching, OpenAI's assistants API, or any provider-specific feature. The continuation mechanism is pure prompt engineering.

**Docker isolation.** For untrusted tasks, Sorcar can run tools inside a Docker container by passing a `docker_image` parameter. The `DockerManager` provides a sandboxed `Bash` tool that executes commands inside the container while the agent runs outside it. This is a simpler security model than Claude Code's shell trust boundaries and permission callbacks.

______________________________________________________________________

## Model Freedom

I always use Claude Opus 4.6. Sorcar supports every major LLM provider through a unified model abstraction. The model picker in the chat UI lists all available models grouped by vendor (Anthropic, OpenAI, Gemini, MiniMax, OpenRouter, Together AI), sorted by price, with usage frequency tracking. The user can switch models between tasks with a single click.

This is architecturally simpler than Cursor's model management, which involves routing different subagent types to different models, or Claude Code, which is tightly coupled to Anthropic's Claude models. In Sorcar, the model is a parameter to `KISSAgent.run()`. Switching from Claude Opus 4.6 to GPT-5.3 to Gemini 3.1 Pro changes one string. The continuation mechanism, tool calling, and UI all work identically.

______________________________________________________________________

## The RelentlessAgent: Elegance Through Continuation

The core innovation of Sorcar lives in [`relentless_agent.py`](../../core/relentless_agent.py). The problem it solves is fundamental: LLMs have finite context windows, and real-world coding tasks routinely exceed them. Cursor addresses this with compaction heuristics and manual `/compact` commands. Claude Code uses server-side context editing and thinking-block clearing. Both approaches are heuristic and lossy -- the agent forgets work it has already done, leading to repeated effort and subtle regressions.

`RelentlessAgent` takes a different approach. It uses **explicit, structured continuation**:

1. A `KISSAgent` sub-session runs with a bounded step count and budget.
1. At step `N-2` (two steps before the limit), the agent is instructed to call `finish(success=False, is_continue=True, summary="...")` with a precise, chronologically-ordered summary of every step it has executed with explanation and relevant code snippets.
1. The next sub-session receives that summary as its `previous_progress` context, along with an explicit instruction: *"Complete the rest of the task. DON'T redo completed work."*
1. This loop continues for up to `max_sub_sessions` iterations (default: 10,000).

The key insight is that the continuation summary is *generated by the agent itself*, not by a separate summarizer or heuristic compactor. The agent knows what matters about its own work. If a sub-session crashes or exceeds its budget, a fallback `Summarizer` agent reads the trajectory and produces the summary instead.

This design has several concrete advantages over compaction-based approaches:

- **No information loss at boundaries.** The summary is an explicit artifact; you can inspect it, log it, and verify what the agent thinks it accomplished.
- **Clean context per session.** Each sub-session starts with a fresh context window containing only the task description and the progress summary. There is no accumulated token bloat from prior tool calls and their results.
- **Predictable cost.** Budget is tracked per sub-session and globally. The agent cannot run away -- it hits a hard wall at `max_budget` and `max_steps`, then either continues with a summary or fails gracefully.
- **Model-agnostic.** Because continuation is prompt-based, it works identically across Claude, GPT, Gemini, and any other model. There is no dependence on provider-specific context management APIs.

The entire mechanism fits in ~60 lines inside `perform_task()`:

```python
for session in range(self.max_sub_sessions):
    executor = KISSAgent(f"{self.name} Session-{session}")
    result = executor.run(
        prompt_template=TASK_PROMPT,
        arguments={
            "task_description": self.task_description,
            "previous_progress": progress_section,
            "step_threshold": str(self.max_steps - 2),
            ...
        },
        tools=all_tools,
        max_steps=self.max_steps,
        max_budget=self.max_budget,
    )
    payload = yaml.safe_load(result)
    if not payload.get("is_continue") or payload.get("success"):
        return result
    progress_section = CONTINUATION_PROMPT.format(
        progress_text=payload.get("summary", "")
    )
```

Compare this to Cursor's approach of managing context through Shadow Virtual File Systems, democratic vs. hierarchical agent coordination, and lock-based contention resolution -- all of which Cursor's own scaling experiments revealed to be fragile. Or compare it to Claude Code's `/compact` workflow, where background task polling can consume the entire context window before compaction even runs. Sorcar sidesteps these problems entirely by treating each sub-session as a stateless function call with an explicit input (the summary) and an explicit output (the result or the next summary).

______________________________________________________________________

## The KISSAgent: A Transparent ReAct Loop

Beneath `RelentlessAgent` sits `KISSAgent`, the workhorse that actually calls LLMs and executes tools. Its design is deliberately minimal:

1. **Setup:** Register tools as plain Python functions. The agent introspects their signatures and docstrings to build the function-calling schema automatically.
1. **Loop:** Call `model.generate_and_process_with_tools()`. If the model returns function calls, execute them and feed results back. If the model returns a `finish()` call, return the result. If the model returns no function calls, prompt it to try again.
1. **Limits:** Check budget and step count after every iteration. Raise `KISSError` if exceeded.

There is no planning phase, no tool-selection heuristic, and no routing layer. The LLM decides which tools to call and in what order. This is not a limitation -- it is the design. Modern frontier models (Claude Opus 4.6, GPT-5.4, Gemini 3.1 Pro) are excellent at tool selection when given clear tool definitions and a well-structured prompt. Adding an orchestration layer on top introduces latency, complexity, and a new source of errors without meaningfully improving tool selection.

Cursor's architecture, by contrast, routes tasks through a planner that selects subagent types (generalPurpose, explore, shell, browser-use), each with its own context window and model selection. Claude Code's agent loop has a similar structure but adds shell trust boundaries and permission callbacks. Both are valid for their respective product requirements (multi-user IDEs, enterprise security), but they come at the cost of opacity. When something goes wrong in a Cursor subagent, the failure is several layers of abstraction away from the user.

In Sorcar, every tool call, every LLM response, and every token count is streamed directly to the chat panel. The user sees exactly what the agent sees. There is no hidden state.

______________________________________________________________________

## The Tool Surface: Five Tools and a set of Browser tools

SorcarAgent provides six core tools:

| Tool | Purpose |
|------|---------|
| `Bash` | Execute shell commands with timeout, output truncation, and disallowed-command filtering |
| `Read` | Read file contents with optional line ranges |
| `Edit` | Apply targeted edits to existing files |
| `Write` | Create new files |
| `ask_user_question` | Ask the user a question and wait for their typed response |
| `WebUseTool` | Playwright-based browser automation (go_to_url, click, type, screenshot, etc.) |

That's it. There is no separate "search" tool, no "grep" tool, no "find" tool, no "list directory" tool. The agent uses `Bash` with `grep`, `find`, `rg`, `ls`, or any other Unix command it needs. This is not laziness -- it is a deliberate design choice that keeps the tool surface minimal while giving the agent access to the full power of the operating system.

Cursor provides 10+ specialized tools (semantic search, file navigation, web search, image generation, linter integration, etc.). Claude Code provides a similarly rich but different tool set. In both cases, the tool surface is large enough that tool selection itself becomes a source of errors -- the agent might use semantic search when grep would be faster, or use the built-in file reader when `cat` would give it more control.

Sorcar avoids this by trusting the LLM to compose the right Unix commands from a single `Bash` tool. This works remarkably well in practice because modern LLMs have deep knowledge of command-line tooling.

______________________________________________________________________

## Summary

KISS Sorcar demonstrates that you do not need a complex multi-agent architecture to build a capable AI coding IDE. A single agent with a clean continuation mechanism, a minimal tool surface, and a transparent execution model can accomplish tasks that require dozens of tool calls across multiple context windows -- reliably, predictably, and at a cost the user can see and control.

The elegance of the system is not in what it adds, but in what it leaves out. There are no subagent hierarchies, no shadow file systems, no lock contention, no planner/worker/judge roles, and no context compaction heuristics. There is just a loop: prompt the LLM, execute the tools, check the limits, and if the work isn't done, summarize and continue.

That's it. That's the whole thing.

______________________________________________________________________

## Design: Human-in-the-Loop Browser Actions

### Problem

The agent sometimes needs the human to interact with the browser directly — CAPTCHAs, 2FA/MFA, OAuth flows, cookie consent, or any complex interaction the agent cannot automate. Currently, the agent either fails or tries to work around these situations.

### Solution: `ask_user_browser_action` Tool

A new tool that lets the agent hand control of the browser to the human, wait for them to finish, then resume with the updated page state.

### Architecture

The design uses a **callback-based approach** that keeps `WebUseTool` decoupled from the server:

```
Agent calls ask_user_browser_action("Please solve the CAPTCHA", url="...")
  → WebUseTool ensures browser is open (non-headless)
  → Optionally navigates to the given URL
  → Calls wait_for_user_callback(instruction, current_url)
    → Server stores a new threading.Event
    → Server broadcasts SSE: {"type": "user_browser_action", "instruction": "..."}
    → Chat UI shows a prominent card with instruction + "I'm Done" button
    → Callback blocks on event.wait(timeout=300)
    → User interacts with the browser directly
    → User clicks "I'm Done" in chat UI
    → UI POSTs to /user-browser-done
    → Server sets the threading.Event
    → Callback unblocks, returns
  → WebUseTool captures page accessibility tree
  → Returns updated page state to agent
```

### Components (4 touch points)

| Component | File | Change |
|-----------|------|--------|
| `ask_user_browser_action(instruction, url?)` | `web_use_tool.py` | New tool method; calls `_wait_for_user_callback` then returns `_get_ax_tree()` |
| `_wait_for_user_callback` | `web_use_tool.py` | Constructor parameter: `Callable[[str, str], None]` — blocks until user is done |
| `/user-browser-done` REST endpoint | `sorcar.py` | Sets a `threading.Event` stored in server state |
| `user_browser_action` SSE event handler | `chatbot_ui.py` | Renders a card with instruction text + "I'm Done" button |

### Key Design Decision: Why Callback-Based?

- **No coupling:** `WebUseTool` doesn't import or know about the server, printer, or SSE. It just calls a callback.
- **Testable:** In tests, provide a callback that immediately returns.
- **Works in non-sorcar contexts:** The `SorcarAgent` CLI mode (without the server UI) could use a different callback that prints to console and waits for `input("Press Enter when done...")`.

### Implementation Details

**`WebUseTool` changes:**

```python
class WebUseTool:
    def __init__(self, ..., wait_for_user_callback=None):
        self._wait_for_user_callback = wait_for_user_callback
        # ... existing init ...

    def ask_user_browser_action(self, instruction: str, url: str = "") -> str:
        """Launch browser for user interaction, wait for completion, return page state.

        Args:
            instruction: What the user should do (e.g. "Please solve the CAPTCHA").
            url: Optional URL to navigate to before handing control to the user.

        Returns:
            Updated accessibility tree after the user signals they are done.
        """
        self._ensure_browser()
        if url:
            self._page.goto(url, wait_until="domcontentloaded", timeout=30000)
            self._wait_for_stable()
        if self._wait_for_user_callback:
            self._wait_for_user_callback(instruction, self._page.url)
        return self._get_ax_tree()

    def get_tools(self):
        return [
            # ... existing tools ...
            self.ask_user_browser_action,
        ]
```

**Server-side callback (in `sorcar.py`):**

```python
user_action_event: threading.Event | None = None

def _wait_for_user_browser(instruction: str, url: str) -> None:
    nonlocal user_action_event
    event = threading.Event()
    user_action_event = event
    printer.broadcast({
        "type": "user_browser_action",
        "instruction": instruction,
        "url": url,
    })
    # Wait for user to click "Done" — also check stop event
    while not event.wait(timeout=0.5):
        stop_ev = current_stop_event
        if stop_ev and stop_ev.is_set():
            user_action_event = None
            raise KeyboardInterrupt("Agent stopped while waiting for user")
    user_action_event = None
```

**REST endpoint:**

```python
async def user_browser_done(request: Request) -> JSONResponse:
    if user_action_event is not None:
        user_action_event.set()
        return JSONResponse({"status": "ok"})
    return JSONResponse({"error": "No pending action"}, status_code=404)

# Add to routes:
Route("/user-browser-done", user_browser_done, methods=["POST"]),
```

**Chat UI (JavaScript event handler):**

```javascript
case 'user_browser_action': {
    var card = mkEl('div', 'ev user-action-card');
    card.innerHTML =
        '<div style="border:2px solid var(--yellow);border-radius:8px;'
        + 'padding:16px;margin:10px 0;background:rgba(210,153,34,.08)">'
        + '<div style="font-weight:600;color:var(--yellow);margin-bottom:8px">'
        + '⏸️ User Action Required</div>'
        + '<div style="margin-bottom:12px">' + esc(ev.instruction) + '</div>'
        + '<button onclick="fetch(\'/user-browser-done\',{method:\'POST\'})'
        + '.then(()=>this.disabled=true,this.textContent=\'Resumed\')"'
        + ' style="padding:8px 20px;background:var(--green);color:#000;'
        + 'border:none;border-radius:6px;cursor:pointer;font-weight:600">'
        + 'I\'m Done</button></div>';
    O.appendChild(card);
    break;
}
```

### Edge Cases

| Scenario | Handling |
|----------|----------|
| Agent stopped while waiting | Check `current_stop_event` in the wait loop; raise `KeyboardInterrupt` |
| Timeout (no user action for 5 min) | `event.wait(timeout=0.5)` loop with an overall timeout counter |
| Browser was headless | The tool could error, or close/relaunch in non-headless mode |
| No server UI (CLI mode) | Fallback callback: `print(instruction); input("Press Enter when done...")` |
| Multiple concurrent actions | Only one `user_action_event` at a time (enforced by single agent thread) |

### Thread Count Impact

This design adds **zero new threads**. The blocking happens in the existing agent thread via `event.wait()`. The REST endpoint runs in the existing asyncio event loop.
