opencode-swarm 7.130.2 → 7.131.0
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.
- package/dist/cli/{config-doctor-489wej17.js → config-doctor-rjvb0hhn.js} +2 -2
- package/dist/cli/{curation-policy-aaz0hap6.js → curation-policy-f5bybzmh.js} +2 -2
- package/dist/cli/{curator-s2ynewsr.js → curator-kjzqa5m7.js} +13 -13
- package/dist/cli/{curator-llm-factory-mxjcctn8.js → curator-llm-factory-ezq4fakt.js} +13 -13
- package/dist/cli/{evidence-summary-service-hf7qq777.js → evidence-summary-service-jfagmvrh.js} +1 -1
- package/dist/cli/{guardrail-explain-s85kv81r.js → guardrail-explain-n93qh8k6.js} +14 -14
- package/dist/cli/{guardrail-log-2cejc33f.js → guardrail-log-h4tmcjwx.js} +3 -3
- package/dist/cli/{hive-promoter-qdgxajht.js → hive-promoter-mammxe5p.js} +13 -13
- package/dist/cli/{index-jvyn76xe.js → index-01nyrk95.js} +3 -3
- package/dist/cli/{index-kzvwb2se.js → index-03zyn94g.js} +110 -85
- package/dist/cli/{index-xa29259t.js → index-1atz85c8.js} +1 -1
- package/dist/cli/{index-6fqj8fcp.js → index-6vg0qczz.js} +2 -2
- package/dist/cli/{index-zft4hp35.js → index-cnwh164k.js} +1 -1
- package/dist/cli/{index-hjar4crm.js → index-cq22pnd2.js} +1 -1
- package/dist/cli/{index-etdgtk3m.js → index-d2tgc580.js} +2688 -1240
- package/dist/cli/{index-jxha73tm.js → index-fepakwhp.js} +1 -1
- package/dist/cli/{index-5xxgeksy.js → index-hynbzh7s.js} +46 -2
- package/dist/cli/{index-p6qgjkbt.js → index-kbfhyxtv.js} +9 -1
- package/dist/cli/{index-3rnkyb60.js → index-m6zfm7cm.js} +5 -5
- package/dist/cli/{index-s8h2cedv.js → index-pft1qxqh.js} +1 -1
- package/dist/cli/{index-ts9wh1ba.js → index-q68sprcy.js} +60 -8
- package/dist/cli/{index-agte9w1e.js → index-szkswbqf.js} +2 -2
- package/dist/cli/{index-nr5pr54q.js → index-tw33v84d.js} +1 -1
- package/dist/cli/{index-jvwkxz8q.js → index-xhd797bz.js} +15 -15
- package/dist/cli/{index-wy3q73j2.js → index-xv2ydncg.js} +18 -14
- package/dist/cli/index.js +13 -13
- package/dist/cli/{knowledge-escalator-1ypz0266.js → knowledge-escalator-cf1kkmfe.js} +3 -3
- package/dist/cli/{knowledge-events-yvcczmja.js → knowledge-events-p8t5ev44.js} +1 -1
- package/dist/cli/{knowledge-store-h8bp9eje.js → knowledge-store-vpdmcwem.js} +3 -1
- package/dist/cli/{knowledge-validator-sz9v1fdq.js → knowledge-validator-w1t0sw5f.js} +4 -4
- package/dist/cli/{scan-cursor-k9zyt374.js → scan-cursor-z4c51902.js} +2 -2
- package/dist/cli/{schema-fkpn2r1s.js → schema-yqzzfavj.js} +5 -1
- package/dist/cli/{skill-generator-a6a9jxfd.js → skill-generator-aaabj00n.js} +5 -5
- package/dist/commands/consolidate.d.ts +14 -0
- package/dist/commands/curate.d.ts +2 -0
- package/dist/commands/promote.d.ts +25 -0
- package/dist/commands/registry.d.ts +2 -2
- package/dist/config/schema.d.ts +101 -0
- package/dist/consensus/contracts.d.ts +479 -0
- package/dist/consensus/corpus.d.ts +261 -0
- package/dist/consensus/miner.d.ts +200 -0
- package/dist/consensus/public-api.d.ts +42 -0
- package/dist/consensus/store.d.ts +119 -0
- package/dist/evidence/immutable-store.d.ts +86 -0
- package/dist/evidence/manager.d.ts +31 -1
- package/dist/hooks/actionability-predicate.d.ts +36 -0
- package/dist/hooks/curator.d.ts +3 -0
- package/dist/hooks/hive-policy.d.ts +11 -0
- package/dist/hooks/hive-promoter.d.ts +27 -1
- package/dist/hooks/knowledge-dedup-sweep.d.ts +145 -0
- package/dist/hooks/knowledge-injector.d.ts +4 -0
- package/dist/hooks/knowledge-store.d.ts +26 -0
- package/dist/hooks/knowledge-types.d.ts +6 -0
- package/dist/hooks/knowledge-validator.d.ts +10 -14
- package/dist/hooks/micro-reflector.d.ts +106 -2
- package/dist/hooks/realtime-learning-nudge.d.ts +19 -0
- package/dist/index.js +426 -418
- package/dist/knowledge/entry-merge.d.ts +112 -0
- package/dist/knowledge/family-migration.d.ts +6 -2
- package/dist/learning/admission.d.ts +154 -0
- package/dist/learning/candidate-queue.d.ts +178 -0
- package/dist/learning/fingerprint.d.ts +56 -0
- package/dist/learning/prm-pattern-support.d.ts +124 -0
- package/dist/learning/provenance.d.ts +86 -0
- package/dist/prm/index.d.ts +46 -1
- package/dist/services/recommendation-ledger.d.ts +266 -0
- package/dist/services/skill-improver.d.ts +4 -0
- package/dist/services/status-service.d.ts +13 -0
- package/dist/services/trajectory-cluster.d.ts +65 -2
- package/dist/tools/consensus-mine.d.ts +172 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/knowledge-add.d.ts +34 -0
- package/dist/tools/manifest.d.ts +1 -0
- package/dist/tools/tool-metadata.d.ts +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning provenance records (issue #1821, Lane 0a).
|
|
3
|
+
*
|
|
4
|
+
* Every durable learning write — a curator sweep decision, a micro-reflection,
|
|
5
|
+
* a PRM pattern, a consensus-mined lesson, a skill-improver edit — must be able
|
|
6
|
+
* to say *where it came from*. `LearningProvenanceV1` is the shared envelope:
|
|
7
|
+
* which mechanism produced it, which knowledge/task/evidence/run/model ids fed
|
|
8
|
+
* it, and which session/agent physically wrote it.
|
|
9
|
+
*
|
|
10
|
+
* Every reference class is deduplicated, sorted, and hard-capped at
|
|
11
|
+
* `MAX_REFS_PER_CLASS` so a long-running session cannot grow a provenance record
|
|
12
|
+
* without bound (AGENTS.md invariant 8 — global/persisted state must be
|
|
13
|
+
* bounded).
|
|
14
|
+
*
|
|
15
|
+
* This module is pure: no filesystem, no network, no module-level mutable state.
|
|
16
|
+
* The only ambient input is the wall clock, and only when the caller does not
|
|
17
|
+
* supply `producedAt` — callers that need determinism pass it explicitly.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
/**
|
|
21
|
+
* Hard cap on entries retained per reference class. Sorting happens before the
|
|
22
|
+
* cap so truncation is deterministic rather than insertion-order dependent.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_REFS_PER_CLASS = 50;
|
|
25
|
+
/** The learning mechanism that produced a record. */
|
|
26
|
+
export type LearningMechanism = 'curator_sweep' | 'micro_reflection' | 'prm_pattern' | 'consensus_mine' | 'skill_improver';
|
|
27
|
+
export declare const LearningProvenanceV1Schema: z.ZodObject<{
|
|
28
|
+
v: z.ZodLiteral<1>;
|
|
29
|
+
mechanism: z.ZodEnum<{
|
|
30
|
+
skill_improver: "skill_improver";
|
|
31
|
+
consensus_mine: "consensus_mine";
|
|
32
|
+
curator_sweep: "curator_sweep";
|
|
33
|
+
micro_reflection: "micro_reflection";
|
|
34
|
+
prm_pattern: "prm_pattern";
|
|
35
|
+
}>;
|
|
36
|
+
sourceKnowledgeIds: z.ZodArray<z.ZodString>;
|
|
37
|
+
sourceTaskIds: z.ZodArray<z.ZodString>;
|
|
38
|
+
sourceEvidenceRefs: z.ZodArray<z.ZodString>;
|
|
39
|
+
sourceRunIds: z.ZodArray<z.ZodString>;
|
|
40
|
+
sourceModelIds: z.ZodArray<z.ZodString>;
|
|
41
|
+
writeOrigin: z.ZodObject<{
|
|
42
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
43
|
+
agentRole: z.ZodOptional<z.ZodString>;
|
|
44
|
+
producedAt: z.ZodISODateTime;
|
|
45
|
+
}, z.core.$strict>;
|
|
46
|
+
}, z.core.$strict>;
|
|
47
|
+
export interface LearningProvenanceV1 {
|
|
48
|
+
v: 1;
|
|
49
|
+
mechanism: LearningMechanism;
|
|
50
|
+
sourceKnowledgeIds: string[];
|
|
51
|
+
sourceTaskIds: string[];
|
|
52
|
+
sourceEvidenceRefs: string[];
|
|
53
|
+
sourceRunIds: string[];
|
|
54
|
+
sourceModelIds: string[];
|
|
55
|
+
/** See `LearningWriteOriginSchema` for why there is no `agentId` here. */
|
|
56
|
+
writeOrigin: {
|
|
57
|
+
sessionId?: string;
|
|
58
|
+
agentRole?: string;
|
|
59
|
+
producedAt: string;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Caller-supplied provenance body. Every reference class is optional. */
|
|
63
|
+
export interface LearningProvenanceInput {
|
|
64
|
+
mechanism: LearningMechanism;
|
|
65
|
+
sourceKnowledgeIds?: string[];
|
|
66
|
+
sourceTaskIds?: string[];
|
|
67
|
+
sourceEvidenceRefs?: string[];
|
|
68
|
+
sourceRunIds?: string[];
|
|
69
|
+
sourceModelIds?: string[];
|
|
70
|
+
}
|
|
71
|
+
/** Caller-supplied write origin. `producedAt` defaults to now when omitted. */
|
|
72
|
+
export interface LearningWriteOriginInput {
|
|
73
|
+
sessionId?: string;
|
|
74
|
+
agentRole?: string;
|
|
75
|
+
producedAt?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build a validated `LearningProvenanceV1` from a partial body and a write
|
|
79
|
+
* origin. Fills `v`, missing reference classes, and `producedAt`; deduplicates,
|
|
80
|
+
* sorts, and caps every reference class; then parses through
|
|
81
|
+
* `LearningProvenanceV1Schema` so the returned value is guaranteed valid.
|
|
82
|
+
*
|
|
83
|
+
* Throws (via Zod) on an unknown mechanism or an unparseable `producedAt` —
|
|
84
|
+
* a malformed provenance record must never be persisted silently.
|
|
85
|
+
*/
|
|
86
|
+
export declare function stampLearningProvenance(partial: LearningProvenanceInput, origin?: LearningWriteOriginInput): LearningProvenanceV1;
|
package/dist/prm/index.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export { formatCourseCorrectionForInjection, generateCourseCorrection, } from '.
|
|
|
16
16
|
export { createDefaultEscalationState, EscalationTracker } from './escalation';
|
|
17
17
|
export { detectContextThrash, detectExpansionDrift, detectPatterns, detectPingPong, detectRepetitionLoop, detectStuckOnTest, } from './pattern-detector';
|
|
18
18
|
export type { CourseCorrection, EscalationState, PatternDetectionResult, PatternMatch, PatternSeverity, PatternType, PrmConfig, TaxonomyCategory, TrajectoryEntry, } from './types';
|
|
19
|
+
import type { LearningConfig } from '../config/schema.js';
|
|
20
|
+
import { appendInsightCandidates } from '../hooks/micro-reflector.js';
|
|
21
|
+
import { enqueueCandidate } from '../learning/candidate-queue.js';
|
|
22
|
+
import { recordPatternObservation } from '../learning/prm-pattern-support.js';
|
|
19
23
|
import { getAgentSession } from '../state';
|
|
20
24
|
import { telemetry } from '../telemetry';
|
|
21
25
|
import { formatCourseCorrectionForInjection, generateCourseCorrection } from './course-correction';
|
|
@@ -44,6 +48,9 @@ export declare const _internals: {
|
|
|
44
48
|
recordReplayEntry: typeof recordReplayEntry;
|
|
45
49
|
startReplayRecording: typeof startReplayRecording;
|
|
46
50
|
telemetry: typeof telemetry;
|
|
51
|
+
recordPatternObservation: typeof recordPatternObservation;
|
|
52
|
+
enqueueCandidate: typeof enqueueCandidate;
|
|
53
|
+
appendInsightCandidates: typeof appendInsightCandidates;
|
|
47
54
|
};
|
|
48
55
|
/**
|
|
49
56
|
* Context passed to toolAfter handler
|
|
@@ -94,4 +101,42 @@ export declare function resetPrmSessionState(session: ResettablePrmSessionState,
|
|
|
94
101
|
* // Wire prmHook.toolAfter into your tool.execute.after hook
|
|
95
102
|
* ```
|
|
96
103
|
*/
|
|
97
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Knobs the PRM hook needs to hand supported patterns to the real-time
|
|
106
|
+
* admission queue (issue #1821, AC10). Optional so existing callers and tests
|
|
107
|
+
* keep working unchanged; when absent, pattern persistence is simply off.
|
|
108
|
+
*/
|
|
109
|
+
export interface PrmPatternPersistenceOptions {
|
|
110
|
+
/**
|
|
111
|
+
* `learning.prm_persistence.enabled`. Governs the WHOLE producer, durable
|
|
112
|
+
* append included.
|
|
113
|
+
*/
|
|
114
|
+
enabled: boolean;
|
|
115
|
+
min_support: number;
|
|
116
|
+
cooldown_ms: number;
|
|
117
|
+
/**
|
|
118
|
+
* `learning.realtime_admission.enabled`. Governs ONLY the in-memory enqueue.
|
|
119
|
+
*
|
|
120
|
+
* It must never gate the durable append (issue #1821 F3): AC8 says disabled
|
|
121
|
+
* or crashed real-time work loses nothing, and the phase-boundary backstop
|
|
122
|
+
* can only see candidates that reached `.swarm/insight-candidates.jsonl`.
|
|
123
|
+
* ANDing the two flags at the call site made `realtime_admission.enabled=false`
|
|
124
|
+
* silently discard every PRM candidate. Mirrors `micro-reflector.ts`, which
|
|
125
|
+
* appends unconditionally and gates only its enqueue.
|
|
126
|
+
*/
|
|
127
|
+
admission_enabled: boolean;
|
|
128
|
+
/** `learning.realtime_admission.max_queue_size` — bounds the shared queue. */
|
|
129
|
+
max_queue_size: number;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Map a parsed `learning` config onto the PRM producer's knobs (issue #1821 F3).
|
|
133
|
+
*
|
|
134
|
+
* The mapping lives here, beside the interface that documents which flag governs
|
|
135
|
+
* what, and `src/index.ts` is its only production caller — so the AC8 coupling
|
|
136
|
+
* ("durable persistence is gated by `prm_persistence.enabled` ALONE") is
|
|
137
|
+
* expressed once and can be asserted directly by a test instead of being
|
|
138
|
+
* re-derived from an object literal at the plugin's wiring site, where the two
|
|
139
|
+
* flags were previously ANDed together.
|
|
140
|
+
*/
|
|
141
|
+
export declare function resolvePrmPatternPersistenceOptions(learning: LearningConfig): PrmPatternPersistenceOptions;
|
|
142
|
+
export declare function createPrmHook(config: PrmConfig, directory: string, patternPersistence?: PrmPatternPersistenceOptions): PrmHook;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-producer recommendation dedup ledger (issue #1821, AC21).
|
|
3
|
+
*
|
|
4
|
+
* Three mechanisms propose learning recommendations: the curator sweep
|
|
5
|
+
* (`src/hooks/curator.ts`), the skill improver's macro-reflector
|
|
6
|
+
* (`src/services/trajectory-cluster.ts`), and the consensus miner
|
|
7
|
+
* (`src/consensus/miner.ts`). `src/learning/fingerprint.ts` gave them a shared
|
|
8
|
+
* identity function, but a fingerprint alone dedups nothing: somebody has to
|
|
9
|
+
* remember what was already emitted. This module is that memory.
|
|
10
|
+
*
|
|
11
|
+
* ## Why a second key
|
|
12
|
+
*
|
|
13
|
+
* `computeRecommendationFingerprint` deliberately folds `kind` into the digest —
|
|
14
|
+
* its own doc says "dedup across mechanisms is a policy decision made by the
|
|
15
|
+
* caller, not baked into the identity". This module *is* that caller, and the
|
|
16
|
+
* policy it implements is: **two recommendations are the same recommendation
|
|
17
|
+
* when they say the same thing about the same scope, regardless of which
|
|
18
|
+
* mechanism noticed it first.** So every ledger entry carries two ids:
|
|
19
|
+
*
|
|
20
|
+
* - `fingerprint` — the producer-scoped `lrec_…` id, kept verbatim for audit.
|
|
21
|
+
* - `crossKey` — an `lxk_…` id over `{ normalizedStatement, sortedScopeKeys }`.
|
|
22
|
+
*
|
|
23
|
+
* `crossKey` drops both `kind` and `target`. Dropping `kind` is the whole point.
|
|
24
|
+
* Dropping `target` is a redundancy call, not a necessity: each producer already
|
|
25
|
+
* interpolates its target into the statement it emits (the miner's `intent`
|
|
26
|
+
* names its `proposedTarget`; the improver's statement names the tool and
|
|
27
|
+
* failure kind its slug is built from; the curator's `entry_id` travels in
|
|
28
|
+
* `scopeKeys`), so keeping `target` would add no discriminating power while
|
|
29
|
+
* splitting identity on producer-local vocabulary.
|
|
30
|
+
*
|
|
31
|
+
* Disambiguation that genuinely must survive therefore travels in `scopeKeys`,
|
|
32
|
+
* which is order- and duplicate-insensitive (`normalizeScopeKeys`). The curator
|
|
33
|
+
* uses that to keep `archive entry-X` and `rewrite entry-X` distinct even when
|
|
34
|
+
* both carry the same lesson text; recommendations that mint *new* knowledge
|
|
35
|
+
* carry no scope keys, because content alone is their identity.
|
|
36
|
+
*
|
|
37
|
+
* ## What this does and does not achieve today
|
|
38
|
+
*
|
|
39
|
+
* The key is an EXACT hash of normalized text. Two producers dedup against each
|
|
40
|
+
* other only when they emit the same sentence. In the current codebase the
|
|
41
|
+
* improver and the miner build their statements from fixed templates
|
|
42
|
+
* (`Avoid the recurring … failure in …`, `Investigate the smallest … change …`)
|
|
43
|
+
* while the curator's statement is a free-form LLM lesson, so a cross-producer
|
|
44
|
+
* collision is possible but uncommon. The concrete, everyday win is therefore:
|
|
45
|
+
* (a) WITHIN-producer dedup, which the curator and the improver had none of, and
|
|
46
|
+
* (b) one shared, provenance-stamped record of every emitted recommendation that
|
|
47
|
+
* any producer can consult. Making cross-producer suppression routine would need
|
|
48
|
+
* near-duplicate matching (see `findNearDuplicate` / the knowledge dedup sweep)
|
|
49
|
+
* rather than an exact fingerprint, which is a different mechanism than the one
|
|
50
|
+
* `src/learning/fingerprint.ts` defines.
|
|
51
|
+
*
|
|
52
|
+
* ## Check, emit, then record — never "reserve"
|
|
53
|
+
*
|
|
54
|
+
* The two phases are deliberately separate:
|
|
55
|
+
*
|
|
56
|
+
* - `checkRecommendations` is read-only. It answers "has this been emitted
|
|
57
|
+
* before?" and writes nothing.
|
|
58
|
+
* - `recordEmittedRecommendations` is the locked write, and callers run it only
|
|
59
|
+
* for recommendations that **actually took effect**.
|
|
60
|
+
*
|
|
61
|
+
* An earlier design claimed the key up-front, before the caller emitted. That is
|
|
62
|
+
* wrong here: the curator legitimately *defers* recommendations (cohort-safe
|
|
63
|
+
* authorization not yet granted, target entry temporarily inactive, CAS revision
|
|
64
|
+
* drift, fair-scan generation already curated, actionability quarantine pending
|
|
65
|
+
* the hardening loop). Every one of those paths expects a later sweep to retry,
|
|
66
|
+
* and a key claimed before the emit would suppress that retry permanently.
|
|
67
|
+
* Recording after the fact costs a small race — two producers can both pass
|
|
68
|
+
* `check` and both emit — whose worst case is one duplicate, exactly the
|
|
69
|
+
* pre-#1821 behaviour. A burned key, by contrast, loses a lesson forever. The
|
|
70
|
+
* record step re-checks under the lock, so the ledger itself never grows a
|
|
71
|
+
* duplicate.
|
|
72
|
+
*
|
|
73
|
+
* ## Boundedness and containment
|
|
74
|
+
*
|
|
75
|
+
* The ledger is a JSONL file at `.swarm/learning/recommendation-ledger.jsonl`,
|
|
76
|
+
* hard-capped at `MAX_RECOMMENDATION_LEDGER_ENTRIES` entries with oldest-first
|
|
77
|
+
* FIFO eviction, and every entry is capped at `MAX_ENTRY_BYTES` (provenance is
|
|
78
|
+
* dropped rather than allowed to blow the bound), so the file has a hard ceiling
|
|
79
|
+
* of roughly 2 MB (AGENTS.md invariant 8). It never touches `process.cwd()`
|
|
80
|
+
* (invariant 4). Its own subdirectory is deliberate: `transactFile` locks the
|
|
81
|
+
* *containing directory*, so living under `learning/` keeps this lock disjoint
|
|
82
|
+
* from the `.swarm/` root lock that `transactKnowledge` takes.
|
|
83
|
+
*
|
|
84
|
+
* The root is `resolveKnowledgeStoreDir`, NOT a hardcoded `<directory>/.swarm`.
|
|
85
|
+
* That is the same link-aware resolution `resolveSwarmKnowledgePath` uses, so in
|
|
86
|
+
* a linked cohort the ledger lives beside the knowledge store it guards instead
|
|
87
|
+
* of being stranded per-worktree — a lane worktree would otherwise discard its
|
|
88
|
+
* ledger while the lessons it recorded persisted in the shared store.
|
|
89
|
+
*
|
|
90
|
+
* Eviction means a recommendation older than the last 500 emissions can surface
|
|
91
|
+
* again. That is the intended trade: bounded state beats perfect recall. Note
|
|
92
|
+
* this cap is independent of `knowledge.swarm_max_entries` (default 100): a
|
|
93
|
+
* lesson the knowledge store has already FIFO-evicted can still be suppressed
|
|
94
|
+
* here until its own entry ages out.
|
|
95
|
+
*
|
|
96
|
+
* ## Fail-open
|
|
97
|
+
*
|
|
98
|
+
* Every failure mode — unreadable ledger, lock timeout, corrupt lines, a
|
|
99
|
+
* provenance record the schema rejects — resolves to "emit everything", i.e.
|
|
100
|
+
* exactly the behaviour that existed before this module. A broken dedup ledger
|
|
101
|
+
* must never silence the learning loops.
|
|
102
|
+
*/
|
|
103
|
+
import { transactFile } from '../hooks/knowledge-store.js';
|
|
104
|
+
import { type RecommendationKind } from '../learning/fingerprint.js';
|
|
105
|
+
import { type LearningProvenanceInput, type LearningProvenanceV1, type LearningWriteOriginInput } from '../learning/provenance.js';
|
|
106
|
+
/**
|
|
107
|
+
* Hard cap on retained ledger entries (AGENTS.md invariant 8). Sized to match
|
|
108
|
+
* `INSIGHT_CANDIDATES_MAX_ENTRIES` in `src/hooks/micro-reflector.ts`, the
|
|
109
|
+
* closest sibling queue: the same order of magnitude of learning artifacts flows
|
|
110
|
+
* through both, and one shared number is easier to reason about than two.
|
|
111
|
+
*/
|
|
112
|
+
export declare const MAX_RECOMMENDATION_LEDGER_ENTRIES = 500;
|
|
113
|
+
/**
|
|
114
|
+
* Hard cap on one serialized entry. Provenance reference lists are individually
|
|
115
|
+
* bounded by `stampLearningProvenance`, but 5 classes x 50 refs x 512 chars is
|
|
116
|
+
* still ~128 KB, which would put the file's ceiling in the tens of megabytes.
|
|
117
|
+
* An entry over this budget keeps its identity fields and drops provenance —
|
|
118
|
+
* dedup is the contract, provenance is the bonus.
|
|
119
|
+
*/
|
|
120
|
+
export declare const MAX_ENTRY_BYTES = 4096;
|
|
121
|
+
/** A recommendation a producer is about to emit, or has just emitted. */
|
|
122
|
+
export interface RecommendationCandidate {
|
|
123
|
+
/** Producing mechanism. Recorded on the entry; excluded from `crossKey`. */
|
|
124
|
+
kind: RecommendationKind;
|
|
125
|
+
/** Producer-local target (knowledge id, proposal slug, subsystem name). */
|
|
126
|
+
target: string;
|
|
127
|
+
/** The recommendation body. Normalized before hashing. */
|
|
128
|
+
statement: string;
|
|
129
|
+
/** Scope disambiguators. Order- and duplicate-insensitive. */
|
|
130
|
+
scopeKeys?: string[];
|
|
131
|
+
/** Learning provenance stamped onto the ledger entry when recorded. */
|
|
132
|
+
provenance?: LearningProvenanceInput;
|
|
133
|
+
/** Write origin for the provenance stamp. */
|
|
134
|
+
origin?: LearningWriteOriginInput;
|
|
135
|
+
}
|
|
136
|
+
/** The two ids a candidate resolves to. */
|
|
137
|
+
export interface RecommendationIdentity {
|
|
138
|
+
/** Producer-scoped `lrec_…` id from `src/learning/fingerprint.ts`. */
|
|
139
|
+
fingerprint: string;
|
|
140
|
+
/** Kind- and target-independent `lxk_…` id used for dedup. */
|
|
141
|
+
crossKey: string;
|
|
142
|
+
}
|
|
143
|
+
/** One durable record of an emitted recommendation. */
|
|
144
|
+
export interface RecommendationLedgerEntry extends RecommendationIdentity {
|
|
145
|
+
v: 1;
|
|
146
|
+
kind: RecommendationKind;
|
|
147
|
+
target: string;
|
|
148
|
+
/** ISO-8601 timestamp of the record call that appended this entry. */
|
|
149
|
+
emittedAt: string;
|
|
150
|
+
provenance?: LearningProvenanceV1;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Why a candidate was not accepted for emission.
|
|
154
|
+
*
|
|
155
|
+
* Module-private: consumers reach this set through
|
|
156
|
+
* `RecommendationDecision['suppressedBy']`, which is exported and narrows to the
|
|
157
|
+
* same two literals. Exporting the alias as well gave it a name no importer ever
|
|
158
|
+
* used.
|
|
159
|
+
*/
|
|
160
|
+
type RecommendationSuppressionSource = 'ledger' | 'batch';
|
|
161
|
+
/** Per-candidate outcome, in input order. */
|
|
162
|
+
export interface RecommendationDecision extends RecommendationIdentity {
|
|
163
|
+
/** Index into the candidate array this decision belongs to. */
|
|
164
|
+
index: number;
|
|
165
|
+
/** True when the caller should emit this recommendation. */
|
|
166
|
+
emit: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* `'ledger'` — a previous emission (possibly by another producer) already
|
|
169
|
+
* claimed this `crossKey`. `'batch'` — an earlier candidate in this same call
|
|
170
|
+
* claimed it. Absent when `emit` is true.
|
|
171
|
+
*/
|
|
172
|
+
suppressedBy?: RecommendationSuppressionSource;
|
|
173
|
+
}
|
|
174
|
+
export interface CheckRecommendationsResult {
|
|
175
|
+
decisions: RecommendationDecision[];
|
|
176
|
+
/** Count of `emit === true` decisions. */
|
|
177
|
+
accepted: number;
|
|
178
|
+
/** Count of `emit === false` decisions. */
|
|
179
|
+
suppressed: number;
|
|
180
|
+
/**
|
|
181
|
+
* True when the ledger could not be read and every candidate was accepted by
|
|
182
|
+
* fail-open default rather than by an actual dedup check.
|
|
183
|
+
*/
|
|
184
|
+
degraded: boolean;
|
|
185
|
+
}
|
|
186
|
+
export interface RecordRecommendationsResult {
|
|
187
|
+
/** Entries actually appended. */
|
|
188
|
+
recorded: number;
|
|
189
|
+
/** Candidates skipped because the ledger already carried their cross key. */
|
|
190
|
+
suppressed: number;
|
|
191
|
+
/** Entries dropped by the FIFO cap during this record. */
|
|
192
|
+
evicted: number;
|
|
193
|
+
/** True when the ledger could not be written. */
|
|
194
|
+
degraded: boolean;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Absolute path of the ledger, link-aware.
|
|
198
|
+
*
|
|
199
|
+
* Byte-identical to `<directory>/.swarm/learning/recommendation-ledger.jsonl`
|
|
200
|
+
* for an unlinked worktree; redirects to the shared cohort store when
|
|
201
|
+
* `.swarm/link.json` is active, exactly as `resolveSwarmKnowledgePath` does for
|
|
202
|
+
* the knowledge file this ledger shadows.
|
|
203
|
+
*/
|
|
204
|
+
export declare function resolveRecommendationLedgerPath(directory: string): string;
|
|
205
|
+
/** Shape check for a value that claims to be a cross-producer key. */
|
|
206
|
+
export declare function isRecommendationCrossKey(value: string): boolean;
|
|
207
|
+
/**
|
|
208
|
+
* Compute the kind- and target-independent dedup key.
|
|
209
|
+
*
|
|
210
|
+
* Built from the same normalizers the `lrec_` fingerprint uses, so whitespace
|
|
211
|
+
* runs, surrounding space, letter case, trailing sentence punctuation, scope-key
|
|
212
|
+
* order, and scope-key duplicates all collapse to one identity.
|
|
213
|
+
*/
|
|
214
|
+
export declare function computeCrossProducerKey(input: {
|
|
215
|
+
statement: string;
|
|
216
|
+
scopeKeys?: string[];
|
|
217
|
+
}): string;
|
|
218
|
+
/** Compute both ids for a candidate. Pure. */
|
|
219
|
+
export declare function computeRecommendationIdentity(candidate: RecommendationCandidate): RecommendationIdentity;
|
|
220
|
+
/**
|
|
221
|
+
* Read every retained ledger entry, propagating a real read failure.
|
|
222
|
+
*
|
|
223
|
+
* A missing ledger is the first-run case and yields `[]`; anything else throws
|
|
224
|
+
* so `checkRecommendations` can report `degraded` truthfully instead of treating
|
|
225
|
+
* an unreadable ledger as an empty one and silently claiming it deduped.
|
|
226
|
+
*/
|
|
227
|
+
declare function readLedgerStrict(directory: string): Promise<RecommendationLedgerEntry[]>;
|
|
228
|
+
/**
|
|
229
|
+
* Read every retained ledger entry. Fail-open: returns `[]` when the ledger is
|
|
230
|
+
* absent or unreadable. This is the inspection entry point — the dedup decision
|
|
231
|
+
* itself goes through `readLedgerStrict` so a failure is visible rather than
|
|
232
|
+
* disguised as an empty ledger.
|
|
233
|
+
*/
|
|
234
|
+
export declare function readRecommendationLedger(directory: string): Promise<RecommendationLedgerEntry[]>;
|
|
235
|
+
/**
|
|
236
|
+
* Answer "has this been emitted before?" without writing anything.
|
|
237
|
+
*
|
|
238
|
+
* Suppression is decided against the ledger's existing cross keys plus the keys
|
|
239
|
+
* claimed by earlier candidates in this same batch. Fail-open: an unreadable
|
|
240
|
+
* ledger yields `degraded: true` and accepts everything.
|
|
241
|
+
*/
|
|
242
|
+
export declare function checkRecommendations(directory: string, candidates: readonly RecommendationCandidate[]): Promise<CheckRecommendationsResult>;
|
|
243
|
+
/**
|
|
244
|
+
* Append the recommendations a producer actually emitted.
|
|
245
|
+
*
|
|
246
|
+
* Runs one locked read-modify-write and re-checks every cross key under that
|
|
247
|
+
* lock, so a candidate another producer recorded between `checkRecommendations`
|
|
248
|
+
* and here is counted as `suppressed` rather than duplicated into the ledger.
|
|
249
|
+
* Candidates whose statement normalizes to nothing are never recorded.
|
|
250
|
+
*/
|
|
251
|
+
export declare function recordEmittedRecommendations(directory: string, candidates: readonly RecommendationCandidate[], options?: {
|
|
252
|
+
producedAt?: string;
|
|
253
|
+
}): Promise<RecordRecommendationsResult>;
|
|
254
|
+
/**
|
|
255
|
+
* DI seam (AGENTS.md invariant 7). `now` lets tests pin `emittedAt`;
|
|
256
|
+
* `transactFile`, `readRecommendationLedger`, and
|
|
257
|
+
* `resolveRecommendationLedgerPath` let them exercise the fail-open paths
|
|
258
|
+
* without `mock.module`. Restore each entry in `afterEach`.
|
|
259
|
+
*/
|
|
260
|
+
export declare const _internals: {
|
|
261
|
+
now: () => Date;
|
|
262
|
+
transactFile: typeof transactFile;
|
|
263
|
+
readLedgerStrict: typeof readLedgerStrict;
|
|
264
|
+
resolveRecommendationLedgerPath: typeof resolveRecommendationLedgerPath;
|
|
265
|
+
};
|
|
266
|
+
export {};
|
|
@@ -83,11 +83,15 @@ export interface SkillImproveResult {
|
|
|
83
83
|
macroMotifs?: {
|
|
84
84
|
motifs: number;
|
|
85
85
|
proposalsWritten: number;
|
|
86
|
+
/** #1821 AC21: motifs suppressed by the cross-producer dedup ledger. */
|
|
87
|
+
duplicatesSuppressed?: number;
|
|
86
88
|
};
|
|
87
89
|
/** #1234 Part 4: success workflow-motif proposals written this run. */
|
|
88
90
|
successMotifs?: {
|
|
89
91
|
motifs: number;
|
|
90
92
|
proposalsWritten: number;
|
|
93
|
+
/** #1821 AC21: motifs suppressed by the cross-producer dedup ledger. */
|
|
94
|
+
duplicatesSuppressed?: number;
|
|
91
95
|
};
|
|
92
96
|
/** Issue #1477: self-healing reconciliation of stale active generated skills
|
|
93
97
|
* (regenerate-or-retire). In proposal-only runs this is a dry run (the
|
|
@@ -78,6 +78,19 @@ export interface StatusData {
|
|
|
78
78
|
unactionableQueueDepth?: number;
|
|
79
79
|
/** #1234 Part 3: pending insight candidates awaiting phase boundary consumption */
|
|
80
80
|
insightCandidatesPending?: number;
|
|
81
|
+
/**
|
|
82
|
+
* #1821 AC22: consensus report FILES stored under
|
|
83
|
+
* `.swarm/evolution/consensus/`.
|
|
84
|
+
*
|
|
85
|
+
* The reader half of the consensus store. Without it the miner's reports were
|
|
86
|
+
* a write-only directory — nothing in the product enumerated them, so a user
|
|
87
|
+
* had no way to learn they existed. A file count, deliberately: it is one
|
|
88
|
+
* `readdir` with no JSON parse and no integrity recomputation, so it costs the
|
|
89
|
+
* same whether the store holds one report or fifty. It therefore includes any
|
|
90
|
+
* corrupt report, because what it counts is files whose name is a well-formed
|
|
91
|
+
* report id.
|
|
92
|
+
*/
|
|
93
|
+
consensusReports?: number;
|
|
81
94
|
/**
|
|
82
95
|
* Cohort/link status (issue #1846). Makes the linked knowledge store and its
|
|
83
96
|
* health obvious in `/swarm status`. `undefined` when link state is absent.
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* Read-only over the knowledge store; writes only proposal markdown (never
|
|
17
17
|
* active skills). Fail-open.
|
|
18
18
|
*/
|
|
19
|
+
import { checkRecommendations, recordEmittedRecommendations } from './recommendation-ledger.js';
|
|
19
20
|
/** Trajectories scanned per macro pass (the plan's N=200 window). */
|
|
20
21
|
export declare const MACRO_TRAJECTORY_WINDOW = 200;
|
|
21
22
|
/** A motif must recur across at least this many distinct tasks to propose. */
|
|
@@ -36,11 +37,31 @@ export declare function gatherFailureMotifs(directory: string, opts?: {
|
|
|
36
37
|
window?: number;
|
|
37
38
|
minTasks?: number;
|
|
38
39
|
}): Promise<FailureMotif[]>;
|
|
40
|
+
/**
|
|
41
|
+
* The recommendation a failure motif asserts, in cross-producer form
|
|
42
|
+
* (issue #1821 AC21).
|
|
43
|
+
*
|
|
44
|
+
* The agent role is folded into the *statement* rather than carried as a scope
|
|
45
|
+
* key so this statement can collide with an equivalent lesson proposed by the
|
|
46
|
+
* curator or the consensus miner — the whole point of a shared identity. The
|
|
47
|
+
* motif signature's constituents (tool, failure kind) are what actually vary
|
|
48
|
+
* between motifs, so two distinct motifs never produce the same statement.
|
|
49
|
+
*/
|
|
50
|
+
declare function motifStatement(motif: FailureMotif): string;
|
|
39
51
|
/** Render a draft SKILL.md proposal body for a motif (with full provenance). */
|
|
40
|
-
export declare function buildMotifProposal(motif: FailureMotif
|
|
52
|
+
export declare function buildMotifProposal(motif: FailureMotif, options?: {
|
|
53
|
+
fingerprint?: string;
|
|
54
|
+
producedAt?: string;
|
|
55
|
+
}): string;
|
|
41
56
|
export interface MotifProposalResult {
|
|
42
57
|
motifs: number;
|
|
43
58
|
proposalsWritten: string[];
|
|
59
|
+
/**
|
|
60
|
+
* #1821 AC21: motifs whose recommendation was already emitted — by an earlier
|
|
61
|
+
* improver run, the curator sweep, or the consensus miner — and were therefore
|
|
62
|
+
* not re-proposed.
|
|
63
|
+
*/
|
|
64
|
+
duplicatesSuppressed: number;
|
|
44
65
|
}
|
|
45
66
|
/**
|
|
46
67
|
* Run the macro motif pass and write one proposal per recurring motif. Returns
|
|
@@ -50,6 +71,8 @@ export declare function writeMotifProposals(directory: string, opts?: {
|
|
|
50
71
|
window?: number;
|
|
51
72
|
minTasks?: number;
|
|
52
73
|
maxProposals?: number;
|
|
74
|
+
/** Recorded in the ledger entry's provenance write origin. */
|
|
75
|
+
sessionId?: string;
|
|
53
76
|
}): Promise<MotifProposalResult>;
|
|
54
77
|
/** Minimum number of steps in a trajectory for it to qualify as a workflow. */
|
|
55
78
|
export declare const SUCCESS_SEQUENCE_MIN_STEPS = 3;
|
|
@@ -66,16 +89,56 @@ export interface SuccessMotif {
|
|
|
66
89
|
export interface SuccessMotifProposalResult {
|
|
67
90
|
motifs: number;
|
|
68
91
|
proposalsWritten: string[];
|
|
92
|
+
/** #1821 AC21: see `MotifProposalResult.duplicatesSuppressed`. */
|
|
93
|
+
duplicatesSuppressed: number;
|
|
69
94
|
}
|
|
70
95
|
export declare function gatherSuccessMotifs(directory: string, opts?: {
|
|
71
96
|
window?: number;
|
|
72
97
|
minTasks?: number;
|
|
73
98
|
minSteps?: number;
|
|
74
99
|
}): Promise<SuccessMotif[]>;
|
|
75
|
-
|
|
100
|
+
/**
|
|
101
|
+
* The recommendation a success motif asserts, in cross-producer form
|
|
102
|
+
* (issue #1821 AC21). Same agent-in-statement rationale as `motifStatement`.
|
|
103
|
+
*
|
|
104
|
+
* The rendered sequence MUST mirror `sequenceSignature` (`tool:action` per
|
|
105
|
+
* step), not just the tool names: two motifs that share a tool chain but differ
|
|
106
|
+
* in actions have different signatures, different slugs, and different proposal
|
|
107
|
+
* files, so a tool-only statement would collapse two genuinely different
|
|
108
|
+
* workflows onto one cross key and suppress the second.
|
|
109
|
+
*/
|
|
110
|
+
declare function workflowStatement(motif: SuccessMotif): string;
|
|
111
|
+
export declare function buildWorkflowProposal(motif: SuccessMotif, options?: {
|
|
112
|
+
fingerprint?: string;
|
|
113
|
+
producedAt?: string;
|
|
114
|
+
}): string;
|
|
76
115
|
export declare function writeSuccessMotifProposals(directory: string, opts?: {
|
|
77
116
|
window?: number;
|
|
78
117
|
minTasks?: number;
|
|
79
118
|
minSteps?: number;
|
|
80
119
|
maxProposals?: number;
|
|
120
|
+
/** Recorded in the ledger entry's provenance write origin. */
|
|
121
|
+
sessionId?: string;
|
|
81
122
|
}): Promise<SuccessMotifProposalResult>;
|
|
123
|
+
/**
|
|
124
|
+
* DI seam (AGENTS.md invariant 7). `now` pins the shared `producedAt` that the
|
|
125
|
+
* ledger entry and the proposal frontmatter both carry; the two ledger functions
|
|
126
|
+
* let a test exercise the suppressed / degraded branches without touching the
|
|
127
|
+
* real ledger. Restore each entry in `afterEach`.
|
|
128
|
+
*/
|
|
129
|
+
export declare const _internals: {
|
|
130
|
+
now: () => Date;
|
|
131
|
+
checkRecommendations: typeof checkRecommendations;
|
|
132
|
+
recordEmittedRecommendations: typeof recordEmittedRecommendations;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Pure-function seam for tests (writing-tests SKILL.md, Tier 0). The two
|
|
136
|
+
* statement builders define the cross-producer identity of a motif, so a test
|
|
137
|
+
* asserting cross-producer suppression must derive the competing statement from
|
|
138
|
+
* them rather than hardcode a copy that can silently drift.
|
|
139
|
+
*/
|
|
140
|
+
export declare const _test_exports: {
|
|
141
|
+
motifStatement: typeof motifStatement;
|
|
142
|
+
workflowStatement: typeof workflowStatement;
|
|
143
|
+
};
|
|
144
|
+
export {};
|