Skip to main content

Send review comments to the agent

Leave review comments on specific lines of the agent's diff, then send them back as the next message. The agent reads each comment with the lines it points at and addresses them in one turn. Review the changes shows the same flow in the web app.

Before you start​

Set PROJECT_ID and SESSION_ID, as in Ship a change end to end. Reading the diff needs a running sandbox, so request the session's preview first:

curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/preview?sessionId=$SESSION_ID" \
-H "Authorization: Bearer $KOSUKE_API_KEY" | jq '.data'

Sandboxes and previews explains the flags this can answer with.

1. Find the changed lines​

Without a scope, both requests below show what the agent's last turn changed. To see everything the pushed branch changes, add ?scope=branch to the file list and --data-urlencode "scope=branch" to the patch request. The patch answers 404 for a file outside its scope.

List the changed files:

curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/preview/diff/files" \
-H "Authorization: Bearer $KOSUKE_API_KEY" | jq '.data.files[] | {path, status, additions, deletions}'

An empty list with unavailable set to "Changes require a running sandbox" means the sandbox is paused. Wake it and ask again.

Read one file's patch. The hunk headers, such as @@ -38,6 +38,9 @@, give the line numbers on each side:

curl -s -G "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/preview/diff/patch" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
--data-urlencode "path=src/search/route.ts" | jq -r '.data[0].windows[].patch'

2. Comment on a line range​

curl -s -X POST \
"https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/preview/diff/comments" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filePath": "src/search/route.ts",
"side": "new",
"startLineNumber": 42,
"endLineNumber": 44,
"body": "This shares one limiter across tenants. Key it by tenant id.",
"lineSnapshot": "const limiter = new RateLimiter()"
}' | jq '.data' >> comments.json
FieldRequiredWhat it holds
filePathYesThe file's path, as the file list shows it.
sideYesnew for lines in the changed file. old for lines the change removed.
startLineNumberYesThe first line of the range, counted on that side.
endLineNumberYesThe last line of the range. Equal to or after startLineNumber.
bodyYesYour comment, up to 4,000 characters.
lineSnapshotYesThe text of the commented lines as they read now. The web app compares it with the current diff to show the comment as outdated. The API never does, and always returns isOutdated: false. It may be empty when the side you comment on has no lines.

The response answers 201 with the comment, including its id:

{
"id": "5d0c8e21-…",
"chatSessionId": "7a3f0b92-…",
"filePath": "src/search/route.ts",
"side": "new",
"startLineNumber": 42,
"endLineNumber": 44,
"body": "This shares one limiter across tenants. Key it by tenant id.",
"lineSnapshot": "const limiter = new RateLimiter()",
"resolvedAt": null,
"resolvedByUserId": null,
"createdAt": "2026-10-08T09:12:04.000Z",
"updatedAt": "2026-10-08T09:12:04.000Z",
"author": {
"userId": "u8Rk2mQ4xT7vB1nP9sL3cW6yD0fH5jZa",
"name": "Sam Rivera",
"email": "sam@acme.com",
"imageUrl": null
},
"isOutdated": false
}

The command appends each comment to comments.json, which keeps the ids for the next step. Repeat it for every comment. With the CLI, run kosuke sessions diff comments add.

3. Send the comments to the agent​

Build diffCommentsContext from the saved comments. Each item carries exactly these fields, and one message takes up to 50 items:

jq -s '{
content: "Please address these review comments.",
diffCommentsContext: [.[] | {id, filePath, side, startLineNumber, endLineNumber, body, lineSnapshot: .lineSnapshot[:4000], isOutdated}] | .[:50]
}' comments.json > message.json

curl -s -X POST "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/jobs/chat" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d @message.json | jq '.data'

content may be an empty string when you send only comments. Kosuke reads each comment again by its id, so the agent sees the comment as it is stored, and a resolved comment is left out. Items with fields beyond those listed, other than an optional createdAt, fail validation.

The response carries delivery. Follow the turn as Follow a turn shows.

4. Resolve the comments​

When the agent has dealt with a comment, resolve it:

for COMMENT_ID in $(jq -r '.id' comments.json); do
curl -s -X PATCH \
"https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/preview/diff/comments/$COMMENT_ID" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"resolved": true}' > /dev/null
done

Anyone who can reach the project can resolve a comment, and {"resolved": false} reopens it. Only its author can change its body or delete it. A request changes the body or the resolved state, one of the two. To see what is still open, list the comments with GET /api/projects/{projectId}/chat-sessions/{sessionId}/preview/diff/comments.

Next steps​