Skip to content

Commit 11cb574

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
Add Pi Babysit mode
1 parent 6bd708c commit 11cb574

24 files changed

Lines changed: 3628 additions & 84 deletions

.agents/plans/pi-babysit-mode.plan.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,31 @@ overview: Add a fourth Pi mode, Babysit, that drives an open PR's review threads
44
todos:
55
- id: shared-extraction
66
content: Move the reusable Create PR scripts/constants and Review Code's PR snapshot helpers into shared modules, harden the token-bearing push, and keep existing behavior unchanged
7-
status: pending
7+
status: completed
88
- id: github-tools
99
content: Add the four GraphQL tools (list/reply/resolve review threads plus the status-check rollup) and the REST job-log reader under apps/sim/tools/github/, extend the PR parser with the head repo, register them, and unit-test them
10-
status: pending
10+
status: completed
1111
- id: sandbox-lifetime
12-
content: Add a clamped sandbox lifetime option, map it to E2B timeoutMs and Daytona autoStopInterval, and verify a Pi run past five minutes survives
13-
status: pending
12+
content: Add a clamped sandbox lifetime option for E2B, keep Daytona's inactivity timeout unchanged, and verify a Pi run past five minutes survives
13+
status: in_progress
1414
- id: block-and-handler
1515
content: Add the babysit mode, fields, conditions, and outputs to pi.ts, plus PiBabysitRunParams, handler dispatch, mode-before-task parsing, search routing, cancellation plumbing, and the BYOK key-mode entry
16-
status: pending
16+
status: completed
1717
- id: babysit-github
1818
content: "Implement babysit-github.ts: strict snapshot fetch and validation, paginated fully-trusted thread reads, fail-closed check state and diagnostics for the pinned SHA, two-phase replies and resolves, re-review issue comments, and the review-landed signal"
19-
status: pending
19+
status: completed
2020
- id: round-contract
2121
content: "Implement babysit-round.ts: the typebox round-file schema, parser, scrubbing, and host-side thread-id membership check"
22-
status: pending
22+
status: completed
2323
- id: babysit-backend
2424
content: "Implement babysit-backend.ts: the clone, the round loop with its advancing head pin, one-commit enforcement, exact-refspec push, replies, cancellation-aware waits, stop conditions, and the final report"
25-
status: pending
25+
status: completed
2626
- id: tests
2727
content: Write the babysit test suites and the pi.ts/pi-handler/keys additions, then run the full gate set
28-
status: pending
28+
status: in_progress
2929
- id: docs-and-review
3030
content: Document Babysit in pi.mdx and every place that enumerates the three modes
31-
status: pending
31+
status: completed
3232
isProject: false
3333
---
3434

apps/docs/content/docs/en/workflows/blocks/pi.mdx

Lines changed: 68 additions & 18 deletions
Large diffs are not rendered by default.

apps/sim/blocks/blocks/pi.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,46 @@ describe('Pi block search fields', () => {
6565
expect(PiBlock.inputs.searchApiKey).toBeDefined()
6666
})
6767
})
68+
69+
describe('Pi Babysit block surface', () => {
70+
it('declares bounded-round inputs and all result outputs', () => {
71+
const maxRounds = PiBlock.subBlocks.find((subBlock) => subBlock.id === 'maxRounds')
72+
const mentions = PiBlock.subBlocks.find((subBlock) => subBlock.id === 'reviewMentions')
73+
74+
expect(maxRounds).toMatchObject({
75+
type: 'short-input',
76+
defaultValue: '3',
77+
condition: { field: 'mode', value: 'babysit' },
78+
})
79+
expect(mentions).toMatchObject({
80+
type: 'short-input',
81+
defaultValue: '',
82+
mode: 'advanced',
83+
condition: { field: 'mode', value: 'babysit' },
84+
})
85+
for (const output of [
86+
'rounds',
87+
'threadsClean',
88+
'checksGreen',
89+
'threadsResolved',
90+
'commitsPushed',
91+
'stopReason',
92+
]) {
93+
expect(PiBlock.outputs[output]).toMatchObject({
94+
condition: { field: 'mode', value: 'babysit' },
95+
})
96+
}
97+
})
98+
99+
it('makes task optional only for Babysit and exposes skills but not tools or memory', () => {
100+
const task = PiBlock.subBlocks.find((subBlock) => subBlock.id === 'task')
101+
const skills = PiBlock.subBlocks.find((subBlock) => subBlock.id === 'skills')
102+
const tools = PiBlock.subBlocks.find((subBlock) => subBlock.id === 'tools')
103+
const memory = PiBlock.subBlocks.find((subBlock) => subBlock.id === 'memoryType')
104+
105+
expect(task?.required).toEqual({ field: 'mode', value: 'babysit', not: true })
106+
expect(evaluateSubBlockCondition(skills?.condition, { mode: 'babysit' })).toBe(true)
107+
expect(evaluateSubBlockCondition(tools?.condition, { mode: 'babysit' })).toBe(false)
108+
expect(evaluateSubBlockCondition(memory?.condition, { mode: 'babysit' })).toBe(false)
109+
})
110+
})

