@yemi33/minions 0.1.2368 → 0.1.2370

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,9 @@ const shared = require('./shared');
11
11
  const queries = require('./queries');
12
12
  const { wrapUntrusted, buildSource } = require('./untrusted-fence');
13
13
  const { MINIONS_BRAND_URL } = require('./comment-format');
14
+ const features = require('./features');
15
+ const memoryRetrieval = require('./memory-retrieval');
16
+ const memoryStore = require('./memory-store');
14
17
 
15
18
  const { safeJsonArr, safeRead, getProjects, log, ts, dateStamp, truncateTextBytes, ENGINE_DEFAULTS, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT, getProjectOrg } = shared;
16
19
  const { getConfig, getDispatch, getNotes, getPrs, getKnowledgeBaseIndex, AGENTS_DIR } = queries;
@@ -716,10 +719,61 @@ function renderPlaybook(type, vars) {
716
719
  inertAppendices.push('\n\n---\n\n## Pinned Context (CRITICAL — READ FIRST)\n\n' + (fenced || pinnedContent));
717
720
  }
718
721
 
722
+ // Structured memory rollout is deliberately two-stage. The feature flag
723
+ // turns retrieval on; shadow mode (default) records selection telemetry but
724
+ // leaves the legacy notes/personal-memory prompt unchanged. Once shadow mode
725
+ // is disabled, a non-empty bounded retrieval pack replaces those broad
726
+ // appendices. Pinned operator context always remains fully visible.
727
+ let relevantMemoryInjected = false;
728
+ if (features.isFeatureOn('memoryRetrieval', configEarly)) {
729
+ const engineConfig = configEarly.engine || {};
730
+ const shadow = engineConfig.memoryRetrievalShadowMode ?? ENGINE_DEFAULTS.memoryRetrievalShadowMode;
731
+ try {
732
+ const retrieved = memoryRetrieval.retrieveRelevantMemory({
733
+ workItemId: vars.item_id || vars.task_id || '',
734
+ title: vars.item_name || vars.pr_title || '',
735
+ description: vars.item_description || vars.task_description || vars.pr_description || '',
736
+ type: vars.work_type || type,
737
+ project: projectName || '',
738
+ agent: vars.agent_id || '',
739
+ failureClass: vars.failure_class || '',
740
+ sourcePlan: vars.source_plan || '',
741
+ references: vars.references || '',
742
+ prTitle: vars.pr_title || '',
743
+ prBranch: vars.pr_branch || '',
744
+ }, {
745
+ topK: engineConfig.memoryRetrievalTopK ?? ENGINE_DEFAULTS.memoryRetrievalTopK,
746
+ maxBytes: engineConfig.memoryRetrievalMaxBytes ?? ENGINE_DEFAULTS.memoryRetrievalMaxBytes,
747
+ candidateLimit: engineConfig.memoryRetrievalCandidateLimit ?? ENGINE_DEFAULTS.memoryRetrievalCandidateLimit,
748
+ });
749
+ memoryStore.recordRetrievalRun({
750
+ workItemId: vars.item_id || vars.task_id || null,
751
+ project: projectName,
752
+ agent: vars.agent_id || null,
753
+ query: retrieved.query,
754
+ candidateCount: retrieved.candidates.length,
755
+ selectedCount: retrieved.selected.length,
756
+ selectedBytes: retrieved.bytes,
757
+ durationMs: retrieved.durationMs,
758
+ shadow,
759
+ });
760
+ if (!shadow && retrieved.text) {
761
+ const fenced = wrapUntrusted(
762
+ retrieved.text,
763
+ buildSource('memory-retrieval', { item: vars.item_id || vars.task_id || 'unknown' })
764
+ );
765
+ inertAppendices.push('\n\n---\n\n## Relevant Memory (task-selected; verify against live code)\n\n' + (fenced || retrieved.text));
766
+ relevantMemoryInjected = true;
767
+ }
768
+ } catch (e) {
769
+ log('warn', `Memory retrieval failed for ${vars.item_id || vars.task_id || type}; using legacy memory: ${e.message}`);
770
+ }
771
+ }
772
+
719
773
  // Inject team notes (single injection point — not in buildAgentContext) — capped via ENGINE_DEFAULTS.
