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. Jev sanity-checks the proposed calls before they run.

#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

In the 65–95% token-budget range, Jev looks at what actually happened recently and decides adaptively whether to compact — not just a fixed threshold.

#05 Verify
core/loop.ts

When the model says "stop", Jev checks the last output and recent tool results before agreeing — a hard programmatic gate, not a prompt asking nicely.

#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 The decision layer

A model that never generates text

Jev (from TypeSafe AI) is a "System One" model — it takes state and typed questions, and returns structured decisions with calibrated probabilities in 70–500ms, no hallucination possible. Used for the four places this loop had to make a judgment call, not write code.

83%
Fewer tokens per eval task
5×
Faster per task
11/11
Still passing
3.41/4
Avg quality score
#01 Stop/continue gate
core/loop.ts

When DeepSeek says stop, Jev checks recent tool outputs before agreeing. Replaced a soft "verify before done" prompt rule with a hard check — and stopped a 15×-repeat runaway-loop failure mode outright.

#02 Tool routing check
core/loop.ts

Before tool calls execute, Jev flags ones that look wrong for the task. Non-blocking today — logged as jev_tool_warning for visibility, not yet a hard stop.

#03 Adaptive compaction
core/context.ts

Between 65–95% of the token budget, Jev decides whether to compact from what's actually in recent messages, instead of one fixed percentage for every task.

#04 Eval judge
evals/jev-judge.ts

Scores every eval solution independently of check.sh — quality (0–4), regression risk, and one of 8 failure reasons. Caught two "passing" tasks that were actually weak solutions.

007-fix-bug, before: stop=max-iteration · 40s · 67,974 tokens (model kept repeating its final message until the iteration ceiling hit). After: stop=stop · 9s · 10,214 tokens.

#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
+ Jev decision layer12/18 · 67%score holds, cost drops

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. Jev holds that same score while cutting cost — the remaining 6 failures are large-repo navigation problems (6000+ file Django/matplotlib repos), not a stopping or verification issue anymore.

#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