Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions docs/SENTINEL-ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Sentinel Architecture: Universal Stream Processing for Autonomous Agents

> **SUPERSEDED**: This is an early design document. The sentinel system has been fully re-architected as a Rust pipeline engine with 10 step types (Shell, LLM, Command, Condition, Loop, Parallel, Emit, Watch, Sentinel, CodingAgent), 103 tests, and full TypeScript lifecycle integration (persistence, triggers, escalation, event bridge). See **[src/docs/SENTINEL-ARCHITECTURE.md](src/docs/SENTINEL-ARCHITECTURE.md)** for the canonical, up-to-date documentation.

## Executive Summary

Sentinels are **programmable stream observers** that watch any process, match patterns, classify output, and route events to persona inboxes. They solve the "last mile" problem preventing AI personas from effectively coding.
Expand Down
2 changes: 2 additions & 0 deletions docs/SENTINEL-WORKERS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Sentinel Workers: Focused Agentic Loops

> **SUPERSEDED**: This is an early conceptual document. The sentinel system has been fully implemented as a Rust pipeline engine with 10 step types and 103 tests. See **[src/docs/SENTINEL-ARCHITECTURE.md](src/docs/SENTINEL-ARCHITECTURE.md)** for the canonical architecture documentation, and **[src/docs/personas/ACADEMY-DOJO-ARCHITECTURE.md](src/docs/personas/ACADEMY-DOJO-ARCHITECTURE.md)** for the dual-sentinel teacher/student system.

## Vision

Sentinels are **focused agents** that sit between dumb scripts and full citizen AIs. They have enough intelligence to interpret results, adjust approach, and know when to escalate - but they're laser-focused on a single goal.
Expand Down
20 changes: 19 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 218 commands and 2 adapters and 28 widgets.
* Contains 11 daemons and 221 commands and 2 adapters and 28 widgets.
* Generated by scripts/generate-structure.ts - DO NOT EDIT MANUALLY
*/

