Skip to content

Commit ec180d7

Browse files
ndemiancclaude
andcommitted
Atom++ Agent Sketch: visual multi-agent flow canvas (SK1)
Sketch an agentic flow on a canvas, run it on Atom++'s own multi-provider layer (BYOK, text-only nodes), and meter tokens + cost per agent. - sketch/agentCatalog.js: 96 agent archetypes across 13 palette groups (from ruflo, MIT (c) ruvnet -- names + one-line roles only; descriptions de-truncated). sketch/graph.js: DAG validate + Kahn topo levels (parallel where possible). sketch/pricing.js: $/MTok table + cost. sketch/templates.js: ready-made flows including the "Design a Key-Value Store" topology (System Design Interview / ByteByteGo). - sketch.js runner: reentrancy-guarded + abortable; auto-continuation so a node's work is never truncated (continues across turns when it hits the per-turn cap); board command (natural language -> graph ops); resolved price map; cross-provider model guard. - media/sketch.html: zoom/pan canvas, drag-drop palette (search + tier chips), edge wiring, per-node model + rename, Run/Stop with live per-node meters, what-if cost chips, Tidy. - test/sketch.test.js (16 tests); docs/atompp-agent-sketch-design.md. - command atompp.ai.sketch + Customize panel button. extension.js/package.json also carry editor-prep that shares these files: the ai.focus keybinding -> ctrl+cmd+i (was colliding with Toggle Developer Tools), the launch-capped auto-reveal, and the atompp.ai.agent.maxTokens setting that backs the continuation cap. Hardened via two adversarial-review passes (36 findings fixed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c6e2f76 commit ec180d7

11 files changed

Lines changed: 3955 additions & 281 deletions

File tree

‎docs/atompp-agent-sketch-design.md‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Atom++ — Agent Sketch — Design
2+
3+
> **Status:** SK1 skeleton shipped · **Date:** 2026-07-01 · **Owner:** Sergii Demianchuk
4+
> **Vision:** Sketch a whole agentic system visually, right inside the Atom++ editor — connect
5+
> specialized agents into flows, watch **tokens burned per step**, and **recalculate the same flow
6+
> against different models** before committing to one. Chaining in the spirit of
7+
> [ruflo](https://github.com/ruvnet/ruflo) ("Agent = Model + Harness"; 100+ specialized agents),
8+
> with the canvas interaction model adapted from the SystemSketch (SysDes) prototype.
9+
10+
## 0. Why this is a natural Atom++ feature
11+
12+
Everything below sits on the multi-provider layer already shipped:
13+
14+
| Need | Already built |
15+
|---|---|
16+
| Run one agent step on ANY provider, BYO-key | P1/P2 — `providers.streamAgentTurn` (Anthropic native + OpenAI-compatible translation) |
17+
| **Real token usage per step** | P2 — `usage` from `message_start`/`stream_options.include_usage` |
18+
| Per-model capabilities to pick sensible defaults | P4 — `providers/catalog.js` tiers/caps |
19+
| Cost math for the meter + what-if | `sketch/pricing.js` ($/MTok table; Anthropic published, others list-price estimates, labeled "est.") |
20+
| No middle-man backend, keys in the keychain | The Atom++ architecture invariant |
21+
22+
## 1. The SK1 skeleton (shipped)
23+
24+
```
25+
extensions/atom-ai/
26+
sketch.js controller: panel, persistence, THE RUNNER (reentrancy-guarded, abortable)
27+
sketch/agentCatalog.js 96 agent archetypes in 13 palette groups (derived from ruflo, MIT © ruvnet — names + one-line roles only)
28+
sketch/templates.js ready-made topologies — incl. the "Design a Key-Value Store" hero template [unit-tested]
29+
sketch/graph.js pure DAG logic: validate (cycles/dangling), Kahn topo LEVELS, node-input builder [unit-tested]
30+
sketch/pricing.js $/MTok table + exact→basename→family matching + costOf() [unit-tested]
31+
media/sketch.html the canvas webview (vanilla JS, no build step)
32+
```
33+
34+
### Templates — drop a whole topology, set the goal, run
35+
`sketch/templates.js` ships pre-wired flows loadable from the **Templates…** dropdown; each is a full
36+
sketch (nodes + edges + per-node instructions) laid out on a grid. The hero is **Design a Key-Value
37+
Store**, a faithful 11-node / 6-level topology of the *System Design Interview* ("Design a Key-Value
38+
Store" / ByteByteGo) chapter:
39+
40+
```
41+
requirements → architecture → [ storage-engine · consistent-hash · replication+quorum ·
42+
vector-clocks · gossip+failover ] (parallel)
43+
→ integrate+server → [ tests · build+validate ] → review+run-guide
44+
```
45+
46+
Every node carries a precise task pinning its aspect (WAL/memtable/SSTable/bloom/compaction;
47+
consistent-hashing ring; N/W/R quorum + read-repair; vector-clock reconciliation; gossip + sloppy
48+
quorum + hinted handoff + Merkle anti-entropy; coordinator/request routing; cluster tests). The
49+
flow **goal** (top of the canvas) sets the language — type *"build using C"* and the flow produces a
50+
C implementation covering every aspect. Other templates: feature-pipeline, research-synthesize,
51+
code-review-swarm, SPARC, TDD, bug-fix, API-service, security-audit, refactor.
52+
53+
### Hardening (post adversarial review)
54+
The `Workflow` adversarial review surfaced 31 confirmed findings; the load-bearing ones are fixed:
55+
- **Reentrancy / unstoppable spend** — `runSketch` claims a run slot *synchronously* before any
56+
`await` and clears it only if still its own; a double-clicked Run can't orphan the first run, so
57+
**Stop always aborts** (real BYOK money).
58+
- **Abort ≠ failure** — a user Stop paints in-flight nodes `stopped`, not red `error`, and `runDone`
59+
no longer reports `failed`.
60+
- **Truncation surfaced** — a node that hits the per-node cap is badged `⚠ truncated` and its stored
61+
output marked incomplete (cap raised 4096 → 8192 so a full module fits).
62+
- **`uiError` channel** — save/load/clipboard failures no longer masquerade as run errors.
63+
- **Pricing drift killed** — the webview's hand-rolled matcher is gone; the extension resolves each
64+
model's price once (authoritative matcher) and sends the map.
65+
- **Hostile-JSON safe** — loaded sketches are sanitized (array coercion, `__proto__`/dup-id
66+
rejection, dangling-edge drop); disk-sourced strings are HTML-escaped before `innerHTML`.
67+
- **Cross-provider guard** — a per-node model override invalid on the active provider falls back to
68+
the tier default with a note (no stray `gpt-4o` 400 on a Claude key).
69+
- **UX** — node rename, run-gating of Load/Templates/Clear, catalog descriptions de-truncated.
70+
71+
- **Palette → canvas → connect → Run.** Drag agents (coder, planner, reviewer, security-manager,
72+
raft-manager, pr-manager, …) onto the canvas; wire output→input ports into a DAG (cycles are
73+
rejected live). `Run` executes **levels in parallel, chains in sequence** (Kahn levels — ruflo's
74+
mesh/pipeline hybrid).
75+
- **Each node = one turn on the active provider** via `streamAgentTurn` (no tools in SK1): system
76+
prompt = the agent's role from the catalog (+ optional per-node task), user message = flow goal +
77+
upstream agents' outputs.
78+
- **Metering:** every node card shows real tokens in/out + estimated cost; the footer totals roll up
79+
live. The **what-if selector re-prices the whole recorded run on a different model** without
80+
re-running — the feature that answers "what would this flow cost on Haiku vs Opus?"
81+
- **Per-node model:** catalog `tier` (fast/balanced/powerful) → provider-specific default
82+
(`MODEL_TIERS`), overridable per node in the inspector.
83+
- **Persistence:** named sketches in the workspace at `.atompp/sketches/<name>.json`.
84+
- Command: **`atompp.ai.sketch`** ("AI: Agent Sketch…"), also in the Customize panel.
85+
86+
### Safety posture (SK1)
87+
Sketch nodes are **text-only** — no file edits, no commands, no tools. A sketch run cannot touch the
88+
workspace; it only spends the user's own tokens (BYOK, direct to provider, key never in the sketch
89+
files). Costs shown are planning estimates from list prices — the provider bills the user directly.
90+
91+
### Canvas UX (SK1.1)
92+
- **No lost work.** A node runs to completion via `runNodeTurn` auto-continuation: `streamAgentTurn`
93+
caps a single turn at 8192 tokens, and if the model stops on `max_tokens` the runner feeds its own
94+
partial back with a "continue where you left off" instruction and keeps going — accumulating text
95+
and usage across up to 8 turns. A whole code module (or the KV-store integrator's output) is no
96+
longer truncated. Nodes that needed more than one turn show `⟳n`; only exhausting the 8-turn safety
97+
cap marks a node truncated.
98+
- **Zoom + bigger board.** 12000×8000 world; `translate(pan)·scale(zoom)` with wheel-zoom (cursor-
99+
anchored), +/−/reset/fit controls, ⌘0 / ⌘± shortcuts, and a **Fit** that frames the whole flow.
100+
All drop/drag/wire math goes through `toWorld()` so it's correct at any zoom.
101+
- **Board command bar (LLM edits the graph).** Type an instruction — *"add a security-auditor after
102+
coder"*, *"set every agent to Opus 4.8"*, *"tidy the layout"* — and the active model returns a JSON
103+
op list (`add`/`connect`/`disconnect`/`remove`/`setModel` scoped all|agent|node/`setTask`/`setGoal`/
104+
`layout`) applied deterministically in the webview. Unknown agents/refs are skipped with a note; the
105+
DAG stays acyclic.
106+
- **Active-node highlight redesigned.** The marching-ants border is gone (it flickered over the
107+
connector dots); the running node now has a breathing glow + a header shimmer, and ports sit above
108+
it (`z-index`) so they never blink.
109+
- **Palette optimized.** Live search filter, per-agent tier chip (f/b/p), sticky group headers,
110+
expand/collapse-all.
111+
- **What-if reworked.** Instead of one dropdown, a row of chips shows what *this exact flow* would cost
112+
on each candidate model (the active provider's fast/balanced/powerful tiers + any model actually
113+
used), cheapest first, current run marked. Clicking a chip switches every agent to that model — so
114+
the comparison is also the action.
115+
- **Tidy.** One-click `autoLayout` (topological columns) re-arranges the flow left→right by dependency.
116+
117+
## 2. Roadmap
118+
119+
| Phase | Deliverable |
120+
|---|---|
121+
| **SK2** | **Tool-using nodes**: a node can run the full `agent.js` loop (read/edit/run with Keep-Undo review + per-run checkpoint) so a sketched flow actually edits the workspace; live streamed output in the inspector; run history. |
122+
| **SK3** | **Topologies**: coordinator/hierarchical patterns (a queen node that dispatches + collects, ruflo-style), conditional edges, retry/verify loops; per-flow token budgets with early stop. |
123+
| **SK4** | **Interop**: import/export ruflo swarm configs; publish sketches as shareable templates; sketch → `atompp.ai` chat-agent handoff ("run this flow on the current repo"). |
124+
125+
## 3. Attribution & licensing
126+
Agent names/roles derived from ruflo's `.claude/agents/` library (MIT © 2024–2026 ruvnet) — metadata
127+
only (name + one-line role); every node executes on Atom++'s own agent loop and providers. The canvas
128+
interaction model (palette/nodes/edges/live metrics) is adapted from the internal SysDes prototype.

‎extensions/atom-ai/customize.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ function htmlFor(nonce, s) {
7777
'<div class="btns">' + btn('Change model / provider', 'atompp.ai.pickModel', true) +
7878
btn(keyBtn, 'atompp.ai.setApiKey', !s.keySet && !s.noKey) +
7979
btn('Sign in to Atom++', 'atompp.ai.account') +
80-
btn('Open chat', 'atompp.ai.focus') + '</div>');
80+
btn('Open chat', 'atompp.ai.focus') +
81+
btn('Agent Sketch', 'atompp.ai.sketch') + '</div>');
8182

8283
const look = section('🎨', 'Appearance & keys',
8384
row('Theme', s.theme || '') +

‎extensions/atom-ai/extension.js‎

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function post(msg) { if (activeWebview) { activeWebview.postMessage(msg); } }
5656
function aiConfig() { return vscode.workspace.getConfiguration('atompp.ai'); }
5757

5858
/** Inline debug trace — prints a 🐛 DEBUG line in the chat (gated by atompp.ai.debug). */
59-
function dbg(label, data) { if (aiConfig().get('debug', true)) { post({ type: 'debug', label, data: data != null ? data : null }); } }
59+
function dbg(label, data) { if (aiConfig().get('debug', false)) { post({ type: 'debug', label, data: data != null ? data : null }); } }
6060

6161
/** The currently selected provider id (settings value; `claude` is the default/legacy Anthropic). */
6262
function currentProviderId() { return providers.normId(aiConfig().get('provider', 'claude')); }
@@ -688,6 +688,7 @@ async function agentFlow(text) {
688688
apiKey: req.apiKey,
689689
model: req.model,
690690
maxSteps: Math.max(1, cfg.get('agent.maxSteps', 25)),
691+
maxTokens: Math.max(1024, cfg.get('agent.maxTokens', 8192)), // per-turn output cap; continued across turns if hit
691692
post, dbg,
692693
approve: requestApproval, // run_command only
693694
ask: requestQuestions, // ask_user — clickable clarifying questions
@@ -714,6 +715,7 @@ async function agentFlow(text) {
714715

715716
async function handleSend(text) {
716717
if (!text || !text.trim()) { return; }
718+
if (ctx) { ctx.globalState.update('atompp.ai.hasSentMessage', true); } // user engaged → stop auto-revealing the panel on launch
717719
if (agentMode) { await agentFlow(text); return; }
718720
const cfg = aiConfig();
719721
const providerId = currentProviderId();
@@ -1029,6 +1031,14 @@ function activate(context) {
10291031
vscode.window.onDidChangeActiveTextEditor(() => postActiveFile()),
10301032
vscode.commands.registerCommand('atompp.ai.focus', () => vscode.commands.executeCommand('atomAi.chat.focus')),
10311033
vscode.commands.registerCommand('atompp.customize', () => openCustomize(context)),
1034+
// Agent Sketch: the visual multi-agent flow canvas. Lazy require — only loads when opened.
1035+
vscode.commands.registerCommand('atompp.ai.sketch', () => {
1036+
try {
1037+
require('./sketch').openSketch(context, { prepProviderRequest, aiConfig, currentProviderId });
1038+
} catch (e) {
1039+
vscode.window.showErrorMessage('Agent Sketch failed to load: ' + ((e && e.message) || e));
1040+
}
1041+
}),
10321042
vscode.commands.registerCommand('atompp.import.vscode', () => importFromVscode(context)),
10331043
vscode.commands.registerCommand('atompp.ai.newChat', newChat),
10341044
vscode.commands.registerCommand('atompp.ai.pickModel', pickModel),
@@ -1081,12 +1091,19 @@ function activate(context) {
10811091
fastCompletionModel: catalog.fastCompletionModel
10821092
});
10831093

1084-
// AI-first: reveal the chat (in the secondary side bar) on first launch. We do this once
1085-
// and then defer to VS Code's own per-workspace layout persistence, so if the user later
1086-
// closes the panel we don't keep forcing it back open.
1087-
if (!context.globalState.get('atompp.ai.didAutoOpen')) {
1088-
context.globalState.update('atompp.ai.didAutoOpen', true);
1089-
setTimeout(() => { vscode.commands.executeCommand('atomAi.chat.focus'); }, 600);
1094+
// AI-first: reveal the chat (in the secondary side bar) on EVERY launch until the user has actually
1095+
// engaged (sent their first message). This makes sure new users always see it, instead of it only
1096+
// showing once. Once they've sent a message (handleSend sets the flag) we stop forcing it and defer
1097+
// to VS Code's own per-workspace layout persistence, so closing it stays closed.
1098+
// Fallback guard: if the webview never renders (provider error, missing resource) hasSentMessage
1099+
// is never set, which would otherwise force the panel open forever. Stop after a few launches.
1100+
const AUTO_REVEAL_MAX_LAUNCHES = 5;
1101+
if (!context.globalState.get('atompp.ai.hasSentMessage')) {
1102+
const launches = (Number(context.globalState.get('atompp.ai.autoRevealLaunches')) || 0) + 1;
1103+
context.globalState.update('atompp.ai.autoRevealLaunches', launches);
1104+
if (launches <= AUTO_REVEAL_MAX_LAUNCHES) {
1105+
setTimeout(() => { vscode.commands.executeCommand('atomAi.chat.focus'); }, 600);
1106+
}
10901107
}
10911108

10921109
// First-launch onboarding: open the Welcome walkthrough once.

0 commit comments

Comments
 (0)