Skip to content

Commit a89d332

Browse files
NamedIdentityclaude
andcommitted
fix(task): change budget scope from per-message to per-session
The task_budget was incorrectly keyed by (sessionID, messageID), causing the budget counter to reset every turn since each assistant response generates a new messageID. Changed to per-session tracking so all task calls within a delegated session count toward the same budget. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 19e1231 commit a89d332

File tree

2 files changed

+125
-18
lines changed

2 files changed

+125
-18
lines changed

CLAUDE.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
OpenCode is an open-source AI-powered coding agent, similar to Claude Code but provider-agnostic. It supports multiple LLM providers (Anthropic, OpenAI, Google, Azure, local models) and features a TUI built with SolidJS, LSP support, and client/server architecture.
8+
9+
## Development Commands
10+
11+
```bash
12+
# Install and run development server
13+
bun install
14+
bun dev # Run in packages/opencode directory
15+
bun dev <directory> # Run against a specific directory
16+
bun dev . # Run against repo root
17+
18+
# Type checking
19+
bun run typecheck # Single package
20+
bun turbo typecheck # All packages
21+
22+
# Testing (per-package, not from root)
23+
cd packages/opencode && bun test
24+
25+
# Build standalone executable
26+
./packages/opencode/script/build.ts --single
27+
# Output: ./packages/opencode/dist/opencode-<platform>/bin/opencode
28+
29+
# Regenerate SDK after API changes
30+
./script/generate.ts
31+
# Or for JS SDK specifically:
32+
./packages/sdk/js/script/build.ts
33+
34+
# Web app development
35+
bun run --cwd packages/app dev # http://localhost:5173
36+
37+
# Desktop app (requires Tauri/Rust)
38+
bun run --cwd packages/desktop tauri dev # Native + web server
39+
bun run --cwd packages/desktop dev # Web only (port 1420)
40+
bun run --cwd packages/desktop tauri build # Production build
41+
```
42+
43+
## Architecture
44+
45+
**Monorepo Structure** (Bun workspaces + Turbo):
46+
47+
| Package | Purpose |
48+
|---------|---------|
49+
| `packages/opencode` | Core CLI, server, business logic |
50+
| `packages/app` | Shared web UI components (SolidJS + Vite) |
51+
| `packages/desktop` | Native desktop app (Tauri wrapper) |
52+
| `packages/ui` | Shared component library (Kobalte + Tailwind) |
53+
| `packages/console/app` | Console dashboard (Solid Start) |
54+
| `packages/console/core` | Backend services (Hono + DrizzleORM) |
55+
| `packages/sdk/js` | JavaScript SDK |
56+
| `packages/plugin` | Plugin system API |
57+
58+
**Key Directories in `packages/opencode/src`**:
59+
- `cli/cmd/tui/` - Terminal UI (SolidJS + opentui)
60+
- `agent/` - Agent logic and state
61+
- `provider/` - AI provider implementations
62+
- `server/` - Server mode
63+
- `mcp/` - Model Context Protocol integration
64+
- `lsp/` - Language Server Protocol support
65+
66+
**Default branch**: `dev`
67+
68+
## Code Style
69+
70+
- Keep logic in single functions unless reusable
71+
- Avoid destructuring: use `obj.a` instead of `const { a } = obj`
72+
- Avoid `try/catch` - prefer `.catch()`
73+
- Avoid `else` statements
74+
- Avoid `any` type
75+
- Avoid `let` - use immutable patterns
76+
- Prefer single-word variable names when descriptive
77+
- Use Bun APIs (e.g., `Bun.file()`) when applicable
78+
79+
## Built-in Agents
80+
81+
- **build** - Default agent with full access for development
82+
- **plan** - Read-only agent for analysis (denies edits, asks before bash)
83+
- **general** - Subagent for complex tasks, invoked with `@general`
84+
85+
Switch agents with `Tab` key in TUI.
86+
87+
## Debugging
88+
89+
```bash
90+
# Debug with inspector
91+
bun run --inspect=ws://localhost:6499/ dev
92+
93+
# Debug server separately
94+
bun run --inspect=ws://localhost:6499/ ./src/index.ts serve --port 4096
95+
opencode attach http://localhost:4096
96+
97+
# Debug TUI
98+
bun run --inspect=ws://localhost:6499/ --conditions=browser ./src/index.ts
99+
100+
# Use spawn for breakpoints in server code
101+
bun dev spawn
102+
```
103+
104+
Use `--inspect-wait` or `--inspect-brk` for different breakpoint behaviors.
105+
106+
## PR Guidelines
107+
108+
- All PRs must reference an existing issue (`Fixes #123`)
109+
- UI/core feature changes require design review with core team
110+
- PR titles follow conventional commits: `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`
111+
- Optional scope: `feat(app):`, `fix(desktop):`
112+
- Include screenshots/videos for UI changes
113+
- Explain verification steps for logic changes

packages/opencode/src/tool/task.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,20 @@ import { Config } from "../config/config"
1313
import { PermissionNext } from "@/permission/next"
1414
import { Instance } from "../project/instance"
1515

16-
// Track task calls per request: Map<sessionID, Map<messageID, count>>
17-
// Budget is per-request (one "work assignment" within a session), resets on new messageID
18-
// Note: State grows with sessions/messages but entries are small. Future optimization:
16+
// Track task calls per session: Map<sessionID, count>
17+
// Budget is per-session (all calls within the delegated work count toward the limit)
18+
// Note: State grows with sessions but entries are small. Future optimization:
1919
// clean up completed sessions via Session lifecycle hooks if memory becomes a concern.
20-
const taskCallState = Instance.state(() => new Map<string, Map<string, number>>())
20+
const taskCallState = Instance.state(() => new Map<string, number>())
2121

22-
function getCallCount(sessionID: string, messageID: string): number {
23-
const sessionCounts = taskCallState().get(sessionID)
24-
return sessionCounts?.get(messageID) ?? 0
22+
function getCallCount(sessionID: string): number {
23+
return taskCallState().get(sessionID) ?? 0
2524
}
2625

27-
function incrementCallCount(sessionID: string, messageID: string): number {
26+
function incrementCallCount(sessionID: string): number {
2827
const state = taskCallState()
29-
let sessionCounts = state.get(sessionID)
30-
if (!sessionCounts) {
31-
sessionCounts = new Map()
32-
state.set(sessionID, sessionCounts)
33-
}
34-
const newCount = (sessionCounts.get(messageID) ?? 0) + 1
35-
sessionCounts.set(messageID, newCount)
28+
const newCount = (state.get(sessionID) ?? 0) + 1
29+
state.set(sessionID, newCount)
3630
return newCount
3731
}
3832

@@ -127,8 +121,8 @@ export const TaskTool = Tool.define("task", async (ctx) => {
127121
)
128122
}
129123

130-
// Check 3: Budget not exhausted for this request (messageID)
131-
const currentCount = getCallCount(ctx.sessionID, ctx.messageID)
124+
// Check 3: Budget not exhausted for this session
125+
const currentCount = getCallCount(ctx.sessionID)
132126
if (currentCount >= callerTaskBudget) {
133127
throw new Error(
134128
`Task budget exhausted (${currentCount}/${callerTaskBudget} calls). ` +
@@ -137,7 +131,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
137131
}
138132

139133
// Increment count after passing all checks (including ownership above)
140-
incrementCallCount(ctx.sessionID, ctx.messageID)
134+
incrementCallCount(ctx.sessionID)
141135
}
142136

143137
const session = await iife(async () => {

0 commit comments

Comments
 (0)