Product

From task to merged PR,
with nothing left to chance.

Kestrel is the runtime between your agents and your repo. It decides where they run, what they remember, which model does each step and what has to pass before anyone reviews the work.

Run trace One agent run recorded as a trace on a time axis: a playhead moves along it and five steps run one after another, each with its tool calls nested under it; the test step takes most of the run and is marked slow, and once the evals finish the pull request opens. const float X0 = 158.0; const float PX = 6.6; // lit once the playhead (now, in seconds of the run) has reached column c, or the span's start a, whichever is later float L(float c, float a, float now) { return now >= max((4.0 + 7.0 * c - X0) / PX, a) ? 1.0 : 0.0; } float field(vec2 p, float t) { if (p.x < 18.0 || p.x > 702.0 || p.y < 18.0 || p.y > 442.0) return 0.0; float v = 0.0; float gr = floor(p.y / 9.4); // the cell row float c = floor((p.x - 4.0) / 7.0 + 0.5); // the bar column float hs = hash(vec2(c, gr) + 2.7); // This run's clock: before 0.6 s the last run is still on screen, so it reads the clock a cycle on and clears // from 0.3 to 0.5 s. float tt = t < 0.6 ? t + T : t; float env = 1.0 - sm(T + 0.3, T + 0.5, tt); float now = (tt - 0.6) / 0.0418; // seconds into the run float cEnd = 96.0; // The trace's frame: a faint gridline every 10 s of the axis, dotted, and a faint track along each step's row. if (c >= 22.0 && c <= 97.0) { float sx = (4.0 + 7.0 * c - X0) / PX; if (abs(sx - 10.0 * floor(sx / 10.0 + 0.5)) < 0.6 && gr >= 11.0 && gr <= 40.0 && mod(gr, 2.0) > 0.5) v = 0.17; if (gr == 13.0 && hs > 0.3) v = max(v, 0.2); if (gr == 18.0 && hs > 0.3) v = max(v, 0.2); if (gr == 24.0 && hs > 0.3) v = max(v, 0.2); if (gr == 30.0 && hs > 0.3) v = max(v, 0.2); if (gr == 38.0 && hs > 0.3) v = max(v, 0.2); } // The RUN root span: two rows of the densest bars from 0 to the playhead, and a row of notches under them, the // first column of each step marked dense. if (c >= 22.0 && c <= cEnd) { if (gr >= 4.0 && gr <= 5.0) v = max(v, env * L(c, 0.0, now) * 1.05); if (gr == 6.0) { bool mark = c == 22.0 || c == 24.0 || c == 38.0 || c == 44.0 || c == 90.0; v = max(v, env * L(c, 0.0, now) * (mark ? 1.05 : 0.3)); } } // The sandbox's CPU under the root span: a bar a column, as high as the CPU was busy then (low while the agent waits // on a model, high while tests run), drawn as the playhead reaches it. if (c >= 22.0 && c <= cEnd && gr >= 7.0 && gr <= 9.0) { float sx = (4.0 + 7.0 * c - X0) / PX; float busy = (sx < 0.3 ? 0.45 : (sx < 0.9 ? 0.5 : (sx < 1.5 ? 0.5 : (sx < 2.1 ? 0.45 : (sx < 8.3 ? 0.15 : (sx < 9.1 ? 0.5 : (sx < 10.2 ? 0.45 : (sx < 11.0 ? 0.5 : (sx < 16.9 ? 0.15 : (sx < 17.8 ? 0.45 : (sx < 21.5 ? 0.15 : (sx < 22.3 ? 0.4 : (sx < 23.2 ? 0.6 : (sx < 26.3 ? 0.65 : (sx < 40.9 ? 0.95 : (sx < 47.1 ? 0.9 : (sx < 56.9 ? 0.95 : (sx < 61.3 ? 0.85 : (sx < 68.6 ? 0.95 : (sx < 71.4 ? 0.8 : (sx < 72.9 ? 0.35 : (sx < 74.4 ? 0.3 : (sx < 75.9 ? 0.35 : (sx < 77.4 ? 0.3 : 0.35)))))))))))))))))))))))); float h = clamp(floor(busy * 3.0 * (0.72 + 0.45 * hash(vec2(c, 4.1))) + 0.5), 0.0, 3.0); float lv = 9.0 - gr; float bar = lv < h ? 1.05 : (lv < 0.5 ? 0.3 : 0.0); v = max(v, env * L(c, 0.0, now) * bar); } // The steps and their child spans. if (gr >= 12.0 && gr <= 12.0 && c >= 22.0 && c <= 23.0) v = max(v, env * L(c, 0.0, now) * 1.05); if (gr >= 13.0 && gr <= 13.0 && c >= 22.0 && c <= 23.0) v = max(v, env * L(c, 0.0, now) * (c == 22.0 || c == 23.0 ? 0.6 : 1.05)); if (gr >= 14.0 && gr <= 14.0 && c >= 22.0 && c <= 22.0) v = max(v, env * L(c, 0.0, now) * (c < 22.5 ? 0.7 : 0.42)); if (gr >= 15.0 && gr <= 15.0 && c >= 22.0 && c <= 22.0) v = max(v, env * L(c, 0.3, now) * (c < 22.5 ? 0.7 : 0.42)); if (gr >= 14.0 && gr <= 14.0 && c >= 23.0 && c <= 23.0) v = max(v, env * L(c, 0.9, now) * (c < 23.5 ? 0.7 : 0.42)); if (gr >= 15.0 && gr <= 15.0 && c >= 23.0 && c <= 23.0) v = max(v, env * L(c, 1.5, now) * (c < 23.5 ? 0.7 : 0.42)); if (gr >= 17.0 && gr <= 17.0 && c >= 24.0 && c <= 37.0) v = max(v, env * L(c, 2.1, now) * 1.05); if (gr >= 18.0 && gr <= 18.0 && c >= 24.0 && c <= 37.0) v = max(v, env * L(c, 2.1, now) * (c == 30.0 || c == 31.0 || c == 32.0 ? 0.6 : 1.05)); if (gr >= 19.0 && gr <= 19.0 && c >= 24.0 && c <= 29.0) v = max(v, env * L(c, 2.1, now) * 0.7); if (gr >= 20.0 && gr <= 20.0 && c >= 30.0 && c <= 30.0) v = max(v, env * L(c, 8.3, now) * (c < 30.5 ? 0.7 : 0.42)); if (gr >= 21.0 && gr <= 21.0 && c >= 31.0 && c <= 31.0) v = max(v, env * L(c, 9.1, now) * (c < 31.5 ? 0.7 : 0.42)); if (gr >= 20.0 && gr <= 20.0 && c >= 32.0 && c <= 32.0) v = max(v, env * L(c, 10.2, now) * (c < 32.5 ? 0.7 : 0.42)); if (gr >= 19.0 && gr <= 19.0 && c >= 32.0 && c <= 37.0) v = max(v, env * L(c, 11.0, now) * 0.7); if (gr >= 23.0 && gr <= 23.0 && c >= 38.0 && c <= 43.0) v = max(v, env * L(c, 16.9, now) * 1.05); if (gr >= 24.0 && gr <= 24.0 && c >= 38.0 && c <= 43.0) v = max(v, env * L(c, 16.9, now) * (c == 39.0 || c == 42.0 || c == 43.0 ? 0.6 : 1.05)); if (gr >= 26.0 && gr <= 26.0 && c >= 38.0 && c <= 38.0) v = max(v, env * L(c, 16.9, now) * (c < 38.5 ? 0.7 : 0.42)); if (gr >= 25.0 && gr <= 25.0 && c >= 39.0 && c <= 41.0) v = max(v, env * L(c, 17.8, now) * 0.7); if (gr >= 26.0 && gr <= 26.0 && c >= 42.0 && c <= 42.0) v = max(v, env * L(c, 21.5, now) * (c < 42.5 ? 0.7 : 0.42)); if (gr >= 26.0 && gr <= 26.0 && c >= 43.0 && c <= 43.0) v = max(v, env * L(c, 22.3, now) * (c < 43.5 ? 0.7 : 0.42)); if (gr >= 29.0 && gr <= 29.0 && c >= 44.0 && c <= 89.0) v = max(v, env * L(c, 23.2, now) * 1.05); if (gr >= 30.0 && gr <= 30.0 && c >= 44.0 && c <= 89.0) v = max(v, env * L(c, 23.2, now) * (c == 47.0 || c == 61.0 || c == 66.0 || c == 76.0 || c == 80.0 || c == 87.0 ? 0.6 : 1.05)); if (gr == 31.0 && c >= 44.0 && c <= 89.0) v = max(v, env * L(c, 23.2, now) * (c > 46.7971 && hash(vec2(c, 7.3)) > 0.42 ? 1.05 : 0.42)); if (gr >= 32.0 && gr <= 32.0 && c >= 44.0 && c <= 46.0) v = max(v, env * L(c, 23.2, now) * (c < 44.5 ? 0.7 : 0.42)); if (gr >= 33.0 && gr <= 33.0 && c >= 47.0 && c <= 60.0) v = max(v, env * L(c, 26.3, now) * 0.7); if (gr >= 34.0 && gr <= 34.0 && c >= 61.0 && c <= 65.0) v = max(v, env * L(c, 40.9, now) * 0.7); if (gr >= 32.0 && gr <= 32.0 && c >= 66.0 && c <= 75.0) v = max(v, env * L(c, 47.1, now) * 0.7); if (gr >= 33.0 && gr <= 33.0 && c >= 76.0 && c <= 79.0) v = max(v, env * L(c, 56.9, now) * 0.7); if (gr >= 34.0 && gr <= 34.0 && c >= 80.0 && c <= 86.0) v = max(v, env * L(c, 61.3, now) * 0.7); if (gr >= 32.0 && gr <= 32.0 && c >= 87.0 && c <= 88.0) v = max(v, env * L(c, 68.6, now) * 0.7); if (gr >= 37.0 && gr <= 37.0 && c >= 90.0 && c <= 96.0) v = max(v, env * L(c, 71.4, now) * 1.05); if (gr >= 38.0 && gr <= 38.0 && c >= 90.0 && c <= 96.0) v = max(v, env * L(c, 71.4, now) * (c == 91.0 || c == 92.0 || c == 94.0 || c == 95.0 ? 0.6 : 1.05)); if (gr >= 39.0 && gr <= 39.0 && c >= 90.0 && c <= 90.0) v = max(v, env * L(c, 71.4, now) * 0.7); if (gr >= 40.0 && gr <= 40.0 && c >= 91.0 && c <= 91.0) v = max(v, env * L(c, 72.9, now) * 0.7); if (gr >= 39.0 && gr <= 39.0 && c >= 92.0 && c <= 93.0) v = max(v, env * L(c, 74.4, now) * 0.7); if (gr >= 40.0 && gr <= 40.0 && c >= 94.0 && c <= 94.0) v = max(v, env * L(c, 75.9, now) * 0.7); if (gr >= 39.0 && gr <= 39.0 && c >= 95.0 && c <= 95.0) v = max(v, env * L(c, 77.4, now) * 0.7); // The run's end: the root span's last column flares as the run ends. if (c > cEnd - 1.5 && c < cEnd + 0.5 && gr >= 4.0 && gr <= 6.0) v += env * 0.6 * exp(-pow((tt - 3.95) / 0.06, 2.0)); // The playhead: a line down the trace at the column it is on, with a head on the ruler. This run's shows from // 0.4 s at 0 and moves at the run's pace; the last run's stays at the end until the trace clears. float pNew = t >= 0.4 ? sm(0.4, 0.6, t) : 0.0; float pOld = t < 0.6 ? env : 0.0; float cNew = floor((X0 + PX * clamp((t - 0.6) / 0.0418, 0.0, 78.9) - 4.0) / 7.0); float cOld = floor((X0 + PX * 78.9 - 4.0) / 7.0); float ph = max(c == cNew ? pNew : 0.0, c == cOld ? pOld : 0.0); if (ph > 0.0) { if (gr >= 3.0 && gr <= 40.0) v = max(v, 0.36 * ph); if (gr == 41.0 || gr == 42.0) v = max(v, 1.05 * ph); } float ph2 = max(abs(c - cNew) < 1.5 ? pNew : 0.0, abs(c - cOld) < 1.5 ? pOld : 0.0); if (gr == 42.0) v = max(v, 0.7 * ph2); return v; }
The life of a run

