cognitive-runtime 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,30 @@
1
1
  /**
2
2
  * Module Runner - Execute Cognitive Modules
3
+ * v2.1: Envelope format support, clean input mapping
3
4
  */
4
5
 
5
- import type { Provider, CognitiveModule, ModuleResult, Message } from '../types.js';
6
+ import type {
7
+ Provider,
8
+ CognitiveModule,
9
+ ModuleResult,
10
+ Message,
11
+ ModuleInput,
12
+ EnvelopeResponse,
13
+ ModuleResultData
14
+ } from '../types.js';
6
15
 
7
16
  export interface RunOptions {
17
+ // Clean input (v2 style)
18
+ input?: ModuleInput;
19
+
20
+ // Legacy CLI args (v1 compatibility) - mapped to input.code or input.query
8
21
  args?: string;
9
- input?: Record<string, unknown>;
10
- validateInput?: boolean;
11
- validateOutput?: boolean;
22
+
23
+ // Runtime options
12
24
  verbose?: boolean;
25
+
26
+ // Force envelope format (default: auto-detect from module.output.envelope)
27
+ useEnvelope?: boolean;
13
28
  }
14
29
 
15
30
  export async function runModule(
@@ -17,46 +32,90 @@ export async function runModule(
17
32
  provider: Provider,
18
33
  options: RunOptions = {}
19
34
  ): Promise<ModuleResult> {
20
- const { args, input, verbose = false } = options;
35
+ const { args, input, verbose = false, useEnvelope } = options;
21
36
 
22
- // Build input data
23
- let inputData: Record<string, unknown> = input || {};
24
- if (args) {
25
- inputData = { $ARGUMENTS: args, query: args };
37
+ // Determine if we should use envelope format
38
+ const shouldUseEnvelope = useEnvelope ?? (module.output?.envelope === true || module.format === 'v2');
39
+
40
+ // Build clean input data (v2 style: no $ARGUMENTS pollution)
41
+ const inputData: ModuleInput = input || {};
42
+
43
+ // Map legacy --args to clean input
44
+ if (args && !inputData.code && !inputData.query) {
45
+ // Determine if args looks like code or natural language
46
+ if (looksLikeCode(args)) {
47
+ inputData.code = args;
48
+ } else {
49
+ inputData.query = args;
50
+ }
26
51
  }
27
52
 
28
- // Build prompt
53
+ // Build prompt with clean substitution
29
54
  const prompt = buildPrompt(module, inputData);
30
55
 
31
56
  if (verbose) {
57
+ console.error('--- Module ---');
58
+ console.error(`Name: ${module.name} (${module.format})`);
59
+ console.error(`Responsibility: ${module.responsibility}`);
60
+ console.error(`Envelope: ${shouldUseEnvelope}`);
61
+ console.error('--- Input ---');
62
+ console.error(JSON.stringify(inputData, null, 2));
32
63
  console.error('--- Prompt ---');
33
64
  console.error(prompt);
34
- console.error('--- End Prompt ---');
65
+ console.error('--- End ---');
66
+ }
67
+
68
+ // Build system message based on module config
69
+ const systemParts: string[] = [
70
+ `You are executing the "${module.name}" Cognitive Module.`,
71
+ '',
72
+ `RESPONSIBILITY: ${module.responsibility}`,
73
+ ];
74
+
75
+ if (module.excludes.length > 0) {
76
+ systemParts.push('', 'YOU MUST NOT:');
77
+ module.excludes.forEach(e => systemParts.push(`- ${e}`));
78
+ }
79
+
80
+ if (module.constraints) {
81
+ systemParts.push('', 'CONSTRAINTS:');
82
+ if (module.constraints.no_network) systemParts.push('- No network access');
83
+ if (module.constraints.no_side_effects) systemParts.push('- No side effects');
84
+ if (module.constraints.no_file_write) systemParts.push('- No file writes');
85
+ if (module.constraints.no_inventing_data) systemParts.push('- Do not invent data');
86
+ }
87
+
88
+ if (module.output?.require_behavior_equivalence) {
89
+ systemParts.push('', 'BEHAVIOR EQUIVALENCE:');
90
+ systemParts.push('- You MUST set behavior_equivalence=true ONLY if the output is functionally identical');
91
+ systemParts.push('- If unsure, set behavior_equivalence=false and explain in rationale');
92
+
93
+ const maxConfidence = module.constraints?.behavior_equivalence_false_max_confidence ?? 0.7;
94
+ systemParts.push(`- If behavior_equivalence=false, confidence MUST be <= ${maxConfidence}`);
95
+ }
96
+
97
+ // Add envelope format instructions
98
+ if (shouldUseEnvelope) {
99
+ systemParts.push('', 'RESPONSE FORMAT (Envelope):');
100
+ systemParts.push('- Wrap your response in the envelope format');
101
+ systemParts.push('- Success: { "ok": true, "data": { ...your output... } }');
102
+ systemParts.push('- Error: { "ok": false, "error": { "code": "ERROR_CODE", "message": "..." } }');
103
+ systemParts.push('- Include "confidence" (0-1) and "rationale" in data');
104
+ if (module.output?.require_behavior_equivalence) {
105
+ systemParts.push('- Include "behavior_equivalence" (boolean) in data');
106
+ }
107
+ } else {
108
+ systemParts.push('', 'OUTPUT FORMAT:');
109
+ systemParts.push('- Respond with ONLY valid JSON');
110
+ systemParts.push('- Include "confidence" (0-1) and "rationale" fields');
111
+ if (module.output?.require_behavior_equivalence) {
112
+ systemParts.push('- Include "behavior_equivalence" (boolean) field');
113
+ }
35
114
  }
36
115
 
37
- // Build messages
38
116
  const messages: Message[] = [
39
- {
40
- role: 'system',
41
- content: `You are executing the "${module.name}" Cognitive Module.
42
-
43
- RESPONSIBILITY: ${module.responsibility}
44
-
45
- YOU MUST NOT:
46
- ${module.excludes.map(e => `- ${e}`).join('\n')}
47
-
48
- REQUIRED OUTPUT FORMAT:
49
- You MUST respond with a valid JSON object. Include these fields:
50
- - All fields required by the output schema
51
- - "confidence": a number between 0 and 1
52
- - "rationale": a string explaining your reasoning
53
-
54
- Respond with ONLY valid JSON, no markdown code blocks.`,
55
- },
56
- {
57
- role: 'user',
58
- content: prompt,
59
- },
117
+ { role: 'system', content: systemParts.join('\n') },
118
+ { role: 'user', content: prompt },
60
119
  ];
61
120
 
62
121
  // Invoke provider
@@ -73,49 +132,147 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
73
132
  }
74
133
 
75
134
  // Parse response
76
- let output: unknown;
135
+ let parsed: unknown;
77
136
  try {
78
- // Try to extract JSON from markdown code blocks
79
137
  const jsonMatch = result.content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
80
138
  const jsonStr = jsonMatch ? jsonMatch[1] : result.content;
81
- output = JSON.parse(jsonStr.trim());
139
+ parsed = JSON.parse(jsonStr.trim());
82
140
  } catch {
83
141
  throw new Error(`Failed to parse JSON response: ${result.content.substring(0, 500)}`);
84
142
  }
85
143
 
86
- // Extract confidence and rationale
144
+ // Handle envelope format
145
+ if (shouldUseEnvelope && isEnvelopeResponse(parsed)) {
146
+ return parseEnvelopeResponse(parsed, result.content);
147
+ }
148
+
149
+ // Handle legacy format (non-envelope)
150
+ return parseLegacyResponse(parsed, result.content);
151
+ }
152
+
153
+ /**
154
+ * Check if response is in envelope format
155
+ */
156
+ function isEnvelopeResponse(obj: unknown): obj is EnvelopeResponse {
157
+ if (typeof obj !== 'object' || obj === null) return false;
158
+ const o = obj as Record<string, unknown>;
159
+ return typeof o.ok === 'boolean';
160
+ }
161
+
162
+ /**
163
+ * Parse envelope format response
164
+ */
165
+ function parseEnvelopeResponse(response: EnvelopeResponse, raw: string): ModuleResult {
166
+ if (response.ok) {
167
+ const data = response.data as ModuleResultData;
168
+ return {
169
+ ok: true,
170
+ data: {
171
+ ...data,
172
+ confidence: typeof data.confidence === 'number' ? data.confidence : 0.5,
173
+ rationale: typeof data.rationale === 'string' ? data.rationale : '',
174
+ behavior_equivalence: data.behavior_equivalence,
175
+ },
176
+ raw,
177
+ };
178
+ } else {
179
+ return {
180
+ ok: false,
181
+ error: response.error,
182
+ partial_data: response.partial_data,
183
+ raw,
184
+ };
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Parse legacy (non-envelope) format response
190
+ */
191
+ function parseLegacyResponse(output: unknown, raw: string): ModuleResult {
87
192
  const outputObj = output as Record<string, unknown>;
88
193
  const confidence = typeof outputObj.confidence === 'number' ? outputObj.confidence : 0.5;
89
194
  const rationale = typeof outputObj.rationale === 'string' ? outputObj.rationale : '';
195
+ const behaviorEquivalence = typeof outputObj.behavior_equivalence === 'boolean'
196
+ ? outputObj.behavior_equivalence
197
+ : undefined;
198
+
199
+ // Check if this is an error response (has error.code)
200
+ if (outputObj.error && typeof outputObj.error === 'object') {
201
+ const errorObj = outputObj.error as Record<string, unknown>;
202
+ if (typeof errorObj.code === 'string') {
203
+ return {
204
+ ok: false,
205
+ error: {
206
+ code: errorObj.code,
207
+ message: typeof errorObj.message === 'string' ? errorObj.message : 'Unknown error',
208
+ },
209
+ raw,
210
+ };
211
+ }
212
+ }
90
213
 
91
214
  return {
92
- output,
93
- confidence,
94
- rationale,
95
- raw: result.content,
215
+ ok: true,
216
+ data: {
217
+ ...outputObj,
218
+ confidence,
219
+ rationale,
220
+ behavior_equivalence: behaviorEquivalence,
221
+ } as ModuleResultData,
222
+ raw,
96
223
  };
97
224
  }
98
225
 
99
- function buildPrompt(module: CognitiveModule, inputData: Record<string, unknown>): string {
226
+ /**
227
+ * Build prompt with clean variable substitution
228
+ */
229
+ function buildPrompt(module: CognitiveModule, input: ModuleInput): string {
100
230
  let prompt = module.prompt;
101
231
 
102
- // Substitute $ARGUMENTS
103
- const argsValue = String(inputData.$ARGUMENTS || inputData.query || '');
232
+ // v2 style: substitute ${variable} placeholders
233
+ for (const [key, value] of Object.entries(input)) {
234
+ const strValue = typeof value === 'string' ? value : JSON.stringify(value);
235
+ prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), strValue);
236
+ }
237
+
238
+ // v1 compatibility: substitute $ARGUMENTS
239
+ const argsValue = input.code || input.query || '';
104
240
  prompt = prompt.replace(/\$ARGUMENTS/g, argsValue);
105
241
 
106
- // Substitute $N placeholders
107
- const argsList = argsValue.split(/\s+/);
108
- argsList.forEach((arg, i) => {
109
- prompt = prompt.replace(new RegExp(`\\$${i}`, 'g'), arg);
110
- prompt = prompt.replace(new RegExp(`\\$ARGUMENTS\\[${i}\\]`, 'g'), arg);
111
- });
242
+ // Substitute $N placeholders (v1 compatibility)
243
+ if (typeof argsValue === 'string') {
244
+ const argsList = argsValue.split(/\s+/);
245
+ argsList.forEach((arg, i) => {
246
+ prompt = prompt.replace(new RegExp(`\\$${i}\\b`, 'g'), arg);
247
+ });
248
+ }
112
249
 
113
- // Substitute other input fields
114
- for (const [key, value] of Object.entries(inputData)) {
115
- if (key !== '$ARGUMENTS' && key !== 'query') {
116
- prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value));
250
+ // Append input summary if not already in prompt
251
+ if (!prompt.includes(argsValue) && argsValue) {
252
+ prompt += '\n\n## Input\n\n';
253
+ if (input.code) {
254
+ prompt += '```\n' + input.code + '\n```\n';
255
+ }
256
+ if (input.query) {
257
+ prompt += input.query + '\n';
258
+ }
259
+ if (input.language) {
260
+ prompt += `\nLanguage: ${input.language}\n`;
117
261
  }
118
262
  }
119
263
 
120
264
  return prompt;
121
265
  }
266
+
267
+ /**
268
+ * Heuristic to detect if input looks like code
269
+ */
270
+ function looksLikeCode(str: string): boolean {
271
+ const codeIndicators = [
272
+ /^(def|function|class|const|let|var|import|export|public|private)\s/,
273
+ /[{};()]/,
274
+ /=>/,
275
+ /\.(py|js|ts|go|rs|java|cpp|c|rb)$/,
276
+ ];
277
+ return codeIndicators.some(re => re.test(str));
278
+ }
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Cognitive Runtime - Core Types
3
+ * Version 2.1 - With envelope format, tools policy, failure contract
3
4
  */
4
5
 
5
6
  // Provider interface - all LLM providers implement this
@@ -30,23 +31,133 @@ export interface InvokeResult {
30
31
  };
31
32
  }
32
33
 
33
- // Module types
34
+ // Module types (v2.1)
34
35
  export interface CognitiveModule {
36
+ // Core identity
35
37
  name: string;
36
38
  version: string;
37
39
  responsibility: string;
40
+
41
+ // Constraints
38
42
  excludes: string[];
43
+ constraints?: ModuleConstraints;
44
+
45
+ // Unified policies (v2.1)
46
+ policies?: ModulePolicies;
47
+
48
+ // Tools policy
49
+ tools?: ToolsPolicy;
50
+
51
+ // Output contract
52
+ output?: OutputContract;
53
+
54
+ // Failure contract
55
+ failure?: FailureContract;
56
+
57
+ // Runtime requirements
58
+ runtimeRequirements?: RuntimeRequirements;
59
+
60
+ // Execution context
39
61
  context?: 'fork' | 'main';
62
+
63
+ // Prompt (from prompt.md or MODULE.md body)
40
64
  prompt: string;
65
+
66
+ // Schemas
41
67
  inputSchema?: object;
42
68
  outputSchema?: object;
69
+ errorSchema?: object;
70
+
71
+ // Metadata
43
72
  location: string;
73
+ format: 'v1' | 'v2'; // v1 = MODULE.md, v2 = module.yaml + prompt.md
74
+ }
75
+
76
+ export interface ModuleConstraints {
77
+ no_network?: boolean;
78
+ no_side_effects?: boolean;
79
+ no_file_write?: boolean;
80
+ no_inventing_data?: boolean;
81
+ behavior_equivalence_false_max_confidence?: number;
82
+ }
83
+
84
+ export interface ModulePolicies {
85
+ network?: 'allow' | 'deny';
86
+ filesystem_write?: 'allow' | 'deny';
87
+ side_effects?: 'allow' | 'deny';
88
+ code_execution?: 'allow' | 'deny';
89
+ }
90
+
91
+ export interface ToolsPolicy {
92
+ policy?: 'allow_by_default' | 'deny_by_default';
93
+ allowed: string[];
94
+ denied?: string[];
95
+ }
96
+
97
+ export interface OutputContract {
98
+ format?: 'json_strict' | 'json_lenient' | 'text';
99
+ envelope?: boolean; // v2.1: Use {ok, data/error} wrapper
100
+ require?: string[];
101
+ require_confidence?: boolean;
102
+ require_rationale?: boolean;
103
+ require_behavior_equivalence?: boolean;
104
+ }
105
+
106
+ export interface FailureContract {
107
+ contract?: 'error_union' | 'throw';
108
+ partial_allowed?: boolean;
109
+ must_return_error_schema?: boolean;
110
+ schema?: object;
111
+ }
112
+
113
+ export interface RuntimeRequirements {
114
+ structured_output?: boolean;
115
+ max_input_tokens?: number;
116
+ preferred_capabilities?: string[];
117
+ }
118
+
119
+ // Envelope response format (v2.1)
120
+ export interface EnvelopeSuccess<T = unknown> {
121
+ ok: true;
122
+ data: T;
123
+ }
124
+
125
+ export interface EnvelopeError {
126
+ ok: false;
127
+ error: {
128
+ code: string;
129
+ message: string;
130
+ };
131
+ partial_data?: unknown;
132
+ }
133
+
134
+ export type EnvelopeResponse<T = unknown> = EnvelopeSuccess<T> | EnvelopeError;
135
+
136
+ // Module result types
137
+ export interface ModuleResultData {
138
+ [key: string]: unknown;
139
+ confidence: number;
140
+ rationale: string;
141
+ behavior_equivalence?: boolean;
44
142
  }
45
143
 
46
144
  export interface ModuleResult {
145
+ ok: boolean;
146
+ data?: ModuleResultData;
147
+ error?: {
148
+ code: string;
149
+ message: string;
150
+ };
151
+ partial_data?: unknown;
152
+ raw?: string;
153
+ }
154
+
155
+ // Legacy result format (for backward compatibility)
156
+ export interface LegacyModuleResult {
47
157
  output: unknown;
48
158
  confidence: number;
49
159
  rationale: string;
160
+ behaviorEquivalence?: boolean;
50
161
  raw?: string;
51
162
  }
52
163
 
@@ -62,3 +173,12 @@ export interface CommandResult {
62
173
  data?: unknown;
63
174
  error?: string;
64
175
  }
176
+
177
+ // Module input (clean, no CLI pollution)
178
+ export interface ModuleInput {
179
+ code?: string;
180
+ query?: string;
181
+ language?: string;
182
+ options?: Record<string, unknown>;
183
+ [key: string]: unknown;
184
+ }