Use Qdrant for music knowledge injection

This commit is contained in:
Meta Vens 2026-06-01 00:11:36 +01:00
commit 94cf7174f7
5 changed files with 420 additions and 18 deletions

View file

@ -375,6 +375,13 @@ form, target-specific roles, and low template/noise-percussion risk. Stock
four-lane loops and raw noise percussion belong in failure documents even when
they keep RMS and motion metrics nonzero.
Music-production retrieval must be an evidence packet, not a lexical slogan
list. Fresh agents should receive Qdrant-backed knowledge hits with owner,
summary, transfer rules, AquaSynth patterns, failure modes, and the retrieved
chunk text. A pass-level curator agent should read musician model output after
scoring and write only novel, actionable entries into the next knowledge store;
raw musician reports remain evidence, not doctrine.
Do not confuse high metric agreement with musical success. The game context,
the reference sound, and the user’s ear still outrank the spreadsheet. Annoying,
but civilization has survived worse.

View file

@ -264,3 +264,4 @@
{"date":"2026-05-31","area":"song-mode-collapse-evaluator","summary":"The corpus run exposed a scoring blind spot: agents could write a short opening phrase and coast for the remaining clip on low-motion texture/noise while still receiving weak-but-ranked evidence. `song-score` now emits song-continuity metrics for active coverage, motion coverage, first-second energy share, tail energy share, and mode-collapse risk; high collapse risk changes verdict to `weak-mode-collapse`, lowers distillation ranking, and is carried into music knowledge failure modes. Swarm prompts now require later-section motifs/events and explicitly reject one-second riff plus nine seconds of texture as submitted song evidence.","verification":"dotnet build E:\\Projects\\AquaSynth\\tools\\IpaTrialWorker\\IpaTrialWorker.csproj --no-restore: passed; dotnet test E:\\Projects\\AquaSynth\\tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter CultCache: 7 passed; rescoring pass-003 agent_02__retrievedmesh__target_04_a05 produced verdict weak-mode-collapse, candidate_first_second_energy_share 0.883627, candidate_motion_coverage 0.573295, mode_collapse_risk 1","evidence":"tools/IpaTrialWorker/Program.cs; tools/run-song-snippet-trial-swarm.ps1; docs/audio-doctrine.md; artifacts/parity/song-continuity-smoke/20260531Tmodecollapse-v2/continuity-smoke/summary.csv"}
{"timestamp":"2026-05-31T22:25:00+01:00","topic":"composition-curriculum-redirect","claim":"The song CultCache run learned transferable sound-production role owners, but not composition. Next passes should require meter, progression or tonal center, note-degree sampling, instrument-lane gates, section events, composition-scale automation, and mix movement. AquaSynth now has small Strudel-ish sugar for `meter`, `sequence`, `chords`, and `mix`, all lowering to visible `param`/`curve` owners alongside existing `pattern` and `scale`.","evidence":"Trawl of pass-003 music-production quality standard, distillation report, and abstraction ledgers showed 56 of 75 official candidates had mode-collapse risk while ledgers repeatedly converged on role owners and sugar candidates. Added parser/test coverage for composition-scale sugar and redirected the song swarm prompt toward Composition Map evidence."}
{"timestamp":"2026-05-31T23:15:00+01:00","topic":"producer-apprenticeship-curriculum-gate","claim":"Song curriculum admission must be owned by producer evidence and audible structure, not syntax attendance. `song-score` now records studio document coverage, producer musicianship, composition section score, template-loop risk, noise-percussion risk, and Aqua gap count; verdicts can fail as `weak-producer-missing` or `weak-slop-template`. `music-distill` promotes candidate and role doctrine only from rendered rows that clear the producer gate, while slop/noise/template rows become failure pressure.","evidence":"tools/IpaTrialWorker/Program.cs; tools/run-song-snippet-trial-swarm.ps1; docs/audio-doctrine.md; state/scratch.md"}
{"timestamp":"2026-06-01T00:12:00+01:00","topic":"qdrant-music-knowledge-and-curator-pass","claim":"Music-production knowledge injection is now Qdrant-backed instead of lexical summary matching. `music-index` embeds full knowledge packets in `aquasynth_music_production_knowledge`; `music-search` defaults to Qdrant/Ollama retrieval and emits full owner, transfer rules, patterns, failure modes, and retrieved chunks. The song swarm now runs a dedicated knowledge-curator agent after each musician phase, and `music-distill` ingests `knowledge-curation.md` as curator-distillation doctrine pressure before indexing the next pass store.","evidence":"tools/IpaTrialWorker/Program.cs; tools/run-song-snippet-trial-swarm.ps1; docs/audio-doctrine.md; artifacts/parity/music-qdrant-smoke/20260601T000755/music-search.md"}