Five stops, every time

  1. 01Queue

    A task arrives from the CLI, an issue label or the API and waits its turn.

  2. 02Sandbox

    A fresh, sealed copy of the repo spins up with the network off.

  3. 03Agent

    The agent plans, reads and edits, routed step by step to the right model.

  4. 04Evals

    Your tests and our evals run on the diff. Failures go back to the agent.

  5. 05Pull request

    Only a passing diff leaves, as a pull request with its full trace attached.

Sandboxes

Walls around every task

Each task gets a throwaway machine with a copy of your repo and nothing else. The agent can install, build and break things freely, because nothing it does survives the task.

  • 0.8scold start for a sandbox
  • 0outbound calls unless you allow them
  • 1task per sandbox, never shared
Sandbox A rack of sealed cells, one task in each: the repo is copied into one cell, an agent works inside it, its request to the network stops at the wall, and only the diff it wrote leaves through the out port before the cell is wiped clean. const float T0 = 3.52; float rr(float a, float lo, float hi) { return step(lo - 0.5, a) * step(a, hi + 0.5); } float eq(float a, float b) { return step(abs(a - b), 0.5); } float X(float c) { return 4.0 + 7.0 * c; } float Y(float r) { return 3.5 + 9.4 * r; } float eout(float u) { u = clamp(u, 0.0, 1.0); return 1.0 - pow(1.0 - u, 3.0); } vec3 treeRow(float i) { if (i < 0.5) return vec3(0.0, 4.0, 1.0); if (i < 1.5) return vec3(1.0, 6.0, 0.0); if (i < 2.5) return vec3(1.0, 8.0, 0.0); if (i < 3.5) return vec3(1.0, 3.0, 1.0); if (i < 4.5) return vec3(2.0, 5.0, 0.0); if (i < 5.5) return vec3(2.0, 7.0, 0.0); if (i < 6.5) return vec3(1.0, 5.0, 0.0); if (i < 7.5) return vec3(0.0, 4.0, 1.0); if (i < 8.5) return vec3(1.0, 7.0, 0.0); if (i < 9.5) return vec3(1.0, 4.0, 0.0); if (i < 10.5) return vec3(0.0, 6.0, 0.0); if (i < 11.5) return vec3(0.0, 3.0, 0.0); return vec3(0.0); } vec2 diffRow(float i) { if (i < 0.5) return vec2(1.0, 7.0); if (i < 1.5) return vec2(1.0, 5.0); if (i < 2.5) return vec2(0.0, 4.0); if (i < 3.5) return vec2(1.0, 6.0); if (i < 4.5) return vec2(0.0, 5.0); if (i < 5.5) return vec2(1.0, 3.0); return vec2(0.0); } float entry(float C, float s, vec3 e) { return eq(C, s) * (e.z > 0.5 ? 1.05 : 0.72) + rr(C, s + 2.0, s + 1.0 + e.y) * (e.z > 0.5 ? 0.62 : 0.42); } float dline(float C, float g, vec2 d, float upto) { float txt = rr(C, g + 2.0, min(g + 1.0 + d.y, g + 1.0 + upto)); return eq(C, g) * (d.x > 0.5 ? 1.0 : 0.45) + txt * (d.x > 0.5 ? 0.72 : 0.4); } float walls(float C, float R, float c0, float c1, float r0, float r1, float pr, float oin, float oout) { float port = rr(R, pr - 1.0, pr + 1.0); float wall = (eq(C, c0) + eq(C, c1)) * rr(R, r0, r1) + (eq(R, r0) + eq(R, r1)) * rr(C, c0 + 1.0, c1 - 1.0); wall *= 1.0 - port * (eq(C, c0) * oin + eq(C, c1) * oout); float inner = (eq(C, c0 + 1.0) + eq(C, c1 - 1.0)) * rr(R, r0 + 1.0, r1 - 1.0); inner *= 1.0 - port * (eq(C, c0 + 1.0) * oin + eq(C, c1 - 1.0) * oout); float liner = (eq(R, r0 + 1.0) + eq(R, r1 - 1.0)) * rr(C, c0 + 2.0, c1 - 2.0); float collar = (rr(C, c0 - 2.0, c0 - 1.0) + rr(C, c1 + 1.0, c1 + 2.0)) * (eq(R, pr - 2.0) + eq(R, pr + 2.0)); float bolt = (eq(C, c0 - 1.0) + eq(C, c1 + 1.0)) * rr(R, r0, r1) * eq(mod(R - r0, 4.0), 2.0) * step(2.5, abs(R - pr)); bolt += (eq(R, r0 - 1.0) + eq(R, r1 + 1.0)) * rr(C, c0, c1) * eq(mod(C - c0, 5.0), 2.0); float room = rr(C, c0 + 2.0, c1 - 2.0) * rr(R, r0 + 2.0, r1 - 2.0); return 1.05 * wall + 0.62 * inner + 0.4 * liner + 0.62 * collar + 0.5 * bolt + room * (0.13 + 0.12 * step(0.9, hash(vec2(C, R) + 7.1))); } float agent(vec2 p, vec2 c, float R0, float on, float work, float ang, float ph, float fi) { float d = length(p - c); float churn = noise(p * 0.1 + vec2(3.0 * fi + 1.3 * cos(ph), 2.0 * fi + 1.3 * sin(ph))); float v = blob(p, c, R0 * 0.6, on * (1.3 + 0.2 * work + 0.4 * (churn - 0.5))); v += on * 0.45 * exp(-pow(d - R0, 2.0) / 30.0); v += on * 0.12 * step(d, R0 - 4.0); v += work * blob(p, c + R0 * vec2(cos(ang), sin(ang)), 6.0, 1.2); return v; } float sibling(vec2 p, float C, float R, float r0, float fi, float ang, float lines, float ph) { float v = walls(C, R, 30.0, 70.0, r0, r0 + 8.0, r0 + 4.0, 0.0, 0.0); float i = R - (r0 + 2.0); if (i > -0.5 && i < 4.5) v += entry(C, 34.0 + treeRow(i).x, treeRow(i)) + eq(C, 33.0) * 0.26; float k = R - (r0 + 3.0); if (k > -0.5 && k < lines - 0.5) v += dline(C, 58.0, diffRow(k), 9.0); v += agent(p, vec2(X(52.0), Y(r0 + 4.0)), 15.0, 1.0, 1.0, ang, ph, fi); return 0.5 * v; } float field(vec2 p, float t) { if (p.x < 18.0 || p.x > 702.0 || p.y < 18.0 || p.y > 442.0) return 0.0; float C = floor((p.x - 4.0) / 7.0 + 0.5); float R = floor((p.y - 3.5) / 9.4 + 0.5); float ph = TAU * t / T; float tau = mod(t + T0, T); float v = 0.0; float oin = sm(0.0, 0.08, tau) * (1.0 - sm(0.98, 1.06, tau)); float oout = sm(3.10, 3.18, tau) * (1.0 - sm(3.42, 3.50, tau)); float wx = tau < 3.55 ? -10.0 : mix(214.0, 500.0, ease((tau - 3.55) / 0.45)); float keep = 1.0 - step(p.x, wx); v += walls(C, R, 30.0, 70.0, 16.0, 33.0, 25.0, oin, oout); v += sibling(p, C, R, 4.0, 1.0, ph + 0.8, 2.0, ph); v += sibling(p, C, R, 37.0, 2.0, ph + 3.4, 0.0, ph); float hit = sm(2.19, 2.22, tau) * (1.0 - sm(2.24, 2.55, tau)); v += 0.45 * hit * eq(C, 70.0) * rr(R, 17.0, 21.0) * exp(-pow(R - 19.0, 2.0) / 3.0); v += 0.3 * (eq(R, 24.0) + eq(R, 26.0)) * (rr(C, 21.0, 27.0) + rr(C, 73.0, 75.0)); { float i = R - 19.0; if (i > -0.5 && i < 11.5) { vec3 e = treeRow(i); float dep = 0.10 + 0.1 * floor(i / 2.0); float lit = sm(dep, dep + 0.04, tau) * (1.0 - sm(dep + 0.12, dep + 0.28, tau)); v += entry(C, 9.0 + e.x, e) * (1.0 + 0.6 * lit) + eq(C, 8.0) * 0.26; } } float crossed = 0.0; for (int j = 0; j < 6; j++) { float dep = 0.10 + 0.1 * float(j); float u = (tau - dep) / 0.36; crossed += step(dep + 0.2, tau); if (u > 0.0 && u < 1.0) { vec2 A = vec2(146.0, 238.5), B = vec2(228.0, 238.5), D = vec2(300.0, Y(19.5 + 2.0 * float(j))); float l1 = length(B - A), l2 = length(D - B); float s = u * (l1 + l2); vec2 c = s < l1 ? mix(A, B, s / l1) : mix(B, D, (s - l1) / l2); v += blob(p, c, 6.5, 1.15 * sm(0.0, 0.08, u) * (1.0 - sm(0.88, 1.0, u))); } } { float i = R - 19.0; if (i > -0.5 && i < 11.5) { vec3 e = treeRow(i); float j = floor(i / 2.0); float arr = 0.46 + 0.1 * j; float rd = 1.18 + 0.1333 * j; float lit = sm(rd, rd + 0.03, tau) * (1.0 - sm(rd + 0.12, rd + 0.2, tau)); v += keep * sm(arr - 0.03, arr + 0.03, tau) * (entry(C, 34.0 + e.x, e) * (1.0 + 0.6 * lit) + eq(C, 33.0) * 0.26); } } vec2 core = vec2(X(52.0), Y(25.0)); float on = sm(0.98, 1.18, tau); float work = sm(1.18, 1.26, tau) * (1.0 - sm(2.98, 3.08, tau)); v += keep * agent(p, core, 24.0, on, work, TAU * 3.0 * (tau - 1.18) / 1.9, ph, 0.0); if (tau > 1.18 && tau < 1.98) { float j = floor((tau - 1.18) / 0.1333); v += seg(p, core, vec2(X(45.0), Y(19.5 + 2.0 * j)), 2.8, 0.42); } if (tau > 2.46 && tau < 3.06) { float i = floor((tau - 2.46) / 0.1); v += seg(p, core, vec2(X(59.0), Y(21.0 + i)), 2.8, 0.42); } { vec2 A = vec2(388.0, 226.0), W = vec2(476.0, 182.1); vec2 back = normalize(A - W); float k = 1.15 * sm(1.98, 2.03, tau) * (1.0 - sm(2.28, 2.42, tau)); if (k > 0.0) { vec2 c = tau < 2.2 ? mix(A, W, ease((tau - 1.98) / 0.22)) : W + back * 20.0 * eout((tau - 2.2) / 0.22); float sq = exp(-pow((tau - 2.2) / 0.05, 2.0)); vec2 d = (p - c) / vec2(9.0 * (1.0 - 0.45 * sq), 9.0 * (1.0 + 0.6 * sq)); v += k * exp(-dot(d, d)); } float sy = 10.0 + 60.0 * max(tau - 2.2, 0.0); v += 0.8 * hit * step(p.x, 488.0) * exp(-pow(p.x - 482.0, 2.0) / 18.0 - pow(p.y - 182.1, 2.0) / (sy * sy)); } { float g = sm(3.05, 3.2, tau); float e = ease(g); for (int k = 0; k < 6; k++) { float i = float(k); float rowR = floor(mix(21.0 + i, 25.0, e) + 0.5); float wr = clamp((tau - 2.46 - 0.1 * i) / 0.08, 0.0, 1.0); if (wr > 0.0 && eq(R, rowR) > 0.5) v += keep * (1.0 - sm(0.6, 1.0, g)) * dline(C - floor(3.0 * e + 0.5), 58.0, diffRow(i), 8.0 * wr); } float u = (tau - 3.2) / 0.22; v += blob(p, vec2(462.0, 238.5), 9.0, 1.3 * sm(3.12, 3.2, tau) * step(tau, 3.2)); if (u > 0.0 && u < 1.0) { vec2 A = vec2(462.0, 238.5), B = vec2(518.0, 238.5), D = vec2(572.0, 252.0); float l1 = length(B - A), l2 = length(D - B); float s = ease(u) * (l1 + l2); vec2 c = s < l1 ? mix(A, B, s / l1) : mix(B, D, (s - l1) / l2); v += blob(p, c, 9.0, 1.3 * (1.0 - sm(0.85, 1.0, u))); } } { float i = R - 24.0; if (i > -0.5 && i < 5.5) { float a0 = 3.38 + 0.015 * i; float hand = ease((tau - 4.02) / 0.28); float vis = sm(a0, a0 + 0.04, tau) * (1.0 - sm(4.06, 4.3, tau)); v += vis * dline(C - floor(3.0 * hand + 0.5), 79.0, diffRow(i), 9.0); } } if (tau > 3.55 && tau < 4.0) { float wv = sm(3.55, 3.6, tau) * (1.0 - sm(3.94, 4.0, tau)); v += wv * rr(R, 18.0, 31.0) * (1.05 * exp(-pow(p.x - wx, 2.0) / 28.0) + 0.22 * step(p.x, wx) * step(wx - 40.0, p.x) * step(0.6, hash(vec2(C, R) + 2.3))); } { float clr = tau < 0.12 ? 1.0 - sm(0.0, 0.12, tau) : 0.0; float hIn = tau < 0.12 ? 6.0 * clr : crossed; float nNet = tau < 0.12 ? clr : step(2.22, tau); float hOut = tau < 0.12 ? clr : step(3.29, tau); float base = 43.0; float slots = (rr(C, 79.0, 80.0) + rr(C, 85.0, 86.0) + rr(C, 91.0, 92.0)) * rr(R, 37.0, base) * 0.13; float inBar = rr(C, 79.0, 80.0) * rr(R, base - hIn + 1.0, base) * step(0.5, hIn) * (eq(R, base - floor(hIn + 0.5) + 1.0) > 0.5 ? 1.05 : 0.62); float outBar = rr(C, 91.0, 92.0) * eq(R, base) * hOut * 1.05; float netMark = rr(C, 85.0, 86.0) * eq(R, base) * nNet * 0.5; v += max(slots, max(inBar, max(outBar, netMark))); } float near = 0.0; v += blob(p, vec2(566.0, 102.0), 6.0, 1.0 + 0.12 * sin(ph + 0.0)); near = max(near, exp(-dot(p - vec2(566.0, 102.0), p - vec2(566.0, 102.0)) / 420.0)); v += blob(p, vec2(600.0, 88.0), 6.0, 1.0 + 0.12 * sin(ph + 1.3)); near = max(near, exp(-dot(p - vec2(600.0, 88.0), p - vec2(600.0, 88.0)) / 420.0)); v += blob(p, vec2(636.0, 100.0), 6.0, 1.0 + 0.12 * sin(ph + 2.6)); near = max(near, exp(-dot(p - vec2(636.0, 100.0), p - vec2(636.0, 100.0)) / 420.0)); v += blob(p, vec2(584.0, 128.0), 6.0, 1.0 + 0.12 * sin(ph + 3.9)); near = max(near, exp(-dot(p - vec2(584.0, 128.0), p - vec2(584.0, 128.0)) / 420.0)); v += blob(p, vec2(620.0, 128.0), 6.0, 1.0 + 0.12 * sin(ph + 5.2)); near = max(near, exp(-dot(p - vec2(620.0, 128.0), p - vec2(620.0, 128.0)) / 420.0)); v += blob(p, vec2(648.0, 118.0), 6.0, 1.0 + 0.12 * sin(ph + 6.5)); near = max(near, exp(-dot(p - vec2(648.0, 118.0), p - vec2(648.0, 118.0)) / 420.0)); float ln = 0.0; ln = max(ln, seg(p, vec2(566.0, 102.0), vec2(600.0, 88.0), 3.4, 0.3)); ln = max(ln, seg(p, vec2(600.0, 88.0), vec2(636.0, 100.0), 3.4, 0.3)); ln = max(ln, seg(p, vec2(566.0, 102.0), vec2(584.0, 128.0), 3.4, 0.3)); ln = max(ln, seg(p, vec2(584.0, 128.0), vec2(620.0, 128.0), 3.4, 0.3)); ln = max(ln, seg(p, vec2(600.0, 88.0), vec2(620.0, 128.0), 3.4, 0.3)); ln = max(ln, seg(p, vec2(636.0, 100.0), vec2(648.0, 118.0), 3.4, 0.3)); ln = max(ln, seg(p, vec2(620.0, 128.0), vec2(648.0, 118.0), 3.4, 0.3)); v += ln; float hs = hash(vec2(C, R) + 3.7); v += near * step(0.78, hs) * (0.24 + 0.1 * sin(ph + hs * 40.0)); float ne = length((p - vec2(607.0, 110.0)) / vec2(56.0, 36.0)); v += (1.0 - sm(0.82, 1.0, ne)) * step(0.5, hs) * 0.19 + 0.3 * exp(-pow(ne - 1.0, 2.0) / 0.006) * step(0.35, hs); { vec2 A = vec2(510.0, 176.0), B = vec2(578.0, 132.0); vec2 ab = B - A; float h = clamp(dot(p - A, ab) / dot(ab, ab), 0.0, 1.0); vec2 d = (p - A - ab * h) / vec2(3.6, 4.8); v += 0.32 * (1.0 - sm(0.8, 1.05, length(d))) * step(0.5, fract(h * length(ab) / 16.0)); } return v; }
Agent memory vec3 memory(float t) { vec2 A = vec2(96.0, 96.0), S = vec2(236.0, 147.0), M = vec2(392.0, 150.0), D = vec2(420.0, 304.0 + 7.0 * sin(420.0 * 0.017 + 2.0 * 1.9) + 3.0 * sin(420.0 * 0.05 + 2.0)); if (t < 0.2) return vec3(A, 0.0); if (t < 0.7) return vec3(mix(A, S, ease((t - 0.2) / 0.5)), 1.2 * sm(0.2, 0.3, t)); if (t < 1.7) { float u = (t - 0.7) / 1.0; return vec3(mix(S, M, u) + vec2(0.0, 7.0 * sin(u * 9.0)), 1.2); } if (t < 3.0) { float u = (t - 1.7) / 1.3; float e = 1.0 - pow(1.0 - u, 2.4); return vec3(mix(M, D, e), 1.2); } return vec3(D, 0.0); } float field(vec2 p, float t) { float ph = TAU * t / T; float v = blob(p, vec2(96.0, 96.0), 26.0, 1.05); if (p.x > 164.0 && p.x < 676.0) { // short-term: a surface that churns, blobs welling up and sinking back float n = noise(vec2(p.x * 0.03 + 0.8 * cos(ph), p.y * 0.07 + 0.8 * sin(ph))) * 0.6 + noise(vec2(p.x * 0.07 - 0.5 * sin(ph), p.y * 0.12 + 0.5 * cos(ph))) * 0.4; v += exp(-pow(p.y - 147.0, 2.0) / 500.0) * (0.12 + 1.05 * smoothstep(0.35, 0.85, n)); // long-term: wavy strata of uneven thickness, older and fainter the deeper they lie, the top two sliding faster. // Each layer's tokens repeat every 12 columns and slide a whole number of those a cycle, so the loop closes. for (int i = 0; i < 5; i++) { float fi = float(i); float y = 224.0 + 40.0 * fi + 7.0 * sin(p.x * 0.017 + fi * 1.9) + 3.0 * sin(p.x * 0.05 + fi); float th = 9.0 + 5.0 * noise(vec2(p.x * 0.02, fi * 3.0)); float slide = 84.0 * (i < 2 ? 2.0 : 1.0) * t / T; float tok = step(0.18, hash(vec2(mod(floor((p.x + slide) / 7.0), 12.0), fi))); v += tok * exp(-pow(p.y - y, 2.0) / (th * th)) * (0.85 - 0.1 * fi); } } vec3 m = memory(t); vec2 dir = normalize(vec2(0.18, 1.0)); v += blob(p, m.xy, 11.0, m.z) + blob(p, m.xy - dir * 13.0, 8.0, m.z * 0.35 * step(1.7, t) * step(t, 3.0)); // it settles: a seam spreading along its layer, cooling into it if (t > 2.9) { float u = sm(2.9, 4.1, t); vec2 d = p - memory(2.99).xy; v += (1.0 - u) * 1.1 * exp(-(d.x * d.x) / pow(10.0 + 80.0 * u, 2.0) - (d.y * d.y) / 36.0); } return v; }
Memory

Learns your repo, keeps it

Build commands, flaky tests, the module nobody touches: what one run works out is written to memory your whole team shares, and every later run starts from it.

  • 38%fewer steps on a repo's tenth run
  • Per repomemory, never mixed across repos
  • Editableby your team at any time
Evals

A reviewer that never gets tired

Before a pull request opens, the diff runs your test suite and Kestrel's evals for scope, style and risk. A failing diff goes back to the agent with the failure attached.

  • 48checks on a typical run
  • 93%of pull requests merged unchanged
  • Customevals in plain YAML
Eval gate An agent writes a diff that runs through six checks in order; it fails the evals and goes back to the agent with the failure, and the revised diff passes every check before a pull request opens. const float LANE = 200.9; const float LK = 21.0; float stepx(float u) { return u - sin(TAU * u) / TAU; } // one check to the next, at rest at each // A diff hunk of five lines: a gutter mark (added, removed, context) and a code line of its own length. float hunk(float c, float r, float ver) { float kind = 0.0; float len = 2.0; if (ver < 0.5) { if (r < 0.5) { kind = 1.0; len = 4.0; } else if (r < 1.5) { kind = -1.0; len = 3.0; } else if (r < 2.5) { kind = 0.0; len = 2.0; } else if (r < 3.5) { kind = 1.0; len = 4.0; } else { kind = 1.0; len = 2.0; } } else { if (r < 0.5) { kind = 1.0; len = 3.0; } else if (r < 1.5) { kind = 1.0; len = 4.0; } else if (r < 2.5) { kind = -1.0; len = 2.0; } else if (r < 3.5) { kind = 1.0; len = 4.0; } else { kind = 0.0; len = 3.0; } } if (c < 0.5) return kind > 0.5 ? 1.1 : kind < -0.5 ? 0.5 : 0.2; if (c > len + 0.5) return 0.0; return kind > 0.5 ? 0.68 : kind < -0.5 ? 0.4 : 0.3; } // The diff at centre q, written as far as w (0 to 1, line by line), with its failure attached under it (fl). float diff(vec2 p, vec2 q, float ver, float w, float fl, float k) { float c = floor((p.x - q.x) / 7.0 + 2.5); float r = floor((p.y - q.y) / 9.4 + 2.5); if (c < 0.0 || c > 4.0 || r < 0.0 || r > 5.0) return 0.0; if (r > 4.5) return fl * (c < 0.5 ? 1.3 : 1.05); if (r + c / 5.0 >= w * 5.0) return 0.0; return k * hunk(c, r, ver); } // The point a distance d along the return path. vec2 back(float d) { if (d <= 21.0) return mix(vec2(427.5, 200.9), vec2(406.5, 200.9), (d - 0.0) / 21.0); if (d <= 124.4) return mix(vec2(406.5, 200.9), vec2(406.5, 304.3), (d - 21.0) / 103.4); if (d <= 421.9) return mix(vec2(406.5, 304.3), vec2(109.0, 304.3), (d - 124.4) / 297.5); if (d <= 489.3) return mix(vec2(109.0, 304.3), vec2(109.0, 236.9), (d - 421.9) / 67.4); return vec2(109.0, 236.9); } // One check's run for a diff that reached it at a: its cells light out from the lane, a cell at a time, up and down // the stage; stopAt under 7 is a test that failed there, and the run stops. Then the light fades back to idle. float scan(float dc, float a, float stopAt, float t) { float s0 = a - 0.06; if (t < s0) return 0.0; float reach = min(7.0 * (t - s0) / 0.12, stopAt); if (dc > floor(reach) + 0.5) return 0.0; float done = s0 + 0.12 * stopAt / 7.0; float fade = 1.0 - sm(done, done + (stopAt < 6.5 ? 0.25 : 0.45), t); float head = (t < done && abs(dc - floor(reach)) < 0.5) ? 0.2 : 0.0; return fade * (1.0 + head); } // Held across the seam: on from on0, and still on from the last cycle until the board clears. float held(float on0, float t) { return max(sm(on0, on0 + 0.06, t), 1.0 - sm(0.25, 0.43, t)); } float field(vec2 p, float t) { if (p.x < 18.0 || p.x > 702.0) return 0.0; float ph = TAU * t / T; float col = floor((p.x - 4.0) / 7.0 + 0.5) - 2.0; float cell = floor(p.y / 9.4); float v = 0.0; // the agent: a bright rim, an inner ring, a speckled floor and a core that churns, and flares while it writes float wr = sm(0.2, 0.3, t) * (1.0 - sm(0.5, 0.7, t)) + sm(2.26, 2.36, t) * (1.0 - sm(2.58, 2.78, t)); { vec2 c = vec2(109.0, LANE); float d = length(p - c) / 30.0; float inside = 1.0 - sm(0.9, 1.0, d); float churn = noise(p * 0.09 + vec2(1.4 * cos(ph), 1.4 * sin(ph))); v += 0.46 * exp(-pow(d - 1.0, 2.0) / 0.008); v += inside * 0.26 * exp(-pow(d - 0.64, 2.0) / 0.005); v += inside * (0.12 + 0.12 * step(0.82, hash(floor(p / vec2(7.0, 9.4)) + 1.3))); v += blob(p, c, 13.2, 0.82 + 0.5 * (churn - 0.5) + 0.45 * wr); } // the forward lane, agent to pull request, and the return lane under the gate, back up into the agent if (abs(cell - LK) < 0.5 && col > 17.0 && col < 81.0) v = max(v, 0.2); if (abs(cell - 32.0) < 0.5 && col >= 13.0 && col <= 65.0) v = max(v, 0.2); if (abs(col - 13.0) < 0.5 && cell > 24.0 && cell < 32.0) v = max(v, 0.2); // the gate's floor: a speckle between the stages, so the gate reads as one housing if (col > 32.0 && col < 67.0 && abs(cell - LK) < 7.5 && step(0.62, hash(vec2(col, cell) + 2.3)) > 0.5) v = max(v, 0.22); // the gate: six stages of test cells, a header and footer cell on each, in order; each runs as the diff reaches it float rel = col - 34.0; float s = floor(rel / 6.0); float dc = abs(cell - LK); if (s >= 0.0 && s <= 5.0 && rel - 6.0 * s < 1.5 && dc < 7.5) { float hs = hash(vec2(col, cell) + 7.1); float base = dc > 6.5 ? 0.85 : 0.42 + 0.2 * step(0.55, hs) * (0.7 + 0.3 * sin(ph + hs * 40.0)); float a1 = 0.84 + s * 0.18; float a2 = 2.92 + s * 0.18; float lit = 0.0; if (s < 4.0 - 0.5) lit = scan(dc, a1, 7.0, t); else if (s < 4.0 + 0.5) lit = scan(dc, a1, 5.0, t); lit = max(lit, scan(dc, a2, 7.0, t)); v = max(v, max(base, lit * (dc > 6.5 ? 1.1 : 1.0))); // the failing test: it flares as the run reaches it, and dims once the diff is back at the agent if (s > 4.0 - 0.5 && s < 4.0 + 0.5 && abs(cell - 16.0) < 0.5) v = max(v, 1.3 * sm(1.576, 1.616, t) * (1.0 - sm(2.07, 2.42, t))); } // the record: a row of six slots per run, each filled as its check passes; run 1 stops at EVALS if (s >= 0.0 && s <= 5.0 && rel - 6.0 * s < 1.5) { if (cell > 35.5 && cell < 37.5) { float fl = s < 4.0 - 0.5 ? held(0.9 + s * 0.18, t) : 0.0; v = max(v, 0.15 + 0.85 * fl); } if (cell > 38.5 && cell < 40.5) v = max(v, 0.15 + 0.85 * held(2.98 + s * 0.18, t)); } // the pull request: a faint outline waiting; it opens once the diff that passed reaches it float open = held(4.18, t); float pc = col - 81.0; float pk = cell - 16.0; if (pc >= 0.0 && pc <= 8.0 && pk >= 0.0 && pk <= 10.0) { bool rim = pc < 0.5 || pc > 7.5 || pk < 0.5 || pk > 9.5; float card = rim ? 0.42 : 0.0; if (abs(pk - 1.0) < 0.5 && pc >= 1.0 && pc <= 6.0) card = 1.05; // the title if (abs(pk - 2.0) < 0.5 && pc >= 1.0 && pc <= 3.0) card = 0.3; // the branch if (pk > 3.5 && pk < 8.5 && pc >= 1.0 && pc <= 5.0) card = max(card, 0.95 * hunk(pc - 1.0, pk - 4.0, 1.0)); // the diff's lines if (abs(pk - 9.0) < 0.5 && pc >= 1.0 && pc <= 6.0) card = max(0.2, 0.95 * held(4.24 + 0.035 * pc, t)); // six check pips v = max(v, rim ? max(0.15, card * open) : card * open); } // diff 1: written, through the four checks it passes, stopped at EVALS, then back to the agent with its failure float fl = sm(1.586, 1.646, t); if (t > 0.25 && t < 0.5) v = max(v, diff(p, vec2(179.0, LANE), 0.0, (t - 0.25) / 0.25, 0.0, 1.0)); else if (t >= 0.5 && t < 0.84) v = max(v, diff(p, vec2(mix(179.0, 259.5, ease((t - 0.5) / 0.34)), LANE), 0.0, 1.0, 0.0, 1.0)); else if (t >= 0.84 && t < 1.66) { float x = 259.5 + 42.0 * stepx(min((t - 0.84) / 0.18, 4.0)); v = max(v, diff(p, vec2(x, LANE), 0.0, 1.0, fl, 1.0)); } else if (t >= 1.66 && t < 2.32) { float u = (t - 1.66) / 0.66; float k = 1.0 - sm(0.88, 1.0, u); v = max(v, diff(p, back(ease(u) * 489.3), 0.0, 1.0, k, k)); } // diff 2: written, through all six checks, out to the pull request if (t > 2.34 && t < 2.58) v = max(v, diff(p, vec2(179.0, LANE), 1.0, (t - 2.34) / 0.24, 0.0, 1.0)); else if (t >= 2.58 && t < 2.92) v = max(v, diff(p, vec2(mix(179.0, 259.5, ease((t - 2.58) / 0.34)), LANE), 1.0, 1.0, 0.0, 1.0)); else if (t >= 2.92 && t < 3.88) v = max(v, diff(p, vec2(259.5 + 42.0 * stepx((t - 2.92) / 0.18), LANE), 1.0, 1.0, 0.0, 1.0)); else if (t >= 3.88 && t < 4.2) { float u = (t - 3.88) / 0.32; v = max(v, diff(p, vec2(mix(469.5, 613.0, ease(u)), LANE), 1.0, 1.0, 0.0, 1.0 - sm(0.8, 1.0, u))); } return v; }
Model routing Prompts flow into a router that sends most of them to a small model, some to a medium one, and the rare hard prompt to a large one, which flares as it lands. const vec2 GATE = vec2(252.0, 230.0); const float IN0 = 24.0; // the ribbon's left end const float GX = 236.0; // where it meets the gate const float SPEED = 212.0; // route of prompt i: 0 small, 1 medium, 2 large (seven, two, one) float route(int i) { return i == 1 || i == 5 ? 1.0 : i == 8 ? 2.0 : 0.0; } float exitY(float r) { return r < 0.5 ? 150.0 : r < 1.5 ? 230.0 : 310.0; } vec2 poolC(float r) { return r < 0.5 ? vec2(390.0, 92.0) : r < 1.5 ? vec2(500.0, 200.0) : vec2(590.0, 344.0); } float poolR(float r) { return r < 0.5 ? 32.0 : r < 1.5 ? 48.0 : 68.0; } vec2 ctrl(float r) { return r < 0.5 ? vec2(336.0, 152.0) : r < 1.5 ? vec2(390.0, 228.0) : vec2(380.0, 312.0); } float travel(float r) { return r < 0.5 ? 0.42 : r < 1.5 ? 0.72 : 1.2; } // a bigger model is further and slower vec2 bez(vec2 a, vec2 c, vec2 b, float u) { return mix(mix(a, c, u), mix(c, b, u), u); } vec2 chanAt(float r, float u) { return bez(vec2(270.0, exitY(r)), ctrl(r), poolC(r), u); } // A channel from its gate exit to its pool, as far as the pool's rim. float channel(vec2 p, float r, float w, float k) { float v = 0.0; for (int i = 0; i < 8; i++) v = max(v, seg(p, chanAt(r, float(i) / 8.0 * 0.9), chanAt(r, float(i + 1) / 8.0 * 0.9), w, k)); return v; } // A pool: a disc with a bright rim, a ring per layer of the model, a core as hot as it is busy, and a flare. float pool(vec2 p, float r, float busy, float flare) { vec2 c = poolC(r); float R = poolR(r); float d = length(p - c) / R; float inside = 1.0 - sm(0.92, 1.0, d); float v = 0.46 * exp(-pow(d - 1.0, 2.0) / 0.006); float layers = r + 1.0; float q = d * (layers + 1.0); v += inside * 0.26 * exp(-pow(q - floor(q + 0.5), 2.0) / 0.03) * step(0.5, q) * step(q, layers + 0.5); v += inside * 0.13; v += blob(p, c, R * 0.55, busy); v += flare * (inside * (0.55 - 0.35 * d) + blob(p, c, R * 0.6, 0.6)); v += flare * 0.7 * exp(-pow(d - 1.05 - 0.5 * (1.0 - flare), 2.0) / 0.01); return v; } float field(vec2 p, float t) { if (p.x < 18.0 || p.x > 702.0) return 0.0; float ph = TAU * t / T; // the ribbon of prompts, a flow of tokens running right float rib = exp(-pow(p.y - 230.0, 2.0) / 70.0) * sm(IN0 - 6.0, IN0 + 40.0, p.x) * step(p.x, GX); float v = rib * (0.3 + 0.25 * pow(0.5 + 0.5 * sin(p.x * 0.0674 - ph * 10.0), 3.0)); // the gate: two rails, a faint interior, open where the ribbon comes in and at its three exits if (p.y > 110.0 && p.y < 350.0) { float openL = 1.0 - exp(-pow(p.y - 230.0, 2.0) / 60.0); float openR = 1.0 - exp(-pow(p.y - 150.0, 2.0) / 50.0) - exp(-pow(p.y - 230.0, 2.0) / 50.0) - exp(-pow(p.y - 310.0, 2.0) / 50.0); v += 0.46 * exp(-pow(p.x - 235.0, 2.0) / 8.0) * openL + 0.46 * exp(-pow(p.x - 270.0, 2.0) / 8.0) * max(openR, 0.0); v += 0.14 * step(238.0, p.x) * step(p.x, 266.0); } // the channels: wide and busy to the small pool, thin to the large one, each with its flow v += channel(p, 0.0, 8.0, 0.42 + 0.2 * pow(0.5 + 0.5 * sin(p.x * 0.09 - ph * 8.0), 2.0)); v += channel(p, 1.0, 6.5, 0.32 + 0.14 * pow(0.5 + 0.5 * sin(p.x * 0.07 - ph * 4.0), 2.0)); v += channel(p, 2.0, 5.0, 0.24); float hitS = 0.0, hitM = 0.0, flare = 0.0; for (int i = 0; i < 10; i++) { float r = route(i); float a0 = 0.22 + 0.44 * float(i); for (int w = -1; w <= 1; w++) { float a = a0 + float(w) * T; // when it reaches the gate // in the ribbon: as long and as dense as it is hard; the hard one runs three lines deep if (t > a - 1.0 && t < a) { vec2 c = vec2(GX - (a - t) * SPEED, 230.0); float len = r < 0.5 ? 9.0 : r < 1.5 ? 16.0 : 30.0; float ex = exp(-pow((p.x - c.x) / len, 4.0)); if (r > 1.5) v += ex * 1.3 * (exp(-pow(p.y - 220.6, 2.0) / 14.0) + exp(-pow(p.y - 230.0, 2.0) / 14.0) + exp(-pow(p.y - 239.4, 2.0) / 14.0)); else v += ex * (r < 0.5 ? 0.9 : 1.1) * exp(-pow(p.y - 230.0, 2.0) / 22.0); } // in the gate: it settles to its exit, the heavy one sinking float g = 0.2; if (t >= a && t < a + g) { float y = mix(230.0, exitY(r), ease((t - a) / g)); v += blob(p, vec2(GATE.x, y), r > 1.5 ? 12.0 : 8.0, r > 1.5 ? 1.35 : 1.1); } // down its channel to its pool float D = travel(r); if (t >= a + g && t < a + g + D) { float u = (t - a - g) / D; u = r > 1.5 ? mix(u, ease(u), 0.5) : u; float fade = 1.0 - sm(0.82, 1.0, u); if (r > 1.5) { v += blob(p, chanAt(r, u), 13.0, 1.4 * fade) + seg(p, chanAt(r, max(0.0, u - 0.16)), chanAt(r, u), 5.0, 0.55 * fade); } else v += blob(p, chanAt(r, u), r < 0.5 ? 7.5 : 9.0, 1.15 * fade); } float arr = a + g + D; float h = exp(-pow(t - arr, 2.0) / 0.012); if (r < 0.5) hitS += h; else if (r < 1.5) hitM += h; else flare += sm(arr - 0.04, arr + 0.06, t) * (1.0 - sm(arr + 0.25, arr + 1.5, t)); } } v += pool(p, 0.0, 0.8 + 0.35 * hitS, 0.0); v += pool(p, 1.0, 0.55 + 0.4 * hitM, 0.0); v += pool(p, 2.0, 0.28 + 0.6 * flare, flare); v += 0.6 * hitS * exp(-pow(length(p - poolC(0.0)) - 36.0, 2.0) / 20.0); return v; }
Model routing

Pay for reasoning only where it counts

Kestrel scores each step before it runs. Lookups and renames go to a small model, and only the hard steps reach a large one. You set the thresholds.

  • 72%of steps run on a small model
  • 3.1xcheaper than one large model for everything
  • Anyprovider, or your own
And the rest

Everything a run needs

Traces

Every file read, tool call and test of every run, kept for search and replay.

Rate limits

Cap how fast each repo may spend, turned away at the edge before it costs.

Queues

A thousand tasks during a release drain in order. Nothing is dropped.

Webhooks

Run started, evals failed, pull request opened: post each to your own tools.

CLI and API

Start runs from a terminal, a script, CI or an issue label.

Long context

One million tokens of working context, compacted instead of cut off.

Security

Built for the repo you can't leak

Code never leaves the sandbox it was cloned into, and the sandbox is gone when the task ends. On Enterprise, so is the question of whose cloud it ran in.

SSO and SCIM

Sign in with Okta or Google, or any SAML provider, and provision seats from your directory.

Audit log

Every run, setting change and approval, exportable to your SIEM.

Your VPC

Enterprise runs Kestrel's workers inside your own cloud account.

Works with

Fits the tools you already use

  • Repos and pull requests
  • Repos and merge requests
  • Repos and pull requests
  • Start runs from issues
  • Start runs from tickets
  • Run alerts in a channel

Give your agents
a runtime.