Add paired song curriculum distillation

This commit is contained in:
Meta Vens 2026-05-31 08:49:07 +01:00
commit 3c07852968
5 changed files with 506 additions and 68 deletions

View file

@ -37,6 +37,9 @@ try
case "dump":
await DumpAsync(options);
return 0;
case "distill":
await DistillAsync(options);
return 0;
case "search":
await SearchAsync(options);
return 0;
@ -320,6 +323,39 @@ static async Task DumpAsync(Dictionary<string, string> options)
Console.WriteLine(output);
}
static async Task DistillAsync(Dictionary<string, string> options)
{
var store = Required(options, "store");
var outputStore = Required(options, "output-store");
var output = Required(options, "output");
var maxResults = int.TryParse(Value(options, "max-results", "40"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedMax)
? Math.Clamp(parsedMax, 1, 500)
: 40;
var minCosine = FloatValue(options, "min-cosine", .35f);
var results = await IpaTrialResultCultCacheStore.ReadResultsAsync(store);
var evidence = await IpaTrialResultCultCacheStore.ReadSongChallengeEvidenceAsync(store);
var selected = DistillResults(results, maxResults, minCosine).ToArray();
var selectedReferenceIds = selected.Select(result => result.ReferenceId).ToHashSet(StringComparer.OrdinalIgnoreCase);
var selectedEvidence = evidence
.Where(document => selectedReferenceIds.Contains(document.ChallengeId))
.ToArray();
var distillation = SongTrialDistillation(store, results, selected, minCosine);
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputStore))!);
if (File.Exists(outputStore))
{
File.Delete(outputStore);
}
await IpaTrialResultCultCacheStore.UpsertResultsAsync(outputStore, selected);
await IpaTrialResultCultCacheStore.UpsertSongChallengeEvidenceAsync(outputStore, selectedEvidence);
await IpaTrialResultCultCacheStore.UpsertSongTrialDistillationsAsync(outputStore, [distillation]);
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(output))!);
await File.WriteAllTextAsync(output, DistillationReport(store, outputStore, results, selected, evidence, selectedEvidence, distillation, minCosine), Encoding.UTF8);
Console.WriteLine(outputStore);
Console.WriteLine(output);
}
static async Task SearchAsync(Dictionary<string, string> options)
{
var store = Required(options, "store");
@ -1173,6 +1209,43 @@ static IEnumerable<EvidenceChunk> BuildEvidenceChunks(IReadOnlyList<IpaTrialResu
.GetResult()
.GroupBy(document => document.ChallengeId, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.ToArray(), StringComparer.OrdinalIgnoreCase);
var distillations = IpaTrialResultCultCacheStore.ReadSongTrialDistillationsAsync(store)
.GetAwaiter()
.GetResult();
foreach (var document in distillations)
{
var linkedTrialIds = document.KeptTrialIds.Length == 0
? [document.DistillationId]
: document.KeptTrialIds;
foreach (var linkedTrialId in linkedTrialIds)
{
yield return new EvidenceChunk(
$"song-trial-distillation:{StoreKey(store)}:{document.DistillationId}:{linkedTrialId}",
linkedTrialId,
"distillation",
"song-trial-distillation",
"distillation",
$"""
song trial distillation
id {document.DistillationId}
linked_trial {linkedTrialId}
source_store {document.SourceStore}
input_trials {document.InputTrialCount}
kept_trials {document.KeptTrialCount}
summary {document.Summary}
reusable_scene_roles
{string.Join(Environment.NewLine, document.ReusableSceneRoles)}
transfer_rules
{string.Join(Environment.NewLine, document.TransferRules)}
failure_patterns
{string.Join(Environment.NewLine, document.FailurePatterns)}
aggregate_metrics
{string.Join(Environment.NewLine, document.AggregateMetrics.Select(metric => $"{metric.Name} {metric.Value.ToString("0.######", CultureInfo.InvariantCulture)} weight {metric.Weight.ToString("0.######", CultureInfo.InvariantCulture)}"))}
""",
store);
}
}
foreach (var result in results)
{
@ -1533,6 +1606,256 @@ static string StoreReport(string store, IReadOnlyList<IpaTrialResult> results)
return builder.ToString();
}
static IReadOnlyList<IpaTrialResult> DistillResults(
IReadOnlyList<IpaTrialResult> results,
int maxResults,
float minCosine)
{
var keep = new Dictionary<string, IpaTrialResult>(StringComparer.OrdinalIgnoreCase);
foreach (var result in results
.Where(result => result.Verdict.Equals("promising", StringComparison.OrdinalIgnoreCase) ||
result.Verdict.Equals("pressure", StringComparison.OrdinalIgnoreCase) ||
MetricValue(result, "log_mel_cosine") >= minCosine)
.OrderByDescending(ScoreSort))
{
keep.TryAdd(result.TrialId, result);
}
foreach (var result in results
.GroupBy(result => result.ReferenceId, StringComparer.OrdinalIgnoreCase)
.Select(group => group.OrderByDescending(ScoreSort).First()))
{
keep.TryAdd(result.TrialId, result);
}
foreach (var result in results
.GroupBy(result => result.CandidateId, StringComparer.OrdinalIgnoreCase)
.Select(group => group.OrderByDescending(ScoreSort).First()))
{
keep.TryAdd(result.TrialId, result);
}
return keep.Values
.OrderByDescending(ScoreSort)
.ThenByDescending(result => MetricValue(result, "audio_score"))
.ThenBy(result => result.TrialId, StringComparer.Ordinal)
.Take(maxResults)
.ToArray();
}
static string DistillationReport(
string sourceStore,
string outputStore,
IReadOnlyList<IpaTrialResult> allResults,
IReadOnlyList<IpaTrialResult> selected,
IReadOnlyList<SongChallengeEvidenceDocument> allEvidence,
IReadOnlyList<SongChallengeEvidenceDocument> selectedEvidence,
SongTrialDistillationDocument distillation,
float minCosine)
{
var selectedIds = selected.Select(result => result.TrialId).ToHashSet(StringComparer.OrdinalIgnoreCase);
var dropped = allResults.Where(result => !selectedIds.Contains(result.TrialId)).ToArray();
var builder = new StringBuilder();
builder.AppendLine("# Song Trial Distillation");
builder.AppendLine();
builder.AppendLine($"source_store: `{sourceStore}`");
builder.AppendLine($"output_store: `{outputStore}`");
builder.AppendLine($"min_cosine: `{minCosine.ToString("0.######", CultureInfo.InvariantCulture)}`");
builder.AppendLine($"input_trials: `{allResults.Count}`");
builder.AppendLine($"kept_trials: `{selected.Count}`");
builder.AppendLine($"dropped_trials: `{dropped.Length}`");
builder.AppendLine($"input_evidence_docs: `{allEvidence.Count}`");
builder.AppendLine($"kept_evidence_docs: `{selectedEvidence.Count}`");
builder.AppendLine($"distillation_document: `{distillation.DistillationId}`");
builder.AppendLine();
builder.AppendLine("## Distilled Signal");
builder.AppendLine(distillation.Summary);
builder.AppendLine();
builder.AppendLine("### Reusable Scene Roles");
foreach (var role in distillation.ReusableSceneRoles)
{
builder.Append("- ");
builder.AppendLine(role);
}
builder.AppendLine();
builder.AppendLine("### Transfer Rules");
foreach (var rule in distillation.TransferRules)
{
builder.Append("- ");
builder.AppendLine(rule);
}
builder.AppendLine();
builder.AppendLine("### Failure Patterns");
foreach (var pattern in distillation.FailurePatterns)
{
builder.Append("- ");
builder.AppendLine(pattern);
}
builder.AppendLine();
builder.AppendLine("## Kept Trials");
foreach (var result in selected)
{
builder.Append("- ");
builder.Append(result.TrialId);
builder.Append(" / ");
builder.Append(result.CandidateId);
builder.Append(" / ");
builder.Append(result.Verdict);
builder.Append(" / cosine=");
builder.Append(Metric(result, "log_mel_cosine"));
builder.Append(" / score=");
builder.Append(Metric(result, "audio_score"));
builder.Append(" / articulation=");
builder.Append(Metric(result, "articulation_score"));
builder.Append(" / reference=");
builder.AppendLine(result.ReferenceId);
}
builder.AppendLine();
builder.AppendLine("## Candidate Aggregates");
foreach (var group in selected.GroupBy(result => result.CandidateId, StringComparer.OrdinalIgnoreCase)
.OrderByDescending(group => group.Average(result => MetricValue(result, "log_mel_cosine"))))
{
builder.Append("- ");
builder.Append(group.Key);
builder.Append(": trials=");
builder.Append(group.Count());
builder.Append(", mean_cosine=");
builder.Append(group.Average(result => MetricValue(result, "log_mel_cosine")).ToString("0.######", CultureInfo.InvariantCulture));
builder.Append(", min_cosine=");
builder.Append(group.Min(result => MetricValue(result, "log_mel_cosine")).ToString("0.######", CultureInfo.InvariantCulture));
builder.Append(", max_cosine=");
builder.Append(group.Max(result => MetricValue(result, "log_mel_cosine")).ToString("0.######", CultureInfo.InvariantCulture));
builder.AppendLine();
}
builder.AppendLine();
builder.AppendLine("## Dropped Noise");
foreach (var result in dropped.OrderBy(ScoreSort).Take(30))
{
builder.Append("- ");
builder.Append(result.TrialId);
builder.Append(" / ");
builder.Append(result.CandidateId);
builder.Append(" / ");
builder.Append(result.Verdict);
builder.Append(" / cosine=");
builder.Append(Metric(result, "log_mel_cosine"));
builder.AppendLine();
}
return builder.ToString();
}
static SongTrialDistillationDocument SongTrialDistillation(
string sourceStore,
IReadOnlyList<IpaTrialResult> allResults,
IReadOnlyList<IpaTrialResult> selected,
float minCosine)
{
var droppedIds = allResults
.Where(result => !selected.Any(kept => kept.TrialId.Equals(result.TrialId, StringComparison.OrdinalIgnoreCase)))
.Select(result => result.TrialId)
.ToArray();
var top = selected.OrderByDescending(ScoreSort).Take(5).ToArray();
var bestCosine = selected.Select(result => MetricValue(result, "log_mel_cosine")).DefaultIfEmpty(0).Max();
var meanCosine = selected.Select(result => MetricValue(result, "log_mel_cosine")).DefaultIfEmpty(0).Average();
var meanScore = selected.Select(result => MetricValue(result, "audio_score")).DefaultIfEmpty(0).Average();
var text = string.Join(' ', selected.Select(result =>
$"{result.CandidateId} {result.Hypothesis} {result.EvaluationSummary} {string.Join(' ', result.KnownLies)}"));
var roles = SceneRolesFrom(text).ToArray();
var failures = FailurePatternsFrom(allResults, selected).ToArray();
var transferRules = TransferRulesFrom(selected, roles).ToArray();
var summary = selected.Count == 0
? $"No high-signal trials met min cosine {minCosine:0.###}; keep only best-per-reference scaffolding."
: $"Kept {selected.Count} of {allResults.Count} trials. Best cosine {bestCosine:0.######}; kept mean cosine {meanCosine:0.######}; best candidates: {string.Join(", ", top.Select(result => result.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(5))}.";
return new SongTrialDistillationDocument(
$"song-distill-{DateTimeOffset.UtcNow:yyyyMMddTHHmmssfff}-{StableUuid(sourceStore)[..8]}",
sourceStore,
DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture),
allResults.Count,
selected.Count,
summary,
roles,
transferRules,
failures,
selected.Select(result => result.TrialId).ToArray(),
droppedIds,
[
new SpeechScoreMetric("distilled_best_log_mel_cosine", bestCosine, 0),
new SpeechScoreMetric("distilled_mean_log_mel_cosine", meanCosine, 0),
new SpeechScoreMetric("distilled_mean_audio_score", meanScore, 0),
new SpeechScoreMetric("distilled_kept_ratio", allResults.Count == 0 ? 0 : selected.Count / (float)allResults.Count, 0)
]);
}
static IEnumerable<string> SceneRolesFrom(string text)
{
var lower = text.ToLowerInvariant();
if (lower.Contains("formant", StringComparison.Ordinal) || lower.Contains("vowel", StringComparison.Ordinal))
{
yield return "bright formant/vowel lead: owns pitched synthetic body and vocal-ish spectral peaks.";
}
if (lower.Contains("dust", StringComparison.Ordinal) || lower.Contains("hat", StringComparison.Ordinal))
{
yield return "clocked dust/hat texture: owns short gated high-band transients through `texture role=dust`, not static white hiss.";
}
if (lower.Contains("codec", StringComparison.Ordinal) || lower.Contains("bit", StringComparison.Ordinal))
{
yield return "codec/bit bed: owns narrow mid-high grit through shaped `texture role=codec` with slow motion.";
}
if (lower.Contains("bass", StringComparison.Ordinal) || lower.Contains("sub", StringComparison.Ordinal))
{
yield return "rubber/sub bass: owns low rhythmic support and should stay register-bounded.";
}
if (lower.Contains("air", StringComparison.Ordinal) || lower.Contains("room", StringComparison.Ordinal) || lower.Contains("bed", StringComparison.Ordinal))
{
yield return "air/room bed: owns low-level recording color through shaped moving texture, not broad full-duration noise.";
}
}
static IEnumerable<string> TransferRulesFrom(IReadOnlyList<IpaTrialResult> selected, IReadOnlyList<string> roles)
{
if (roles.Count == 0)
{
yield return "Prefer target analysis artifacts over memorized song names; choose roles from tempo, register, band deltas, and envelope autocorr.";
}
else
{
yield return "Start zero-shot production by mapping target analysis to reusable scene roles, then instantiate one owner per role.";
}
if (selected.Any(result => MetricValue(result, "rms_ratio") is > 1.35f or < .7f))
{
yield return "Normalize loudness after role selection; high cosine can still hide bad RMS balance.";
}
yield return "Use `texture` for background/recording noise and reserve raw `wave=noise` for short gated transients with narrow filters.";
yield return "Treat repeated pressure verdicts as reusable direction, not acceptance; keep the patch family but mutate timing/register/noise shaping.";
}
static IEnumerable<string> FailurePatternsFrom(IReadOnlyList<IpaTrialResult> allResults, IReadOnlyList<IpaTrialResult> selected)
{
var weakCount = allResults.Count(result => result.Verdict.Equals("weak", StringComparison.OrdinalIgnoreCase));
if (weakCount > 0)
{
yield return $"{weakCount} weak trials are dropped from retrieval pressure unless they were best for their reference.";
}
if (allResults.Any(result => MetricValue(result, "log_mel_cosine") < 0))
{
yield return "Negative cosine candidates are curriculum noise unless preserved as contrast; they should not steer zero-shot production.";
}
if (selected.Any(result => result.KnownLies.Any(lie => lie.Contains("noise", StringComparison.OrdinalIgnoreCase))))
{
yield return "Scene/noise helper voices are allowed, but only shaped role owners should transfer.";
}
}
static float ScoreSort(IpaTrialResult result) =>
result.Metrics.FirstOrDefault(metric => metric.Name == "log_mel_cosine")?.Value ?? float.NegativeInfinity;
@ -2550,6 +2873,7 @@ static void PrintHelp()
song-prepare --source <audio-file> --artifact-root <dir> [--duration-seconds 10] [--seed n] [--output challenge.json]
song-score --patch-root <dir> --challenge <challenge.json> --artifact-root <dir> [--batch-id round-001] [--store <trial-results.cc>] [--hypothesizer id]
dump --store <trial-results.cc> --output <report.md>
distill --store <trial-results.cc> --output-store <distilled.cc> --output <report.md> [--min-cosine .35] [--max-results 40]
index --store <trial-results.cc> [--output <report.md>] [--force true]
search --store <trial-results.cc> --query <text> --output <report.md> [--limit 12] [--no-vector true] [--require-vector true] [--skip-index true]
show --store <trial-results.cc> --trial-id <id-or-candidate> --output <detail.md>

View file

@ -1,6 +1,7 @@
param(
[int]$Passes = 1,
[int]$AgentsPerPass = 5,
[int]$SongsPerAgent = 2,
[string]$Source = "D:\Music\Reinier\Heyoka\Gate Code\Heyoka - Alien Gibberish.mp3",
[string]$SourceFolder = "D:\Music\Reinier\Heyoka",
[float]$DurationSeconds = 30,
@ -175,6 +176,7 @@ Set-Content -LiteralPath (Join-Path $loopRoot "loop-index.md") -Value @"
- codex: $($codex.Source)
- passes: $Passes
- agents_per_pass: $AgentsPerPass
- songs_per_agent: $SongsPerAgent
- seed: $seedValue
- duration_seconds: $DurationSeconds
- new_segment_per_pass: $NewSegmentPerPass
@ -184,6 +186,7 @@ Set-Content -LiteralPath (Join-Path $loopRoot "loop-index.md") -Value @"
"@
$latestDistilledStore = ""
for ($pass = 1; $pass -le $Passes; $pass++) {
$passId = "pass-{0:000}" -f $pass
$passDir = Join-Path $loopRoot $passId
@ -192,7 +195,8 @@ for ($pass = 1; $pass -le $Passes; $pass++) {
New-Item -ItemType Directory -Force -Path $passDir, $patchRoot | Out-Null
$preEvidence = Join-Path $passDir "semantic-search-before.md"
if (Test-Path -LiteralPath $storePath) {
$evidenceStore = if ($latestDistilledStore.Length -gt 0 -and (Test-Path -LiteralPath $latestDistilledStore)) { $latestDistilledStore } else { $storePath }
if (Test-Path -LiteralPath $evidenceStore) {
Invoke-LoggedProcess `
-FilePath "dotnet" `
-ArgumentList @(
@ -202,7 +206,7 @@ for ($pass = 1; $pass -le $Passes; $pass++) {
"--",
"search",
"--store",
$storePath,
$evidenceStore,
"--query",
"song snippet alien gibberish rhythm tempo timbre spectral envelope modulation scene voices weak promising",
"--limit",
@ -212,7 +216,7 @@ for ($pass = 1; $pass -le $Passes; $pass++) {
"--skip-index",
"true"
) `
-LogPath (Join-Path $logRoot "$passId-search-before.log")
-LogPath (Join-Path $logRoot "$passId-search-before.log")
}
else {
Set-Content -LiteralPath $preEvidence -Value "# No prior song-snippet evidence yet"
@ -223,60 +227,87 @@ for ($pass = 1; $pass -le $Passes; $pass++) {
$agentId = "agent-{0:00}" -f $agent
$agentDir = Join-Path $passDir $agentId
$agentPatchDir = Join-Path $patchRoot $agentId
$agentChallengeRoot = Join-Path $agentDir "challenge"
$agentChallengePath = Join-Path $agentChallengeRoot "challenge.json"
New-Item -ItemType Directory -Force -Path $agentDir, $agentPatchDir, $agentChallengeRoot | Out-Null
$sourceIndex = if ($RandomSourcePerAgent) {
(($phaseSeed + ($agent * 1543)) % $songSources.Count)
New-Item -ItemType Directory -Force -Path $agentDir, $agentPatchDir | Out-Null
$challengePaths = @()
$challengeReports = @()
$targetPatchDirs = @()
$agentSources = @()
$agentSeeds = @()
for ($song = 1; $song -le $SongsPerAgent; $song++) {
$songId = "target-{0:00}" -f $song
$agentChallengeRoot = Join-Path $agentDir $songId
$agentChallengePath = Join-Path $agentChallengeRoot "challenge.json"
$targetPatchDir = Join-Path $agentPatchDir $songId
New-Item -ItemType Directory -Force -Path $agentChallengeRoot, $targetPatchDir | Out-Null
$sourceIndex = if ($RandomSourcePerAgent) {
(($phaseSeed + ($agent * 1543) + ($song * 7919)) % $songSources.Count)
}
else {
(($phaseSeed - $seedValue) / 7919 + $song - 1) % $songSources.Count
}
$agentSource = $songSources[[int]$sourceIndex]
$agentSeed = $phaseSeed + ($agent * 104729) + ($song * 8191)
Invoke-LoggedProcess `
-FilePath "dotnet" `
-ArgumentList @(
"run",
"--project",
$workerProject,
"--",
"song-prepare",
"--source",
$agentSource,
"--artifact-root",
$agentChallengeRoot,
"--duration-seconds",
([string]$DurationSeconds),
"--seed",
([string]$agentSeed),
"--challenge-id",
"song-snippet-$loopId-$passId-$agentId-$songId",
"--output",
$agentChallengePath
) `
-LogPath (Join-Path $logRoot "$passId-$agentId-$songId-challenge-prepare.log")
$challengePaths += $agentChallengePath
$challengeReports += (Join-Path $agentChallengeRoot "challenge.md")
$targetPatchDirs += $targetPatchDir
$agentSources += $agentSource
$agentSeeds += $agentSeed
}
else {
(($phaseSeed - $seedValue) / 7919) % $songSources.Count
}
$agentSource = $songSources[[int]$sourceIndex]
$agentSeed = $phaseSeed + ($agent * 104729)
Invoke-LoggedProcess `
-FilePath "dotnet" `
-ArgumentList @(
"run",
"--project",
$workerProject,
"--",
"song-prepare",
"--source",
$agentSource,
"--artifact-root",
$agentChallengeRoot,
"--duration-seconds",
([string]$DurationSeconds),
"--seed",
([string]$agentSeed),
"--challenge-id",
"song-snippet-$loopId-$passId-$agentId",
"--output",
$agentChallengePath
) `
-LogPath (Join-Path $logRoot "$passId-$agentId-challenge-prepare.log")
$challengeMarkdown = Join-Path $agentChallengeRoot "challenge.md"
$challengeList = ($challengePaths | ForEach-Object { "- frozen challenge JSON: $_" }) -join "`n"
$challengeReportList = ($challengeReports | ForEach-Object { "- human-readable challenge report: $_" }) -join "`n"
$targetPatchList = ($targetPatchDirs | ForEach-Object { "- target patch output directory: $_" }) -join "`n"
$agentSourceList = ($agentSources | ForEach-Object { "- assigned source: $_" }) -join "`n"
$agentSeedList = ($agentSeeds | ForEach-Object { "- target seed: $_" }) -join "`n"
$outputPath = Join-Path $agentDir "hypothesis-agent.md"
$promptPath = Join-Path $agentDir "hypothesis-agent.prompt.md"
$logPath = Join-Path $logRoot "$passId-$agentId-hypothesis-agent.log"
$prompt = @"
You are an AquaSynth song-snippet parity worker.
The dataset is on trial. Your patches, hypotheses, failures, and evaluator scores become curriculum evidence in CultCache and the vector database. Write useful sample data and reusable reasoning, not just a one-off trick that happens to flatter one metric.
Challenge:
- frozen challenge JSON: $agentChallengePath
- human-readable challenge report: $challengeMarkdown
$challengeList
$challengeReportList
- shared prior evidence: $preEvidence
- patch output directory: $agentPatchDir
$targetPatchList
- candidate filename prefix: $agentId
- phase id: $passId
- phase seed: $phaseSeed
- assigned source: $agentSource
- agent seed: $agentSeed
$agentSourceList
$agentSeedList
- required duration: $DurationSeconds seconds
Goal:
Write exactly one parseable AquaSynth `.aqua` patch that tries to reproduce the frozen $DurationSeconds second reference clip. This is scene-audio parity, not IPA articulation. You may use ordinary voices, FM, AM/tremolo, filters, formants, noise layers, acoustic vocal/syrinx primitives, curves, and helper voices.
Write exactly one parseable AquaSynth `.aqua` patch for each frozen $DurationSeconds second reference clip assigned to you. This is scene-audio parity, not IPA articulation. You may use ordinary voices, FM, AM/tremolo, filters, formants, noise layers, acoustic vocal/syrinx primitives, curves, and helper voices.
Generalization:
- You own two target patches, but your report must compare both targets and state what reusable scene/instrument knowledge transfers between them.
- The two patches may share instrument roles, texture roles, control idioms, and DSL conventions, but each patch should fit its own target's tempo/register/spectral evidence.
- Future agents will receive distilled evidence from this trial when asked to zero-shot an audio production request. Make your `hypotheses.md` useful retrieval context: name what transferred, what did not, and what evidence would change your next patch.
Noise and texture:
- Do not model background or recording color as a full-duration raw `voice wave=noise` bed. That produces static white-noise wash and will be treated as broken subtractive synthesis, not scene modeling.
@ -316,12 +347,12 @@ Instrument and DSL convention invention:
- The `.aqua` candidate must use today's implemented syntax only: `pattern`, `scale`, `param`, `curve`, and ordinary patch/voice syntax are legal. Proposed future sugar is evidence for the next DSL cut, not magic accepted by the parser.
Evidence contract:
- Read $challengeMarkdown and $preEvidence.
- Write $agentDir/hypotheses.md with: reference features, cited analysis artifacts, tempo/rhythm plan, register/scale plan, scene voices, synthesis owners, invented instrument roles, expected metric movement, known risks.
- Write exactly one `.aqua` file under $agentPatchDir named `<agent-id>__<family>__<hypothesis>.aqua`.
- Read every assigned challenge report and $preEvidence.
- Write $agentDir/hypotheses.md with: per-target reference features, shared cross-target invariants, cited analysis artifacts, tempo/rhythm plan, register/scale plan, scene voices, synthesis owners, invented instrument roles, expected metric movement on both targets, known risks.
- Write exactly one `.aqua` file under each target patch output directory named `<agent-id>__<family>__<hypothesis>.aqua`.
- Include at least three scene voices or layers in the patch, such as primary alien-voice/body, rhythmic transient/noise, and background/recording color.
- Background/recording-color layers must use `texture` or an equivalently gated and band-shaped explicit voice; a static full-duration white-noise bed is failed evidence.
- Make the patch duration/gates cover the $DurationSeconds second target.
- Make each patch duration/gates cover its matching $DurationSeconds second target.
Return a short final summary naming the patch and report.
"@
@ -337,35 +368,39 @@ Return a short final summary naming the patch and report.
}
Wait-CodexJobs -Jobs $jobs
Assert-SongCandidates -PatchDirectory $patchRoot -ExpectedCount $AgentsPerPass
Assert-SongCandidates -PatchDirectory $patchRoot -ExpectedCount ($AgentsPerPass * $SongsPerAgent)
for ($agent = 1; $agent -le $AgentsPerPass; $agent++) {
$agentId = "agent-{0:00}" -f $agent
$agentDir = Join-Path $passDir $agentId
Invoke-LoggedProcess `
-FilePath "dotnet" `
-ArgumentList @(
"run",
"--project",
$workerProject,
"--",
"song-score",
"--patch-root",
(Join-Path $patchRoot $agentId),
"--challenge",
(Join-Path $agentDir "challenge/challenge.json"),
"--artifact-root",
$agentDir,
"--batch-id",
"$passId-$agentId",
"--store",
$storePath,
"--hypothesizer",
"external-codex-song-swarm-$passId-$agentId"
) `
-LogPath (Join-Path $logRoot "$passId-$agentId-score-candidate.log")
for ($song = 1; $song -le $SongsPerAgent; $song++) {
$songId = "target-{0:00}" -f $song
Invoke-LoggedProcess `
-FilePath "dotnet" `
-ArgumentList @(
"run",
"--project",
$workerProject,
"--",
"song-score",
"--patch-root",
(Join-Path (Join-Path $patchRoot $agentId) $songId),
"--challenge",
(Join-Path $agentDir "$songId/challenge.json"),
"--artifact-root",
(Join-Path $agentDir $songId),
"--batch-id",
"$passId-$agentId-$songId",
"--store",
$storePath,
"--hypothesizer",
"external-codex-song-swarm-$passId-$agentId-$songId"
) `
-LogPath (Join-Path $logRoot "$passId-$agentId-$songId-score-candidate.log")
}
}
$distilledStore = Join-Path $passDir "song-trial-results.distilled.cc"
Invoke-LoggedProcess `
-FilePath "dotnet" `
-ArgumentList @(
@ -403,6 +438,28 @@ Return a short final summary naming the patch and report.
"true"
) `
-LogPath (Join-Path $logRoot "$passId-search-after.log")
Invoke-LoggedProcess `
-FilePath "dotnet" `
-ArgumentList @(
"run",
"--project",
$workerProject,
"--",
"distill",
"--store",
$storePath,
"--output-store",
$distilledStore,
"--output",
(Join-Path $passDir "distillation-report.md"),
"--min-cosine",
"0.35",
"--max-results",
"40"
) `
-LogPath (Join-Path $logRoot "$passId-distill.log")
$latestDistilledStore = $distilledStore
}
Write-Host "Song snippet trial swarm complete: $loopRoot"