View file

@ -1952,3 +1952,20 @@ Next likely slice:
`listening-journal.md`, `aqua-gap-ledger.md`, and `studio-lesson.md`, and use
the self-iteration loop as a listening/revision record rather than a metric
casino.
2026-06-01 Qdrant music retrieval and curator slice:
- The old music-production "semantic" injection was lexical keyword matching
over short summaries. It is no longer the swarm path.
- `music-index` now embeds full `MusicProductionKnowledgeDocument` packets into
Qdrant collection `aquasynth_music_production_knowledge`; `music-search`
defaults to Qdrant/Ollama retrieval and emits full owner, summary, transfer
rules, AquaSynth patterns, failure modes, vector score, and retrieved chunk
text. Lexical fallback is only an explicit debugging escape hatch.
- After each musician phase, `run-song-snippet-trial-swarm.ps1` starts a
dedicated `knowledge-curator` agent. Its only output is
`knowledge-curation.md`, with novel actionable entries, evidence paths,
owner sentences, current AquaSynth lowering, and rejected-copy notes.
- `music-distill` ingests those curator files as `curator-distillation`
documents, then the pass indexes the resulting `.cc` into Qdrant before the
next phase uses it.

View file

@ -44,6 +44,9 @@ try
case "music-distill":
await MusicDistillAsync(options);
return 0;
case "music-index":
await MusicIndexAsync(options);
return 0;
case "music-search":
await MusicSearchAsync(options);
return 0;
@ -443,6 +446,36 @@ static async Task MusicDistillAsync(Dictionary<string, string> options)
Console.WriteLine(output);
}
static async Task MusicIndexAsync(Dictionary<string, string> options)
{
var store = Required(options, "store");
var output = Value(options, "output", "");
var documents = await IpaTrialResultCultCacheStore.ReadMusicProductionKnowledgeAsync(store);
var searchOptions = MusicSearchOptionsFrom(options);
var index = await EnsureMusicVectorIndexAsync(documents, store, searchOptions, force: BoolValue(options, "force"));
var report = new StringBuilder();
report.AppendLine("# Music Production Knowledge Vector Index");
report.AppendLine();
report.AppendLine($"store: `{store}`");
report.AppendLine($"collection: `{searchOptions.Collection}`");
report.AppendLine($"qdrant: `{searchOptions.QdrantUrl}`");
report.AppendLine($"ollama: `{searchOptions.OllamaUrl}`");
report.AppendLine($"embedder: `{searchOptions.EmbedModel}`");
report.AppendLine($"chunks: {index.ChunkCount}");
report.AppendLine($"vector_dimensions: {index.VectorSize}");
report.AppendLine($"store_key: `{StoreKey(store)}`");
if (!string.IsNullOrWhiteSpace(output))
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(output))!);
await File.WriteAllTextAsync(output, report.ToString(), Encoding.UTF8);
Console.WriteLine(output);
}
else
{
Console.Write(report.ToString());
}
}
static async Task MusicSearchAsync(Dictionary<string, string> options)
{
var store = Required(options, "store");
@ -452,9 +485,32 @@ static async Task MusicSearchAsync(Dictionary<string, string> options)
? Math.Clamp(parsed, 1, 100)
: 12;
var documents = await IpaTrialResultCultCacheStore.ReadMusicProductionKnowledgeAsync(store);
var hits = RankMusicKnowledge(documents, query, limit);
var searchOptions = MusicSearchOptionsFrom(options);
IReadOnlyList<MusicKnowledgeHit> hits;
if (!searchOptions.UseVector)
{
hits = RankMusicKnowledge(documents, query, limit, "lexical-disabled");
}
else
{
try
{
if (!searchOptions.SkipIndex)
{
await EnsureMusicVectorIndexAsync(documents, store, searchOptions, force: false);
}
var vectorHits = await QueryMusicVectorIndexAsync(query, limit * 3, store, searchOptions);
hits = MergeMusicVectorHits(documents, query, limit, vectorHits);
}
catch when (!searchOptions.RequireVector)
{
hits = RankMusicKnowledge(documents, query, limit, "lexical-fallback");
}
}
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(output))!);
await File.WriteAllTextAsync(output, MusicKnowledgeSearchReport(store, query, hits), Encoding.UTF8);
await File.WriteAllTextAsync(output, MusicKnowledgeSearchReport(store, query, hits, searchOptions), Encoding.UTF8);
Console.WriteLine(output);
}
@ -626,6 +682,80 @@ static async Task<IReadOnlyList<VectorSearchHit>> QueryVectorIndexAsync(
.ToArray();
}
static async Task<VectorIndexResult> EnsureMusicVectorIndexAsync(
IReadOnlyList<MusicProductionKnowledgeDocument> documents,
string store,
SearchOptions options,
bool force)
{
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(options.TimeoutSeconds) };
var chunks = BuildMusicKnowledgeChunks(documents, store).ToArray();
if (chunks.Length == 0)
{
return new VectorIndexResult(0, 0);
}
if (force)
{
await DeleteCollectionIfExistsAsync(http, options);
}
var sampleVector = await EmbedOneAsync(http, options, chunks[0].Text);
await EnsureQdrantCollectionAsync(http, options, sampleVector.Length);
const int batchSize = 32;
for (var index = 0; index < chunks.Length; index += batchSize)
{
var batch = chunks.Skip(index).Take(batchSize).ToArray();
var vectors = await EmbedManyAsync(http, options, batch.Select(chunk => chunk.Text).ToArray());
if (vectors.Length != batch.Length)
{
throw new InvalidDataException($"Ollama returned {vectors.Length} vectors for {batch.Length} music knowledge chunks.");
}
var points = batch.Select((chunk, offset) => new QdrantPoint(
StableUuid(chunk.Id),
vectors[offset],
QdrantPayloadFrom(chunk, options.EmbedModel, "music_production_knowledge"))).ToArray();
var upsert = new QdrantUpsertRequest(points);
var response = await http.PutAsJsonAsync(
$"{options.QdrantUrl.TrimEnd('/')}/collections/{Uri.EscapeDataString(options.Collection)}/points?wait=true",
upsert,
JsonOptions());
response.EnsureSuccessStatusCode();
}
await EnsurePayloadIndexesAsync(http, options);
return new VectorIndexResult(chunks.Length, sampleVector.Length);
}
static async Task<IReadOnlyList<VectorSearchHit>> QueryMusicVectorIndexAsync(
string query,
int limit,
string store,
SearchOptions options)
{
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(options.TimeoutSeconds) };
var queryVector = await EmbedOneAsync(http, options, query);
var request = new QdrantSearchRequest(
queryVector,
Math.Clamp(limit, 1, 100),
true,
false,
QdrantFilter.Store(StoreKey(store), options.EmbedModel));
var response = await http.PostAsJsonAsync(
$"{options.QdrantUrl.TrimEnd('/')}/collections/{Uri.EscapeDataString(options.Collection)}/points/search",
request,
JsonOptions());
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<QdrantSearchResponse>(JsonOptions())
?? new QdrantSearchResponse([]);
return result.Result
.Select(VectorSearchHitFrom)
.Where(hit => !string.IsNullOrWhiteSpace(hit.TrialId))
.ToArray();
}
static IReadOnlyList<SearchHit> MergeVectorAndLexicalHits(
IReadOnlyList<IpaTrialResult> results,
string query,
@ -1427,6 +1557,49 @@ static IEnumerable<EvidenceChunk> BuildEvidenceChunks(IReadOnlyList<IpaTrialResu
}
}
static IEnumerable<EvidenceChunk> BuildMusicKnowledgeChunks(IReadOnlyList<MusicProductionKnowledgeDocument> documents, string store)
{
foreach (var document in documents)
{
yield return new EvidenceChunk(
$"music-knowledge:{StoreKey(store)}:{document.KnowledgeId}:full",
document.KnowledgeId,
document.Topic,
document.Kind,
document.QualityTier,
MusicKnowledgeIndexText(document),
store);
}
}
static string MusicKnowledgeIndexText(MusicProductionKnowledgeDocument document)
{
var builder = new StringBuilder();
builder.AppendLine("music production knowledge");
builder.AppendLine($"id {document.KnowledgeId}");
builder.AppendLine($"kind {document.Kind}");
builder.AppendLine($"topic {document.Topic}");
builder.AppendLine($"tier {document.QualityTier}");
builder.AppendLine($"owner {document.Owner}");
builder.AppendLine("summary");
builder.AppendLine(document.Summary);
builder.AppendLine("transfer rules");
builder.AppendLine(string.Join(Environment.NewLine, document.TransferRules));
builder.AppendLine("aquasynth patterns");
builder.AppendLine(string.Join(Environment.NewLine, document.AquaSynthPatterns));
builder.AppendLine("failure modes");
builder.AppendLine(string.Join(Environment.NewLine, document.FailureModes));
builder.AppendLine("metrics");
builder.AppendLine(string.Join(Environment.NewLine, document.Metrics.Select(metric => $"{metric.Name} {metric.Value.ToString("0.######", CultureInfo.InvariantCulture)} weight {metric.Weight.ToString("0.######", CultureInfo.InvariantCulture)}")));
builder.AppendLine("evidence trials");
builder.AppendLine(string.Join(Environment.NewLine, document.EvidenceTrialIds));
builder.AppendLine("evidence candidates");
builder.AppendLine(string.Join(Environment.NewLine, document.EvidenceCandidateIds));
builder.AppendLine("source artifacts");
builder.AppendLine(string.Join(Environment.NewLine, document.SourceArtifactUris));
return builder.ToString();
}
static string EvidenceIndexText(SongChallengeEvidenceDocument document)
{
const int maxIndexedChars = 32768;
@ -1779,6 +1952,11 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
{
yield return ledgerDocument;
}
foreach (var curatorDocument in MusicCuratorDocuments(artifactRoot, created))
{
yield return curatorDocument;
}
}
static IEnumerable<MusicProductionKnowledgeDocument> MusicRoleDocuments(IReadOnlyList<SongSummaryRow> rendered, string created)
@ -1997,7 +2175,36 @@ static IEnumerable<MusicProductionKnowledgeDocument> MusicAbstractionLedgerDocum
}
}
static IReadOnlyList<MusicKnowledgeHit> RankMusicKnowledge(IReadOnlyList<MusicProductionKnowledgeDocument> documents, string query, int limit)
static IEnumerable<MusicProductionKnowledgeDocument> MusicCuratorDocuments(string artifactRoot, string created)
{
foreach (var ledger in Directory.EnumerateFiles(artifactRoot, "knowledge-curation.md", SearchOption.AllDirectories).Where(IsOfficialMusicArtifactPath).Order(StringComparer.OrdinalIgnoreCase))
{
var text = ReadBoundedText(ledger, 9000);
if (string.IsNullOrWhiteSpace(text))
{
continue;
}
var agent = Directory.GetParent(ledger)?.Name ?? "curator";
yield return new MusicProductionKnowledgeDocument(
$"music-curator-distillation-{SafeName(agent)}-{StableUuid(ledger)[..8]}",
"curator-distillation",
$"{agent} actionable music-production distillation",
"curated-doctrine-pressure",
"The curator distillation owns novel, actionable lessons extracted from musician model output after a phase; it is not a raw transcript.",
text,
LinesContaining(text, "do:", "transfer", "because", "owner", "keep", "novel").Take(16).ToArray(),
LinesContaining(text, "aqua", "lower", "syntax", "pattern", "scale", "chords", "mix", "syrinx", "texture").Take(16).ToArray(),
LinesContaining(text, "cut", "reject", "failure", "slop", "static", "missing").Take(16).ToArray(),
[],
[],
[new SpeechScoreMetric("curation_chars", text.Length, 0)],
[ledger],
created);
}
}
static IReadOnlyList<MusicKnowledgeHit> RankMusicKnowledge(IReadOnlyList<MusicProductionKnowledgeDocument> documents, string query, int limit, string retrievalMode = "lexical")
{
var terms = ExpandTerms(Tokenize(query)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
return documents
@ -2028,7 +2235,7 @@ static IReadOnlyList<MusicKnowledgeHit> RankMusicKnowledge(IReadOnlyList<MusicPr
"summary" => pair.Value.Length * 2f,
_ => pair.Value.Length
}) + KnowledgeTierBias(document);
return new MusicKnowledgeHit(document, score, matched);
return new MusicKnowledgeHit(document, score, matched, retrievalMode, null, "", "");
})
.Where(hit => hit.Score > 0 || terms.Length == 0)
.OrderByDescending(hit => hit.Score)
@ -2037,6 +2244,40 @@ static IReadOnlyList<MusicKnowledgeHit> RankMusicKnowledge(IReadOnlyList<MusicPr
.ToArray();
}
static IReadOnlyList<MusicKnowledgeHit> MergeMusicVectorHits(
IReadOnlyList<MusicProductionKnowledgeDocument> documents,
string query,
int limit,
IReadOnlyList<VectorSearchHit> vectorHits)
{
var lexical = RankMusicKnowledge(documents, query, Math.Max(limit, documents.Count), "hybrid-lexical")
.ToDictionary(hit => hit.Document.KnowledgeId, StringComparer.OrdinalIgnoreCase);
var documentsById = documents.ToDictionary(document => document.KnowledgeId, StringComparer.OrdinalIgnoreCase);
return vectorHits
.GroupBy(hit => hit.TrialId, StringComparer.OrdinalIgnoreCase)
.Select(group => group.OrderByDescending(hit => hit.Score).First())
.Where(hit => documentsById.ContainsKey(hit.TrialId))
.Select(hit =>
{
var document = documentsById[hit.TrialId];
var lexicalHit = lexical.GetValueOrDefault(document.KnowledgeId);
var matched = lexicalHit?.MatchedByField ?? new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
var score = hit.Score * 10f + MathF.Min(lexicalHit?.Score ?? 0, 8f) + KnowledgeTierBias(document);
return new MusicKnowledgeHit(
document,
score,
matched,
"qdrant-ollama",
hit.Score,
hit.ChunkId,
hit.Text);
})
.OrderByDescending(hit => hit.Score)
.ThenBy(hit => hit.Document.Topic, StringComparer.OrdinalIgnoreCase)
.Take(limit)
.ToArray();
}
static float KnowledgeTierBias(MusicProductionKnowledgeDocument document) =>
document.QualityTier switch
{
@ -2071,22 +2312,69 @@ static string MusicKnowledgeDistillationReport(string artifactRoot, string outpu
return builder.ToString();
}
static string MusicKnowledgeSearchReport(string store, string query, IReadOnlyList<MusicKnowledgeHit> hits)
static string MusicKnowledgeSearchReport(string store, string query, IReadOnlyList<MusicKnowledgeHit> hits, SearchOptions options)
{
var builder = new StringBuilder();
builder.AppendLine("# Music Knowledge Search");
builder.AppendLine();
builder.AppendLine($"store: `{store}`");
builder.AppendLine($"query: `{query}`");
builder.AppendLine($"retrieval: `{(hits.Any(hit => hit.RetrievalMode == "qdrant-ollama") ? "qdrant-ollama" : "lexical-fallback")}`");
builder.AppendLine($"collection: `{options.Collection}`");
builder.AppendLine($"embedder: `{options.EmbedModel}`");
builder.AppendLine($"matches: {hits.Count}");
builder.AppendLine();
foreach (var hit in hits)
{
var document = hit.Document;
builder.AppendLine($"- `{document.KnowledgeId}` score={hit.Score:0.###} kind={document.Kind} tier={document.QualityTier}");
builder.AppendLine($" topic: {document.Topic}");
builder.AppendLine($" summary: {TrimForReport(document.Summary, 260)}");
builder.AppendLine($" matched: {string.Join(", ", hit.MatchedByField.Select(pair => $"{pair.Key}=[{string.Join('|', pair.Value)}]"))}");
builder.AppendLine($"## {document.Topic}");
builder.AppendLine();
builder.AppendLine($"id: `{document.KnowledgeId}`");
builder.AppendLine($"kind: `{document.Kind}`");
builder.AppendLine($"tier: `{document.QualityTier}`");
builder.AppendLine($"score: `{hit.Score:0.###}`");
if (hit.VectorScore is not null)
{
builder.AppendLine($"vector_score: `{hit.VectorScore.Value:0.###}`");
builder.AppendLine($"vector_chunk: `{hit.VectorChunkId}`");
}
builder.AppendLine($"matched: `{string.Join(", ", hit.MatchedByField.Select(pair => $"{pair.Key}=[{string.Join('|', pair.Value)}]"))}`");
builder.AppendLine();
builder.AppendLine("### Owner");
builder.AppendLine(document.Owner);
builder.AppendLine();
builder.AppendLine("### Summary");
builder.AppendLine(document.Summary);
builder.AppendLine();
builder.AppendLine("### Transfer Rules");
foreach (var rule in document.TransferRules.Take(12))
{
builder.AppendLine($"- {rule}");
}
builder.AppendLine();
builder.AppendLine("### AquaSynth Patterns");
foreach (var pattern in document.AquaSynthPatterns.Take(12))
{
builder.AppendLine($"- {pattern}");
}
builder.AppendLine();
builder.AppendLine("### Failure Modes");
foreach (var mode in document.FailureModes.Take(8))
{
builder.AppendLine($"- {mode}");
}
if (!string.IsNullOrWhiteSpace(hit.VectorText))
{
builder.AppendLine();
builder.AppendLine("### Retrieved Chunk");
builder.AppendLine(TrimForReport(hit.VectorText, 1600));
}
builder.AppendLine();
}
return builder.ToString();
@ -4228,11 +4516,23 @@ static SearchOptions SearchOptionsFrom(Dictionary<string, string> options) => ne
BoolValue(options, "require-vector"),
BoolValue(options, "skip-index"));
static QdrantPayload QdrantPayloadFrom(EvidenceChunk chunk, string embedderId) => new(
static SearchOptions MusicSearchOptionsFrom(Dictionary<string, string> options) => new(
Value(options, "qdrant-url", "http://127.0.0.1:6333"),
Value(options, "ollama-url", "http://127.0.0.1:11434"),
Value(options, "embed-model", "qwen3-embedding:0.6b"),
Value(options, "collection", "aquasynth_music_production_knowledge"),
int.TryParse(Value(options, "timeout-seconds", "120"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var timeout)
? Math.Clamp(timeout, 5, 600)
: 120,
!BoolValue(options, "no-vector"),
!BoolValue(options, "allow-lexical-fallback") || BoolValue(options, "require-vector"),
BoolValue(options, "skip-index"));
static QdrantPayload QdrantPayloadFrom(EvidenceChunk chunk, string embedderId, string corpusKind = "ipa_trial_result") => new(
chunk.Id,
chunk.TrialId,
"ipa_trial_result",
"ipa_trial_result",
corpusKind,
corpusKind,
StoreKey(chunk.Store),
chunk.TrialId,
chunk.CandidateId,
@ -4315,7 +4615,8 @@ static void PrintHelp()
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]
music-distill --artifact-root <song-swarm-dir> --output-store <music-knowledge.cc> --output <report.md> [--max-candidates 16]
music-search --store <music-knowledge.cc> --query <text> --output <report.md> [--limit 12]
music-index --store <music-knowledge.cc> [--output <report.md>] [--force true]
music-search --store <music-knowledge.cc> --query <text> --output <report.md> [--limit 12] [--allow-lexical-fallback true]
music-show --store <music-knowledge.cc> --knowledge-id <id-or-topic> --output <detail.md>
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]
@ -4326,6 +4627,7 @@ static void PrintHelp()
--ollama-url http://127.0.0.1:11434
--embed-model qwen3-embedding:0.6b
--collection aquasynth_ipa_trial_results
music-index/music-search default collection: aquasynth_music_production_knowledge
Use song-prepare --duration-seconds 0 to freeze the full decoded source file from sample zero.
Agent-authored patch files must be named <targetId>__candidate-name.aqua.
@ -4466,7 +4768,11 @@ sealed record SongSummaryRow(
sealed record MusicKnowledgeHit(
MusicProductionKnowledgeDocument Document,
float Score,
IReadOnlyDictionary<string, string[]> MatchedByField);
IReadOnlyDictionary<string, string[]> MatchedByField,
string RetrievalMode,
float? VectorScore,
string VectorChunkId,
string VectorText);
sealed record SongRegister(
float DominantHz,

View file

@ -316,9 +316,11 @@ for ($pass = 1; $pass -le $Passes; $pass++) {
"--store",
$latestMusicKnowledgeStore,
"--query",
"syrinx voice subtractive drums additive pad texture reusable abstraction music production quality standard",
"producer brief listening journal actionable composition arrangement groove harmony melody bass drums syrinx subtractive additive pad texture mix automation failure novelty AquaSynth",
"--limit",
"40",
"--require-vector",
"true",
"--output",
$preMusicEvidence
) `
@ -402,7 +404,7 @@ Challenge:
$challengeList
$challengeReportList
- shared prior evidence: $preEvidence
- shared music-production knowledge: $preMusicEvidence
- shared Qdrant music-production knowledge: $preMusicEvidence
- patch output directory: $agentPatchDir
$targetPatchList
- candidate filename prefix: $agentId
@ -426,7 +428,7 @@ Composition objective:
Generalization:
- You own $SongsPerAgent target patches, and your report must compare all assigned targets and state what reusable scene/instrument knowledge transfers between them.
- The 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 studio reports useful retrieval context: name what transferred, what did not, what sounded alive, what sounded fake, and what evidence would change your next patch.
- Future agents will receive distilled evidence from this trial when asked to zero-shot an audio production request. Read the shared Qdrant music-production knowledge file before writing patches, then make your studio reports useful retrieval context: name what transferred, what did not, what sounded alive, what sounded fake, and what evidence would change your next patch.
Producer evidence:
- Write $agentDir/producer-brief.md before patching. For each target, include the artistic reading, likely genre/tempo feel, emotional/energy contour, section map, instrument role map, mix priorities, and the exact challenge artifacts you are trusting.
@ -517,7 +519,7 @@ Instrument and DSL convention invention:
- Legal oscillator `wave=` values are `sine`, `square`, `saw`, `triangle`, and `noise` only. Use `square` plus filters/envelopes for pulse-like tone; `pulse` is not accepted syntax.
Evidence contract:
- Read every assigned challenge report and $preEvidence.
- Read every assigned challenge report, $preEvidence, and $preMusicEvidence.
- 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 assigned targets, known risks.
- Include a `Composition Map` section in $agentDir/hypotheses.md: meter/time signature, progression or tonal center, instrument lanes, section events after the opening, automation/mix moves, and which evaluator metrics should prove the arrangement did not collapse.
- Also write $agentDir/producer-brief.md, $agentDir/listening-journal.md, $agentDir/aqua-gap-ledger.md, and $agentDir/studio-lesson.md. These reports are now curriculum admission evidence, not decorative paperwork.
@ -575,6 +577,58 @@ Return a short final summary naming the patch and report.
Write-PassPlaylist -PassDirectory $passDir -TopCount $PlaylistTopCount
$curatorDir = Join-Path $passDir "knowledge-curator"
New-Item -ItemType Directory -Force -Path $curatorDir | Out-Null
$curatorPromptPath = Join-Path $curatorDir "knowledge-curator.prompt.md"
$curatorOutputPath = Join-Path $curatorDir "knowledge-curator.md"
$curatorPrompt = @"
You are the AquaSynth music-production knowledge curator for $passId.
Your only job is to distill musician model output from this phase into actionable and novel knowledgebase entries. Do not write patches. Do not praise effort. Do not summarize everything. Extract the lessons that should change what the next musician does.
Read:
- pass directory: $passDir
- proposed patches: $patchRoot
- evaluator reports and summary CSV files under each agent target directory
- producer-brief.md, listening-journal.md, aqua-gap-ledger.md, studio-lesson.md, abstraction-ledger.md, instrument-conventions.md from musician agents
- playlist report: $(Join-Path $passDir "top-scoring-candidates.md")
- prior Qdrant music knowledge injection: $preMusicEvidence
Write exactly this file:
- $curatorDir/knowledge-curation.md
Format knowledge-curation.md as compact entries. Each entry must include:
- title: short reusable lesson
- novelty: what this adds beyond the prior Qdrant knowledge file
- owner sentence: X owns Y so Z remains true
- do: concrete steps a fresh AquaSynth musician can apply
- lower to AquaSynth: exact current syntax or patch idiom if applicable
- evidence: file paths, candidate ids, metrics, and musician report lines used
- reject: what not to copy from the phase
Admission standard:
- Keep only lessons with evidence and a clear next action.
- Prefer composition, arrangement, sound design, mixing, and AquaSynth sugar lessons that transfer across targets.
- Reject generic advice, duplicate role-name slogans, and anything that merely restates the prompt.
- If the phase learned mostly negative evidence, write negative entries with precise failure signatures.
Return a short final note naming knowledge-curation.md.
"@
$curatorJob = Start-CodexAgentJob `
-RepoRoot $repoRoot `
-Prompt $curatorPrompt `
-PromptPath $curatorPromptPath `
-OutputPath $curatorOutputPath `
-LogPath (Join-Path $logRoot "$passId-knowledge-curator.log") `
-CodexCommand $CodexCommand `
-CodexModel $CodexModel `
-JobName "$passId-knowledge-curator"
Wait-CodexJobs -Jobs @($curatorJob)
if (!(Test-Path -LiteralPath (Join-Path $curatorDir "knowledge-curation.md")) -or
((Get-Item -LiteralPath (Join-Path $curatorDir "knowledge-curation.md")).Length -lt 64)) {
throw "Knowledge curator did not write a usable knowledge-curation.md for $passId."
}
$distilledStore = Join-Path $passDir "song-trial-results.distilled.cc"
Invoke-LoggedProcess `
-FilePath "dotnet" `
@ -655,6 +709,23 @@ Return a short final summary naming the patch and report.
"40"
) `
-LogPath (Join-Path $logRoot "$passId-music-distill.log")
Invoke-LoggedProcess `
-FilePath "dotnet" `
-ArgumentList @(
"run",
"--project",
$workerProject,
"--",
"music-index",
"--store",
$musicKnowledgeStore,
"--output",
(Join-Path $passDir "music-vector-index-after.md"),
"--timeout-seconds",
"300"
) `
-LogPath (Join-Path $logRoot "$passId-music-index.log")
$latestMusicKnowledgeStore = $musicKnowledgeStore
Invoke-LoggedProcess `