Skip to content

Commit bed366e

Browse files
authored
Sentinel: CodingAgent + Demo Pipeline E2E (Claude Code → LoRA training)
* initial work on senteinels Rust (Pipeline Engine) ┌────────────────────────────────────────────┬────────────────────────────────────────────────────────────────────┐ │ File │ Action │ ├────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤ │ workers/.../sentinel/types.rs │ Added CodingAgent variant with 12 fields to PipelineStep enum │ ├────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤ │ workers/.../sentinel/steps/coding_agent.rs │ New — delegates to TS via execute_ts_json("sentinel/coding-agent") │ ├────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤ │ workers/.../sentinel/steps/mod.rs │ Added module + dispatch arm │ ├────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤ │ shared/generated/sentinel/PipelineStep.ts │ Auto-regenerated by ts-rs │ └────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────┘ TypeScript (Provider Architecture) ┌──────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────┐ │ File │ Action │ ├──────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤ │ system/sentinel/coding-agents/CodingAgentProvider.ts │ Interface: CodingAgentProvider, CodingAgentConfig, CodingAgentResult │ ├──────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤ │ system/sentinel/coding-agents/ClaudeCodeProvider.ts │ SDK wrapper — spawns child process, streams messages, captures interactions │ ├──────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤ │ system/sentinel/coding-agents/CodingAgentRegistry.ts │ Dynamic registry — no switch, no enum, providers self-register │ ├──────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤ │ system/sentinel/coding-agents/index.ts │ Barrel + auto-registration of built-in providers │ └──────────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────┘ Command (sentinel/coding-agent) ┌─────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────┐ │ File │ Action │ ├─────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ commands/sentinel/coding-agent/shared/SentinelCodingAgentTypes.ts │ Params, Result, static executor │ ├─────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ commands/sentinel/coding-agent/server/SentinelCodingAgentServerCommand.ts │ Resolves provider, executes, emits events, captures training data │ ├─────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ commands/sentinel/coding-agent/browser/SentinelCodingAgentBrowserCommand.ts │ Delegates to server │ └─────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────┘ Bindings ┌──────────────────────────────────────────┬─────────────────────────────────────────┐ │ File │ Action │ ├──────────────────────────────────────────┼─────────────────────────────────────────┤ │ workers/.../bindings/modules/sentinel.ts │ Added codingagent to PipelineStep union │ └──────────────────────────────────────────┴─────────────────────────────────────────┘ Verification - 109 Rust tests pass (0 failures) - TypeScript compiles clean (strict mode) - SDK installed: @anthropic-ai/claude-agent-sdk added to package.json * cleanup for stability stuff i think * Sentinel docs overhaul + gap analysis vs 10 agentic coding tools Canonical SENTINEL-ARCHITECTURE.md rewritten to match actual Rust/TS implementation: all 10 step types documented (was 6), variable interpolation syntax corrected from $variable to {{variable}}, runtime/safety/commands sections updated with real structs and commands, CodingAgent step type added throughout. 7 supporting docs updated with status headers pointing to canonical doc. Superseded docs marked. Academy step-type count fixed (6/10). New SENTINEL-GAP-ANALYSIS.md compares our sentinel system against Claude Code, Codex, Aider, OpenCode, GSD, SWE-agent, OpenHands, Cline, Cursor, and Sweep. Identifies 7 gaps (codebase understanding, context management, multi-agent isolation, quality scoring, multi- provider support, developer UX, persona integration depth) and 6 unique strengths (pipeline composition, LoRA training, Academy dual-sentinel, training capture, persona ownership, event-based inter-agent communication). Includes 5-phase prioritized roadmap and research references for distillation pipeline hardening. * Demo pipeline E2E: Claude Code builds software → LoRA trains local AI Sentinel demo system proven end-to-end: setup → milestones (CodingAgent loop with test feedback) → training-export → genome/train. Pipeline ran successfully on task-tracker project (3 milestones, all passed, adapter trained with loss=2.49). Key additions: - DemoPipeline builder + DemoTypes (pipeline orchestration) - genome/training-export command (accumulator buffer → JSONL → disk) - genome/demo-run command (entry point for demo pipelines) - task-tracker project (3 milestones, 14+ deterministic tests) - Shell allowFailure flag (test runners don't kill loops) - Interpolation fix: {{steps.N}} searches by step_index not array position (loop sub-steps shift positions in shared results array) - ClaudeCodeProvider: strip ANTHROPIC_API_KEY for OAuth auth - Startup self-healing: Postgres DB health check + auto-create + auto-seed
1 parent 0558cec commit bed366e

File tree

62 files changed

+4575
-414
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+4575
-414
lines changed

docs/SENTINEL-ARCHITECTURE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Sentinel Architecture: Universal Stream Processing for Autonomous Agents
22

3+
> **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.
4+
35
## Executive Summary
46

57
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.

docs/SENTINEL-WORKERS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Sentinel Workers: Focused Agentic Loops
22

3+
> **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.
4+
35
## Vision
46

57
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.

src/browser/generated.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Browser Structure Registry - Auto-generated
33
*
4-
* Contains 11 daemons and 218 commands and 2 adapters and 28 widgets.
4+
* Contains 11 daemons and 221 commands and 2 adapters and 28 widgets.
55
* Generated by scripts/generate-structure.ts - DO NOT EDIT MANUALLY
66
*/
77

@@ -129,9 +129,11 @@ import { GenomeAcademySessionBrowserCommand } from './../commands/genome/academy
129129
import { GenomeBatchMicroTuneBrowserCommand } from './../commands/genome/batch-micro-tune/browser/GenomeBatchMicroTuneBrowserCommand';
130130
import { GenomeDatasetPrepareBrowserCommand } from './../commands/genome/dataset-prepare/browser/GenomeDatasetPrepareBrowserCommand';
131131
import { GenomeDatasetSynthesizeBrowserCommand } from './../commands/genome/dataset-synthesize/browser/GenomeDatasetSynthesizeBrowserCommand';
132+
import { GenomeDemoRunBrowserCommand } from './../commands/genome/demo-run/browser/GenomeDemoRunBrowserCommand';
132133
import { GenomeJobCreateBrowserCommand } from './../commands/genome/job-create/browser/GenomeJobCreateBrowserCommand';
133134
import { GenomeJobStatusBrowserCommand } from './../commands/genome/job-status/browser/GenomeJobStatusBrowserCommand';
134135
import { GenomeTrainBrowserCommand } from './../commands/genome/train/browser/GenomeTrainBrowserCommand';
136+
import { GenomeTrainingExportBrowserCommand } from './../commands/genome/training-export/browser/GenomeTrainingExportBrowserCommand';
135137
import { GenomeTrainingPipelineBrowserCommand } from './../commands/genome/training-pipeline/browser/GenomeTrainingPipelineBrowserCommand';
136138
import { HelpBrowserCommand } from './../commands/help/browser/HelpBrowserCommand';
137139
import { IndicatorBrowserCommand } from './../commands/indicator/browser/IndicatorBrowserCommand';
@@ -181,6 +183,7 @@ import { PingBrowserCommand } from './../commands/ping/browser/PingBrowserComman
181183
import { PositronCursorBrowserCommand } from './../commands/positron/cursor/browser/PositronCursorBrowserCommand';
182184
import { ProcessRegistryBrowserCommand } from './../commands/process-registry/browser/ProcessRegistryBrowserCommand';
183185
import { RuntimeMetricsBrowserCommand } from './../commands/runtime/metrics/browser/RuntimeMetricsBrowserCommand';
186+
import { SentinelCodingAgentBrowserCommand } from './../commands/sentinel/coding-agent/browser/SentinelCodingAgentBrowserCommand';
184187
import { SentinelRunBrowserCommand } from './../commands/sentinel/run/browser/SentinelRunBrowserCommand';
185188
import { SentinelStatusBrowserCommand } from './../commands/sentinel/status/browser/SentinelStatusBrowserCommand';
186189
import { SessionCreateBrowserCommand } from './../commands/session/create/browser/SessionCreateBrowserCommand';
@@ -890,6 +893,11 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
890893
className: 'GenomeDatasetSynthesizeBrowserCommand',
891894
commandClass: GenomeDatasetSynthesizeBrowserCommand
892895
},
896+
{
897+
name: 'genome/demo-run',
898+
className: 'GenomeDemoRunBrowserCommand',
899+
commandClass: GenomeDemoRunBrowserCommand
900+
},
893901
{
894902
name: 'genome/job-create',
895903
className: 'GenomeJobCreateBrowserCommand',
@@ -905,6 +913,11 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
905913
className: 'GenomeTrainBrowserCommand',
906914
commandClass: GenomeTrainBrowserCommand
907915
},
916+
{
917+
name: 'genome/training-export',
918+
className: 'GenomeTrainingExportBrowserCommand',
919+
commandClass: GenomeTrainingExportBrowserCommand
920+
},
908921
{
909922
name: 'genome/training-pipeline',
910923
className: 'GenomeTrainingPipelineBrowserCommand',
@@ -1150,6 +1163,11 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
11501163
className: 'RuntimeMetricsBrowserCommand',
11511164
commandClass: RuntimeMetricsBrowserCommand
11521165
},
1166+
{
1167+
name: 'sentinel/coding-agent',
1168+
className: 'SentinelCodingAgentBrowserCommand',
1169+
commandClass: SentinelCodingAgentBrowserCommand
1170+
},
11531171
{
11541172
name: 'sentinel/run',
11551173
className: 'SentinelRunBrowserCommand',
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# genome/demo-run
2+
3+
Run a sentinel demo pipeline where Claude Code builds real software from a project spec, capturing all interactions for LoRA training.
4+
5+
## Usage
6+
7+
```bash
8+
# Run task-tracker demo (3 milestones, ~$3-7, ~20 minutes)
9+
./jtag genome/demo-run --project=task-tracker --personaId=<uuid>
10+
11+
# With custom settings
12+
./jtag genome/demo-run \
13+
--project=task-tracker \
14+
--personaId=<uuid> \
15+
--personaName="Helper AI" \
16+
--maxRetries=3 \
17+
--maxBudget=10 \
18+
--maxTurns=40
19+
```
20+
21+
## Parameters
22+
23+
| Parameter | Required | Default | Description |
24+
|-----------|----------|---------|-------------|
25+
| project | Yes | - | Project name (maps to `src/projects/<name>/`) |
26+
| personaId | Yes | - | Target persona UUID for training |
27+
| personaName | No | 'demo-persona' | Persona display name |
28+
| baseModel | No | LOCAL_MODELS.DEFAULT | Base model for LoRA training |
29+
| maxRetries | No | 2 | Max CodingAgent retries per milestone |
30+
| maxBudget | No | 5.0 | Max USD per milestone |
31+
| maxTurns | No | 30 | Max CodingAgent turns per milestone |
32+
| provider | No | 'claude-code' | CodingAgent provider |
33+
| epochs | No | 3 | LoRA training epochs |
34+
| rank | No | 32 | LoRA rank |
35+
36+
## Pipeline Flow
37+
38+
```
39+
Step 0: Shell — create temp dir, copy scaffold, npm install
40+
Step 1: Loop (milestones):
41+
loop.0: Shell — read milestone spec
42+
loop.1: CodingAgent — Claude Code builds it (captureTraining=true)
43+
loop.2: Shell — run deterministic tests
44+
loop.3: Condition — passed?
45+
then: emit milestone:passed
46+
else: CodingAgent retry → re-run tests (up to maxRetries)
47+
loop.4: Emit milestone:complete
48+
Step 2: Command — genome/train (captured interactions → LoRA adapter)
49+
Step 3: Command — genome/phenotype-validate (before vs after)
50+
Step 4: Emit — demo:complete
51+
```
52+
53+
## Available Projects
54+
55+
| Project | Milestones | Difficulty | Est. Cost |
56+
|---------|-----------|------------|-----------|
57+
| task-tracker | 3 | Intermediate | $3-7 |
58+
| ecommerce-api | 6 | Advanced | $10-20 |
59+
| url-shortener | 3 | Beginner | $2-5 |
60+
61+
## Monitoring
62+
63+
```bash
64+
# Check pipeline status
65+
./jtag sentinel/status --handle=<returned-handle>
66+
67+
# Tail pipeline logs
68+
./jtag sentinel/logs/tail --handle=<returned-handle>
69+
70+
# Check training data
71+
./jtag data/list --collection=genome_layers --limit=5
72+
```
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Genome Demo Run Command - Browser Implementation
3+
*
4+
* Delegates to server for demo pipeline orchestration.
5+
*/
6+
7+
import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
8+
import type { JTAGContext } from '@system/core/types/JTAGTypes';
9+
import type { GenomeDemoRunParams, GenomeDemoRunResult } from '../shared/GenomeDemoRunTypes';
10+
11+
export class GenomeDemoRunBrowserCommand extends CommandBase<GenomeDemoRunParams, GenomeDemoRunResult> {
12+
13+
constructor(context: JTAGContext, subpath: string, commander: ICommandDaemon) {
14+
super('genome/demo-run', context, subpath, commander);
15+
}
16+
17+
async execute(params: GenomeDemoRunParams): Promise<GenomeDemoRunResult> {
18+
return await this.remoteExecute(params);
19+
}
20+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "@jtag-commands/genome/demo-run",
3+
"version": "1.0.0",
4+
"description": "Run a sentinel demo pipeline — Claude Code builds real software, interactions are captured for LoRA training",
5+
"main": "server/GenomeDemoRunServerCommand.ts",
6+
"types": "shared/GenomeDemoRunTypes.ts",
7+
"scripts": {
8+
"test": "echo 'Integration test: ./jtag genome/demo-run --project=task-tracker --personaId=<uuid>'",
9+
"lint": "npx eslint **/*.ts",
10+
"typecheck": "npx tsc --noEmit"
11+
},
12+
"peerDependencies": {
13+
"@jtag/core": "*"
14+
},
15+
"files": [
16+
"shared/**/*.ts",
17+
"browser/**/*.ts",
18+
"server/**/*.ts",
19+
"README.md"
20+
],
21+
"keywords": [
22+
"jtag",
23+
"command",
24+
"genome/demo-run"
25+
],
26+
"license": "MIT"
27+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* Genome Demo Run Command - Server Implementation
3+
*
4+
* Loads a project spec, builds a demo pipeline via buildDemoPipeline(),
5+
* and dispatches it to the Rust sentinel executor via sentinel/run.
6+
*
7+
* The pipeline has Claude Code build real software milestone by milestone,
8+
* capturing every interaction for LoRA training. After all milestones:
9+
* genome/train produces a LoRA adapter, genome/phenotype-validate proves improvement.
10+
*/
11+
12+
import * as fs from 'fs';
13+
import * as path from 'path';
14+
import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
15+
import type { JTAGContext } from '@system/core/types/JTAGTypes';
16+
import { ValidationError } from '@system/core/types/ErrorTypes';
17+
import type { GenomeDemoRunParams, GenomeDemoRunResult } from '../shared/GenomeDemoRunTypes';
18+
import { createGenomeDemoRunResultFromParams } from '../shared/GenomeDemoRunTypes';
19+
import { Commands } from '@system/core/shared/Commands';
20+
import type { SentinelStep } from '@system/sentinel/SentinelDefinition';
21+
import type { PipelineSentinelParams, SentinelRunResult } from '@commands/sentinel/run/shared/SentinelRunTypes';
22+
import type { ProjectSpec } from '@system/genome/shared/AcademyTypes';
23+
import { buildDemoPipeline } from '@system/sentinel/pipelines/DemoPipeline';
24+
import { DEFAULT_DEMO_TRAINING_CONFIG, DEMO_DEFAULTS } from '@system/sentinel/pipelines/DemoTypes';
25+
import type { DemoPipelineConfig } from '@system/sentinel/pipelines/DemoTypes';
26+
import { LOCAL_MODELS } from '@system/shared/Constants';
27+
28+
export class GenomeDemoRunServerCommand extends CommandBase<GenomeDemoRunParams, GenomeDemoRunResult> {
29+
30+
constructor(context: JTAGContext, subpath: string, commander: ICommandDaemon) {
31+
super('genome/demo-run', context, subpath, commander);
32+
}
33+
34+
async execute(params: GenomeDemoRunParams): Promise<GenomeDemoRunResult> {
35+
const { project, personaId } = params;
36+
37+
if (!project) {
38+
throw new ValidationError('project', 'Required. Project name (e.g., "task-tracker").');
39+
}
40+
if (!personaId) {
41+
throw new ValidationError('personaId', 'Required. Target persona ID for training.');
42+
}
43+
44+
// Resolve project directory
45+
const srcRoot = path.resolve(__dirname, '../../../../');
46+
const projectDir = path.join(srcRoot, 'projects', project);
47+
const projectJsonPath = path.join(projectDir, 'project.json');
48+
49+
if (!fs.existsSync(projectJsonPath)) {
50+
return createGenomeDemoRunResultFromParams(params, {
51+
success: false,
52+
handle: '',
53+
projectName: project,
54+
milestoneCount: 0,
55+
error: `Project not found: ${projectJsonPath}. Available projects are in src/projects/.`,
56+
});
57+
}
58+
59+
// Parse project spec
60+
let projectSpec: ProjectSpec;
61+
try {
62+
projectSpec = JSON.parse(fs.readFileSync(projectJsonPath, 'utf8'));
63+
} catch (err) {
64+
return createGenomeDemoRunResultFromParams(params, {
65+
success: false,
66+
handle: '',
67+
projectName: project,
68+
milestoneCount: 0,
69+
error: `Failed to parse project.json: ${err}`,
70+
});
71+
}
72+
73+
// Validate scaffold exists
74+
const scaffoldDir = path.join(projectDir, 'scaffold');
75+
if (!fs.existsSync(path.join(scaffoldDir, 'package.json'))) {
76+
return createGenomeDemoRunResultFromParams(params, {
77+
success: false,
78+
handle: '',
79+
projectName: projectSpec.name,
80+
milestoneCount: projectSpec.milestones.length,
81+
error: `Missing scaffold/package.json in ${projectDir}`,
82+
});
83+
}
84+
85+
// Validate test files exist
86+
for (const milestone of projectSpec.milestones) {
87+
const testPath = path.join(projectDir, milestone.testFile);
88+
if (!fs.existsSync(testPath)) {
89+
return createGenomeDemoRunResultFromParams(params, {
90+
success: false,
91+
handle: '',
92+
projectName: projectSpec.name,
93+
milestoneCount: projectSpec.milestones.length,
94+
error: `Missing test file: ${milestone.testFile} (milestone ${milestone.index}: ${milestone.name})`,
95+
});
96+
}
97+
}
98+
99+
const personaName = params.personaName ?? 'demo-persona';
100+
const baseModel = params.baseModel ?? LOCAL_MODELS.DEFAULT;
101+
102+
console.log(`\u{1F680} DEMO RUN: project="${projectSpec.name}", persona="${personaName}", milestones=${projectSpec.milestones.length}`);
103+
104+
// Build demo pipeline config
105+
const config: DemoPipelineConfig = {
106+
projectDir,
107+
project: projectSpec,
108+
personaId,
109+
personaName,
110+
baseModel,
111+
maxRetries: params.maxRetries ?? DEMO_DEFAULTS.maxRetries,
112+
maxBudgetPerMilestone: params.maxBudget ?? DEMO_DEFAULTS.maxBudgetPerMilestone,
113+
maxTurnsPerMilestone: params.maxTurns ?? DEMO_DEFAULTS.maxTurnsPerMilestone,
114+
provider: params.provider ?? DEMO_DEFAULTS.provider,
115+
training: {
116+
...DEFAULT_DEMO_TRAINING_CONFIG,
117+
...(params.epochs !== undefined && { epochs: params.epochs }),
118+
...(params.rank !== undefined && { rank: params.rank }),
119+
},
120+
};
121+
122+
// Build the pipeline
123+
const pipeline = buildDemoPipeline(config);
124+
125+
console.log(` Pipeline: ${pipeline.name}, ${pipeline.steps.length} top-level steps`);
126+
127+
// Dispatch to Rust sentinel executor
128+
// PipelineStep[] (Rust bindings) → SentinelStep[] (TS definitions) — structurally compatible wire types
129+
const pipelineSteps = pipeline.steps as unknown as SentinelStep[];
130+
131+
const sentinelResult = await Commands.execute<PipelineSentinelParams, SentinelRunResult>('sentinel/run', {
132+
type: 'pipeline',
133+
definition: {
134+
type: 'pipeline',
135+
name: pipeline.name ?? `demo-${project}`,
136+
description: `Demo pipeline: Claude Code builds ${projectSpec.name} (${projectSpec.milestones.length} milestones), captures training data for ${personaName}`,
137+
version: '1.0',
138+
steps: pipelineSteps,
139+
loop: { type: 'once' },
140+
tags: ['demo', project, projectSpec.skill],
141+
},
142+
parentPersonaId: personaId,
143+
sentinelName: pipeline.name ?? `demo-${project}`,
144+
});
145+
146+
const handle = sentinelResult.handle ?? '';
147+
console.log(`\u2705 DEMO RUN: Pipeline dispatched, handle=${handle}`);
148+
149+
return createGenomeDemoRunResultFromParams(params, {
150+
success: true,
151+
handle,
152+
projectName: projectSpec.name,
153+
milestoneCount: projectSpec.milestones.length,
154+
});
155+
}
156+
}

0 commit comments

Comments
 (0)