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

@ -56,6 +56,24 @@ public sealed record SongChallengeEvidenceDocument(
[property: Key(6)] string SourcePath,
[property: Key(7)] string CreatedAtUtc);
[CultDocument("aquasynth.song_trial_distillation", "aquasynth.song_trial_distillation.v1")]
[MessagePackObject]
public sealed record SongTrialDistillationDocument(
[property: Key(0)]
[property: CultName]
string DistillationId,
[property: Key(1)] string SourceStore,
[property: Key(2)] string CreatedAtUtc,
[property: Key(3)] int InputTrialCount,
[property: Key(4)] int KeptTrialCount,
[property: Key(5)] string Summary,
[property: Key(6)] string[] ReusableSceneRoles,
[property: Key(7)] string[] TransferRules,
[property: Key(8)] string[] FailurePatterns,
[property: Key(9)] string[] KeptTrialIds,
[property: Key(10)] string[] DroppedTrialIds,
[property: Key(11)] SpeechScoreMetric[] AggregateMetrics);
public static class IpaTrialResultCultCacheStore
{
public static Task UpsertResultsAsync(string filePath, IEnumerable<IpaTrialResult> results) =>
@ -64,6 +82,9 @@ public static class IpaTrialResultCultCacheStore
public static Task UpsertSongChallengeEvidenceAsync(string filePath, IEnumerable<SongChallengeEvidenceDocument> documents) =>
UpsertAsync(filePath, documents, document => $"song-challenge-evidence:{document.EvidenceId}");
public static Task UpsertSongTrialDistillationsAsync(string filePath, IEnumerable<SongTrialDistillationDocument> documents) =>
UpsertAsync(filePath, documents, document => $"song-trial-distillation:{document.DistillationId}");
public static async Task<IReadOnlyList<IpaTrialResult>> ReadResultsAsync(string filePath)
{
if (!File.Exists(filePath))
@ -90,6 +111,19 @@ public static class IpaTrialResultCultCacheStore
.ToArray();
}
public static async Task<IReadOnlyList<SongTrialDistillationDocument>> ReadSongTrialDistillationsAsync(string filePath)
{
if (!File.Exists(filePath))
{
return [];
}
using var cache = await CultCacheMessagePack.OpenAsync(filePath).ConfigureAwait(false);
return cache.GetAll<SongTrialDistillationDocument>()
.OrderBy(document => document.DistillationId, StringComparer.Ordinal)
.ToArray();
}
private static async Task UpsertAsync<T>(
string filePath,
IEnumerable<T> documents,

View file

@ -253,3 +253,4 @@
{"date":"2026-05-31","area":"song-snippet-album-swarm","lesson":"A 3-pass album swarm over random 30-second clips produced 15 scored trials and 15 full target evidence bundles in the shared `.cc` store. The agents improved from pass-001 best cosine `0.434277` to pass-002 best `0.590958`, then pass-003 best `0.502573`; aggregate mean cosine was `0.353315`, with 11 pressure verdicts and 4 weak verdicts. The strongest families converged on bright formant/vowel leads plus clocked dust/noise grids, while the semantic index successfully carried 90 bounded Qdrant chunks derived from full CultCache evidence docs.","evidence":"artifacts/parity/song-snippet-swarms/20260531T031003707/experiment-report.md; artifacts/parity/song-snippet-swarms/20260531T031003707/song-trial-results.cc; artifacts/parity/song-snippet-swarms/20260531T031003707/pass-003/vector-index-after.md; artifacts/parity/song-snippet-swarms/20260531T031003707/pass-003/semantic-search-after.md"}
{"date":"2026-05-31","area":"song-scene-texture-owner","lesson":"The album-swarm patches exposed a bad scene-noise owner: agents modeled air/dust/recording condition with full-duration broad `wave=noise` voices, producing static white hiss that sounds like broken subtractive synthesis. The DSL now has `texture`/`noise_texture` sugar with role defaults for dust, air, codec, room, tape/vinyl; it lowers into a gated noise voice with bandpass/highpass/lowpass/tremolo shaping and an owned gate curve or pattern. Future song prompts require texture or an equivalently gated/band-shaped explicit voice for background noise.","evidence":"src/AquaSynth.Core/PatchScript.cs; tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs; tools/run-song-snippet-trial-swarm.ps1; docs/audio-doctrine.md"}
{"date":"2026-05-31","area":"song-source-corpus","lesson":"The song-snippet swarm source corpus now defaults one folder up to `D:\\Music\\Reinier\\Heyoka` and recursively discovers supported audio files, so agents sample across the Heyoka `Gate Code`, `Mandelbass`, and `Marklar` folders instead of only one album folder.","evidence":"tools/run-song-snippet-trial-swarm.ps1; local corpus discovery found eight supported Heyoka tracks under the parent folder."}
{"date":"2026-05-31","area":"song-curriculum-dataset-distillation","lesson":"The song trial loop trains the dataset, not a single patch. Each agent now gets paired song challenges and must write one patch per target; every patch, hypothesis, metric, challenge artifact, evaluator fact, and distillation document becomes curriculum evidence in CultCache/Qdrant. The post-pass distiller writes pruned `.cc` stores with reusable scene roles, transfer rules, failure patterns, kept trials, dropped noise, and aggregate metrics, and retrieval links those distillation chunks back to kept scored trial IDs so future zero-shot production agents can actually receive the distilled context.","evidence":"tools/run-song-snippet-trial-swarm.ps1; tools/IpaTrialWorker/Program.cs; src/AquaSynth.Core/IpaTrialResults.cs; state/scratch.md"}

View file

@ -1753,3 +1753,25 @@ Next likely slice:
defaults `SourceFolder` to `D:\Music\Reinier\Heyoka` and recursively discovers
supported audio files. Current local corpus is eight tracks across `Gate Code`,
`Mandelbass`, and `Marklar`.
2026-05-31 paired song-curriculum slice:
- Corrected the swarm contract: each agent receives two independent song
challenges and must write one patch for each target. The point is not one
blended answer; the point is to train the evidence substrate so a future
zero-shot audio-production agent receives better context from CultCache and
Qdrant.
- The dataset is now explicitly on trial. Patch files, hypotheses, challenge
evidence, metrics, and evaluator/distiller output are curriculum material;
individual patches are sample data and witnesses.
- `run-song-snippet-trial-swarm.ps1` now accepts `-SongsPerAgent`, writes
`agent-XX/target-YY/challenge.json`, expects patches under
`proposed-patches/agent-XX/target-YY`, scores every target separately, and
runs a distillation pass after each swarm pass.
- `IpaTrialWorker distill` writes a pruned `.cc` store plus
`SongTrialDistillationDocument` records summarizing reusable scene roles,
transfer rules, failure patterns, kept trials, dropped noise, and aggregate
metrics.
- Semantic retrieval now indexes distillation documents as chunks linked to
kept scored trial IDs, so the search merge can project distilled lessons into
the next agent context instead of dropping them as orphan curriculum prose.

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"