@parallel-cli/parallel 0.4.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +23 -6
- package/dist/agents/agent.js +194 -25
- package/dist/agents/execution-policy.js +58 -0
- package/dist/agents/tools.js +71 -17
- package/dist/commands.js +62 -5
- package/dist/controller.js +136 -5
- package/dist/diagnostics.js +209 -0
- package/dist/i18n.js +40 -4
- package/dist/index.js +12 -2
- package/dist/llm/client.js +7 -3
- package/dist/project-context.js +477 -0
- package/dist/project-index.js +186 -0
- package/dist/ui/AgentPanel.js +5 -2
- package/dist/ui/App.js +4 -2
- package/dist/ui/SettingsPanel.js +22 -23
- package/dist/ui/Wizard.js +49 -21
- package/dist/ui/views.js +4 -2
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Parallel are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.5.0 - 2026-06-25
|
|
6
|
+
|
|
7
|
+
### 0.5.0 Added
|
|
8
|
+
|
|
9
|
+
- Added an automatically generated, versioned project context in `.parallel/project-context.json`, shared by every new agent in the same folder.
|
|
10
|
+
- Added targeted freshness tracking for files inspected by agents, including content hashes and stale-file warnings.
|
|
11
|
+
- Added visible project-memory indexing, deterministic fallback, token/cost accounting, `/memory`, and `/memory refresh`.
|
|
12
|
+
- Added restored-session summaries directly to new-agent bootstrap context instead of relying on historical notes.
|
|
13
|
+
- Added an agent performance diagnostician and deterministic simulator for model rounds, tool churn, shell micro-commands, repeated reads, hidden compactions, and context amplification.
|
|
14
|
+
- Added adaptive Quick, Standard, and Deep execution profiles with visible badges and `--quick`, `--standard`, and `--deep` overrides.
|
|
15
|
+
- Added a persistent incremental lexical/symbol index under `.parallel/index/` and task-oriented retrieval before the first model call.
|
|
16
|
+
- Added targeted line-range reads, bounded tool output artifacts, provider retry/cache telemetry, and runtime convergence budgets.
|
|
17
|
+
|
|
18
|
+
### 0.5.0 Changed
|
|
19
|
+
|
|
20
|
+
- New agents now start from shared architecture, conventions, pitfalls, entry points, and recent work instead of treating the repository as unknown.
|
|
21
|
+
- Replaced generic “explore first” prompting with targeted verification of relevant, unknown, stale, or soon-to-be-modified files.
|
|
22
|
+
- Kept full conversations isolated per agent; `/restore` remains the explicit path for exact conversation continuity.
|
|
23
|
+
- Session snapshots now record inspected files and project-context metadata while remaining compatible with older snapshots.
|
|
24
|
+
- Agent telemetry now records provider wait time, hidden compaction time/calls, and peak prompt tokens.
|
|
25
|
+
- Quick and Standard agents now keep a bounded recent window plus a deterministic work ledger instead of repeatedly sending every raw tool result.
|
|
26
|
+
- Project-memory enrichment now runs in the background; startup no longer waits up to 20 seconds before useful work can begin.
|
|
27
|
+
- Ordinary inter-agent notes are batched for the next natural turn instead of aborting an in-flight model request.
|
|
28
|
+
- Included stable Settings and Wizard list navigation/windowing fixes.
|
|
29
|
+
|
|
30
|
+
### 0.5.0 Fixed
|
|
31
|
+
|
|
32
|
+
- Fixed newly spawned agents ignoring all useful work, notes, and conclusions that existed before their creation.
|
|
33
|
+
- Fixed loaded-session summaries being added to the blackboard and then skipped by the next agent’s note cursor.
|
|
34
|
+
- Fixed repetitive generic “Explore the project” progress steps when a valid project map already exists.
|
|
35
|
+
- Fixed simple investigations inheriting the same 60-turn allowance as long-running plans.
|
|
36
|
+
- Fixed large command and inspection results remaining in every later prompt.
|
|
37
|
+
|
|
5
38
|
## 0.4.9 - 2026-06-24
|
|
6
39
|
|
|
7
40
|
### 0.4.9 Added
|
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ Parallel lets several AI coding agents co-edit the same repository at the same t
|
|
|
32
32
|
- Keep shell execution controlled with `ask`, `auto-safe`, or `yolo` approvals.
|
|
33
33
|
- Get prompted for npm updates at startup, with an explicit skip path.
|
|
34
34
|
- Save and restore project sessions.
|
|
35
|
+
- Reuse a persistent, automatically synthesized project map across new agents in the same folder.
|
|
35
36
|
- Run headless multi-agent jobs for CI or scripts.
|
|
36
37
|
|
|
37
38
|
## Install
|
|
@@ -138,6 +139,14 @@ The reviewer is ask-only: it does not edit, does not gate the session globally,
|
|
|
138
139
|
|
|
139
140
|
Task and plan agents maintain a small Cursor-style checklist with one active step at a time. The runtime also encourages batched inspection through `read_many` and `inspect_project` so agents avoid slow chains of tiny read-only shell commands.
|
|
140
141
|
|
|
142
|
+
Every agent also receives an execution profile:
|
|
143
|
+
|
|
144
|
+
- `quick`: targeted questions, diagnostics, and small changes; six model turns by default.
|
|
145
|
+
- `standard`: bounded multi-file work; sixteen model turns by default.
|
|
146
|
+
- `deep`: plans, migrations, and long-running refactors; up to the configured global limit.
|
|
147
|
+
|
|
148
|
+
Parallel selects the profile locally without spending a model call. Override it when needed with `--quick`, `--standard`, or `--deep`, for example `/task --quick fix the sound toggle`. A profile only escalates automatically when the agent discovers concrete cross-file complexity; repeated exploration does not earn more budget.
|
|
149
|
+
|
|
141
150
|
Aliases:
|
|
142
151
|
|
|
143
152
|
- `/a` -> `/ask`
|
|
@@ -340,6 +349,8 @@ If the update succeeds, restart Parallel to run the new version. Use `parallel -
|
|
|
340
349
|
- `/diff`: live diff history.
|
|
341
350
|
- `/cost`: token and cost breakdown.
|
|
342
351
|
- `/status`: session model, approval mode, agents, and cost snapshot.
|
|
352
|
+
- `/memory`: show shared project-memory freshness, model, tokens, and cost.
|
|
353
|
+
- `/memory refresh`: force a visible regeneration of the shared project map.
|
|
343
354
|
- `/skills`: available skills.
|
|
344
355
|
- `/specialists`: available specialists.
|
|
345
356
|
- `/save [name]`: save the current session.
|
|
@@ -347,12 +358,16 @@ If the update succeeds, restart Parallel to run the new version. Use `parallel -
|
|
|
347
358
|
- `/session <n|latest>`: load a saved session snapshot. If active agents are running, use `/session <n|latest> --force` after saving/stopping what you need.
|
|
348
359
|
- `/restore <agent>`: relaunch a restored agent by name, alias, or saved id when its conversation history is still available.
|
|
349
360
|
|
|
350
|
-
|
|
361
|
+
Project and session memory have three distinct layers:
|
|
351
362
|
|
|
352
363
|
- Live memory: active agents see statuses, notes, claims, work-map warnings, file activity, and recent diffs before every model action.
|
|
353
|
-
-
|
|
364
|
+
- Project memory: `.parallel/project-context.json` stores a model-generated architecture map, entry points, conventions, pitfalls, file hashes, and recent completed work. It loads automatically for every new agent in the same folder.
|
|
365
|
+
- Local index: `.parallel/index/manifest.json` incrementally records text files, symbols, imports, hashes, and searchable terms. Before the first model call, Parallel uses it to rank the files relevant to the current task.
|
|
366
|
+
- Session/conversation memory: `/save` and autosave persist coordination state and per-agent conversation paths for explicit `/restore`.
|
|
354
367
|
|
|
355
|
-
|
|
368
|
+
Parallel prewarms project memory when a project opens, but the first agent never waits for an LLM-generated synthesis. It immediately uses the persisted map, deterministic fallback, and local task-oriented index while enrichment continues in the background. `/memory` reports both map and index freshness.
|
|
369
|
+
|
|
370
|
+
Agents trust the project map for orientation, but re-read files that are relevant, unknown, stale, or about to be modified. Full conversations are never copied into unrelated new agents. Restore remains best effort and explicit: `/session` reloads coordination memory, while `/restore <agent>` relaunches the selected agent with its prior conversation when available.
|
|
356
371
|
|
|
357
372
|
### Settings And Exit
|
|
358
373
|
|
|
@@ -365,7 +380,7 @@ Restore is best effort and explicit. `/session` reloads coordination memory into
|
|
|
365
380
|
- `/folder [folder]`: alias for `/project`.
|
|
366
381
|
- `/wizard`: relaunch the setup wizard. If agents are active, use `/wizard --force` after saving/stopping what you need.
|
|
367
382
|
- `/setup`: alias for `/wizard`.
|
|
368
|
-
- `/doctor`: run local readiness diagnostics for provider, key, model, endpoint, attach socket, and Git tooling.
|
|
383
|
+
- `/doctor`: run local readiness diagnostics for provider, key, model, endpoint, project memory, attach socket, and Git tooling.
|
|
369
384
|
- `/help`: full command reference.
|
|
370
385
|
- `/quit`: save the session and exit.
|
|
371
386
|
|
|
@@ -392,7 +407,7 @@ Parallel separates agent modes from shell approval behavior.
|
|
|
392
407
|
Parallel stores credentials and session state with owner-only permissions where supported:
|
|
393
408
|
|
|
394
409
|
- `~/.parallel/config.json` and `~/.parallel/update.json` are written privately and atomically.
|
|
395
|
-
- Project runtime files under `.parallel/` use private directories for sessions, conversations, memory, socket state, and attach tokens.
|
|
410
|
+
- Project runtime files under `.parallel/` use private directories for sessions, conversations, project context, memory, socket state, and attach tokens.
|
|
396
411
|
- Attached terminals authenticate to the running session with a per-session token; local clients without the token cannot steer agents or answer approvals.
|
|
397
412
|
- `/doctor` reports local permission warnings alongside provider, model, endpoint, attach socket, `git`, and `gh` checks.
|
|
398
413
|
- Command output shown in logs is sanitized to strip terminal escape/control sequences.
|
|
@@ -402,7 +417,9 @@ Shell safety is still a shared responsibility. `auto-safe` uses conservative heu
|
|
|
402
417
|
|
|
403
418
|
## Sessions, Skills, And Specialists
|
|
404
419
|
|
|
405
|
-
Parallel stores project state under `.parallel/` in the selected project directory. That includes saved sessions,
|
|
420
|
+
Parallel stores project state under `.parallel/` in the selected project directory. That includes saved sessions, the generated project context, durable facts, skills, specialists, and session socket state.
|
|
421
|
+
|
|
422
|
+
`.parallel/state.json` remains a best-effort diagnostic snapshot. It is not loaded as conversation history; use project memory for shared understanding and `/restore` for exact agent continuity.
|
|
406
423
|
|
|
407
424
|
Skills are markdown instruction files agents can load with the `load_skill` tool or that you can force-load with `#skill-name` in a task:
|
|
408
425
|
|
package/dist/agents/agent.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import * as Diff from 'diff';
|
|
2
3
|
import { ToolExecutor, TOOL_DEFINITIONS } from './tools.js';
|
|
3
4
|
import { costOf } from '../pricing.js';
|
|
4
5
|
import { skillsCatalog } from '../skills.js';
|
|
5
6
|
import { getLang, LANG_NAME_EN, t } from '../i18n.js';
|
|
6
|
-
import { appendFilePrivate, sanitizeForPersistence } from '../security.js';
|
|
7
|
+
import { appendFilePrivate, sanitizeForPersistence, writeFileAtomicPrivate } from '../security.js';
|
|
8
|
+
import { EXECUTION_BUDGETS, nextExecutionProfile, shouldEscalateExecution, } from './execution-policy.js';
|
|
7
9
|
// Agent-facing prompts stay in English (canonical for models). Only notes
|
|
8
10
|
// addressed to the user follow the configured UI language.
|
|
9
|
-
const SYSTEM_PROMPT = (name, task, mode, userLang, skillsList, specialist, projectMemory) => `You are agent "${name}", an autonomous software engineer inside PARALLEL, an environment where SEVERAL agents work at the same time on the SAME project, each on its own task given by the user.
|
|
11
|
+
const SYSTEM_PROMPT = (name, task, mode, userLang, skillsList, specialist, projectMemory, projectContext, profile = 'standard') => `You are agent "${name}", an autonomous software engineer inside PARALLEL, an environment where SEVERAL agents work at the same time on the SAME project, each on its own task given by the user.
|
|
10
12
|
${specialist
|
|
11
13
|
? `
|
|
12
14
|
YOUR ROLE — you are the "${specialist.name}" specialist:
|
|
@@ -19,6 +21,19 @@ ${task}
|
|
|
19
21
|
</user_task>
|
|
20
22
|
|
|
21
23
|
AGENT MODE: ${mode}
|
|
24
|
+
EXECUTION PROFILE: ${profile}
|
|
25
|
+
${profile === 'quick'
|
|
26
|
+
? `QUICK PROFILE:
|
|
27
|
+
- This task must converge in a few model turns.
|
|
28
|
+
- Do not create a progress checklist unless the task unexpectedly becomes multi-file.
|
|
29
|
+
- Use the task-oriented local index first, batch the smallest relevant inspection, then conclude.
|
|
30
|
+
- Do not spend a turn only updating status or steps.`
|
|
31
|
+
: profile === 'standard'
|
|
32
|
+
? `STANDARD PROFILE:
|
|
33
|
+
- Keep inspection bounded and use a checklist only when there are multiple distinct outcomes.
|
|
34
|
+
- Escalation is justified by discovered cross-file complexity, not by repeated exploration.`
|
|
35
|
+
: `DEEP PROFILE:
|
|
36
|
+
- Multi-step planning and broader validation are allowed, but every turn must make concrete progress.`}
|
|
22
37
|
${mode === 'ask'
|
|
23
38
|
? `ASK MODE:
|
|
24
39
|
- You are advisory only. Do not modify files.
|
|
@@ -28,8 +43,8 @@ ${mode === 'ask'
|
|
|
28
43
|
- Finish with task_complete using this user-facing structure in ${userLang}: "Réponse courte", "Recommandation", "Pourquoi", "Prochaines étapes".`
|
|
29
44
|
: mode === 'plan'
|
|
30
45
|
? `PLAN MODE:
|
|
31
|
-
-
|
|
32
|
-
- Batch independent reads/searches with read_many or inspect_project.
|
|
46
|
+
- Start from the shared project context. Inspect only the task-relevant files that are unknown, stale, or needed as evidence.
|
|
47
|
+
- Batch independent targeted reads/searches with read_many or inspect_project.
|
|
33
48
|
- Before modifying any file or running mutating commands, call ask_user with a concrete implementation plan.
|
|
34
49
|
- The plan must include steps, files you expect to touch, risks, and validation.
|
|
35
50
|
- Use options ["Approve", "Revise"], recommended "Revise" so timeout never approves changes.
|
|
@@ -37,7 +52,7 @@ ${mode === 'ask'
|
|
|
37
52
|
- Finish with task_complete using this user-facing structure in ${userLang}: "Plan appliqué", "Ce que j’ai modifié", "Validation", "Risques restants".`
|
|
38
53
|
: `TASK MODE:
|
|
39
54
|
- Execute the user's objective end-to-end.
|
|
40
|
-
- Use this loop: create visible steps,
|
|
55
|
+
- Use this loop: create outcome-oriented visible steps, verify the relevant context, act, batch validate, summarize.
|
|
41
56
|
- If the task is a verification/audit and the correct outcome is no file changes, that is valid task work. Say explicitly in task_complete that no modification was necessary and why.
|
|
42
57
|
- Ask the user only when blocked or when a risky product decision cannot be inferred.
|
|
43
58
|
- Finish with task_complete using this user-facing structure in ${userLang}: "Ce que j’ai fait", "Ce que j’ai vérifié", "Résultat", "Détails techniques".`}
|
|
@@ -53,6 +68,13 @@ PROJECT MEMORY — durable facts recorded by previous agents on this project. Tr
|
|
|
53
68
|
<project_memory>
|
|
54
69
|
${projectMemory}
|
|
55
70
|
</project_memory>
|
|
71
|
+
`
|
|
72
|
+
: ''}${projectContext
|
|
73
|
+
? `
|
|
74
|
+
SHARED PROJECT CONTEXT — automatically maintained across agents in this folder:
|
|
75
|
+
<project_context>
|
|
76
|
+
${projectContext}
|
|
77
|
+
</project_context>
|
|
56
78
|
`
|
|
57
79
|
: ''}
|
|
58
80
|
|
|
@@ -73,7 +95,9 @@ PARALLEL'S PHILOSOPHY — REAL-TIME CO-EDITING, NEVER ANY BLOCKING:
|
|
|
73
95
|
|
|
74
96
|
WORK METHOD:
|
|
75
97
|
- For non-trivial work, call update_steps early with 3-6 concrete steps. Keep exactly one step active and mark steps done as you complete them.
|
|
76
|
-
-
|
|
98
|
+
- Do not create a generic "explore the project" step when shared project context already describes the codebase. Steps must state task-specific outcomes.
|
|
99
|
+
- Use shared project context first. Re-read only files directly relevant to the task, files marked stale/unknown, and every file immediately before modifying it.
|
|
100
|
+
- If the shared context is absent or insufficient for the task area, perform a bounded inspection and record durable discoveries.
|
|
77
101
|
- Use run_command for builds/tests/validation and genuinely useful shell scripts. Do NOT spend many turns running grep/head/tail/wc/awk cascades; batch independent shell checks into one labelled command or use inspect_project.
|
|
78
102
|
- Declare your work area with claim_files when you start (and when it changes): it prevents collisions without ever locking anything.
|
|
79
103
|
- If you discover a durable, non-obvious fact about the project (convention, decision, pitfall), save it with remember(fact) for future agents.
|
|
@@ -98,6 +122,12 @@ const EMPTY_PERF = {
|
|
|
98
122
|
shellCommands: 0,
|
|
99
123
|
shellMs: 0,
|
|
100
124
|
readOnlyShellCommands: 0,
|
|
125
|
+
llmMs: 0,
|
|
126
|
+
compactionTurns: 0,
|
|
127
|
+
compactionMs: 0,
|
|
128
|
+
maxPromptTokens: 0,
|
|
129
|
+
retries: 0,
|
|
130
|
+
cachedTokens: 0,
|
|
101
131
|
};
|
|
102
132
|
function noChangeTaskLine() {
|
|
103
133
|
switch (getLang()) {
|
|
@@ -124,20 +154,26 @@ export class Agent {
|
|
|
124
154
|
llm;
|
|
125
155
|
board;
|
|
126
156
|
maxSteps;
|
|
157
|
+
budget;
|
|
127
158
|
abort = new AbortController();
|
|
128
159
|
paused = false;
|
|
129
160
|
stopped = false;
|
|
130
161
|
lastNoteId = 0;
|
|
131
162
|
lastChangeId = 0;
|
|
132
163
|
readOnlyShellStreak = 0;
|
|
164
|
+
artifactSeq = 0;
|
|
165
|
+
convergenceWarned = new Set();
|
|
133
166
|
constructor(opts) {
|
|
134
167
|
this.opts = opts;
|
|
135
168
|
this.id = opts.id;
|
|
136
169
|
this.name = opts.name;
|
|
137
170
|
this.llm = opts.llm;
|
|
138
171
|
this.board = opts.board;
|
|
139
|
-
|
|
140
|
-
|
|
172
|
+
const profile = opts.profile ?? (opts.mode === 'plan' ? 'deep' : opts.mode === 'ask' ? 'quick' : 'standard');
|
|
173
|
+
const budget = opts.budget ?? EXECUTION_BUDGETS[profile];
|
|
174
|
+
this.maxSteps = Math.min(opts.maxSteps, budget.maxRounds);
|
|
175
|
+
this.budget = budget;
|
|
176
|
+
this.executor = new ToolExecutor(opts.board, opts.id, opts.name, opts.projectRoot, opts.requestApproval, opts.requestQuestion, opts.skills, opts.mode, opts.onInspect, profile, budget.maxResultChars);
|
|
141
177
|
const info = {
|
|
142
178
|
id: opts.id,
|
|
143
179
|
name: opts.name,
|
|
@@ -145,6 +181,7 @@ export class Agent {
|
|
|
145
181
|
color: opts.color,
|
|
146
182
|
task: opts.task,
|
|
147
183
|
mode: opts.mode,
|
|
184
|
+
profile,
|
|
148
185
|
model: opts.model,
|
|
149
186
|
state: 'idle',
|
|
150
187
|
currentAction: '',
|
|
@@ -273,13 +310,66 @@ export class Agent {
|
|
|
273
310
|
const current = this.board.agents.get(this.id)?.perf ?? EMPTY_PERF;
|
|
274
311
|
this.board.updateAgent(this.id, {
|
|
275
312
|
perf: {
|
|
276
|
-
modelTurns: current.modelTurns + (delta.modelTurns ?? 0),
|
|
277
|
-
toolCalls: current.toolCalls + (delta.toolCalls ?? 0),
|
|
278
|
-
shellCommands: current.shellCommands + (delta.shellCommands ?? 0),
|
|
279
|
-
shellMs: current.shellMs + (delta.shellMs ?? 0),
|
|
280
|
-
readOnlyShellCommands: current.readOnlyShellCommands + (delta.readOnlyShellCommands ?? 0),
|
|
313
|
+
modelTurns: (current.modelTurns ?? 0) + (delta.modelTurns ?? 0),
|
|
314
|
+
toolCalls: (current.toolCalls ?? 0) + (delta.toolCalls ?? 0),
|
|
315
|
+
shellCommands: (current.shellCommands ?? 0) + (delta.shellCommands ?? 0),
|
|
316
|
+
shellMs: (current.shellMs ?? 0) + (delta.shellMs ?? 0),
|
|
317
|
+
readOnlyShellCommands: (current.readOnlyShellCommands ?? 0) + (delta.readOnlyShellCommands ?? 0),
|
|
318
|
+
llmMs: (current.llmMs ?? 0) + (delta.llmMs ?? 0),
|
|
319
|
+
compactionTurns: (current.compactionTurns ?? 0) + (delta.compactionTurns ?? 0),
|
|
320
|
+
compactionMs: (current.compactionMs ?? 0) + (delta.compactionMs ?? 0),
|
|
321
|
+
maxPromptTokens: Math.max(current.maxPromptTokens ?? 0, delta.maxPromptTokens ?? 0),
|
|
322
|
+
retries: (current.retries ?? 0) + (delta.retries ?? 0),
|
|
323
|
+
cachedTokens: (current.cachedTokens ?? 0) + (delta.cachedTokens ?? 0),
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
boundedHistory() {
|
|
328
|
+
const limit = this.budget.maxRecentMessages;
|
|
329
|
+
if (this.history.length <= limit)
|
|
330
|
+
return this.history;
|
|
331
|
+
let cut = Math.max(1, this.history.length - limit);
|
|
332
|
+
while (cut < this.history.length && this.history[cut].role === 'tool')
|
|
333
|
+
cut++;
|
|
334
|
+
const removed = this.history.slice(1, cut);
|
|
335
|
+
const actions = [];
|
|
336
|
+
for (const message of removed) {
|
|
337
|
+
if (message.role === 'assistant' && Array.isArray(message.tool_calls)) {
|
|
338
|
+
for (const call of message.tool_calls) {
|
|
339
|
+
actions.push(`${call.function?.name ?? 'tool'}(${String(call.function?.arguments ?? '').slice(0, 100)})`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else if (message.role === 'tool') {
|
|
343
|
+
actions.push(`result: ${String(message.content ?? '').replace(/\s+/g, ' ').slice(0, 140)}`);
|
|
344
|
+
}
|
|
345
|
+
if (actions.length >= 24)
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
return [
|
|
349
|
+
this.history[0],
|
|
350
|
+
{
|
|
351
|
+
role: 'user',
|
|
352
|
+
content: `[DETERMINISTIC WORK LEDGER — older raw outputs omitted]\n${actions.map((item) => `- ${item}`).join('\n') || '- Earlier context omitted.'}`,
|
|
281
353
|
},
|
|
354
|
+
...this.history.slice(cut),
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
maybeEscalate() {
|
|
358
|
+
const next = nextExecutionProfile(this.budget.profile);
|
|
359
|
+
if (!next)
|
|
360
|
+
return false;
|
|
361
|
+
const info = this.board.agents.get(this.id);
|
|
362
|
+
const changedFiles = new Set(this.board.changes.filter((change) => change.agentId === this.id).map((change) => change.path)).size;
|
|
363
|
+
if (!shouldEscalateExecution(this.opts.task, info?.inspectedFiles?.length ?? 0, changedFiles))
|
|
364
|
+
return false;
|
|
365
|
+
this.budget = EXECUTION_BUDGETS[next];
|
|
366
|
+
this.maxSteps = Math.min(this.opts.maxSteps, this.budget.maxRounds);
|
|
367
|
+
this.board.updateAgent(this.id, { profile: next, currentAction: `budget escalated to ${next}` });
|
|
368
|
+
this.record({
|
|
369
|
+
role: 'user',
|
|
370
|
+
content: `[EXECUTION PROFILE ESCALATED TO ${next.toUpperCase()}] Concrete task complexity justified more budget. Continue with targeted work; repeated exploration is not justification for another escalation.`,
|
|
282
371
|
});
|
|
372
|
+
return true;
|
|
283
373
|
}
|
|
284
374
|
/**
|
|
285
375
|
* Build the live context injected before EVERY model call:
|
|
@@ -287,6 +377,9 @@ export class Agent {
|
|
|
287
377
|
* Returns { text, hasNews } — hasNews drives the 'listening' state.
|
|
288
378
|
*/
|
|
289
379
|
liveContext() {
|
|
380
|
+
if (this.board.agents.size <= 1) {
|
|
381
|
+
return { text: '[REAL TIME] No other active agent context. Continue with the smallest useful next action.', hasNews: false };
|
|
382
|
+
}
|
|
290
383
|
let hasNews = false;
|
|
291
384
|
const parts = ['[REAL TIME]', this.board.snapshotFor(this.id)];
|
|
292
385
|
const notes = this.board.notesFor(this.name, this.lastNoteId);
|
|
@@ -324,6 +417,14 @@ export class Agent {
|
|
|
324
417
|
return { text: parts.join('\n'), hasNews };
|
|
325
418
|
}
|
|
326
419
|
async run() {
|
|
420
|
+
this.board.setAgentState(this.id, 'working', 'loading project memory');
|
|
421
|
+
let sharedProjectContext = '';
|
|
422
|
+
try {
|
|
423
|
+
sharedProjectContext = (await this.opts.projectContext) ?? '';
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
sharedProjectContext = '';
|
|
427
|
+
}
|
|
327
428
|
this.board.setAgentState(this.id, 'working', 'starting');
|
|
328
429
|
if (this.opts.initialHistory && this.opts.initialHistory.length > 0) {
|
|
329
430
|
// Resume a previous conversation (/restore): re-record everything into
|
|
@@ -333,13 +434,15 @@ export class Agent {
|
|
|
333
434
|
this.record(m);
|
|
334
435
|
this.record({
|
|
335
436
|
role: 'user',
|
|
336
|
-
content:
|
|
437
|
+
content: `[SESSION RESTORED] This conversation was saved and has just been restored. Continue from where you left off. Use the shared project context below to identify what changed, and re-read only task-relevant files marked stale or files you are about to modify.
|
|
438
|
+
|
|
439
|
+
${sharedProjectContext}`,
|
|
337
440
|
});
|
|
338
441
|
}
|
|
339
442
|
else {
|
|
340
443
|
this.record({
|
|
341
444
|
role: 'system',
|
|
342
|
-
content: SYSTEM_PROMPT(this.name, this.opts.task, this.opts.mode, LANG_NAME_EN[getLang()], skillsCatalog(this.opts.skills), this.opts.specialist, this.opts.projectMemory),
|
|
445
|
+
content: SYSTEM_PROMPT(this.name, this.opts.task, this.opts.mode, LANG_NAME_EN[getLang()], skillsCatalog(this.opts.skills), this.opts.specialist, this.opts.projectMemory, sharedProjectContext, this.budget.profile),
|
|
343
446
|
});
|
|
344
447
|
// Pasted images (multimodal models): attached to the very first user turn.
|
|
345
448
|
if (this.opts.images && this.opts.images.length > 0) {
|
|
@@ -360,9 +463,24 @@ export class Agent {
|
|
|
360
463
|
*/
|
|
361
464
|
async loop() {
|
|
362
465
|
let steps = 0;
|
|
466
|
+
let closingTurnGranted = false;
|
|
363
467
|
try {
|
|
364
468
|
this.finished = false;
|
|
365
|
-
while (!this.stopped
|
|
469
|
+
while (!this.stopped) {
|
|
470
|
+
if (steps >= this.maxSteps) {
|
|
471
|
+
if (this.maybeEscalate())
|
|
472
|
+
continue;
|
|
473
|
+
if (!closingTurnGranted) {
|
|
474
|
+
closingTurnGranted = true;
|
|
475
|
+
this.maxSteps++;
|
|
476
|
+
this.record({
|
|
477
|
+
role: 'user',
|
|
478
|
+
content: '[FINAL BUDGET TURN] Do not inspect further. Call task_complete now with the strongest conclusion supported by current evidence, explicitly stating any remaining uncertainty.',
|
|
479
|
+
});
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
366
484
|
await this.waitWhilePaused();
|
|
367
485
|
if (this.stopped)
|
|
368
486
|
break;
|
|
@@ -380,13 +498,12 @@ export class Agent {
|
|
|
380
498
|
if (live.hasNews) {
|
|
381
499
|
// Visible (and audible via state event) cue: the agent is listening to the others.
|
|
382
500
|
this.board.setAgentState(this.id, 'listening', 'reading the other agents’ work…');
|
|
383
|
-
await new Promise((r) => setTimeout(r, 600));
|
|
384
501
|
if (this.stopped)
|
|
385
502
|
break;
|
|
386
503
|
}
|
|
387
504
|
this.repairToolCallHistory();
|
|
388
505
|
const messages = [
|
|
389
|
-
...this.
|
|
506
|
+
...this.boundedHistory(),
|
|
390
507
|
{ role: 'user', content: live.text },
|
|
391
508
|
];
|
|
392
509
|
this.board.setAgentState(this.id, 'thinking');
|
|
@@ -396,8 +513,12 @@ export class Agent {
|
|
|
396
513
|
const onStop = () => this.llmAbort?.abort();
|
|
397
514
|
this.abort.signal.addEventListener('abort', onStop, { once: true });
|
|
398
515
|
let res;
|
|
516
|
+
const llmStartedAt = Date.now();
|
|
399
517
|
try {
|
|
400
|
-
res = await this.llm.chat(messages, TOOL_DEFINITIONS, this.llmAbort.signal
|
|
518
|
+
res = await this.llm.chat(messages, TOOL_DEFINITIONS, this.llmAbort.signal, {
|
|
519
|
+
maxTokens: this.budget.profile === 'quick' ? 2_048 : 4_096,
|
|
520
|
+
timeoutMs: this.budget.profile === 'quick' ? 45_000 : this.budget.profile === 'standard' ? 90_000 : 180_000,
|
|
521
|
+
});
|
|
401
522
|
}
|
|
402
523
|
catch (err) {
|
|
403
524
|
if (!this.stopped && this.steered) {
|
|
@@ -415,7 +536,13 @@ export class Agent {
|
|
|
415
536
|
this.llmAbort = null;
|
|
416
537
|
}
|
|
417
538
|
this.steered = false;
|
|
418
|
-
this.updatePerf({
|
|
539
|
+
this.updatePerf({
|
|
540
|
+
modelTurns: 1,
|
|
541
|
+
llmMs: Date.now() - llmStartedAt,
|
|
542
|
+
maxPromptTokens: res.tokensIn,
|
|
543
|
+
retries: res.retries,
|
|
544
|
+
cachedTokens: res.cachedTokens,
|
|
545
|
+
});
|
|
419
546
|
const a = this.board.agents.get(this.id);
|
|
420
547
|
if (a) {
|
|
421
548
|
// Real-time financial view: accrue the cost of this round immediately.
|
|
@@ -429,6 +556,15 @@ export class Agent {
|
|
|
429
556
|
ctxPct: Math.min(100, Math.round((res.tokensIn / CONTEXT_WINDOW) * 100)),
|
|
430
557
|
});
|
|
431
558
|
}
|
|
559
|
+
const currentPerf = this.board.agents.get(this.id)?.perf;
|
|
560
|
+
const budgetRatio = Math.max(steps / this.budget.maxRounds, (this.board.agents.get(this.id)?.tokensIn ?? 0) / this.budget.maxInputTokens, (currentPerf?.toolCalls ?? 0) / this.budget.maxToolCalls);
|
|
561
|
+
if (budgetRatio >= this.budget.convergenceAt && !this.convergenceWarned.has(this.budget.profile)) {
|
|
562
|
+
this.convergenceWarned.add(this.budget.profile);
|
|
563
|
+
this.record({
|
|
564
|
+
role: 'user',
|
|
565
|
+
content: '[BUDGET CONVERGENCE] You are approaching this execution profile budget. Stop broad exploration. Use the evidence already collected, perform at most one targeted verification, then call task_complete.',
|
|
566
|
+
});
|
|
567
|
+
}
|
|
432
568
|
const msg = res.message;
|
|
433
569
|
if (msg.content && msg.content.trim()) {
|
|
434
570
|
// "✻" marks thinking/commentary steps — visually distinct from tool lines.
|
|
@@ -486,11 +622,34 @@ export class Agent {
|
|
|
486
622
|
this.board.updateAgent(this.id, { currentAction: label.slice(0, 80) });
|
|
487
623
|
const shellStartedAt = tc.function.name === 'run_command' ? Date.now() : 0;
|
|
488
624
|
let result;
|
|
489
|
-
|
|
490
|
-
|
|
625
|
+
const perfBefore = this.board.agents.get(this.id)?.perf;
|
|
626
|
+
if ((perfBefore?.toolCalls ?? 0) >= this.budget.maxToolCalls) {
|
|
627
|
+
result = 'BUDGET: tool-call limit reached. Conclude with the evidence already collected.';
|
|
628
|
+
}
|
|
629
|
+
else if (tc.function.name === 'run_command' && (perfBefore?.shellCommands ?? 0) >= this.budget.maxShellCommands) {
|
|
630
|
+
result = 'BUDGET: shell-command limit reached. Use existing evidence or a non-shell targeted tool, then conclude.';
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
try {
|
|
634
|
+
result = await this.executor.execute(tc.function.name, args);
|
|
635
|
+
}
|
|
636
|
+
catch (err) {
|
|
637
|
+
result = `ERROR: ${err?.message ?? String(err)}`;
|
|
638
|
+
}
|
|
491
639
|
}
|
|
492
|
-
|
|
493
|
-
|
|
640
|
+
if (result.length > this.budget.maxResultChars) {
|
|
641
|
+
const artifactId = `artifact-${++this.artifactSeq}.txt`;
|
|
642
|
+
const artifactFile = path.join(this.opts.projectRoot, '.parallel', 'runs', this.id, 'artifacts', artifactId);
|
|
643
|
+
try {
|
|
644
|
+
writeFileAtomicPrivate(artifactFile, result);
|
|
645
|
+
result =
|
|
646
|
+
`${result.slice(0, this.budget.maxResultChars)}\n` +
|
|
647
|
+
`... (${result.length.toLocaleString()} characters total; full output stored as ${artifactId}. ` +
|
|
648
|
+
`Use read_artifact with this id and a targeted line range if more evidence is required.)`;
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
result = `${result.slice(0, this.budget.maxResultChars)}\n... (truncated by execution budget)`;
|
|
652
|
+
}
|
|
494
653
|
}
|
|
495
654
|
const shellMs = shellStartedAt ? Date.now() - shellStartedAt : 0;
|
|
496
655
|
const readOnlyShell = tc.function.name === 'run_command' && isReadOnlyShell(String(args.command ?? ''));
|
|
@@ -524,6 +683,7 @@ export class Agent {
|
|
|
524
683
|
lastResult: `${noChangePrefix}${summary}`,
|
|
525
684
|
progressSteps: (this.board.agents.get(this.id)?.progressSteps ?? []).map((s) => ({ ...s, status: 'done' })),
|
|
526
685
|
});
|
|
686
|
+
this.opts.onComplete?.(this.id, summary);
|
|
527
687
|
// ONE short headline note (the full summary lives in lastResult and
|
|
528
688
|
// is rendered as the agent's recap) — no duplicated walls of text.
|
|
529
689
|
const headline = summary.split('\n').find((l) => l.trim())?.trim() ?? 'Task complete.';
|
|
@@ -547,7 +707,8 @@ export class Agent {
|
|
|
547
707
|
this.board.setAgentState(this.id, 'done', 'done ✅');
|
|
548
708
|
return;
|
|
549
709
|
}
|
|
550
|
-
|
|
710
|
+
if (this.budget.profile === 'deep')
|
|
711
|
+
await this.compactHistory();
|
|
551
712
|
}
|
|
552
713
|
if (!this.stopped) {
|
|
553
714
|
this.board.setAgentState(this.id, 'error', `step limit of ${this.maxSteps} reached`);
|
|
@@ -572,6 +733,8 @@ export class Agent {
|
|
|
572
733
|
return `📖 read ${args.path}`;
|
|
573
734
|
case 'read_many':
|
|
574
735
|
return `📚 read ${Array.isArray(args.paths) ? args.paths.slice(0, 3).join(', ') : 'files'}`;
|
|
736
|
+
case 'read_artifact':
|
|
737
|
+
return `📖 artifact ${args.id}`;
|
|
575
738
|
case 'write_file':
|
|
576
739
|
return `✏ write ${args.path}`;
|
|
577
740
|
case 'edit_file':
|
|
@@ -662,6 +825,7 @@ export class Agent {
|
|
|
662
825
|
}
|
|
663
826
|
this.board.updateAgent(this.id, { currentAction: t('agent.compactingShort') });
|
|
664
827
|
this.board.log(this.id, 'memory', t('agent.compactingStart'));
|
|
828
|
+
const compactStartedAt = Date.now();
|
|
665
829
|
const res = await this.llm.chat([
|
|
666
830
|
{
|
|
667
831
|
role: 'system',
|
|
@@ -669,6 +833,11 @@ export class Agent {
|
|
|
669
833
|
},
|
|
670
834
|
{ role: 'user', content: lines.join('\n') },
|
|
671
835
|
], undefined, this.abort.signal);
|
|
836
|
+
this.updatePerf({
|
|
837
|
+
compactionTurns: 1,
|
|
838
|
+
compactionMs: Date.now() - compactStartedAt,
|
|
839
|
+
maxPromptTokens: res.tokensIn,
|
|
840
|
+
});
|
|
672
841
|
const a = this.board.agents.get(this.id);
|
|
673
842
|
if (a) {
|
|
674
843
|
const price = this.opts.price;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const EXECUTION_BUDGETS = {
|
|
2
|
+
quick: {
|
|
3
|
+
profile: 'quick',
|
|
4
|
+
maxRounds: 6,
|
|
5
|
+
maxToolCalls: 12,
|
|
6
|
+
maxShellCommands: 2,
|
|
7
|
+
maxInputTokens: 150_000,
|
|
8
|
+
maxResultChars: 8_000,
|
|
9
|
+
maxRecentMessages: 24,
|
|
10
|
+
convergenceAt: 0.7,
|
|
11
|
+
},
|
|
12
|
+
standard: {
|
|
13
|
+
profile: 'standard',
|
|
14
|
+
maxRounds: 16,
|
|
15
|
+
maxToolCalls: 32,
|
|
16
|
+
maxShellCommands: 6,
|
|
17
|
+
maxInputTokens: 600_000,
|
|
18
|
+
maxResultChars: 16_000,
|
|
19
|
+
maxRecentMessages: 42,
|
|
20
|
+
convergenceAt: 0.75,
|
|
21
|
+
},
|
|
22
|
+
deep: {
|
|
23
|
+
profile: 'deep',
|
|
24
|
+
maxRounds: 60,
|
|
25
|
+
maxToolCalls: 120,
|
|
26
|
+
maxShellCommands: 30,
|
|
27
|
+
maxInputTokens: 3_000_000,
|
|
28
|
+
maxResultChars: 32_000,
|
|
29
|
+
maxRecentMessages: 80,
|
|
30
|
+
convergenceAt: 0.82,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const COMPLEX = /\b(migrat|refactor|architecture|redesign|rewrite|exhaustive|end[- ]to[- ]end|across|monorepo|multi[- ]service|security audit|performance audit|release|deploy)\b/i;
|
|
34
|
+
const SIMPLE = /\b(explain|find|locate|where|why|diagnos|inspect|verify|check|typo|rename|toggle|small|simple)\b/i;
|
|
35
|
+
export function classifyExecutionProfile(task, mode, forced) {
|
|
36
|
+
if (forced)
|
|
37
|
+
return forced;
|
|
38
|
+
if (mode === 'plan')
|
|
39
|
+
return 'deep';
|
|
40
|
+
if (mode === 'ask')
|
|
41
|
+
return COMPLEX.test(task) || task.length > 1_200 ? 'standard' : 'quick';
|
|
42
|
+
const pathMentions = task.match(/\b[\w./-]+\.(?:ts|tsx|js|mjs|json|md|py|rs|go|java)\b/g)?.length ?? 0;
|
|
43
|
+
if (COMPLEX.test(task) || pathMentions > 3 || task.length > 1_600)
|
|
44
|
+
return 'standard';
|
|
45
|
+
if (SIMPLE.test(task) || pathMentions <= 1 || task.length < 500)
|
|
46
|
+
return 'quick';
|
|
47
|
+
return 'standard';
|
|
48
|
+
}
|
|
49
|
+
export function nextExecutionProfile(profile) {
|
|
50
|
+
if (profile === 'quick')
|
|
51
|
+
return 'standard';
|
|
52
|
+
if (profile === 'standard')
|
|
53
|
+
return 'deep';
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
export function shouldEscalateExecution(task, inspectedFiles, changedFiles) {
|
|
57
|
+
return COMPLEX.test(task) || inspectedFiles > 3 || changedFiles > 3;
|
|
58
|
+
}
|