Expand Down Expand Up @@ -129,9 +129,11 @@ import { GenomeAcademySessionBrowserCommand } from './../commands/genome/academy
import { GenomeBatchMicroTuneBrowserCommand } from './../commands/genome/batch-micro-tune/browser/GenomeBatchMicroTuneBrowserCommand';
import { GenomeDatasetPrepareBrowserCommand } from './../commands/genome/dataset-prepare/browser/GenomeDatasetPrepareBrowserCommand';
import { GenomeDatasetSynthesizeBrowserCommand } from './../commands/genome/dataset-synthesize/browser/GenomeDatasetSynthesizeBrowserCommand';
import { GenomeDemoRunBrowserCommand } from './../commands/genome/demo-run/browser/GenomeDemoRunBrowserCommand';
import { GenomeJobCreateBrowserCommand } from './../commands/genome/job-create/browser/GenomeJobCreateBrowserCommand';
import { GenomeJobStatusBrowserCommand } from './../commands/genome/job-status/browser/GenomeJobStatusBrowserCommand';
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 { HelpBrowserCommand } from './../commands/help/browser/HelpBrowserCommand';
import { IndicatorBrowserCommand } from './../commands/indicator/browser/IndicatorBrowserCommand';
Expand Down Expand Up @@ -181,6 +183,7 @@ import { PingBrowserCommand } from './../commands/ping/browser/PingBrowserComman
import { PositronCursorBrowserCommand } from './../commands/positron/cursor/browser/PositronCursorBrowserCommand';
import { ProcessRegistryBrowserCommand } from './../commands/process-registry/browser/ProcessRegistryBrowserCommand';
import { RuntimeMetricsBrowserCommand } from './../commands/runtime/metrics/browser/RuntimeMetricsBrowserCommand';
import { SentinelCodingAgentBrowserCommand } from './../commands/sentinel/coding-agent/browser/SentinelCodingAgentBrowserCommand';
import { SentinelRunBrowserCommand } from './../commands/sentinel/run/browser/SentinelRunBrowserCommand';
import { SentinelStatusBrowserCommand } from './../commands/sentinel/status/browser/SentinelStatusBrowserCommand';
import { SessionCreateBrowserCommand } from './../commands/session/create/browser/SessionCreateBrowserCommand';
Expand Down Expand Up @@ -890,6 +893,11 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
className: 'GenomeDatasetSynthesizeBrowserCommand',
commandClass: GenomeDatasetSynthesizeBrowserCommand
},
{
name: 'genome/demo-run',
className: 'GenomeDemoRunBrowserCommand',
commandClass: GenomeDemoRunBrowserCommand
},
{
name: 'genome/job-create',
className: 'GenomeJobCreateBrowserCommand',
Expand All @@ -905,6 +913,11 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
className: 'GenomeTrainBrowserCommand',
commandClass: GenomeTrainBrowserCommand
},
{
name: 'genome/training-export',
className: 'GenomeTrainingExportBrowserCommand',
commandClass: GenomeTrainingExportBrowserCommand
},
{
name: 'genome/training-pipeline',
className: 'GenomeTrainingPipelineBrowserCommand',
Expand Down Expand Up @@ -1150,6 +1163,11 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
className: 'RuntimeMetricsBrowserCommand',
commandClass: RuntimeMetricsBrowserCommand
},
{
name: 'sentinel/coding-agent',
className: 'SentinelCodingAgentBrowserCommand',
commandClass: SentinelCodingAgentBrowserCommand
},
{
name: 'sentinel/run',
className: 'SentinelRunBrowserCommand',
Expand Down
72 changes: 72 additions & 0 deletions src/commands/genome/demo-run/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# genome/demo-run

Run a sentinel demo pipeline where Claude Code builds real software from a project spec, capturing all interactions for LoRA training.

## Usage

```bash
# Run task-tracker demo (3 milestones, ~$3-7, ~20 minutes)
./jtag genome/demo-run --project=task-tracker --personaId=<uuid>

# With custom settings
./jtag genome/demo-run \
--project=task-tracker \
--personaId=<uuid> \
--personaName="Helper AI" \
--maxRetries=3 \
--maxBudget=10 \
--maxTurns=40
```

## Parameters

| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| project | Yes | - | Project name (maps to `src/projects/<name>/`) |
| personaId | Yes | - | Target persona UUID for training |
| personaName | No | 'demo-persona' | Persona display name |
| baseModel | No | LOCAL_MODELS.DEFAULT | Base model for LoRA training |
| maxRetries | No | 2 | Max CodingAgent retries per milestone |
| maxBudget | No | 5.0 | Max USD per milestone |
| maxTurns | No | 30 | Max CodingAgent turns per milestone |
| provider | No | 'claude-code' | CodingAgent provider |
| epochs | No | 3 | LoRA training epochs |
| rank | No | 32 | LoRA rank |

## Pipeline Flow

```
Step 0: Shell — create temp dir, copy scaffold, npm install
Step 1: Loop (milestones):
loop.0: Shell — read milestone spec
loop.1: CodingAgent — Claude Code builds it (captureTraining=true)
loop.2: Shell — run deterministic tests
loop.3: Condition — passed?
then: emit milestone:passed
else: CodingAgent retry → re-run tests (up to maxRetries)
loop.4: Emit milestone:complete
Step 2: Command — genome/train (captured interactions → LoRA adapter)
Step 3: Command — genome/phenotype-validate (before vs after)
Step 4: Emit — demo:complete
```

## Available Projects

| Project | Milestones | Difficulty | Est. Cost |
|---------|-----------|------------|-----------|
| task-tracker | 3 | Intermediate | $3-7 |
| ecommerce-api | 6 | Advanced | $10-20 |
| url-shortener | 3 | Beginner | $2-5 |

## Monitoring

```bash
# Check pipeline status
./jtag sentinel/status --handle=<returned-handle>

# Tail pipeline logs
./jtag sentinel/logs/tail --handle=<returned-handle>

# Check training data
./jtag data/list --collection=genome_layers --limit=5
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Genome Demo Run Command - Browser Implementation
*
* Delegates to server for demo pipeline orchestration.
*/

import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
import type { JTAGContext } from '@system/core/types/JTAGTypes';
import type { GenomeDemoRunParams, GenomeDemoRunResult } from '../shared/GenomeDemoRunTypes';

export class GenomeDemoRunBrowserCommand extends CommandBase<GenomeDemoRunParams, GenomeDemoRunResult> {

constructor(context: JTAGContext, subpath: string, commander: ICommandDaemon) {
super('genome/demo-run', context, subpath, commander);
}

async execute(params: GenomeDemoRunParams): Promise<GenomeDemoRunResult> {
return await this.remoteExecute(params);
}
}
27 changes: 27 additions & 0 deletions src/commands/genome/demo-run/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@jtag-commands/genome/demo-run",
"version": "1.0.0",
"description": "Run a sentinel demo pipeline — Claude Code builds real software, interactions are captured for LoRA training",
"main": "server/GenomeDemoRunServerCommand.ts",
"types": "shared/GenomeDemoRunTypes.ts",
"scripts": {
"test": "echo 'Integration test: ./jtag genome/demo-run --project=task-tracker --personaId=<uuid>'",
"lint": "npx eslint **/*.ts",
"typecheck": "npx tsc --noEmit"
},
"peerDependencies": {
"@jtag/core": "*"
},
"files": [
"shared/**/*.ts",
"browser/**/*.ts",
"server/**/*.ts",
"README.md"
],
"keywords": [
"jtag",
"command",
"genome/demo-run"
],
"license": "MIT"
}
156 changes: 156 additions & 0 deletions src/commands/genome/demo-run/server/GenomeDemoRunServerCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* Genome Demo Run Command - Server Implementation
*
* Loads a project spec, builds a demo pipeline via buildDemoPipeline(),
* and dispatches it to the Rust sentinel executor via sentinel/run.
*
* The pipeline has Claude Code build real software milestone by milestone,
* capturing every interaction for LoRA training. After all milestones:
* genome/train produces a LoRA adapter, genome/phenotype-validate proves improvement.
*/

import * as fs from 'fs';
import * as path from 'path';
import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
import type { JTAGContext } from '@system/core/types/JTAGTypes';
import { ValidationError } from '@system/core/types/ErrorTypes';
import type { GenomeDemoRunParams, GenomeDemoRunResult } from '../shared/GenomeDemoRunTypes';
import { createGenomeDemoRunResultFromParams } from '../shared/GenomeDemoRunTypes';
import { Commands } from '@system/core/shared/Commands';
import type { SentinelStep } from '@system/sentinel/SentinelDefinition';
import type { PipelineSentinelParams, SentinelRunResult } from '@commands/sentinel/run/shared/SentinelRunTypes';
import type { ProjectSpec } from '@system/genome/shared/AcademyTypes';
import { buildDemoPipeline } from '@system/sentinel/pipelines/DemoPipeline';
import { DEFAULT_DEMO_TRAINING_CONFIG, DEMO_DEFAULTS } from '@system/sentinel/pipelines/DemoTypes';
import type { DemoPipelineConfig } from '@system/sentinel/pipelines/DemoTypes';
import { LOCAL_MODELS } from '@system/shared/Constants';

export class GenomeDemoRunServerCommand extends CommandBase<GenomeDemoRunParams, GenomeDemoRunResult> {

constructor(context: JTAGContext, subpath: string, commander: ICommandDaemon) {
super('genome/demo-run', context, subpath, commander);
}

async execute(params: GenomeDemoRunParams): Promise<GenomeDemoRunResult> {
const { project, personaId } = params;

if (!project) {
throw new ValidationError('project', 'Required. Project name (e.g., "task-tracker").');
}
if (!personaId) {
throw new ValidationError('personaId', 'Required. Target persona ID for training.');
}

// Resolve project directory
const srcRoot = path.resolve(__dirname, '../../../../');
const projectDir = path.join(srcRoot, 'projects', project);
const projectJsonPath = path.join(projectDir, 'project.json');

if (!fs.existsSync(projectJsonPath)) {
return createGenomeDemoRunResultFromParams(params, {
success: false,
handle: '',
projectName: project,
milestoneCount: 0,
error: `Project not found: ${projectJsonPath}. Available projects are in src/projects/.`,
});
}

// Parse project spec
let projectSpec: ProjectSpec;
try {
projectSpec = JSON.parse(fs.readFileSync(projectJsonPath, 'utf8'));
} catch (err) {
return createGenomeDemoRunResultFromParams(params, {
success: false,
handle: '',
projectName: project,
milestoneCount: 0,
error: `Failed to parse project.json: ${err}`,
});
}

// Validate scaffold exists
const scaffoldDir = path.join(projectDir, 'scaffold');
if (!fs.existsSync(path.join(scaffoldDir, 'package.json'))) {
return createGenomeDemoRunResultFromParams(params, {
success: false,
handle: '',
projectName: projectSpec.name,
milestoneCount: projectSpec.milestones.length,
error: `Missing scaffold/package.json in ${projectDir}`,
});
}

// Validate test files exist
for (const milestone of projectSpec.milestones) {
const testPath = path.join(projectDir, milestone.testFile);
if (!fs.existsSync(testPath)) {
return createGenomeDemoRunResultFromParams(params, {
success: false,
handle: '',
projectName: projectSpec.name,
milestoneCount: projectSpec.milestones.length,
error: `Missing test file: ${milestone.testFile} (milestone ${milestone.index}: ${milestone.name})`,
});
}
}

const personaName = params.personaName ?? 'demo-persona';
const baseModel = params.baseModel ?? LOCAL_MODELS.DEFAULT;

console.log(`\u{1F680} DEMO RUN: project="${projectSpec.name}", persona="${personaName}", milestones=${projectSpec.milestones.length}`);

// Build demo pipeline config
const config: DemoPipelineConfig = {
projectDir,
project: projectSpec,
personaId,
personaName,
baseModel,
maxRetries: params.maxRetries ?? DEMO_DEFAULTS.maxRetries,
maxBudgetPerMilestone: params.maxBudget ?? DEMO_DEFAULTS.maxBudgetPerMilestone,
maxTurnsPerMilestone: params.maxTurns ?? DEMO_DEFAULTS.maxTurnsPerMilestone,
provider: params.provider ?? DEMO_DEFAULTS.provider,
training: {
...DEFAULT_DEMO_TRAINING_CONFIG,
...(params.epochs !== undefined && { epochs: params.epochs }),
...(params.rank !== undefined && { rank: params.rank }),
},
};

// Build the pipeline
const pipeline = buildDemoPipeline(config);

console.log(` Pipeline: ${pipeline.name}, ${pipeline.steps.length} top-level steps`);

// Dispatch to Rust sentinel executor
// PipelineStep[] (Rust bindings) → SentinelStep[] (TS definitions) — structurally compatible wire types
const pipelineSteps = pipeline.steps as unknown as SentinelStep[];

const sentinelResult = await Commands.execute<PipelineSentinelParams, SentinelRunResult>('sentinel/run', {
type: 'pipeline',
definition: {
type: 'pipeline',
name: pipeline.name ?? `demo-${project}`,
description: `Demo pipeline: Claude Code builds ${projectSpec.name} (${projectSpec.milestones.length} milestones), captures training data for ${personaName}`,
version: '1.0',
steps: pipelineSteps,
loop: { type: 'once' },
tags: ['demo', project, projectSpec.skill],
},
parentPersonaId: personaId,
sentinelName: pipeline.name ?? `demo-${project}`,
});

const handle = sentinelResult.handle ?? '';
console.log(`\u2705 DEMO RUN: Pipeline dispatched, handle=${handle}`);

return createGenomeDemoRunResultFromParams(params, {
success: true,
handle,
projectName: projectSpec.name,
milestoneCount: projectSpec.milestones.length,
});
}
}
Loading
Loading