Skip to content

Commit 008180a

Browse files
V48 Gate 3: Formalize AssetPacksSynthesis onto the real execution/agent/prompt primitives
New asset-packs-synthesis-pipeline.ts runs the measurement on the actual Bitcode primitives instead of the hand-rolled bounded-structured-inference loop: - Execution spine: PipelineExecution('pipeline:asset-packs-synthesis', lineage{family:asset_pack,posture:live,admittedSurface:lens}) -> phase:<lens> -> factoryAgent (AgentExecution) -> measure step -> createFailsafeGenerationSequence (Failsafe[prepare|chunk|stitch] of Thricified[reason|judge|structured_output]) as REAL nested child nodes. - Prompt built UPWARDS via the registry: layered PromptParts (pipeline identity + source-safety law / phase lens-role / agent measurement-catalog + rules / step candidate-shape) registered on the AgentExecution.prompt, composed by buildHierarchicalPrompt; structured generation input becomes the user prompt. - Real ExecutionTool (AssetPackInventoryTool) produces the exclusion-filtered source inventory and is tracked under tool: child execution. AgentExecution auto-resolves the env LLM (no manual wiring). Token accounting walks the execution tree for llm:usage. Additive: typechecks clean; not yet wired into the route (next chunk rewires synthesizeAssetPackCandidates + the deposit route, then deletes bounded-structured-inference.ts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 00a2368 commit 008180a

1 file changed

Lines changed: 285 additions & 0 deletions

File tree

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
/**
2+
* AssetPacksSynthesis — formal pipeline execution (V48 Gate 3).
3+
*
4+
* Runs the synthesis measurement on the real Bitcode primitives instead of a
5+
* hand-rolled inference loop:
6+
* PipelineExecution → phase → factoryAgent (AgentExecution) → step
7+
* → createFailsafeGenerationSequence (Failsafe[prepare|chunk|stitch] ∘
8+
* Thricified[reason|judge|structured_output])
9+
*
10+
* Every LLM call's prompt is built UPWARDS through the execution's prompt
11+
* registry: layered PromptParts registered on the AgentExecution.prompt
12+
* (pipeline identity + source-safety, phase lens role, agent measurement
13+
* catalog + rules, step candidate shape) compose via buildHierarchicalPrompt.
14+
* The exclusion-filtered source inventory is produced by a real ExecutionTool.
15+
*
16+
* Source-safety: the formal LLM substeps store full prompt/response content,
17+
* which is withheld universally by the streaming-layer filter
18+
* (sourceSafeStreamEvent). assertSourceSafeCandidates here is the local
19+
* defense-in-depth check that no admitted candidate leaks raw source.
20+
*/
21+
22+
import { z } from 'zod';
23+
import type { Execution } from '@bitcode/execution-generics/Execution';
24+
import { PipelineExecution } from '@bitcode/pipelines-generics';
25+
import {
26+
factoryAgent,
27+
createFailsafeGenerationSequence,
28+
ExecutionTool,
29+
type AgentStep,
30+
} from '@bitcode/agent-generics';
31+
import { resolveDefaultLLMConfig } from '@bitcode/generic-llms';
32+
import type { PromptPart } from '@bitcode/prompts/parts/PromptPart';
33+
34+
import {
35+
applyExclusionsToInventory,
36+
measurementCatalogForLens,
37+
type AssetPackMeasurementSpec,
38+
type AssetPacksSynthesisLens,
39+
type AssetPacksSynthesisSourceInventory,
40+
type AssetPacksSynthesisSourceSample,
41+
type AssetPacksSynthesisSteering,
42+
} from './asset-packs-synthesis';
43+
44+
const part = (content: string): PromptPart => content as PromptPart;
45+
46+
// ---- Layered system PromptParts (composed by buildHierarchicalPrompt) -------
47+
48+
const PIPELINE_IDENTITY = part(
49+
'You are AssetPacksSynthesis, the single Bitcode synthesis and measurement pipeline. ' +
50+
'Depositing and Reading are the same operation at the core: measuring source knowledge ' +
51+
'into commercially legible AssetPack candidates; the lens carries the variance.',
52+
);
53+
54+
const PIPELINE_SOURCE_SAFETY = part(
55+
'Source-safety law (mandatory): never quote source code, secrets, or file contents in any ' +
56+
'title, summary, or rationale. Describe knowledge and capability, never raw text. Honor the ' +
57+
'protected-IP exclusions absolutely — never cover, reference, or describe excluded material.',
58+
);
59+
60+
const LENS_ROLE: Record<AssetPacksSynthesisLens, PromptPart> = {
61+
deposit: part(
62+
'Lens: deposit. Depositors supply AssetPacks (bounded, source-safe slices of repository ' +
63+
'knowledge) into a Depository where future buyers find Need-fitting packs. Synthesize ' +
64+
'distinct deposit AssetPack options the depositor can review and admit.',
65+
),
66+
read: part(
67+
'Lens: read. Readers ask Bitcode to satisfy a reviewed Need by finding and synthesizing ' +
68+
'Need-fitting AssetPacks from Depository source. Synthesize distinct Need-fitting ' +
69+
'AssetPack candidates the reader can review and buy.',
70+
),
71+
};
72+
73+
function buildCatalogPart(catalog: AssetPackMeasurementSpec[]): PromptPart {
74+
return part(
75+
[
76+
'measurements is an object with EXACTLY these keys, each an honest 0..1 volume:',
77+
...catalog.map((spec) => ` ${spec.measurementKind}: ${spec.guidance}`),
78+
'Justify every measurement in measurementRationale.',
79+
].join('\n'),
80+
);
81+
}
82+
83+
function buildRulesPart(candidateKinds: string[], maxCandidates: number): PromptPart {
84+
return part(
85+
[
86+
`Synthesize 2-${maxCandidates} DISTINCT candidates from the repository inventory.`,
87+
'- coveredSourcePaths must be chosen ONLY from the provided inventory paths, exactly as written.',
88+
'- Each candidate is a distinct knowledge slice (different kind and coverage), commercially legible to a buyer.',
89+
`- candidate kind must be one of: ${candidateKinds.join(', ')}.`,
90+
].join('\n'),
91+
);
92+
}
93+
94+
function buildShapePart(catalog: AssetPackMeasurementSpec[], maxCandidates: number): PromptPart {
95+
return part(
96+
[
97+
'Return ONLY a JSON object with this EXACT top-level shape (no markdown, no prose, no other top-level key):',
98+
`{"options":[{"kind":string,"title":string,"summary":string,"coveredSourcePaths":[string],"measurements":{${catalog
99+
.map((spec) => `"${spec.measurementKind}":number`)
100+
.join(',')}},"measurementRationale":string,"confidence":number}]}`,
101+
`The top-level key MUST be "options" — an array of 2-${maxCandidates} candidate objects. Never return a bare array or any other wrapper key.`,
102+
].join('\n'),
103+
);
104+
}
105+
106+
// ---- Formal source-inventory Tool ------------------------------------------
107+
108+
type InventoryToolArgs = {
109+
paths: string[];
110+
samples: AssetPacksSynthesisSourceSample[];
111+
exclusions: string[];
112+
};
113+
114+
export class AssetPackInventoryTool extends ExecutionTool<
115+
(args: InventoryToolArgs) => Promise<AssetPacksSynthesisSourceInventory>
116+
> {
117+
use = async (args: InventoryToolArgs): Promise<AssetPacksSynthesisSourceInventory> => {
118+
return applyExclusionsToInventory(
119+
{ paths: args.paths, samples: args.samples },
120+
args.exclusions,
121+
);
122+
};
123+
}
124+
125+
// ---- The formal synthesis run ----------------------------------------------
126+
127+
export interface FormalSynthesisRequest {
128+
lens: AssetPacksSynthesisLens;
129+
repositoryFullName: string;
130+
sourceBranch: string | null;
131+
sourceCommit: string | null;
132+
steering: AssetPacksSynthesisSteering;
133+
inventory: AssetPacksSynthesisSourceInventory;
134+
candidateKinds: string[];
135+
maxCandidates: number;
136+
}
137+
138+
export interface FormalSynthesisRawOption {
139+
kind: string;
140+
title: string;
141+
summary: string;
142+
coveredSourcePaths: string[];
143+
measurements: Record<string, number>;
144+
measurementRationale: string;
145+
confidence: number;
146+
}
147+
148+
export interface FormalSynthesisOutcome {
149+
options: FormalSynthesisRawOption[];
150+
provider: string | null;
151+
model: string | null;
152+
totalTokens: number | null;
153+
}
154+
155+
function sumLlmTokens(execution: Execution): number | null {
156+
let total = 0;
157+
let seen = false;
158+
const walk = (node: Execution) => {
159+
try {
160+
const usage = node.get<Record<string, number>>('llm', 'usage');
161+
if (usage && typeof usage === 'object') {
162+
const prompt =
163+
usage.promptTokens ?? usage.prompt_tokens ?? usage.inputTokens ?? usage.input_tokens ?? 0;
164+
const completion =
165+
usage.completionTokens ??
166+
usage.completion_tokens ??
167+
usage.outputTokens ??
168+
usage.output_tokens ??
169+
0;
170+
const t =
171+
typeof usage.totalTokens === 'number'
172+
? usage.totalTokens
173+
: typeof usage.total_tokens === 'number'
174+
? usage.total_tokens
175+
: Number(prompt) + Number(completion);
176+
if (Number.isFinite(t) && t > 0) {
177+
total += t;
178+
seen = true;
179+
}
180+
}
181+
} catch {}
182+
for (const child of node.children.values()) walk(child);
183+
};
184+
walk(execution);
185+
return seen ? total : null;
186+
}
187+
188+
export async function synthesizeAssetPackCandidatesFormal(
189+
request: FormalSynthesisRequest,
190+
candidateSetSchema: z.ZodType<{ options: FormalSynthesisRawOption[] }>,
191+
execution: Execution,
192+
): Promise<FormalSynthesisOutcome> {
193+
const catalog = measurementCatalogForLens(request.lens);
194+
195+
// Execution spine: Pipeline → Phase → Agent → Step → Generation (real nodes).
196+
const pipelineExec = new PipelineExecution(
197+
'pipeline:asset-packs-synthesis',
198+
execution,
199+
{
200+
pipelineName: 'asset-packs-synthesis',
201+
family: 'asset_pack',
202+
posture: 'live',
203+
admittedSurface: request.lens,
204+
} as any,
205+
);
206+
const phaseExec = pipelineExec.child(`phase:${request.lens}`);
207+
208+
// A custom orchestration step (register layered prompts + run the formal
209+
// Failsafe∘Thricified generation). Typed as a plain Executor; factoryAgent
210+
// only invokes it as a function, so it is cast to AgentStep at the call site.
211+
const measureStep = async (
212+
_input: unknown,
213+
agentExec: Execution,
214+
): Promise<{ options: FormalSynthesisRawOption[] }> => {
215+
// Formal Tool: produce the exclusion-filtered inventory and track it.
216+
try {
217+
(agentExec as any).tools?.registerTool?.('source-inventory', new AssetPackInventoryTool());
218+
} catch {}
219+
let inventory = request.inventory;
220+
try {
221+
const tool = (agentExec as any).tools?.getTool?.('source-inventory');
222+
if (tool) {
223+
inventory = await tool.execute({
224+
paths: request.inventory.paths,
225+
samples: request.inventory.samples,
226+
exclusions: request.steering.protectedIpExclusions,
227+
});
228+
}
229+
} catch {}
230+
231+
// Layered system prompt parts on the AgentExecution.prompt (compose upwards).
232+
try {
233+
const p = (agentExec as any).prompt;
234+
if (p?.setSpecificExecution) {
235+
p.setSpecificExecution('pipeline:asset-packs-synthesis:identity', PIPELINE_IDENTITY);
236+
p.setSpecificExecution('pipeline:asset-packs-synthesis:source-safety', PIPELINE_SOURCE_SAFETY);
237+
p.setSpecificExecution(`phase:${request.lens}:role`, LENS_ROLE[request.lens]);
238+
p.setSpecificExecution('agent:measure:catalog', buildCatalogPart(catalog));
239+
p.setSpecificExecution(
240+
'agent:measure:rules',
241+
buildRulesPart(request.candidateKinds, request.maxCandidates),
242+
);
243+
p.setSpecificExecution('step:candidate:shape', buildShapePart(catalog, request.maxCandidates));
244+
}
245+
} catch {}
246+
247+
// The structured context the generation reasons over (becomes the user prompt).
248+
const generationInput = {
249+
repository: request.repositoryFullName,
250+
branch: request.sourceBranch ?? 'unknown',
251+
commit: request.sourceCommit ?? 'unknown',
252+
steeringInstructions: request.steering.instructions,
253+
protectedIpExclusions: request.steering.protectedIpExclusions,
254+
demandContext: request.steering.demandContext,
255+
inventoryPaths: inventory.paths,
256+
excerpts: inventory.samples,
257+
};
258+
259+
const generated: any = await createFailsafeGenerationSequence<
260+
typeof generationInput,
261+
{ options: FormalSynthesisRawOption[] }
262+
>({ outputSchema: candidateSetSchema })(generationInput, agentExec);
263+
264+
const out = generated?.finalOutput ?? generated?.output ?? generated;
265+
return (out && Array.isArray(out.options) ? out : { options: [] }) as {
266+
options: FormalSynthesisRawOption[];
267+
};
268+
};
269+
270+
const agent = factoryAgent<unknown, { options: FormalSynthesisRawOption[] }>({
271+
name: 'asset-packs-measure',
272+
description: 'Measure repository source into source-safe AssetPack candidates.',
273+
steps: [measureStep as unknown as AgentStep<any, any>],
274+
});
275+
276+
const parsed = await agent(null, phaseExec);
277+
const { provider, model } = resolveDefaultLLMConfig();
278+
279+
return {
280+
options: Array.isArray((parsed as any)?.options) ? (parsed as any).options : [],
281+
provider: provider ?? null,
282+
model: model ?? null,
283+
totalTokens: sumLlmTokens(pipelineExec),
284+
};
285+
}

0 commit comments

Comments
 (0)