Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,63 @@ commands/example/

**Never import server/browser code IN shared files!**

### Rust-Backed Commands (IPC Mixin Pattern)

When a command is backed by Rust (via continuum-core IPC), it requires **THREE layers**:

```
1. CommandSpec JSON → generator/specs/gpu-stats.json
2. CommandGenerator → npx tsx generator/CommandGenerator.ts generator/specs/gpu-stats.json
3. IPC Mixin → workers/continuum-core/bindings/modules/gpu.ts
```

**Step-by-step workflow:**

```bash
# 1. Create the Rust module (ServiceModule trait) with IPC commands
# e.g., modules/gpu.rs handles "gpu/stats", "gpu/pressure"

# 2. Create a CommandSpec JSON
cat > generator/specs/gpu-stats.json << 'EOF'
{
"name": "gpu/stats",
"description": "Query GPU memory stats",
"params": [...],
"results": [...],
"examples": [...],
"accessLevel": "ai-safe"
}
EOF

# 3. Run the generator (creates shared/Types, server/Command, browser/Command, README, tests)
npx tsx generator/CommandGenerator.ts generator/specs/gpu-stats.json

# 4. Create IPC mixin (snake_case Rust → camelCase TypeScript)
# workers/continuum-core/bindings/modules/gpu.ts
# Pattern: export function GpuMixin<T>(Base: T) { return class extends Base { ... } }

# 5. Add mixin to RustCoreIPC.ts composition chain
# import { GpuMixin } from './modules/gpu';
# const ComposedClient = ... GpuMixin(RuntimeMixin( ... )) ...

# 6. Implement server command to use mixin
# const stats = await this.rustClient.gpuStats();

# 7. Build and verify
npm run build:ts && npm start
./jtag gpu/stats
```

**The three-layer architecture:**

| Layer | File | Purpose |
|-------|------|---------|
| Rust IPC | `modules/gpu.rs` | ServiceModule, handles `gpu/stats` |
| TS Mixin | `bindings/modules/gpu.ts` | snake_case→camelCase, typed wrapper |
| TS Command | `commands/gpu/stats/` | Generated scaffold, uses mixin |

**Without the mixin + command layer**, Rust IPC commands exist but are invisible to `./jtag` and the command system. The generator creates discoverability (README, help text, CLI params).

---

## 📸 WIDGET DOM PATH
Expand Down
8 changes: 7 additions & 1 deletion src/browser/generated.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Browser Structure Registry - Auto-generated
*
* Contains 11 daemons and 221 commands and 2 adapters and 28 widgets.
* Contains 11 daemons and 222 commands and 2 adapters and 28 widgets.
* Generated by scripts/generate-structure.ts - DO NOT EDIT MANUALLY
*/