720
774
  // F5: wrap in <UNTRUSTED-INPUT> fence — notes.md is an LLM-consolidated mix
721
775
  // of agent inbox notes (semi-trusted) and human edits.
722
- let notes = getNotes();
776
+ let notes = relevantMemoryInjected ? '' : getNotes();
723
777
  if (notes) {
724
778
  if (Buffer.byteLength(notes, 'utf8') > ENGINE_DEFAULTS.maxNotesPromptBytes) {
725
779
  const sections = notes.split(/(?=^### )/m);
@@ -738,7 +792,7 @@ function renderPlaybook(type, vars) {
738
792
  // notes budget; missing file degrades gracefully (silent skip).
739
793
  // F5: fence — agent-authored inbox notes routed into this file; any agent
740
794
  // could include attacker-controlled quoted material.
741
- const agentIdForMemory = vars.agent_id;
795
+ const agentIdForMemory = relevantMemoryInjected ? '' : vars.agent_id;
742
796
  if (agentIdForMemory && /^[a-z][a-z0-9-]{0,40}$/i.test(agentIdForMemory) && !String(agentIdForMemory).toLowerCase().startsWith('temp-')) {
743
797
  const agentMemRel = `knowledge/agents/${String(agentIdForMemory).toLowerCase()}.md`;
744
798
  const agentMemPath = path.join(MINIONS_DIR, agentMemRel);
package/engine/shared.js CHANGED
@@ -3683,6 +3683,11 @@ const ENGINE_DEFAULTS = {
3683
3683
  agentMemorySummaryEnabled: false, // opt-in: when true, eviction batches go through an LLM-compressed summary before being dropped. Default off to mirror the conservative gating on the existing reconcile pass (LLM cost + test stability). Operators flip via engine.agentMemorySummaryEnabled.
3684
3684
  agentMemorySummaryThreshold: 30, // batch window: when summary is enabled and a prune evicts entries, fold at least this many oldest sections into one summary. Means "summary every ~30 entries" in steady state (the original PRD intent).
3685
3685
  agentMemorySummaryDays: 30, // age trigger: when the oldest section is older than this and >= agentMemorySummaryThreshold entries exist, summarize the oldest window even if the file is under the entry cap.
3686
+ memoryRetrievalShadowMode: true, // compute + measure FTS5 retrieval but preserve legacy prompt injection until explicitly disabled
3687
+ memoryRetrievalTopK: 8, // maximum structured memory records injected into one dispatch
3688
+ memoryRetrievalMaxBytes: 8 * 1024, // hard byte budget for the Relevant Memory prompt appendix
3689
+ memoryRetrievalCandidateLimit: 50, // bounded FTS5 candidate pool before deterministic re-ranking
3690
+ memoryEpisodicCapture: false, // opt-in capture of compact task outcomes; never stores transcripts or chain-of-thought
3686
3691
  untrustedFenceMaxBytes: 64 * 1024, // F5 (W-mpeklod3000we69c): per-block cap for `<UNTRUSTED-INPUT>` fences in engine/untrusted-fence.js. 64KB is long enough for realistic PR comments / pinned notes / agent memory sections, short enough that a megabyte-bomb comment cannot blow up the prompt. Content above the cap is truncated INSIDE the fence with a `[truncated N more bytes]` marker so the agent still sees the provenance attribute.
3687
3692
  maxMeetingPromptBytes: 16 * 1024, // cap meeting findings/debate context injected into prompts
3688
3693
  maxMeetingHumanNotesBytes: 2 * 1024, // cap human note bullet lists injected into meeting prompts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2368",
3
+ "version": "0.1.2370",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"