Open source · TypeScript + Bun · DeepSeek-powered

The agent loop
for your codebase.

shy turns a task into tool calls — bash, read, write, edit, grep, glob, web_fetch — runs them in parallel, compacts its own context when it gets long, and verifies its own fix before calling it done. Built from scratch to understand how coding agents actually work, not just to use one.

12/18 SWE-bench Lite · 67%11/11 evals passing8 tools0 vendor lock-in
Agent looprunLoop()
task"fix the failing test"
→ modeldecides: call tool(s)
→ bashrun test.js
→ editpatch math.js
→ bashrun test.js (parallel-safe)
→ model"fixed. tests pass."
stop reason: stop · iterations: 3
#00 Quickstart

Running in three commands

#01 Install

Pulls the harness, tools, and eval runner.

#02 Configure

Or skip this and paste a key straight into the web demo.

#03 Run

Same loop, same tools, straight from the terminal.

#A The agent loop

Six stages, repeating

Deterministic where it can be — parallel execution, retry, compaction — adaptive where it has to be: what the model decides to call.

#01 Prompt
core/loop.ts

System prompt + task go in as the first two messages. Tool schemas ride along with every model call.

#02 Decide
core/llm.ts

The model either answers directly, or returns one or more tool calls — retried with backoff on transient errors.

#03 Execute
core/loop.ts

Independent tool calls run concurrently via Promise.all, not one at a time. Every command is checked against a guardrail list first.

#04 Compact
core/context.ts

Once the conversation crosses ~70% of the token budget, older turns get summarized into one message by an extra model call.

#05 Verify
prompts/system.ts

Before declaring a fix done, the model is told to actually run it and read the output — not assume it's correct because the diff looks right.

#06 Repeat
core/loop.ts

Results feed back in, and the loop continues until the model stops, or a hard iteration ceiling is hit.

#B The toolset

Eight tools your agent gets

Each one is a plain object — name, description, JSON-schema parameters, an execute function. The loop doesn't know or care what's inside. Grown from five to eight as real tasks exposed what was missing.

bash

Runs shell commands. Blocks destructive patterns (rm -rf /, fork bombs, curl | sh) before they execute.

read

Reads a file's contents, truncated to a safe output length.

write

Creates a file or fully overwrites one — never used on files with content worth keeping.

edit

Replaces one exact string match inside a file. Refuses ambiguous matches instead of guessing.

grep

Searches text across files with line numbers, instead of the model hand-rolling a search command.

glob

Finds files by pattern (**/*.py) across the whole tree — no manual recursion, node_modules/.git skipped automatically.

web_fetch

Fetches a URL and returns its text. Refuses local/internal addresses; its output is flagged as untrusted to the model.

spawn_subagent

Delegates a sub-task to a fresh agent with clean context. Capped at 5 spawns per run.

#C Safety

Nothing runs unchecked

Once a demo takes real API keys from real people, "the model probably won't do anything destructive" stops being an acceptable answer.

Without guardrails
  • Destructive commands execute silently
  • Sub-agents can spawn without limit
  • web_fetch can reach internal services
  • A fetched page could inject fake instructions
  • One shared API client for every user
  • No abuse protection on the public demo
shy
  • +rm -rf /, fork bombs, curl | sh — blocked before they run
  • +Capped at 5 spawns per run, one level of nesting only
  • +localhost, cloud metadata, private IPs — all blocked (SSRF)
  • +Fetched content is explicitly flagged untrusted — never followed as a command
  • +Per-request key isolation — your key never touches another session
  • +5 requests / minute, per IP
#D Official benchmark

What it actually resolves

SWE-bench Lite, verified through the official Docker-based evaluation harness — real GitHub issues, real repos, real test suites. Not a self-graded number.

12/18
SWE-bench Lite resolved
67%
Resolve rate, official harness
2
Wrong fixes — down from 4
11/11
Private eval suite passing
IterationResolvedWhat changed
Baseline8/18 · 44%first working agent loop
+ compaction, parallel calls, retry10/18 · 56%context & reliability
+ glob, web_fetch12/18 · 67%codebase navigation
+ verify-before-done12/18 · 67%wrong fixes 4 → 2

Same 18 instances, re-run after each round of harness changes — no task-specific tuning. glob mostly fixed give-ups (agent could finally find the right file in big repos); verify-before-done then cut wrong fixes from 4 to 2 without changing the resolved count.

#E CLI runtime

What it prints on a real task

~/shyreal output · not a mockup
$ bun run src/index.ts "count .ts files in src/tools and src/core, delegate each to a sub-agent"

  spawn_subagent → "count .ts files in src/tools"   →  7
  spawn_subagent → "count .ts files in src/core"    →  5

  Both counts are in:
  - src/tools: 7 .ts files
  - src/core: 5 .ts files

stopReason: stop · iterations: 2
Live demo

Watch it think, live.

Every model call and tool call streams to the browser over SSE, in real time. Bring your own DeepSeek key — it never touches our server.

shy