Expand Down Expand Up @@ -135,6 +135,7 @@ import { GenomeJobStatusBrowserCommand } from './../commands/genome/job-status/b
import { GenomeTrainBrowserCommand } from './../commands/genome/train/browser/GenomeTrainBrowserCommand';
import { GenomeTrainingExportBrowserCommand } from './../commands/genome/training-export/browser/GenomeTrainingExportBrowserCommand';
import { GenomeTrainingPipelineBrowserCommand } from './../commands/genome/training-pipeline/browser/GenomeTrainingPipelineBrowserCommand';
import { GpuStatsBrowserCommand } from './../commands/gpu/stats/browser/GpuStatsBrowserCommand';
import { HelpBrowserCommand } from './../commands/help/browser/HelpBrowserCommand';
import { IndicatorBrowserCommand } from './../commands/indicator/browser/IndicatorBrowserCommand';
import { InferenceGenerateBrowserCommand } from './../commands/inference/generate/browser/InferenceGenerateBrowserCommand';
Expand Down Expand Up @@ -923,6 +924,11 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
className: 'GenomeTrainingPipelineBrowserCommand',
commandClass: GenomeTrainingPipelineBrowserCommand
},
{
name: 'gpu/stats',
className: 'GpuStatsBrowserCommand',
commandClass: GpuStatsBrowserCommand
},
{
name: 'help',
className: 'HelpBrowserCommand',
Expand Down
20 changes: 20 additions & 0 deletions src/commands/gpu/stats/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Development files
.eslintrc*
tsconfig*.json
vitest.config.ts

# Build artifacts
*.js.map
*.d.ts.map

# IDE
.vscode/
.idea/

# Logs
*.log
npm-debug.log*

# OS files
.DS_Store
Thumbs.db
169 changes: 169 additions & 0 deletions src/commands/gpu/stats/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Gpu Stats Command

Query GPU memory manager stats including VRAM detection, per-subsystem budgets (inference, TTS, rendering), usage tracking, and memory pressure. Returns real hardware data from Metal (macOS) or CUDA APIs.

## Table of Contents

- [Usage](#usage)
- [CLI Usage](#cli-usage)
- [Tool Usage](#tool-usage)
- [Parameters](#parameters)
- [Result](#result)
- [Examples](#examples)
- [Testing](#testing)
- [Unit Tests](#unit-tests)
- [Integration Tests](#integration-tests)
- [Getting Help](#getting-help)
- [Access Level](#access-level)
- [Implementation Notes](#implementation-notes)

## Usage

### CLI Usage

From the command line using the jtag CLI:

```bash
./jtag gpu/stats [options]
```

### Tool Usage

From Persona tools or programmatic access using `Commands.execute()`:

```typescript
import { Commands } from '@system/core/shared/Commands';

const result = await Commands.execute('gpu/stats', {
// your parameters here
});
```

## Parameters

- **subsystem** (optional): `string` - Filter to specific subsystem: 'inference', 'tts', or 'rendering'. Omit for full stats.

## Result

Returns `GpuStatsResult` with:

Returns CommandResult with:
- **gpuName**: `string` - GPU hardware name (e.g., 'Apple M3 Max', 'NVIDIA RTX 5090')
- **totalVramMb**: `number` - Total detected VRAM in MB
- **totalUsedMb**: `number` - Total VRAM used across all subsystems in MB
- **pressure**: `number` - Memory pressure 0.0-1.0 (0=idle, 0.6=warning, 0.8=high, 0.95=critical)
- **reserveMb**: `number` - Reserved headroom in MB (5% of total, prevents OOM)
- **rendering**: `SubsystemInfo` - Rendering subsystem budget and usage
- **inference**: `SubsystemInfo` - Inference subsystem budget and usage (models, LoRA adapters)
- **tts**: `SubsystemInfo` - TTS subsystem budget and usage

## Examples

### Get full GPU stats

```bash
./jtag gpu/stats
```

**Expected result:**
{ gpuName: 'Apple M3 Max', totalVramMb: 36864, pressure: 0.12, inference: { budgetMb: 25804, usedMb: 3200 }, ... }

### Get inference subsystem only

```bash
./jtag gpu/stats --subsystem=inference
```

**Expected result:**
{ gpuName: 'Apple M3 Max', totalVramMb: 36864, pressure: 0.12, inference: { budgetMb: 25804, usedMb: 3200 } }

## Getting Help

### Using the Help Tool

Get detailed usage information for this command:

**CLI:**
```bash
./jtag help gpu/stats
```

**Tool:**
```typescript
// Use your help tool with command name 'gpu/stats'
```

### Using the README Tool

Access this README programmatically:

**CLI:**
```bash
./jtag readme gpu/stats
```

**Tool:**
```typescript
// Use your readme tool with command name 'gpu/stats'
```

## Testing

### Unit Tests

Test command logic in isolation using mock dependencies:

```bash
# Run unit tests (no server required)
npx tsx commands/Gpu Stats/test/unit/GpuStatsCommand.test.ts
```

**What's tested:**
- Command structure and parameter validation
- Mock command execution patterns
- Required parameter validation (throws ValidationError)
- Optional parameter handling (sensible defaults)
- Performance requirements
- Assertion utility helpers

**TDD Workflow:**
1. Write/modify unit test first (test-driven development)
2. Run test, see it fail
3. Implement feature
4. Run test, see it pass
5. Refactor if needed

### Integration Tests

Test command with real client connections and system integration:

```bash
# Prerequisites: Server must be running
npm start # Wait 90+ seconds for deployment

# Run integration tests
npx tsx commands/Gpu Stats/test/integration/GpuStatsIntegration.test.ts
```

**What's tested:**
- Client connection to live system
- Real command execution via WebSocket
- ValidationError handling for missing params
- Optional parameter defaults
- Performance under load
- Various parameter combinations

**Best Practice:**
Run unit tests frequently during development (fast feedback). Run integration tests before committing (verify system integration).

## Access Level

**ai-safe** - Safe for AI personas to call autonomously

## Implementation Notes

- **Shared Logic**: Core business logic in `shared/GpuStatsTypes.ts`
- **Browser**: Browser-specific implementation in `browser/GpuStatsBrowserCommand.ts`
- **Server**: Server-specific implementation in `server/GpuStatsServerCommand.ts`
- **Unit Tests**: Isolated testing in `test/unit/GpuStatsCommand.test.ts`
- **Integration Tests**: System testing in `test/integration/GpuStatsIntegration.test.ts`
21 changes: 21 additions & 0 deletions src/commands/gpu/stats/browser/GpuStatsBrowserCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Gpu Stats Command - Browser Implementation
*
* Query GPU memory manager stats including VRAM detection, per-subsystem budgets (inference, TTS, rendering), usage tracking, and memory pressure. Returns real hardware data from Metal (macOS) or CUDA APIs.
*/

import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
import type { JTAGContext } from '@system/core/types/JTAGTypes';
import type { GpuStatsParams, GpuStatsResult } from '../shared/GpuStatsTypes';

export class GpuStatsBrowserCommand extends CommandBase<GpuStatsParams, GpuStatsResult> {

constructor(context: JTAGContext, subpath: string, commander: ICommandDaemon) {
super('gpu/stats', context, subpath, commander);
}

async execute(params: GpuStatsParams): Promise<GpuStatsResult> {
console.log('🌐 BROWSER: Delegating Gpu Stats to server');
return await this.remoteExecute(params);
}
}
35 changes: 35 additions & 0 deletions src/commands/gpu/stats/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@jtag-commands/gpu/stats",
"version": "1.0.0",
"description": "Query GPU memory manager stats including VRAM detection, per-subsystem budgets (inference, TTS, rendering), usage tracking, and memory pressure. Returns real hardware data from Metal (macOS) or CUDA APIs.",
"main": "server/GpuStatsServerCommand.ts",
"types": "shared/GpuStatsTypes.ts",
"scripts": {
"test": "npm run test:unit && npm run test:integration",
"test:unit": "npx vitest run test/unit/*.test.ts",
"test:integration": "npx tsx test/integration/GpuStatsIntegration.test.ts",
"lint": "npx eslint **/*.ts",
"typecheck": "npx tsc --noEmit"
},
"peerDependencies": {
"@jtag/core": "*"
},
"files": [
"shared/**/*.ts",
"browser/**/*.ts",
"server/**/*.ts",
"test/**/*.ts",
"README.md"
],
"keywords": [
"jtag",
"command",
"gpu/stats"
],
"license": "MIT",
"author": "",
"repository": {
"type": "git",
"url": ""
}
}
44 changes: 44 additions & 0 deletions src/commands/gpu/stats/server/GpuStatsServerCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Gpu Stats Command - Server Implementation
*
* Routes to Rust GpuModule via continuum-core IPC:
* - gpu/stats: Full GPU memory manager snapshot
* - gpu/pressure: Quick pressure query (0.0-1.0)
*/

import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
import type { JTAGContext } from '@system/core/types/JTAGTypes';
import type { GpuStatsParams, GpuStatsResult } from '../shared/GpuStatsTypes';
import { createGpuStatsResultFromParams } from '../shared/GpuStatsTypes';
import { RustCoreIPCClient, getContinuumCoreSocketPath } from '../../../../workers/continuum-core/bindings/RustCoreIPC';

export class GpuStatsServerCommand extends CommandBase<GpuStatsParams, GpuStatsResult> {
private rustClient: RustCoreIPCClient;

constructor(context: JTAGContext, subpath: string, commander: ICommandDaemon) {
super('gpu/stats', context, subpath, commander);
this.rustClient = new RustCoreIPCClient(getContinuumCoreSocketPath());
}

async execute(params: GpuStatsParams): Promise<GpuStatsResult> {
await this.rustClient.connect();

try {
const stats = await this.rustClient.gpuStats();

return createGpuStatsResultFromParams(params, {
success: true,
gpuName: stats.gpuName,
totalVramMb: stats.totalVramMb,
totalUsedMb: stats.totalUsedMb,
pressure: stats.pressure,
reserveMb: stats.reserveMb,
rendering: stats.rendering,
inference: stats.inference,
tts: stats.tts,
});
} finally {
this.rustClient.disconnect();
}
}
Comment on lines +28 to +43
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GpuStatsServerCommand.execute() ignores params.subsystem even though the generated types/spec expose it. If the filter is intended to work, pass the param through (and/or filter the returned stats) so ./jtag gpu/stats --subsystem=... behaves as documented.

Suggested change
return createGpuStatsResultFromParams(params, {
success: true,
gpuName: stats.gpuName,
totalVramMb: stats.totalVramMb,
totalUsedMb: stats.totalUsedMb,
pressure: stats.pressure,
reserveMb: stats.reserveMb,
rendering: stats.rendering,
inference: stats.inference,
tts: stats.tts,
});
} finally {
this.rustClient.disconnect();
}
}
const filteredStats = params.subsystem
? this.filterStatsBySubsystem(stats, params.subsystem)
: stats;
return createGpuStatsResultFromParams(params, {
success: true,
gpuName: filteredStats.gpuName,
totalVramMb: filteredStats.totalVramMb,
totalUsedMb: filteredStats.totalUsedMb,
pressure: filteredStats.pressure,
reserveMb: filteredStats.reserveMb,
rendering: filteredStats.rendering,
inference: filteredStats.inference,
tts: filteredStats.tts,
});
} finally {
this.rustClient.disconnect();
}
}
private filterStatsBySubsystem(
stats: {
gpuName: string;
totalVramMb: number;
totalUsedMb: number;
pressure: number;
reserveMb: number;
rendering: unknown;
inference: unknown;
tts: unknown;
},
subsystem: string,
) {
// Preserve overall GPU stats and only filter per-subsystem details.
switch (subsystem) {
case 'rendering':
return {
...stats,
inference: undefined,
tts: undefined,
};
case 'inference':
return {
...stats,
rendering: undefined,
tts: undefined,
};
case 'tts':
return {
...stats,
rendering: undefined,
inference: undefined,
};
default:
// If an unknown subsystem is requested, fall back to unfiltered stats.
return stats;
}
}

Copilot uses AI. Check for mistakes.
}
Loading
Loading