Stop writing fragile AI scripts. Write programs that Claude executes one thought at a time.
CVM is dual-licensed under AGPL-3.0-or-later for open source use and a commercial license for proprietary integration. See COMMERCIAL.md for details.
TRADITIONAL SCRIPT
π Gray = Script Operations
flowchart TD
Start([Start]) --> Loop{More files?}
Loop -->|Yes| Call[Call Claude API with process file prompt]
Call --> Response[Get Response]
Response --> Lost[β Context Lost]
Lost --> Loop
Loop -->|No| End([End])
Response -.->|Crash| Restart[Start Over From Beginning]
classDef script fill:#e0e0e0,stroke:#666,color:#000
class Start,Loop,Call,Response,Lost,End,Restart script
CVM ARCHITECTURE
π’ Green = CVM Operations | π΅ Blue = Claude Actions | π‘ Yellow = Human Control
flowchart TD
Start([Start]) --> Loop{More files?}
Loop -->|Yes| CC[CC: analyze file]
CC --> Pause((βΈοΈ PAUSE))
Pause --> Wait[Wait for Claude]
Wait --> GetTask[Claude: getTask]
GetTask --> Process[Claude analyze file]
Process --> Submit[Claude: submitTask]
Submit --> Save[β State saved]
Save --> Loop
Loop -->|No| End([End])
Pause -.->|Can inspect| Status[Check Progress]
Submit -.->|Crash| Resume[Resume from here, just going to start]
classDef cvm fill:#d4ffd4,stroke:#090,color:#000
classDef claude fill:#d4d4ff,stroke:#00d,color:#000
classDef human fill:#ffffd4,stroke:#990,color:#000
class Start,Loop,CC,Pause,Wait,Save,End cvm
class GetTask,Process,Submit claude
class Status,Resume human
Without CVM, you manually chain calls. If it crashes, state is lost:
// Fragile, stateless, and hard to inspect
const result1 = await claude.call("Step 1: Analyze this");
const result2 = await claude.call("Step 2: Based on '" + result1 + "', do this");
// If this fails, you have to start over from scratchWith CVM, you write a simple program. The VM manages state:
// Resilient, stateful, and observable
function main() {
const step1 = CC("Step 1: Analyze this");
const step2 = CC("Step 2: Based on '" + step1 + "', do this");
return step2;
}The magic: CVM saves step1 before moving to step2. You can stop, inspect, and resume anytime.
Save this as counter.ts:
function main() {
var count = 0;
while (count < 5) {
var next = CC("Current number is " + count + ". What's the next number?");
count = +next;
}
return count;
}Tell Claude: "Run counter.ts with CVM"
What happens:
- CVM loads your program and starts execution
- At each
CC(), CVM pauses and waits - Claude pulls the next task: "Current number is 0. What's the next number?"
- Claude submits "1"
- CVM updates
countand continues the loop - Repeat until done
graph LR
subgraph "CVM System"
PROGRAM["π Program<br/>(Instructions)"]-->|"loaded into"| CVM["π₯οΈ CVM<br/>(CPU + RAM)"]
CVM-->|"executes until CC()<br/>then halts"| HALT["βΈοΈ Halted<br/>(Awaiting input)"]
HALT-->|"getTask"| CLAUDE["π§ Claude<br/>(Driver)"]
CLAUDE-->|"submitTask"| CVM
USER["π€ User<br/>(Observer)"]-."can interact<br/>with Claude".->CLAUDE
end
Traditional scripts treat Claude as a service. CVM treats Claude as the driver.
Unique Architecture Benefits:
- Inverted control flow: Instead of your code calling AI APIs, the VM simply pauses and waits. Claude actively pulls tasks when ready
- State preservation: The VM maintains all state across interactions, preventing context loss in long-running tasks
- Focused cognitive tasks: Developers only need to handle specific decision points, not manage the entire flow
Developer Experience:
- Simple API: Just 6 straightforward functions (load, start, getTask, submitTask, status, etc.)
- TypeScript native: Write programs in familiar TypeScript syntax
- Clean separation: Deterministic logic in code, cognitive tasks delegated to AI
- MongoDB or File persistence: Automatic state management between calls
Use Cases for Developers:
- Complex multi-step workflows that need AI reasoning
- Data processing pipelines with intelligent decision points
- Interactive development tools that adapt based on context
- Testing scenarios that require creative input
- Code generation workflows with checkpoints
What Makes It Special: The "GPS analogy" from Claude's docs is perfect - developers write the route, but don't have to worry about getting lost in complex flows. The VM guides execution step-by-step, making it ideal for tasks that would otherwise overwhelm AI context windows.
It's particularly elegant for developers who want to build AI-augmented tools without wrestling with prompt engineering and state management complexities.
CC(prompt) doesn't mean "call Claude." It means:
stateDiagram-v2
[*] --> Package: CC(analyze this)
Package --> PAUSE: Create task
PAUSE --> WAIT: Execution stops here
WAIT --> PULL: Claude calls getTask
PULL --> PROCESS: Claude processes
PROCESS --> RESUME: Claude submits result
RESUME --> [*]: Continue with result
state Package {
[*] --> p1: Package prompt
p1 --> p2: Into task object
}
state PAUSE {
[*] --> pause: βΈοΈ Program paused
pause --> save: State saved
}
state WAIT {
[*] --> waiting: π Waiting for Claude
}
It's like yield in Python or await in JavaScript, but for cognitive tasks.
function main() {
var files = fs.listFiles("./docs");
var summaries = []; // State lives safely in CVM
for (const file of files) {
// PAUSE: Ask Claude to summarize this file
var content = CC("Read and summarize: " + file);
summaries.push({ filename: file, summary: content });
// CVM automatically resumes here with content
}
// PAUSE: Ask Claude to create final report
var report = CC("Create report from: " + JSON.stringify(summaries));
return report;
}flowchart TD
START([Start]) --> LIST[fs.listFiles]
LIST --> INIT[summaries = empty array]
INIT --> LOOP{For each file}
LOOP -->|Has files| CC1[CC: Read and summarize file]
CC1 --> PAUSE1[βΈοΈ CVM Pauses]
PAUSE1 --> CLAUDE1[Claude pulls task]
CLAUDE1 --> SUMMARY[Claude returns summary]
SUMMARY --> SAVE[Push to summaries array]
SAVE --> LOOP
LOOP -->|No more files| CC2[CC: Create final report]
CC2 --> PAUSE2[βΈοΈ CVM Pauses]
PAUSE2 --> CLAUDE2[Claude creates report]
CLAUDE2 --> RETURN[Return report]
RETURN --> END([End])
CRASH{{If crash at file 500}} -.-> STATE[(CVM State:<br/>499 summaries<br/>preserved)]
STATE -.-> RESUME[Can resume<br/>from file 500]
CVM turns this into a resilient workflow. If it fails on file 500 of 1000, the first 499 summaries are safely stored in CVM's state.
graph LR
subgraph YourScript["Your Script"]
S1[β Stateless]
S2[β Fragile]
S3[β Opaque]
S4[β Rigid]
S1 --> API1[API Call 1]
API1 --> S2
S2 --> API2[API Call 2]
API2 --> S3
S3 --> CRASH[π₯ Crash = Start Over]
end
subgraph CVMArch["CVM"]
C1[β
Stateful]
C2[β
Resilient]
C3[β
Observable]
C4[β
Flexible]
C1 --> TASK1[Task 1]
TASK1 --> STATE1[(State Preserved)]
STATE1 --> TASK2[Task 2]
TASK2 --> STATE2[(State Updated)]
STATE2 --> PAUSE[βΈοΈ Can Pause/Resume]
end
| Your Script | CVM |
|---|---|
| β Stateless: Each API call starts fresh | β Stateful: All variables persist automatically |
| β Fragile: Crash = start over | β Resilient: State survives, resume anytime |
| β Opaque: Can't see progress | β Observable: Check status anytime |
| β Rigid: Can't intervene | β Flexible: Paused by default at each CC() |
CVM is a passive MCP server. Claude actively drives execution:
sequenceDiagram
participant Claude
participant CVM
participant State as CVM State
Claude->>CVM: load("counter", "...program code...")
CVM->>State: Store program
Claude->>CVM: start("counter", "exec-123")
CVM->>State: Initialize count = 0
loop While count < 5
Claude->>CVM: getTask("exec-123")
CVM-->>Claude: "Current number is 0. What's the next number?"
Claude->>Claude: Process task
Claude->>CVM: submitTask("exec-123", "1")
CVM->>State: count = 1
Note over CVM: Continue loop execution
end
Claude->>CVM: getTask("exec-123")
CVM-->>Claude: "Execution completed with result: 5"
CVM is completely passive - it never initiates anything. Claude drives everything.
The getTask/submitTask interaction shown above follows Claude's Model Context Protocol (MCP). MCP is a specification for building stateful, resilient tools that Claude can operate. CVM acts as an MCP server, which is what allows Claude to actively drive the execution. You don't need to understand MCP to use CVM, but knowing it's built on this standard helps explain its unique, passive architecture.
You might wonder: "How can a TypeScript while loop be paused mid-execution?" The answer is that CVM isn't running your code with Node.js or ts-node. Instead, CVM uses a custom interpreter that:
- Parses your TypeScript-like code into an Abstract Syntax Tree (AST)
- Walks this tree step-by-step, executing each instruction
- Pauses when it encounters
CC(), saving the complete execution state - Resumes from the exact same position when Claude submits a result
This is why CVM can pause anywhere - it's not running native JavaScript, but carefully interpreting your code instruction by instruction.
CVM's own development is managed through CVM programs! This isn't just dogfooding - it's proof that CVM can handle complex, real-world development workflows:
π§ Feature Evolution Programs
- RegExp Implementation - Added pattern matching with TDD
- String & Array Methods - Implemented 15 methods systematically
- Object Keys & For-loops - Enhanced language features
ποΈ Architecture Refactoring
- CVM Evolution Executor - Fixed critical architectural flaws
- Test Coverage Analysis - Systematic testing improvements
These programs guide Claude through hundreds of coordinated changes:
- Writing failing tests first (TDD)
- Implementing features step-by-step
- Running test suites between changes
- Committing progress systematically
- Updating documentation automatically
Each program breaks down complex tasks into cognitive checkpoints where Claude provides implementation, reviews code, fixes issues, and verifies correctness - all while CVM maintains perfect state across the entire workflow.
Since CVM uses a custom interpreter, it supports a TypeScript-like subset designed for reliability and safety.
| Feature | Example |
|---|---|
| Variables | var name = "CVM"; var version = 1; |
| Basic Types | Strings, Numbers, Booleans, null, undefined |
| Arrays | [1, 2, 3], items.push(4), items[0] |
| Objects | {name: "Claude", age: 2}, obj.name |
| Loops | while (i < 5), for (const item of array) |
| Conditionals | if (x > 10) { ... } else { ... } |
| Operators | +, -, *, /, ===, !==, &&, ` |
import/requirestatementsasync/await(useCC()instead)- Classes and
newkeyword try...catchblocks (see error handling below)- Arrow functions
() => {} - Spread operator
... - Destructuring
const {a, b} = obj - Template literals
`Hello ${name}`
| Function | Description |
|---|---|
CC(prompt: string): string |
Pauses execution for Claude to process a task |
console.log(...args) |
Outputs to CVM console |
fs.listFiles(path, options?) |
Lists files in directory (sandboxed) |
JSON.stringify(obj) |
Converts object to JSON string |
JSON.parse(str) |
Parses JSON string to object |
CVM runs your code in a secure, isolated environment:
- No Network Access: Scripts cannot make HTTP requests or access the internet
- Filesystem Restrictions:
fs.listFiles()is limited to the CVM working directory - No System Access: Cannot execute shell commands or access environment variables
- Memory Limits: Scripts are bounded by reasonable memory constraints
- Execution Timeouts: Long-running operations are automatically terminated
For additional security, we recommend running CVM in a Docker container when processing sensitive data.
Since CVM doesn't support try...catch, you must validate Claude's responses defensively:
// β Fragile - assumes Claude returns a number
function fragileCounter() {
var count = 0;
while (count < 5) {
var next = CC("Current number is " + count + ". What's next?");
count = +next; // Fails if Claude returns "four" instead of "4"
}
}
// β
Robust - validates the response
function robustCounter() {
var count = 0;
while (count < 5) {
var result = CC("Current number is " + count + ". What's next?");
var nextNum = parseInt(result, 10);
if (isNaN(nextNum)) {
console.log("Invalid response: '" + result + "'. Retrying...");
// Loop continues without incrementing, effectively retrying
} else {
count = nextNum;
}
}
return count;
}You can inspect any running execution using the status tool:
// Claude can check status at any time:
const status = await cvm.status("exec-123");
console.log(status);
// {
// "executionId": "exec-123",
// "status": "PAUSED",
// "currentTask": "Analyzing file 247 of 1000",
// "state": {
// "filesProcessed": 246,
// "summaries": [...]
// }
// }This allows you to monitor long-running tasks and verify progress without interrupting execution.
MCP Tools Claude Uses:
load(programId, source)- Load a programstart(programId, executionId)- Start executiongetTask(executionId)- Pull next tasksubmitTask(executionId, result)- Submit resultstatus(executionId)- Check state anytime
What CVM Maintains:
- All variables and their values
- Current execution position
- Loop counters and conditions
- Complete program state between pauses
Add to Claude's MCP settings:
{
"mcpServers": {
"cvm": {
"command": "npx",
"args": ["cvm-server@latest"]
}
}
}Perfect for any workflow where Claude needs to process many items systematically:
- Document analysis pipelines
- Data extraction from multiple sources
- Report generation with multiple inputs
- Code refactoring across many files
- Any task requiring loops with AI processing
CVM doesn't make Claude smarter. It makes Claude systematic.
Without CVM: Brilliant but chaotic With CVM: Brilliant with perfect memory and a checklist
CVM is dual-licensed:
- AGPL-3.0-or-later β free for open source, personal, research, and internal use that complies with the AGPL network clause (section 13).
- Commercial license β required for proprietary integration, closed-source redistribution, or any use incompatible with AGPL-3.0.
If you are a company that wants to integrate CVM into a commercial product, ship it inside a closed-source tool, or expose it as part of a hosted service without releasing your own code under AGPL, see COMMERCIAL.md.
Note on previous versions: All releases up to and including version
0.16.0-next.8were licensed under Apache-2.0 and remain available under those terms. Starting from version1.0.0, CVM is dual-licensed as described above.
Copyright (C) 2025-2026 Ladislav Sopko.