apps/sim/blocks/blocks/pi.ts

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ interface PiResponse extends ToolResponse {
1919
branch?: string
2020
reviewUrl?: string
2121
commentsPosted?: number
22+
rounds?: number
23+
threadsClean?: boolean
24+
checksGreen?: boolean
25+
threadsResolved?: number
26+
commitsPushed?: number
27+
stopReason?: string
2228
tokens?: {
2329
input?: number
2430
output?: number
@@ -42,12 +48,21 @@ const CLOUD_REVIEW: { field: 'mode'; value: 'cloud_review' } = {
4248
field: 'mode',
4349
value: 'cloud_review',
4450
}
45-
const CLOUD_ANY: { field: 'mode'; value: Array<'cloud' | 'cloud_review'> } = {
51+
const BABYSIT: { field: 'mode'; value: 'babysit' } = { field: 'mode', value: 'babysit' }
52+
const CLOUD_ANY: { field: 'mode'; value: Array<'cloud' | 'cloud_review' | 'babysit'> } = {
4653
field: 'mode',
47-
value: ['cloud', 'cloud_review'],
54+
value: ['cloud', 'cloud_review', 'babysit'],
55+
}
56+
const EXISTING_PR: { field: 'mode'; value: Array<'cloud_review' | 'babysit'> } = {
57+
field: 'mode',
58+
value: ['cloud_review', 'babysit'],
4859
}
4960
const LOCAL: { field: 'mode'; value: 'local' } = { field: 'mode', value: 'local' }
50-
const AUTHORING_MODES: { field: 'mode'; value: Array<'cloud' | 'local'> } = {
61+
const AUTHORING_MODES: { field: 'mode'; value: Array<'cloud' | 'local' | 'babysit'> } = {
62+
field: 'mode',
63+
value: ['cloud', 'local', 'babysit'],
64+
}
65+
const MEMORY_MODES: { field: 'mode'; value: Array<'cloud' | 'local'> } = {
5166
field: 'mode',
5267
value: ['cloud', 'local'],
5368
}
@@ -85,12 +100,13 @@ export const PiBlock: BlockConfig<PiResponse> = {
85100
description: 'Run an autonomous coding agent on a repo',
86101
authMode: AuthMode.ApiKey,
87102
longDescription:
88-
'The Pi Coding Agent runs the Pi harness against a real repository. Create PR spins up an isolated sandbox, clones a GitHub repo, edits with native shell + git, and opens a pull request. Review Code checks out a pinned PR snapshot with read-only tools and posts a structured review with optional inline comments. Local Dev edits files on your own machine over SSH. Create PR and Local Dev can reuse skills and multi-turn memory; Review Code runs without either because PR contents are untrusted. Any mode can optionally get one web_search tool backed by your own Exa, Serper, Parallel AI, or Firecrawl key; the agent writes its own queries, so repository content may reach the provider, and results are untrusted third-party data.',
103+
'The Pi Coding Agent runs the Pi harness against a real repository. Create PR opens a new pull request, Review Code posts a structured review, Babysit drives an existing pull request through trusted review threads and required checks in bounded rounds, and Local Dev edits files over SSH. Create PR, Babysit, and Local Dev can reuse skills; only Create PR and Local Dev use conversation memory. Any mode can optionally get one web_search tool backed by your own key.',
89104
bestPractices: `
90105
- Use Create PR for hands-off changes against a GitHub repo where a reviewable PR is the deliverable.
91106
- Use Review Code to analyze an existing PR and leave summary + inline review comments.
107+
- Use Babysit to fix and answer trusted review threads and required checks on an existing same-repository PR.
92108
- Use Local Dev to edit a repo on your own machine; expose the machine on a public hostname/tunnel so Sim can reach it over SSH.
93-
- Create PR requires your own provider API key because the model runs in the sandbox. Review Code keeps the model key in Sim and can use either BYOK or a hosted key.
109+
- Create PR and Babysit require your own provider API key because the model runs in the sandbox. Review Code keeps the model key in Sim and can use either BYOK or a hosted key.
94110
- Internet Search is off by default and always needs your own key for the selected provider, from the block field or Settings > BYOK. Leave it on None unless the task genuinely needs external information.
95111
`,
96112
category: 'blocks',
@@ -102,7 +118,7 @@ export const PiBlock: BlockConfig<PiResponse> = {
102118
id: 'mode',
103119
title: 'Mode',
104120
type: 'dropdown',
105-
/** Create PR and Review Code require E2B and stay hidden when it is disabled. */
121+
/** Create PR, Review Code, and Babysit require E2B and stay hidden when it is disabled. */
106122
value: () => (isTruthy(getEnv('NEXT_PUBLIC_E2B_ENABLED')) ? 'cloud' : 'local'),
107123
options: () => {
108124
const options = [
@@ -123,6 +139,11 @@ export const PiBlock: BlockConfig<PiResponse> = {
123139
label: 'Review Code',
124140
id: 'cloud_review',
125141
description: 'Reviews an existing PR and posts GitHub review comments',
142+
},
143+
{
144+
label: 'Babysit',
145+
id: 'babysit',
146+
description: 'Fixes review threads and failing checks on an existing PR',
126147
}
127148
)
128149
}
@@ -134,7 +155,7 @@ export const PiBlock: BlockConfig<PiResponse> = {
134155
title: 'Task',
135156
type: 'long-input',
136157
placeholder: 'Describe what the coding agent should do...',
137-
required: true,
158+
required: { field: 'mode', value: 'babysit', not: true },
138159
},
139160
{
140161
id: 'model',
@@ -196,7 +217,7 @@ export const PiBlock: BlockConfig<PiResponse> = {
196217
paramVisibility: 'user-only',
197218
placeholder: 'GitHub personal access token',
198219
tooltip:
199-
'Personal access token used for GitHub access. Create PR needs clone/push/PR permissions; Review Code needs clone + review permissions.',
220+
'Personal access token used for GitHub access. Create PR needs clone/push/PR permissions; Review Code needs clone + review permissions; Babysit also needs check/Actions reads, thread writes, issue comments, and push access.',
200221
required: true,
201222
condition: CLOUD_ANY,
202223
},
@@ -246,7 +267,7 @@ export const PiBlock: BlockConfig<PiResponse> = {
246267
type: 'short-input',
247268
placeholder: 'e.g., 42',
248269
required: true,
249-
condition: CLOUD_REVIEW,
270+
condition: EXISTING_PR,
250271
},
251272
{
252273
id: 'reviewEvent',
@@ -261,6 +282,26 @@ export const PiBlock: BlockConfig<PiResponse> = {
261282
'How GitHub records the submitted review. Comment is neutral; Request changes marks the pull request as changes requested.',
262283
condition: CLOUD_REVIEW,
263284
},
285+
{
286+
id: 'maxRounds',
287+
title: 'Maximum Rounds',
288+
type: 'short-input',
289+
defaultValue: '3',
290+
placeholder: '3',
291+
tooltip: 'Maximum number of agent fixing rounds, from 1 to 10.',
292+
condition: BABYSIT,
293+
},
294+
{
295+
id: 'reviewMentions',
296+
title: 'Re-review Mentions',
297+
type: 'short-input',
298+
defaultValue: '',
299+
placeholder: '@greptile, @cursor review',
300+
tooltip:
301+
'Comma-separated issue comments to post after a pushed fix. Leave empty to skip requesting and waiting for re-review.',
302+
mode: 'advanced',
303+
condition: BABYSIT,
304+
},
264305

265306
{
266307
id: 'host',
@@ -399,7 +440,7 @@ export const PiBlock: BlockConfig<PiResponse> = {
399440
{ label: 'Sliding window (tokens)', id: 'sliding_window_tokens' },
400441
],
401442
mode: 'advanced',
402-
condition: AUTHORING_MODES,
443+
condition: MEMORY_MODES,
403444
},
404445
{
405446
id: 'conversationId',
@@ -452,19 +493,24 @@ export const PiBlock: BlockConfig<PiResponse> = {
452493
inputs: {
453494
mode: {
454495
type: 'string',
455-
description: 'Execution mode: Create PR, Review Code, or Local Dev',
496+
description: 'Execution mode: Create PR, Review Code, Babysit, or Local Dev',
456497
},
457498
task: { type: 'string', description: 'Instruction for the coding agent' },
458499
model: { type: 'string', description: 'AI model to use' },
459-
owner: { type: 'string', description: 'GitHub repository owner (Create PR and Review Code)' },
460-
repo: { type: 'string', description: 'GitHub repository name (Create PR and Review Code)' },
461-
githubToken: { type: 'string', description: 'GitHub token (Create PR and Review Code)' },
500+
owner: { type: 'string', description: 'GitHub repository owner' },
501+
repo: { type: 'string', description: 'GitHub repository name' },
502+
githubToken: { type: 'string', description: 'GitHub token' },
462503
baseBranch: { type: 'string', description: 'Base branch for the PR (Create PR)' },
463504
branchName: { type: 'string', description: 'Branch to create (Create PR)' },
464505
draft: { type: 'boolean', description: 'Open the PR as a draft (Create PR)' },
465506
prTitle: { type: 'string', description: 'Pull request title (Create PR)' },
466507
prBody: { type: 'string', description: 'Pull request body (Create PR)' },
467-
pullNumber: { type: 'number', description: 'Pull request number (Review Code)' },
508+
pullNumber: { type: 'number', description: 'Pull request number (Review Code or Babysit)' },
509+
maxRounds: { type: 'number', description: 'Maximum Babysit fixing rounds (1-10)' },
510+
reviewMentions: {
511+
type: 'string',
512+
description: 'Comma-separated issue comments requesting re-review after a Babysit push',
513+
},
468514
reviewEvent: {
469515
type: 'string',
470516
description: 'GitHub review event: COMMENT or REQUEST_CHANGES',
@@ -516,6 +562,36 @@ export const PiBlock: BlockConfig<PiResponse> = {
516562
description: 'Number of inline review comments posted',
517563
condition: CLOUD_REVIEW,
518564
},
565+
rounds: {
566+
type: 'number',
567+
description: 'Babysit fixing rounds consumed',
568+
condition: BABYSIT,
569+
},
570+
threadsClean: {
571+
type: 'boolean',
572+
description: 'Whether all actionable review threads are resolved',
573+
condition: BABYSIT,
574+
},
575+
checksGreen: {
576+
type: 'boolean',
577+
description: 'Whether required checks are green with none pending',
578+
condition: BABYSIT,
579+
},
580+
threadsResolved: {
581+
type: 'number',
582+
description: 'Review threads resolved by Babysit',
583+
condition: BABYSIT,
584+
},
585+
commitsPushed: {
586+
type: 'number',
587+
description: 'Commits pushed by Babysit',
588+
condition: BABYSIT,
589+
},
590+
stopReason: {
591+
type: 'string',
592+
description: 'Why the Babysit run stopped',
593+
condition: BABYSIT,
594+
},
519595
tokens: { type: 'json', description: 'Token usage statistics' },
520596
cost: { type: 'json', description: 'Cost of the run' },
521597
providerTiming: { type: 'json', description: 'Provider timing information' },

0 commit comments

Comments
 (0)