@caupulican/pi-adaptative 0.81.16 → 0.81.18

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +18 -0
  3. package/dist/core/agent-session.d.ts +6 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +109 -9
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/models/adaptation-store.d.ts +3 -0
  8. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  9. package/dist/core/models/adaptation-store.js +12 -0
  10. package/dist/core/models/adaptation-store.js.map +1 -1
  11. package/dist/core/models/context-sizing.d.ts +30 -0
  12. package/dist/core/models/context-sizing.d.ts.map +1 -0
  13. package/dist/core/models/context-sizing.js +90 -0
  14. package/dist/core/models/context-sizing.js.map +1 -0
  15. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  16. package/dist/core/models/default-model-suggestions.js +9 -0
  17. package/dist/core/models/default-model-suggestions.js.map +1 -1
  18. package/dist/core/models/local-runtime.d.ts +19 -0
  19. package/dist/core/models/local-runtime.d.ts.map +1 -1
  20. package/dist/core/models/local-runtime.js +43 -0
  21. package/dist/core/models/local-runtime.js.map +1 -1
  22. package/dist/core/models/perf-profile.d.ts +36 -0
  23. package/dist/core/models/perf-profile.d.ts.map +1 -0
  24. package/dist/core/models/perf-profile.js +163 -0
  25. package/dist/core/models/perf-profile.js.map +1 -0
  26. package/dist/core/models/runtime-arbiter.d.ts +57 -0
  27. package/dist/core/models/runtime-arbiter.d.ts.map +1 -0
  28. package/dist/core/models/runtime-arbiter.js +49 -0
  29. package/dist/core/models/runtime-arbiter.js.map +1 -0
  30. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  31. package/dist/modes/interactive/local-model-commands.js +30 -3
  32. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  33. package/docs/models.md +1 -0
  34. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  35. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  36. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  37. package/examples/extensions/sandbox/package-lock.json +2 -2
  38. package/examples/extensions/sandbox/package.json +1 -1
  39. package/examples/extensions/with-deps/package-lock.json +2 -2
  40. package/examples/extensions/with-deps/package.json +1 -1
  41. package/npm-shrinkwrap.json +12 -12
  42. package/package.json +34 -4
@@ -1,4 +1,5 @@
1
1
  import { type HostFingerprint } from "./fitness-store.ts";
2
+ import { type ModelPerfProfile, type ModelPerfSample } from "./perf-profile.ts";
2
3
  export interface ModelAdaptationRule {
3
4
  mode: string;
4
5
  text: string;
@@ -35,6 +36,7 @@ export interface ModelAdaptationProfile {
35
36
  rules: ModelAdaptationRule[];
36
37
  protocol?: ModelProtocolCalibration;
37
38
  toolProbe?: ModelToolProbe;
39
+ perf?: ModelPerfProfile;
38
40
  teachStats: Record<string, ModelTeachStats>;
39
41
  }
40
42
  export interface StoredModelAdaptation {
@@ -71,6 +73,7 @@ export declare class ModelAdaptationStore {
71
73
  markRuleFired(model: string, mode: string, at?: Date): StoredModelAdaptation | undefined;
72
74
  setProtocol(model: string, protocol: ModelProtocolCalibration, at?: string): StoredModelAdaptation;
73
75
  setToolProbe(model: string, toolProbe: ModelToolProbe, at?: string): StoredModelAdaptation;
76
+ recordPerfSample(model: string, sample: ModelPerfSample, at?: string): StoredModelAdaptation | undefined;
74
77
  removeProtocol(model: string, at?: Date): boolean;
75
78
  setTeachStats(model: string, mode: string, stats: ModelTeachStats, at?: string): StoredModelAdaptation;
76
79
  /** Profiles for the current host (default) or an explicit host id. */
@@ -1 +1 @@
1
- {"version":3,"file":"adaptation-store.d.ts","sourceRoot":"","sources":["../../../src/core/models/adaptation-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { currentHostFingerprint, type HostFingerprint } from \"./fitness-store.ts\";\n\nconst STORE_VERSION = 1;\nconst MAX_RULES_PER_MODEL = 5;\nconst RETIRE_AFTER_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport interface ModelAdaptationRule {\n\tmode: string;\n\ttext: string;\n\taddedAt: string;\n\tlastFiredAt: string;\n}\n\nexport type ModelProtocolCalibration =\n\t| {\n\t\t\tversion: number;\n\t\t\tstatus?: \"calibrated\";\n\t\t\tvariant: string;\n\t\t\tcalibratedAt: string;\n\t }\n\t| {\n\t\t\tversion: number;\n\t\t\tstatus: \"failed\";\n\t\t\tattemptedAt: string;\n\t\t\tvariantsTried: string[];\n\t };\n\nexport type ModelToolProbeVerdict = \"native\" | \"text-protocol\" | \"none\";\nexport type NativeToolProbeGrade = \"task\" | \"echo-only\" | \"absent\";\n\nexport interface ModelToolProbe {\n\tversion: number;\n\tstatus: ModelToolProbeVerdict;\n\tprobedAt: string;\n\tvariant?: string;\n\tnativeGrade?: NativeToolProbeGrade;\n\tdiagnostic?: string;\n}\n\nexport interface ModelTeachStats {\n\ttaught: number;\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n}\n\nexport interface ModelAdaptationProfile {\n\trules: ModelAdaptationRule[];\n\tprotocol?: ModelProtocolCalibration;\n\ttoolProbe?: ModelToolProbe;\n\tteachStats: Record<string, ModelTeachStats>;\n}\n\nexport interface StoredModelAdaptation {\n\tmodel: string;\n\tprofile: ModelAdaptationProfile;\n\tat: string;\n\thost: HostFingerprint;\n}\n\ninterface AdaptationStoreFile {\n\tversion: 1;\n\t/** hostId -> modelRef -> latest stored adaptation profile. */\n\thosts: Record<string, Record<string, StoredModelAdaptation>>;\n}\n\nfunction emptyProfile(): ModelAdaptationProfile {\n\treturn { rules: [], teachStats: {} };\n}\n\nfunction normalizeProfile(profile: Partial<ModelAdaptationProfile> | undefined): ModelAdaptationProfile {\n\treturn {\n\t\trules: Array.isArray(profile?.rules) ? profile.rules.filter(isRule) : [],\n\t\t...(isProtocol(profile?.protocol) && { protocol: profile.protocol }),\n\t\t...(isToolProbe(profile?.toolProbe) && { toolProbe: profile.toolProbe }),\n\t\tteachStats: isRecord(profile?.teachStats) ? filterTeachStats(profile.teachStats) : {},\n\t};\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isRule(value: unknown): value is ModelAdaptationRule {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.mode === \"string\" &&\n\t\ttypeof value.text === \"string\" &&\n\t\ttypeof value.addedAt === \"string\" &&\n\t\ttypeof value.lastFiredAt === \"string\"\n\t);\n}\n\nfunction isProtocol(value: unknown): value is ModelProtocolCalibration {\n\tif (!isRecord(value) || typeof value.version !== \"number\") return false;\n\tif (value.status === \"failed\") {\n\t\treturn (\n\t\t\ttypeof value.attemptedAt === \"string\" &&\n\t\t\tArray.isArray(value.variantsTried) &&\n\t\t\tvalue.variantsTried.every((variant) => typeof variant === \"string\")\n\t\t);\n\t}\n\treturn (\n\t\t(value.status === undefined || value.status === \"calibrated\") &&\n\t\ttypeof value.variant === \"string\" &&\n\t\ttypeof value.calibratedAt === \"string\"\n\t);\n}\n\nfunction isToolProbe(value: unknown): value is ModelToolProbe {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.version === \"number\" &&\n\t\t(value.status === \"native\" || value.status === \"text-protocol\" || value.status === \"none\") &&\n\t\ttypeof value.probedAt === \"string\" &&\n\t\t(value.variant === undefined || typeof value.variant === \"string\") &&\n\t\t(value.nativeGrade === undefined ||\n\t\t\tvalue.nativeGrade === \"task\" ||\n\t\t\tvalue.nativeGrade === \"echo-only\" ||\n\t\t\tvalue.nativeGrade === \"absent\") &&\n\t\t(value.diagnostic === undefined || typeof value.diagnostic === \"string\")\n\t);\n}\n\nfunction isTeachStats(value: unknown): value is ModelTeachStats {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.taught === \"number\" &&\n\t\ttypeof value.recurrenceBefore === \"number\" &&\n\t\ttypeof value.recurrenceAfter === \"number\"\n\t);\n}\n\nfunction filterTeachStats(value: Record<string, unknown>): Record<string, ModelTeachStats> {\n\treturn Object.fromEntries(\n\t\tObject.entries(value).filter((entry): entry is [string, ModelTeachStats] => isTeachStats(entry[1])),\n\t);\n}\n\nfunction ruleRecency(rule: ModelAdaptationRule): number {\n\tconst lastFired = Date.parse(rule.lastFiredAt);\n\tif (Number.isFinite(lastFired)) return lastFired;\n\tconst added = Date.parse(rule.addedAt);\n\treturn Number.isFinite(added) ? added : 0;\n}\n\nfunction pruneRetiredRules(rules: readonly ModelAdaptationRule[], now: Date): ModelAdaptationRule[] {\n\tconst cutoff = now.getTime() - RETIRE_AFTER_MS;\n\treturn rules.filter((rule) => ruleRecency(rule) >= cutoff);\n}\n\nfunction enforceRuleCap(rules: readonly ModelAdaptationRule[]): ModelAdaptationRule[] {\n\tif (rules.length <= MAX_RULES_PER_MODEL) return [...rules];\n\treturn [...rules].sort((a, b) => ruleRecency(b) - ruleRecency(a)).slice(0, MAX_RULES_PER_MODEL);\n}\n\nfunction mergeRule(rules: readonly ModelAdaptationRule[], rule: ModelAdaptationRule): ModelAdaptationRule[] {\n\tconst withoutSameMode = rules.filter((existing) => existing.mode !== rule.mode);\n\treturn enforceRuleCap([...withoutSameMode, rule]);\n}\n\nexport class ModelAdaptationStore {\n\tprivate readonly filePath: string;\n\tprivate readonly fingerprint: () => HostFingerprint;\n\n\tconstructor(filePath: string, options?: { fingerprint?: () => HostFingerprint }) {\n\t\tthis.filePath = filePath;\n\t\tthis.fingerprint = options?.fingerprint ?? currentHostFingerprint;\n\t}\n\n\tstatic forAgentDir(agentDir: string, options?: { fingerprint?: () => HostFingerprint }): ModelAdaptationStore {\n\t\treturn new ModelAdaptationStore(join(agentDir, \"state\", \"model-adaptation.json\"), options);\n\t}\n\n\tprivate load(): AdaptationStoreFile {\n\t\ttry {\n\t\t\tif (!existsSync(this.filePath)) return { version: STORE_VERSION, hosts: {} };\n\t\t\tconst parsed = JSON.parse(readFileSync(this.filePath, \"utf-8\")) as AdaptationStoreFile;\n\t\t\tif (parsed && parsed.version === STORE_VERSION && parsed.hosts && typeof parsed.hosts === \"object\") {\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Unreadable/corrupt store: start fresh in memory; the next save rewrites the file.\n\t\t}\n\t\treturn { version: STORE_VERSION, hosts: {} };\n\t}\n\n\tprivate write(file: AdaptationStoreFile): void {\n\t\tmkdirSync(dirname(this.filePath), { recursive: true });\n\t\twriteFileSync(this.filePath, `${JSON.stringify(file, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\tprivate store(model: string, profile: ModelAdaptationProfile, at: string): StoredModelAdaptation {\n\t\tconst host = this.fingerprint();\n\t\tconst entry: StoredModelAdaptation = { model, profile: normalizeProfile(profile), at, host };\n\t\tconst file = this.load();\n\t\tfile.hosts[host.id] = { ...(file.hosts[host.id] ?? {}), [model]: entry };\n\t\tthis.write(file);\n\t\treturn entry;\n\t}\n\n\t/** Persist the profile for a model on the CURRENT host. Best-effort, returns the entry. */\n\tsave(model: string, profile: ModelAdaptationProfile, at?: string): StoredModelAdaptation {\n\t\treturn this.store(model, profile, at ?? new Date().toISOString());\n\t}\n\n\t/** Profile for a model on the current host; prunes retired rules before returning. */\n\tget(model: string, now: Date = new Date()): ModelAdaptationProfile {\n\t\tconst host = this.fingerprint();\n\t\tconst file = this.load();\n\t\tconst entry = file.hosts[host.id]?.[model];\n\t\tif (!entry) return emptyProfile();\n\t\tconst profile = normalizeProfile(entry.profile);\n\t\tconst prunedRules = pruneRetiredRules(profile.rules, now);\n\t\tif (prunedRules.length !== profile.rules.length) {\n\t\t\treturn this.store(model, { ...profile, rules: prunedRules }, now.toISOString()).profile;\n\t\t}\n\t\treturn profile;\n\t}\n\n\t/** Add or replace one standing rule, enforcing the per-model cap. */\n\taddRule(\n\t\tmodel: string,\n\t\trule: { mode: string; text: string; addedAt?: string; lastFiredAt?: string },\n\t\tnow = new Date(),\n\t): StoredModelAdaptation {\n\t\tconst profile = this.get(model, now);\n\t\tconst at = now.toISOString();\n\t\tconst nextRule: ModelAdaptationRule = {\n\t\t\tmode: rule.mode,\n\t\t\ttext: rule.text,\n\t\t\taddedAt: rule.addedAt ?? at,\n\t\t\tlastFiredAt: rule.lastFiredAt ?? at,\n\t\t};\n\t\treturn this.store(model, { ...profile, rules: mergeRule(profile.rules, nextRule) }, at);\n\t}\n\n\tremoveRule(model: string, mode: string, at = new Date()): boolean {\n\t\tconst profile = this.get(model, at);\n\t\tconst rules = profile.rules.filter((rule) => rule.mode !== mode);\n\t\tif (rules.length === profile.rules.length) return false;\n\t\tthis.store(model, { ...profile, rules }, at.toISOString());\n\t\treturn true;\n\t}\n\n\t/** Update last-fired recency for an existing rule. No-op when absent. */\n\tmarkRuleFired(model: string, mode: string, at = new Date()): StoredModelAdaptation | undefined {\n\t\tconst profile = this.get(model, at);\n\t\tconst rules = profile.rules.map((rule) =>\n\t\t\trule.mode === mode ? { ...rule, lastFiredAt: at.toISOString() } : rule,\n\t\t);\n\t\tif (rules.every((rule, index) => rule === profile.rules[index])) return undefined;\n\t\treturn this.store(model, { ...profile, rules }, at.toISOString());\n\t}\n\n\tsetProtocol(model: string, protocol: ModelProtocolCalibration, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? (protocol.status === \"failed\" ? protocol.attemptedAt : protocol.calibratedAt);\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, protocol }, now);\n\t}\n\n\tsetToolProbe(model: string, toolProbe: ModelToolProbe, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? toolProbe.probedAt;\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, toolProbe }, now);\n\t}\n\n\tremoveProtocol(model: string, at = new Date()): boolean {\n\t\tconst profile = this.get(model, at);\n\t\tif (!profile.protocol) return false;\n\t\tconst { protocol: _protocol, ...rest } = profile;\n\t\tthis.store(model, rest, at.toISOString());\n\t\treturn true;\n\t}\n\n\tsetTeachStats(model: string, mode: string, stats: ModelTeachStats, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? new Date().toISOString();\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, teachStats: { ...profile.teachStats, [mode]: stats } }, now);\n\t}\n\n\t/** Profiles for the current host (default) or an explicit host id. */\n\tgetForHost(hostId?: string): StoredModelAdaptation[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts[hostId ?? this.fingerprint().id] ?? {}).map((entry) => ({\n\t\t\t...entry,\n\t\t\tprofile: normalizeProfile(entry.profile),\n\t\t}));\n\t}\n\n\t/** Every stored profile across all hosts (for cross-machine comparisons). */\n\tgetAll(): StoredModelAdaptation[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts).flatMap((models) =>\n\t\t\tObject.values(models).map((entry) => ({ ...entry, profile: normalizeProfile(entry.profile) })),\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"adaptation-store.d.ts","sourceRoot":"","sources":["../../../src/core/models/adaptation-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { currentHostFingerprint, type HostFingerprint } from \"./fitness-store.ts\";\nimport {\n\thasUsableModelPerfSample,\n\tisModelPerfProfile,\n\ttype ModelPerfProfile,\n\ttype ModelPerfSample,\n\tupdateModelPerfProfile,\n} from \"./perf-profile.ts\";\n\nconst STORE_VERSION = 1;\nconst MAX_RULES_PER_MODEL = 5;\nconst RETIRE_AFTER_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport interface ModelAdaptationRule {\n\tmode: string;\n\ttext: string;\n\taddedAt: string;\n\tlastFiredAt: string;\n}\n\nexport type ModelProtocolCalibration =\n\t| {\n\t\t\tversion: number;\n\t\t\tstatus?: \"calibrated\";\n\t\t\tvariant: string;\n\t\t\tcalibratedAt: string;\n\t }\n\t| {\n\t\t\tversion: number;\n\t\t\tstatus: \"failed\";\n\t\t\tattemptedAt: string;\n\t\t\tvariantsTried: string[];\n\t };\n\nexport type ModelToolProbeVerdict = \"native\" | \"text-protocol\" | \"none\";\nexport type NativeToolProbeGrade = \"task\" | \"echo-only\" | \"absent\";\n\nexport interface ModelToolProbe {\n\tversion: number;\n\tstatus: ModelToolProbeVerdict;\n\tprobedAt: string;\n\tvariant?: string;\n\tnativeGrade?: NativeToolProbeGrade;\n\tdiagnostic?: string;\n}\n\nexport interface ModelTeachStats {\n\ttaught: number;\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n}\n\nexport interface ModelAdaptationProfile {\n\trules: ModelAdaptationRule[];\n\tprotocol?: ModelProtocolCalibration;\n\ttoolProbe?: ModelToolProbe;\n\tperf?: ModelPerfProfile;\n\tteachStats: Record<string, ModelTeachStats>;\n}\n\nexport interface StoredModelAdaptation {\n\tmodel: string;\n\tprofile: ModelAdaptationProfile;\n\tat: string;\n\thost: HostFingerprint;\n}\n\ninterface AdaptationStoreFile {\n\tversion: 1;\n\t/** hostId -> modelRef -> latest stored adaptation profile. */\n\thosts: Record<string, Record<string, StoredModelAdaptation>>;\n}\n\nfunction emptyProfile(): ModelAdaptationProfile {\n\treturn { rules: [], teachStats: {} };\n}\n\nfunction normalizeProfile(profile: Partial<ModelAdaptationProfile> | undefined): ModelAdaptationProfile {\n\treturn {\n\t\trules: Array.isArray(profile?.rules) ? profile.rules.filter(isRule) : [],\n\t\t...(isProtocol(profile?.protocol) && { protocol: profile.protocol }),\n\t\t...(isToolProbe(profile?.toolProbe) && { toolProbe: profile.toolProbe }),\n\t\t...(isModelPerfProfile(profile?.perf) && { perf: profile.perf }),\n\t\tteachStats: isRecord(profile?.teachStats) ? filterTeachStats(profile.teachStats) : {},\n\t};\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isRule(value: unknown): value is ModelAdaptationRule {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.mode === \"string\" &&\n\t\ttypeof value.text === \"string\" &&\n\t\ttypeof value.addedAt === \"string\" &&\n\t\ttypeof value.lastFiredAt === \"string\"\n\t);\n}\n\nfunction isProtocol(value: unknown): value is ModelProtocolCalibration {\n\tif (!isRecord(value) || typeof value.version !== \"number\") return false;\n\tif (value.status === \"failed\") {\n\t\treturn (\n\t\t\ttypeof value.attemptedAt === \"string\" &&\n\t\t\tArray.isArray(value.variantsTried) &&\n\t\t\tvalue.variantsTried.every((variant) => typeof variant === \"string\")\n\t\t);\n\t}\n\treturn (\n\t\t(value.status === undefined || value.status === \"calibrated\") &&\n\t\ttypeof value.variant === \"string\" &&\n\t\ttypeof value.calibratedAt === \"string\"\n\t);\n}\n\nfunction isToolProbe(value: unknown): value is ModelToolProbe {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.version === \"number\" &&\n\t\t(value.status === \"native\" || value.status === \"text-protocol\" || value.status === \"none\") &&\n\t\ttypeof value.probedAt === \"string\" &&\n\t\t(value.variant === undefined || typeof value.variant === \"string\") &&\n\t\t(value.nativeGrade === undefined ||\n\t\t\tvalue.nativeGrade === \"task\" ||\n\t\t\tvalue.nativeGrade === \"echo-only\" ||\n\t\t\tvalue.nativeGrade === \"absent\") &&\n\t\t(value.diagnostic === undefined || typeof value.diagnostic === \"string\")\n\t);\n}\n\nfunction isTeachStats(value: unknown): value is ModelTeachStats {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.taught === \"number\" &&\n\t\ttypeof value.recurrenceBefore === \"number\" &&\n\t\ttypeof value.recurrenceAfter === \"number\"\n\t);\n}\n\nfunction filterTeachStats(value: Record<string, unknown>): Record<string, ModelTeachStats> {\n\treturn Object.fromEntries(\n\t\tObject.entries(value).filter((entry): entry is [string, ModelTeachStats] => isTeachStats(entry[1])),\n\t);\n}\n\nfunction ruleRecency(rule: ModelAdaptationRule): number {\n\tconst lastFired = Date.parse(rule.lastFiredAt);\n\tif (Number.isFinite(lastFired)) return lastFired;\n\tconst added = Date.parse(rule.addedAt);\n\treturn Number.isFinite(added) ? added : 0;\n}\n\nfunction pruneRetiredRules(rules: readonly ModelAdaptationRule[], now: Date): ModelAdaptationRule[] {\n\tconst cutoff = now.getTime() - RETIRE_AFTER_MS;\n\treturn rules.filter((rule) => ruleRecency(rule) >= cutoff);\n}\n\nfunction enforceRuleCap(rules: readonly ModelAdaptationRule[]): ModelAdaptationRule[] {\n\tif (rules.length <= MAX_RULES_PER_MODEL) return [...rules];\n\treturn [...rules].sort((a, b) => ruleRecency(b) - ruleRecency(a)).slice(0, MAX_RULES_PER_MODEL);\n}\n\nfunction mergeRule(rules: readonly ModelAdaptationRule[], rule: ModelAdaptationRule): ModelAdaptationRule[] {\n\tconst withoutSameMode = rules.filter((existing) => existing.mode !== rule.mode);\n\treturn enforceRuleCap([...withoutSameMode, rule]);\n}\n\nexport class ModelAdaptationStore {\n\tprivate readonly filePath: string;\n\tprivate readonly fingerprint: () => HostFingerprint;\n\n\tconstructor(filePath: string, options?: { fingerprint?: () => HostFingerprint }) {\n\t\tthis.filePath = filePath;\n\t\tthis.fingerprint = options?.fingerprint ?? currentHostFingerprint;\n\t}\n\n\tstatic forAgentDir(agentDir: string, options?: { fingerprint?: () => HostFingerprint }): ModelAdaptationStore {\n\t\treturn new ModelAdaptationStore(join(agentDir, \"state\", \"model-adaptation.json\"), options);\n\t}\n\n\tprivate load(): AdaptationStoreFile {\n\t\ttry {\n\t\t\tif (!existsSync(this.filePath)) return { version: STORE_VERSION, hosts: {} };\n\t\t\tconst parsed = JSON.parse(readFileSync(this.filePath, \"utf-8\")) as AdaptationStoreFile;\n\t\t\tif (parsed && parsed.version === STORE_VERSION && parsed.hosts && typeof parsed.hosts === \"object\") {\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Unreadable/corrupt store: start fresh in memory; the next save rewrites the file.\n\t\t}\n\t\treturn { version: STORE_VERSION, hosts: {} };\n\t}\n\n\tprivate write(file: AdaptationStoreFile): void {\n\t\tmkdirSync(dirname(this.filePath), { recursive: true });\n\t\twriteFileSync(this.filePath, `${JSON.stringify(file, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\tprivate store(model: string, profile: ModelAdaptationProfile, at: string): StoredModelAdaptation {\n\t\tconst host = this.fingerprint();\n\t\tconst entry: StoredModelAdaptation = { model, profile: normalizeProfile(profile), at, host };\n\t\tconst file = this.load();\n\t\tfile.hosts[host.id] = { ...(file.hosts[host.id] ?? {}), [model]: entry };\n\t\tthis.write(file);\n\t\treturn entry;\n\t}\n\n\t/** Persist the profile for a model on the CURRENT host. Best-effort, returns the entry. */\n\tsave(model: string, profile: ModelAdaptationProfile, at?: string): StoredModelAdaptation {\n\t\treturn this.store(model, profile, at ?? new Date().toISOString());\n\t}\n\n\t/** Profile for a model on the current host; prunes retired rules before returning. */\n\tget(model: string, now: Date = new Date()): ModelAdaptationProfile {\n\t\tconst host = this.fingerprint();\n\t\tconst file = this.load();\n\t\tconst entry = file.hosts[host.id]?.[model];\n\t\tif (!entry) return emptyProfile();\n\t\tconst profile = normalizeProfile(entry.profile);\n\t\tconst prunedRules = pruneRetiredRules(profile.rules, now);\n\t\tif (prunedRules.length !== profile.rules.length) {\n\t\t\treturn this.store(model, { ...profile, rules: prunedRules }, now.toISOString()).profile;\n\t\t}\n\t\treturn profile;\n\t}\n\n\t/** Add or replace one standing rule, enforcing the per-model cap. */\n\taddRule(\n\t\tmodel: string,\n\t\trule: { mode: string; text: string; addedAt?: string; lastFiredAt?: string },\n\t\tnow = new Date(),\n\t): StoredModelAdaptation {\n\t\tconst profile = this.get(model, now);\n\t\tconst at = now.toISOString();\n\t\tconst nextRule: ModelAdaptationRule = {\n\t\t\tmode: rule.mode,\n\t\t\ttext: rule.text,\n\t\t\taddedAt: rule.addedAt ?? at,\n\t\t\tlastFiredAt: rule.lastFiredAt ?? at,\n\t\t};\n\t\treturn this.store(model, { ...profile, rules: mergeRule(profile.rules, nextRule) }, at);\n\t}\n\n\tremoveRule(model: string, mode: string, at = new Date()): boolean {\n\t\tconst profile = this.get(model, at);\n\t\tconst rules = profile.rules.filter((rule) => rule.mode !== mode);\n\t\tif (rules.length === profile.rules.length) return false;\n\t\tthis.store(model, { ...profile, rules }, at.toISOString());\n\t\treturn true;\n\t}\n\n\t/** Update last-fired recency for an existing rule. No-op when absent. */\n\tmarkRuleFired(model: string, mode: string, at = new Date()): StoredModelAdaptation | undefined {\n\t\tconst profile = this.get(model, at);\n\t\tconst rules = profile.rules.map((rule) =>\n\t\t\trule.mode === mode ? { ...rule, lastFiredAt: at.toISOString() } : rule,\n\t\t);\n\t\tif (rules.every((rule, index) => rule === profile.rules[index])) return undefined;\n\t\treturn this.store(model, { ...profile, rules }, at.toISOString());\n\t}\n\n\tsetProtocol(model: string, protocol: ModelProtocolCalibration, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? (protocol.status === \"failed\" ? protocol.attemptedAt : protocol.calibratedAt);\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, protocol }, now);\n\t}\n\n\tsetToolProbe(model: string, toolProbe: ModelToolProbe, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? toolProbe.probedAt;\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, toolProbe }, now);\n\t}\n\n\trecordPerfSample(model: string, sample: ModelPerfSample, at?: string): StoredModelAdaptation | undefined {\n\t\tif (!hasUsableModelPerfSample(sample)) return undefined;\n\t\tconst now = at ?? sample.at ?? new Date().toISOString();\n\t\tconst profile = this.get(model, new Date(now));\n\t\tconst perf = updateModelPerfProfile(profile.perf, sample, now);\n\t\tif (!perf) return undefined;\n\t\treturn this.store(model, { ...profile, perf }, now);\n\t}\n\n\tremoveProtocol(model: string, at = new Date()): boolean {\n\t\tconst profile = this.get(model, at);\n\t\tif (!profile.protocol) return false;\n\t\tconst { protocol: _protocol, ...rest } = profile;\n\t\tthis.store(model, rest, at.toISOString());\n\t\treturn true;\n\t}\n\n\tsetTeachStats(model: string, mode: string, stats: ModelTeachStats, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? new Date().toISOString();\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, teachStats: { ...profile.teachStats, [mode]: stats } }, now);\n\t}\n\n\t/** Profiles for the current host (default) or an explicit host id. */\n\tgetForHost(hostId?: string): StoredModelAdaptation[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts[hostId ?? this.fingerprint().id] ?? {}).map((entry) => ({\n\t\t\t...entry,\n\t\t\tprofile: normalizeProfile(entry.profile),\n\t\t}));\n\t}\n\n\t/** Every stored profile across all hosts (for cross-machine comparisons). */\n\tgetAll(): StoredModelAdaptation[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts).flatMap((models) =>\n\t\t\tObject.values(models).map((entry) => ({ ...entry, profile: normalizeProfile(entry.profile) })),\n\t\t);\n\t}\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { currentHostFingerprint } from "./fitness-store.js";
4
+ import { hasUsableModelPerfSample, isModelPerfProfile, updateModelPerfProfile, } from "./perf-profile.js";
4
5
  const STORE_VERSION = 1;
5
6
  const MAX_RULES_PER_MODEL = 5;
6
7
  const RETIRE_AFTER_MS = 30 * 24 * 60 * 60 * 1000;
@@ -12,6 +13,7 @@ function normalizeProfile(profile) {
12
13
  rules: Array.isArray(profile?.rules) ? profile.rules.filter(isRule) : [],
13
14
  ...(isProtocol(profile?.protocol) && { protocol: profile.protocol }),
14
15
  ...(isToolProbe(profile?.toolProbe) && { toolProbe: profile.toolProbe }),
16
+ ...(isModelPerfProfile(profile?.perf) && { perf: profile.perf }),
15
17
  teachStats: isRecord(profile?.teachStats) ? filterTeachStats(profile.teachStats) : {},
16
18
  };
17
19
  }
@@ -170,6 +172,16 @@ export class ModelAdaptationStore {
170
172
  const profile = this.get(model, new Date(now));
171
173
  return this.store(model, { ...profile, toolProbe }, now);
172
174
  }
175
+ recordPerfSample(model, sample, at) {
176
+ if (!hasUsableModelPerfSample(sample))
177
+ return undefined;
178
+ const now = at ?? sample.at ?? new Date().toISOString();
179
+ const profile = this.get(model, new Date(now));
180
+ const perf = updateModelPerfProfile(profile.perf, sample, now);
181
+ if (!perf)
182
+ return undefined;
183
+ return this.store(model, { ...profile, perf }, now);
184
+ }
173
185
  removeProtocol(model, at = new Date()) {
174
186
  const profile = this.get(model, at);
175
187
  if (!profile.protocol)
@@ -1 +1 @@
1
- {"version":3,"file":"adaptation-store.js","sourceRoot":"","sources":["../../../src/core/models/adaptation-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { currentHostFingerprint, type HostFingerprint } from \"./fitness-store.ts\";\n\nconst STORE_VERSION = 1;\nconst MAX_RULES_PER_MODEL = 5;\nconst RETIRE_AFTER_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport interface ModelAdaptationRule {\n\tmode: string;\n\ttext: string;\n\taddedAt: string;\n\tlastFiredAt: string;\n}\n\nexport type ModelProtocolCalibration =\n\t| {\n\t\t\tversion: number;\n\t\t\tstatus?: \"calibrated\";\n\t\t\tvariant: string;\n\t\t\tcalibratedAt: string;\n\t }\n\t| {\n\t\t\tversion: number;\n\t\t\tstatus: \"failed\";\n\t\t\tattemptedAt: string;\n\t\t\tvariantsTried: string[];\n\t };\n\nexport type ModelToolProbeVerdict = \"native\" | \"text-protocol\" | \"none\";\nexport type NativeToolProbeGrade = \"task\" | \"echo-only\" | \"absent\";\n\nexport interface ModelToolProbe {\n\tversion: number;\n\tstatus: ModelToolProbeVerdict;\n\tprobedAt: string;\n\tvariant?: string;\n\tnativeGrade?: NativeToolProbeGrade;\n\tdiagnostic?: string;\n}\n\nexport interface ModelTeachStats {\n\ttaught: number;\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n}\n\nexport interface ModelAdaptationProfile {\n\trules: ModelAdaptationRule[];\n\tprotocol?: ModelProtocolCalibration;\n\ttoolProbe?: ModelToolProbe;\n\tteachStats: Record<string, ModelTeachStats>;\n}\n\nexport interface StoredModelAdaptation {\n\tmodel: string;\n\tprofile: ModelAdaptationProfile;\n\tat: string;\n\thost: HostFingerprint;\n}\n\ninterface AdaptationStoreFile {\n\tversion: 1;\n\t/** hostId -> modelRef -> latest stored adaptation profile. */\n\thosts: Record<string, Record<string, StoredModelAdaptation>>;\n}\n\nfunction emptyProfile(): ModelAdaptationProfile {\n\treturn { rules: [], teachStats: {} };\n}\n\nfunction normalizeProfile(profile: Partial<ModelAdaptationProfile> | undefined): ModelAdaptationProfile {\n\treturn {\n\t\trules: Array.isArray(profile?.rules) ? profile.rules.filter(isRule) : [],\n\t\t...(isProtocol(profile?.protocol) && { protocol: profile.protocol }),\n\t\t...(isToolProbe(profile?.toolProbe) && { toolProbe: profile.toolProbe }),\n\t\tteachStats: isRecord(profile?.teachStats) ? filterTeachStats(profile.teachStats) : {},\n\t};\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isRule(value: unknown): value is ModelAdaptationRule {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.mode === \"string\" &&\n\t\ttypeof value.text === \"string\" &&\n\t\ttypeof value.addedAt === \"string\" &&\n\t\ttypeof value.lastFiredAt === \"string\"\n\t);\n}\n\nfunction isProtocol(value: unknown): value is ModelProtocolCalibration {\n\tif (!isRecord(value) || typeof value.version !== \"number\") return false;\n\tif (value.status === \"failed\") {\n\t\treturn (\n\t\t\ttypeof value.attemptedAt === \"string\" &&\n\t\t\tArray.isArray(value.variantsTried) &&\n\t\t\tvalue.variantsTried.every((variant) => typeof variant === \"string\")\n\t\t);\n\t}\n\treturn (\n\t\t(value.status === undefined || value.status === \"calibrated\") &&\n\t\ttypeof value.variant === \"string\" &&\n\t\ttypeof value.calibratedAt === \"string\"\n\t);\n}\n\nfunction isToolProbe(value: unknown): value is ModelToolProbe {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.version === \"number\" &&\n\t\t(value.status === \"native\" || value.status === \"text-protocol\" || value.status === \"none\") &&\n\t\ttypeof value.probedAt === \"string\" &&\n\t\t(value.variant === undefined || typeof value.variant === \"string\") &&\n\t\t(value.nativeGrade === undefined ||\n\t\t\tvalue.nativeGrade === \"task\" ||\n\t\t\tvalue.nativeGrade === \"echo-only\" ||\n\t\t\tvalue.nativeGrade === \"absent\") &&\n\t\t(value.diagnostic === undefined || typeof value.diagnostic === \"string\")\n\t);\n}\n\nfunction isTeachStats(value: unknown): value is ModelTeachStats {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.taught === \"number\" &&\n\t\ttypeof value.recurrenceBefore === \"number\" &&\n\t\ttypeof value.recurrenceAfter === \"number\"\n\t);\n}\n\nfunction filterTeachStats(value: Record<string, unknown>): Record<string, ModelTeachStats> {\n\treturn Object.fromEntries(\n\t\tObject.entries(value).filter((entry): entry is [string, ModelTeachStats] => isTeachStats(entry[1])),\n\t);\n}\n\nfunction ruleRecency(rule: ModelAdaptationRule): number {\n\tconst lastFired = Date.parse(rule.lastFiredAt);\n\tif (Number.isFinite(lastFired)) return lastFired;\n\tconst added = Date.parse(rule.addedAt);\n\treturn Number.isFinite(added) ? added : 0;\n}\n\nfunction pruneRetiredRules(rules: readonly ModelAdaptationRule[], now: Date): ModelAdaptationRule[] {\n\tconst cutoff = now.getTime() - RETIRE_AFTER_MS;\n\treturn rules.filter((rule) => ruleRecency(rule) >= cutoff);\n}\n\nfunction enforceRuleCap(rules: readonly ModelAdaptationRule[]): ModelAdaptationRule[] {\n\tif (rules.length <= MAX_RULES_PER_MODEL) return [...rules];\n\treturn [...rules].sort((a, b) => ruleRecency(b) - ruleRecency(a)).slice(0, MAX_RULES_PER_MODEL);\n}\n\nfunction mergeRule(rules: readonly ModelAdaptationRule[], rule: ModelAdaptationRule): ModelAdaptationRule[] {\n\tconst withoutSameMode = rules.filter((existing) => existing.mode !== rule.mode);\n\treturn enforceRuleCap([...withoutSameMode, rule]);\n}\n\nexport class ModelAdaptationStore {\n\tprivate readonly filePath: string;\n\tprivate readonly fingerprint: () => HostFingerprint;\n\n\tconstructor(filePath: string, options?: { fingerprint?: () => HostFingerprint }) {\n\t\tthis.filePath = filePath;\n\t\tthis.fingerprint = options?.fingerprint ?? currentHostFingerprint;\n\t}\n\n\tstatic forAgentDir(agentDir: string, options?: { fingerprint?: () => HostFingerprint }): ModelAdaptationStore {\n\t\treturn new ModelAdaptationStore(join(agentDir, \"state\", \"model-adaptation.json\"), options);\n\t}\n\n\tprivate load(): AdaptationStoreFile {\n\t\ttry {\n\t\t\tif (!existsSync(this.filePath)) return { version: STORE_VERSION, hosts: {} };\n\t\t\tconst parsed = JSON.parse(readFileSync(this.filePath, \"utf-8\")) as AdaptationStoreFile;\n\t\t\tif (parsed && parsed.version === STORE_VERSION && parsed.hosts && typeof parsed.hosts === \"object\") {\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Unreadable/corrupt store: start fresh in memory; the next save rewrites the file.\n\t\t}\n\t\treturn { version: STORE_VERSION, hosts: {} };\n\t}\n\n\tprivate write(file: AdaptationStoreFile): void {\n\t\tmkdirSync(dirname(this.filePath), { recursive: true });\n\t\twriteFileSync(this.filePath, `${JSON.stringify(file, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\tprivate store(model: string, profile: ModelAdaptationProfile, at: string): StoredModelAdaptation {\n\t\tconst host = this.fingerprint();\n\t\tconst entry: StoredModelAdaptation = { model, profile: normalizeProfile(profile), at, host };\n\t\tconst file = this.load();\n\t\tfile.hosts[host.id] = { ...(file.hosts[host.id] ?? {}), [model]: entry };\n\t\tthis.write(file);\n\t\treturn entry;\n\t}\n\n\t/** Persist the profile for a model on the CURRENT host. Best-effort, returns the entry. */\n\tsave(model: string, profile: ModelAdaptationProfile, at?: string): StoredModelAdaptation {\n\t\treturn this.store(model, profile, at ?? new Date().toISOString());\n\t}\n\n\t/** Profile for a model on the current host; prunes retired rules before returning. */\n\tget(model: string, now: Date = new Date()): ModelAdaptationProfile {\n\t\tconst host = this.fingerprint();\n\t\tconst file = this.load();\n\t\tconst entry = file.hosts[host.id]?.[model];\n\t\tif (!entry) return emptyProfile();\n\t\tconst profile = normalizeProfile(entry.profile);\n\t\tconst prunedRules = pruneRetiredRules(profile.rules, now);\n\t\tif (prunedRules.length !== profile.rules.length) {\n\t\t\treturn this.store(model, { ...profile, rules: prunedRules }, now.toISOString()).profile;\n\t\t}\n\t\treturn profile;\n\t}\n\n\t/** Add or replace one standing rule, enforcing the per-model cap. */\n\taddRule(\n\t\tmodel: string,\n\t\trule: { mode: string; text: string; addedAt?: string; lastFiredAt?: string },\n\t\tnow = new Date(),\n\t): StoredModelAdaptation {\n\t\tconst profile = this.get(model, now);\n\t\tconst at = now.toISOString();\n\t\tconst nextRule: ModelAdaptationRule = {\n\t\t\tmode: rule.mode,\n\t\t\ttext: rule.text,\n\t\t\taddedAt: rule.addedAt ?? at,\n\t\t\tlastFiredAt: rule.lastFiredAt ?? at,\n\t\t};\n\t\treturn this.store(model, { ...profile, rules: mergeRule(profile.rules, nextRule) }, at);\n\t}\n\n\tremoveRule(model: string, mode: string, at = new Date()): boolean {\n\t\tconst profile = this.get(model, at);\n\t\tconst rules = profile.rules.filter((rule) => rule.mode !== mode);\n\t\tif (rules.length === profile.rules.length) return false;\n\t\tthis.store(model, { ...profile, rules }, at.toISOString());\n\t\treturn true;\n\t}\n\n\t/** Update last-fired recency for an existing rule. No-op when absent. */\n\tmarkRuleFired(model: string, mode: string, at = new Date()): StoredModelAdaptation | undefined {\n\t\tconst profile = this.get(model, at);\n\t\tconst rules = profile.rules.map((rule) =>\n\t\t\trule.mode === mode ? { ...rule, lastFiredAt: at.toISOString() } : rule,\n\t\t);\n\t\tif (rules.every((rule, index) => rule === profile.rules[index])) return undefined;\n\t\treturn this.store(model, { ...profile, rules }, at.toISOString());\n\t}\n\n\tsetProtocol(model: string, protocol: ModelProtocolCalibration, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? (protocol.status === \"failed\" ? protocol.attemptedAt : protocol.calibratedAt);\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, protocol }, now);\n\t}\n\n\tsetToolProbe(model: string, toolProbe: ModelToolProbe, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? toolProbe.probedAt;\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, toolProbe }, now);\n\t}\n\n\tremoveProtocol(model: string, at = new Date()): boolean {\n\t\tconst profile = this.get(model, at);\n\t\tif (!profile.protocol) return false;\n\t\tconst { protocol: _protocol, ...rest } = profile;\n\t\tthis.store(model, rest, at.toISOString());\n\t\treturn true;\n\t}\n\n\tsetTeachStats(model: string, mode: string, stats: ModelTeachStats, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? new Date().toISOString();\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, teachStats: { ...profile.teachStats, [mode]: stats } }, now);\n\t}\n\n\t/** Profiles for the current host (default) or an explicit host id. */\n\tgetForHost(hostId?: string): StoredModelAdaptation[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts[hostId ?? this.fingerprint().id] ?? {}).map((entry) => ({\n\t\t\t...entry,\n\t\t\tprofile: normalizeProfile(entry.profile),\n\t\t}));\n\t}\n\n\t/** Every stored profile across all hosts (for cross-machine comparisons). */\n\tgetAll(): StoredModelAdaptation[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts).flatMap((models) =>\n\t\t\tObject.values(models).map((entry) => ({ ...entry, profile: normalizeProfile(entry.profile) })),\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"adaptation-store.js","sourceRoot":"","sources":["../../../src/core/models/adaptation-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { currentHostFingerprint, type HostFingerprint } from \"./fitness-store.ts\";\nimport {\n\thasUsableModelPerfSample,\n\tisModelPerfProfile,\n\ttype ModelPerfProfile,\n\ttype ModelPerfSample,\n\tupdateModelPerfProfile,\n} from \"./perf-profile.ts\";\n\nconst STORE_VERSION = 1;\nconst MAX_RULES_PER_MODEL = 5;\nconst RETIRE_AFTER_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport interface ModelAdaptationRule {\n\tmode: string;\n\ttext: string;\n\taddedAt: string;\n\tlastFiredAt: string;\n}\n\nexport type ModelProtocolCalibration =\n\t| {\n\t\t\tversion: number;\n\t\t\tstatus?: \"calibrated\";\n\t\t\tvariant: string;\n\t\t\tcalibratedAt: string;\n\t }\n\t| {\n\t\t\tversion: number;\n\t\t\tstatus: \"failed\";\n\t\t\tattemptedAt: string;\n\t\t\tvariantsTried: string[];\n\t };\n\nexport type ModelToolProbeVerdict = \"native\" | \"text-protocol\" | \"none\";\nexport type NativeToolProbeGrade = \"task\" | \"echo-only\" | \"absent\";\n\nexport interface ModelToolProbe {\n\tversion: number;\n\tstatus: ModelToolProbeVerdict;\n\tprobedAt: string;\n\tvariant?: string;\n\tnativeGrade?: NativeToolProbeGrade;\n\tdiagnostic?: string;\n}\n\nexport interface ModelTeachStats {\n\ttaught: number;\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n}\n\nexport interface ModelAdaptationProfile {\n\trules: ModelAdaptationRule[];\n\tprotocol?: ModelProtocolCalibration;\n\ttoolProbe?: ModelToolProbe;\n\tperf?: ModelPerfProfile;\n\tteachStats: Record<string, ModelTeachStats>;\n}\n\nexport interface StoredModelAdaptation {\n\tmodel: string;\n\tprofile: ModelAdaptationProfile;\n\tat: string;\n\thost: HostFingerprint;\n}\n\ninterface AdaptationStoreFile {\n\tversion: 1;\n\t/** hostId -> modelRef -> latest stored adaptation profile. */\n\thosts: Record<string, Record<string, StoredModelAdaptation>>;\n}\n\nfunction emptyProfile(): ModelAdaptationProfile {\n\treturn { rules: [], teachStats: {} };\n}\n\nfunction normalizeProfile(profile: Partial<ModelAdaptationProfile> | undefined): ModelAdaptationProfile {\n\treturn {\n\t\trules: Array.isArray(profile?.rules) ? profile.rules.filter(isRule) : [],\n\t\t...(isProtocol(profile?.protocol) && { protocol: profile.protocol }),\n\t\t...(isToolProbe(profile?.toolProbe) && { toolProbe: profile.toolProbe }),\n\t\t...(isModelPerfProfile(profile?.perf) && { perf: profile.perf }),\n\t\tteachStats: isRecord(profile?.teachStats) ? filterTeachStats(profile.teachStats) : {},\n\t};\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isRule(value: unknown): value is ModelAdaptationRule {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.mode === \"string\" &&\n\t\ttypeof value.text === \"string\" &&\n\t\ttypeof value.addedAt === \"string\" &&\n\t\ttypeof value.lastFiredAt === \"string\"\n\t);\n}\n\nfunction isProtocol(value: unknown): value is ModelProtocolCalibration {\n\tif (!isRecord(value) || typeof value.version !== \"number\") return false;\n\tif (value.status === \"failed\") {\n\t\treturn (\n\t\t\ttypeof value.attemptedAt === \"string\" &&\n\t\t\tArray.isArray(value.variantsTried) &&\n\t\t\tvalue.variantsTried.every((variant) => typeof variant === \"string\")\n\t\t);\n\t}\n\treturn (\n\t\t(value.status === undefined || value.status === \"calibrated\") &&\n\t\ttypeof value.variant === \"string\" &&\n\t\ttypeof value.calibratedAt === \"string\"\n\t);\n}\n\nfunction isToolProbe(value: unknown): value is ModelToolProbe {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.version === \"number\" &&\n\t\t(value.status === \"native\" || value.status === \"text-protocol\" || value.status === \"none\") &&\n\t\ttypeof value.probedAt === \"string\" &&\n\t\t(value.variant === undefined || typeof value.variant === \"string\") &&\n\t\t(value.nativeGrade === undefined ||\n\t\t\tvalue.nativeGrade === \"task\" ||\n\t\t\tvalue.nativeGrade === \"echo-only\" ||\n\t\t\tvalue.nativeGrade === \"absent\") &&\n\t\t(value.diagnostic === undefined || typeof value.diagnostic === \"string\")\n\t);\n}\n\nfunction isTeachStats(value: unknown): value is ModelTeachStats {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.taught === \"number\" &&\n\t\ttypeof value.recurrenceBefore === \"number\" &&\n\t\ttypeof value.recurrenceAfter === \"number\"\n\t);\n}\n\nfunction filterTeachStats(value: Record<string, unknown>): Record<string, ModelTeachStats> {\n\treturn Object.fromEntries(\n\t\tObject.entries(value).filter((entry): entry is [string, ModelTeachStats] => isTeachStats(entry[1])),\n\t);\n}\n\nfunction ruleRecency(rule: ModelAdaptationRule): number {\n\tconst lastFired = Date.parse(rule.lastFiredAt);\n\tif (Number.isFinite(lastFired)) return lastFired;\n\tconst added = Date.parse(rule.addedAt);\n\treturn Number.isFinite(added) ? added : 0;\n}\n\nfunction pruneRetiredRules(rules: readonly ModelAdaptationRule[], now: Date): ModelAdaptationRule[] {\n\tconst cutoff = now.getTime() - RETIRE_AFTER_MS;\n\treturn rules.filter((rule) => ruleRecency(rule) >= cutoff);\n}\n\nfunction enforceRuleCap(rules: readonly ModelAdaptationRule[]): ModelAdaptationRule[] {\n\tif (rules.length <= MAX_RULES_PER_MODEL) return [...rules];\n\treturn [...rules].sort((a, b) => ruleRecency(b) - ruleRecency(a)).slice(0, MAX_RULES_PER_MODEL);\n}\n\nfunction mergeRule(rules: readonly ModelAdaptationRule[], rule: ModelAdaptationRule): ModelAdaptationRule[] {\n\tconst withoutSameMode = rules.filter((existing) => existing.mode !== rule.mode);\n\treturn enforceRuleCap([...withoutSameMode, rule]);\n}\n\nexport class ModelAdaptationStore {\n\tprivate readonly filePath: string;\n\tprivate readonly fingerprint: () => HostFingerprint;\n\n\tconstructor(filePath: string, options?: { fingerprint?: () => HostFingerprint }) {\n\t\tthis.filePath = filePath;\n\t\tthis.fingerprint = options?.fingerprint ?? currentHostFingerprint;\n\t}\n\n\tstatic forAgentDir(agentDir: string, options?: { fingerprint?: () => HostFingerprint }): ModelAdaptationStore {\n\t\treturn new ModelAdaptationStore(join(agentDir, \"state\", \"model-adaptation.json\"), options);\n\t}\n\n\tprivate load(): AdaptationStoreFile {\n\t\ttry {\n\t\t\tif (!existsSync(this.filePath)) return { version: STORE_VERSION, hosts: {} };\n\t\t\tconst parsed = JSON.parse(readFileSync(this.filePath, \"utf-8\")) as AdaptationStoreFile;\n\t\t\tif (parsed && parsed.version === STORE_VERSION && parsed.hosts && typeof parsed.hosts === \"object\") {\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Unreadable/corrupt store: start fresh in memory; the next save rewrites the file.\n\t\t}\n\t\treturn { version: STORE_VERSION, hosts: {} };\n\t}\n\n\tprivate write(file: AdaptationStoreFile): void {\n\t\tmkdirSync(dirname(this.filePath), { recursive: true });\n\t\twriteFileSync(this.filePath, `${JSON.stringify(file, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\tprivate store(model: string, profile: ModelAdaptationProfile, at: string): StoredModelAdaptation {\n\t\tconst host = this.fingerprint();\n\t\tconst entry: StoredModelAdaptation = { model, profile: normalizeProfile(profile), at, host };\n\t\tconst file = this.load();\n\t\tfile.hosts[host.id] = { ...(file.hosts[host.id] ?? {}), [model]: entry };\n\t\tthis.write(file);\n\t\treturn entry;\n\t}\n\n\t/** Persist the profile for a model on the CURRENT host. Best-effort, returns the entry. */\n\tsave(model: string, profile: ModelAdaptationProfile, at?: string): StoredModelAdaptation {\n\t\treturn this.store(model, profile, at ?? new Date().toISOString());\n\t}\n\n\t/** Profile for a model on the current host; prunes retired rules before returning. */\n\tget(model: string, now: Date = new Date()): ModelAdaptationProfile {\n\t\tconst host = this.fingerprint();\n\t\tconst file = this.load();\n\t\tconst entry = file.hosts[host.id]?.[model];\n\t\tif (!entry) return emptyProfile();\n\t\tconst profile = normalizeProfile(entry.profile);\n\t\tconst prunedRules = pruneRetiredRules(profile.rules, now);\n\t\tif (prunedRules.length !== profile.rules.length) {\n\t\t\treturn this.store(model, { ...profile, rules: prunedRules }, now.toISOString()).profile;\n\t\t}\n\t\treturn profile;\n\t}\n\n\t/** Add or replace one standing rule, enforcing the per-model cap. */\n\taddRule(\n\t\tmodel: string,\n\t\trule: { mode: string; text: string; addedAt?: string; lastFiredAt?: string },\n\t\tnow = new Date(),\n\t): StoredModelAdaptation {\n\t\tconst profile = this.get(model, now);\n\t\tconst at = now.toISOString();\n\t\tconst nextRule: ModelAdaptationRule = {\n\t\t\tmode: rule.mode,\n\t\t\ttext: rule.text,\n\t\t\taddedAt: rule.addedAt ?? at,\n\t\t\tlastFiredAt: rule.lastFiredAt ?? at,\n\t\t};\n\t\treturn this.store(model, { ...profile, rules: mergeRule(profile.rules, nextRule) }, at);\n\t}\n\n\tremoveRule(model: string, mode: string, at = new Date()): boolean {\n\t\tconst profile = this.get(model, at);\n\t\tconst rules = profile.rules.filter((rule) => rule.mode !== mode);\n\t\tif (rules.length === profile.rules.length) return false;\n\t\tthis.store(model, { ...profile, rules }, at.toISOString());\n\t\treturn true;\n\t}\n\n\t/** Update last-fired recency for an existing rule. No-op when absent. */\n\tmarkRuleFired(model: string, mode: string, at = new Date()): StoredModelAdaptation | undefined {\n\t\tconst profile = this.get(model, at);\n\t\tconst rules = profile.rules.map((rule) =>\n\t\t\trule.mode === mode ? { ...rule, lastFiredAt: at.toISOString() } : rule,\n\t\t);\n\t\tif (rules.every((rule, index) => rule === profile.rules[index])) return undefined;\n\t\treturn this.store(model, { ...profile, rules }, at.toISOString());\n\t}\n\n\tsetProtocol(model: string, protocol: ModelProtocolCalibration, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? (protocol.status === \"failed\" ? protocol.attemptedAt : protocol.calibratedAt);\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, protocol }, now);\n\t}\n\n\tsetToolProbe(model: string, toolProbe: ModelToolProbe, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? toolProbe.probedAt;\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, toolProbe }, now);\n\t}\n\n\trecordPerfSample(model: string, sample: ModelPerfSample, at?: string): StoredModelAdaptation | undefined {\n\t\tif (!hasUsableModelPerfSample(sample)) return undefined;\n\t\tconst now = at ?? sample.at ?? new Date().toISOString();\n\t\tconst profile = this.get(model, new Date(now));\n\t\tconst perf = updateModelPerfProfile(profile.perf, sample, now);\n\t\tif (!perf) return undefined;\n\t\treturn this.store(model, { ...profile, perf }, now);\n\t}\n\n\tremoveProtocol(model: string, at = new Date()): boolean {\n\t\tconst profile = this.get(model, at);\n\t\tif (!profile.protocol) return false;\n\t\tconst { protocol: _protocol, ...rest } = profile;\n\t\tthis.store(model, rest, at.toISOString());\n\t\treturn true;\n\t}\n\n\tsetTeachStats(model: string, mode: string, stats: ModelTeachStats, at?: string): StoredModelAdaptation {\n\t\tconst now = at ?? new Date().toISOString();\n\t\tconst profile = this.get(model, new Date(now));\n\t\treturn this.store(model, { ...profile, teachStats: { ...profile.teachStats, [mode]: stats } }, now);\n\t}\n\n\t/** Profiles for the current host (default) or an explicit host id. */\n\tgetForHost(hostId?: string): StoredModelAdaptation[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts[hostId ?? this.fingerprint().id] ?? {}).map((entry) => ({\n\t\t\t...entry,\n\t\t\tprofile: normalizeProfile(entry.profile),\n\t\t}));\n\t}\n\n\t/** Every stored profile across all hosts (for cross-machine comparisons). */\n\tgetAll(): StoredModelAdaptation[] {\n\t\tconst file = this.load();\n\t\treturn Object.values(file.hosts).flatMap((models) =>\n\t\t\tObject.values(models).map((entry) => ({ ...entry, profile: normalizeProfile(entry.profile) })),\n\t\t);\n\t}\n}\n"]}
@@ -0,0 +1,30 @@
1
+ export interface ContextSizingModelInfo {
2
+ modelInfo: Record<string, unknown>;
3
+ weightsBytes: number;
4
+ }
5
+ export interface ContextSizingHostProfile {
6
+ totalMemBytes: number;
7
+ headroomFraction?: number;
8
+ }
9
+ export interface ContextSizingRuntimeCaps {
10
+ supportsKvQuantization?: boolean;
11
+ }
12
+ export interface ContextSizingDecision {
13
+ numCtx: number;
14
+ kvCacheType?: "q8_0";
15
+ kvBytesPerToken: number;
16
+ budgetBytes: number;
17
+ estimatedBytes: number;
18
+ }
19
+ export declare function deriveLocalContextSizing(args: {
20
+ host: ContextSizingHostProfile;
21
+ model: ContextSizingModelInfo;
22
+ runtime?: ContextSizingRuntimeCaps;
23
+ rungs?: readonly number[];
24
+ }): ContextSizingDecision | undefined;
25
+ export declare function renderOllamaContextModelfile(args: {
26
+ from: string;
27
+ numCtx: number;
28
+ }): string;
29
+ export declare function sizedLocalModelRef(sourceRef: string, numCtx: number): string;
30
+ //# sourceMappingURL=context-sizing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-sizing.d.ts","sourceRoot":"","sources":["../../../src/core/models/context-sizing.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACxC,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC9C,IAAI,EAAE,wBAAwB,CAAC;IAC/B,KAAK,EAAE,sBAAsB,CAAC;IAC9B,OAAO,CAAC,EAAE,wBAAwB,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,GAAG,qBAAqB,GAAG,SAAS,CAoBpC;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE3F;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG5E","sourcesContent":["const CONTEXT_RUNGS = [4_096, 8_192, 16_384, 32_768] as const;\nconst DEFAULT_HEADROOM_FRACTION = 0.8;\nconst F16_BYTES = 2;\nconst Q8_BYTES = 1;\n\nexport interface ContextSizingModelInfo {\n\tmodelInfo: Record<string, unknown>;\n\tweightsBytes: number;\n}\n\nexport interface ContextSizingHostProfile {\n\ttotalMemBytes: number;\n\theadroomFraction?: number;\n}\n\nexport interface ContextSizingRuntimeCaps {\n\tsupportsKvQuantization?: boolean;\n}\n\nexport interface ContextSizingDecision {\n\tnumCtx: number;\n\tkvCacheType?: \"q8_0\";\n\tkvBytesPerToken: number;\n\tbudgetBytes: number;\n\testimatedBytes: number;\n}\n\nexport function deriveLocalContextSizing(args: {\n\thost: ContextSizingHostProfile;\n\tmodel: ContextSizingModelInfo;\n\truntime?: ContextSizingRuntimeCaps;\n\trungs?: readonly number[];\n}): ContextSizingDecision | undefined {\n\tconst kvShape = deriveKvShape(args.model.modelInfo);\n\tif (!kvShape) return undefined;\n\tconst headroomFraction = args.host.headroomFraction ?? DEFAULT_HEADROOM_FRACTION;\n\tconst budgetBytes = Math.floor(args.host.totalMemBytes * headroomFraction);\n\tconst availableKvBytes = budgetBytes - args.model.weightsBytes;\n\tif (availableKvBytes <= 0) return undefined;\n\tconst rungs = args.rungs ?? CONTEXT_RUNGS;\n\tconst f16Decision = chooseContextRung({ availableKvBytes, bytesPerElement: F16_BYTES, budgetBytes, kvShape, rungs });\n\tif (!args.runtime?.supportsKvQuantization) return f16Decision;\n\tconst q8Decision = chooseContextRung({\n\t\tavailableKvBytes,\n\t\tbytesPerElement: Q8_BYTES,\n\t\tbudgetBytes,\n\t\tkvCacheType: \"q8_0\",\n\t\tkvShape,\n\t\trungs,\n\t});\n\tif (q8Decision && (!f16Decision || q8Decision.numCtx > f16Decision.numCtx)) return q8Decision;\n\treturn f16Decision;\n}\n\nexport function renderOllamaContextModelfile(args: { from: string; numCtx: number }): string {\n\treturn `FROM ${args.from}\\nPARAMETER num_ctx ${args.numCtx}\\n`;\n}\n\nexport function sizedLocalModelRef(sourceRef: string, numCtx: number): string {\n\tconst safeSource = sourceRef.replace(/[^a-zA-Z0-9_.-]+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"local-model\";\n\treturn `pi-${safeSource}:ctx${numCtx}`;\n}\n\nfunction chooseContextRung(args: {\n\tavailableKvBytes: number;\n\tbytesPerElement: number;\n\tbudgetBytes: number;\n\tkvCacheType?: \"q8_0\";\n\tkvShape: { elementsPerToken: number };\n\trungs: readonly number[];\n}): ContextSizingDecision | undefined {\n\tconst kvBytesPerToken = args.kvShape.elementsPerToken * args.bytesPerElement;\n\tfor (const numCtx of [...args.rungs].sort((a, b) => b - a)) {\n\t\tconst estimatedBytes = kvBytesPerToken * numCtx;\n\t\tif (estimatedBytes <= args.availableKvBytes) {\n\t\t\treturn {\n\t\t\t\tnumCtx,\n\t\t\t\tkvCacheType: args.kvCacheType,\n\t\t\t\tkvBytesPerToken,\n\t\t\t\tbudgetBytes: args.budgetBytes,\n\t\t\t\testimatedBytes,\n\t\t\t};\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction deriveKvShape(modelInfo: Record<string, unknown>): { elementsPerToken: number } | undefined {\n\tconst layers = firstPositiveNumber(modelInfo, [\"llama.block_count\", \"qwen2.block_count\", \"gemma3.block_count\"]);\n\tif (!layers) return undefined;\n\tconst headCountKv = firstPositiveNumber(modelInfo, [\n\t\t\"llama.attention.head_count_kv\",\n\t\t\"qwen2.attention.head_count_kv\",\n\t\t\"gemma3.attention.head_count_kv\",\n\t]);\n\tconst keyLength = firstPositiveNumber(modelInfo, [\n\t\t\"llama.attention.key_length\",\n\t\t\"qwen2.attention.key_length\",\n\t\t\"gemma3.attention.key_length\",\n\t]);\n\tconst valueLength = firstPositiveNumber(modelInfo, [\n\t\t\"llama.attention.value_length\",\n\t\t\"qwen2.attention.value_length\",\n\t\t\"gemma3.attention.value_length\",\n\t]);\n\tif (headCountKv && keyLength && valueLength) {\n\t\treturn { elementsPerToken: layers * headCountKv * (keyLength + valueLength) };\n\t}\n\tconst embeddingLength = firstPositiveNumber(modelInfo, [\n\t\t\"llama.embedding_length\",\n\t\t\"qwen2.embedding_length\",\n\t\t\"gemma3.embedding_length\",\n\t]);\n\treturn embeddingLength ? { elementsPerToken: layers * embeddingLength * 2 } : undefined;\n}\n\nfunction firstPositiveNumber(record: Record<string, unknown>, keys: readonly string[]): number | undefined {\n\tfor (const key of keys) {\n\t\tconst value = record[key];\n\t\tif (typeof value === \"number\" && Number.isFinite(value) && value > 0) return value;\n\t}\n\treturn undefined;\n}\n"]}
@@ -0,0 +1,90 @@
1
+ const CONTEXT_RUNGS = [4_096, 8_192, 16_384, 32_768];
2
+ const DEFAULT_HEADROOM_FRACTION = 0.8;
3
+ const F16_BYTES = 2;
4
+ const Q8_BYTES = 1;
5
+ export function deriveLocalContextSizing(args) {
6
+ const kvShape = deriveKvShape(args.model.modelInfo);
7
+ if (!kvShape)
8
+ return undefined;
9
+ const headroomFraction = args.host.headroomFraction ?? DEFAULT_HEADROOM_FRACTION;
10
+ const budgetBytes = Math.floor(args.host.totalMemBytes * headroomFraction);
11
+ const availableKvBytes = budgetBytes - args.model.weightsBytes;
12
+ if (availableKvBytes <= 0)
13
+ return undefined;
14
+ const rungs = args.rungs ?? CONTEXT_RUNGS;
15
+ const f16Decision = chooseContextRung({ availableKvBytes, bytesPerElement: F16_BYTES, budgetBytes, kvShape, rungs });
16
+ if (!args.runtime?.supportsKvQuantization)
17
+ return f16Decision;
18
+ const q8Decision = chooseContextRung({
19
+ availableKvBytes,
20
+ bytesPerElement: Q8_BYTES,
21
+ budgetBytes,
22
+ kvCacheType: "q8_0",
23
+ kvShape,
24
+ rungs,
25
+ });
26
+ if (q8Decision && (!f16Decision || q8Decision.numCtx > f16Decision.numCtx))
27
+ return q8Decision;
28
+ return f16Decision;
29
+ }
30
+ export function renderOllamaContextModelfile(args) {
31
+ return `FROM ${args.from}\nPARAMETER num_ctx ${args.numCtx}\n`;
32
+ }
33
+ export function sizedLocalModelRef(sourceRef, numCtx) {
34
+ const safeSource = sourceRef.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "local-model";
35
+ return `pi-${safeSource}:ctx${numCtx}`;
36
+ }
37
+ function chooseContextRung(args) {
38
+ const kvBytesPerToken = args.kvShape.elementsPerToken * args.bytesPerElement;
39
+ for (const numCtx of [...args.rungs].sort((a, b) => b - a)) {
40
+ const estimatedBytes = kvBytesPerToken * numCtx;
41
+ if (estimatedBytes <= args.availableKvBytes) {
42
+ return {
43
+ numCtx,
44
+ kvCacheType: args.kvCacheType,
45
+ kvBytesPerToken,
46
+ budgetBytes: args.budgetBytes,
47
+ estimatedBytes,
48
+ };
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ function deriveKvShape(modelInfo) {
54
+ const layers = firstPositiveNumber(modelInfo, ["llama.block_count", "qwen2.block_count", "gemma3.block_count"]);
55
+ if (!layers)
56
+ return undefined;
57
+ const headCountKv = firstPositiveNumber(modelInfo, [
58
+ "llama.attention.head_count_kv",
59
+ "qwen2.attention.head_count_kv",
60
+ "gemma3.attention.head_count_kv",
61
+ ]);
62
+ const keyLength = firstPositiveNumber(modelInfo, [
63
+ "llama.attention.key_length",
64
+ "qwen2.attention.key_length",
65
+ "gemma3.attention.key_length",
66
+ ]);
67
+ const valueLength = firstPositiveNumber(modelInfo, [
68
+ "llama.attention.value_length",
69
+ "qwen2.attention.value_length",
70
+ "gemma3.attention.value_length",
71
+ ]);
72
+ if (headCountKv && keyLength && valueLength) {
73
+ return { elementsPerToken: layers * headCountKv * (keyLength + valueLength) };
74
+ }
75
+ const embeddingLength = firstPositiveNumber(modelInfo, [
76
+ "llama.embedding_length",
77
+ "qwen2.embedding_length",
78
+ "gemma3.embedding_length",
79
+ ]);
80
+ return embeddingLength ? { elementsPerToken: layers * embeddingLength * 2 } : undefined;
81
+ }
82
+ function firstPositiveNumber(record, keys) {
83
+ for (const key of keys) {
84
+ const value = record[key];
85
+ if (typeof value === "number" && Number.isFinite(value) && value > 0)
86
+ return value;
87
+ }
88
+ return undefined;
89
+ }
90
+ //# sourceMappingURL=context-sizing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-sizing.js","sourceRoot":"","sources":["../../../src/core/models/context-sizing.ts"],"names":[],"mappings":"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","sourcesContent":["const CONTEXT_RUNGS = [4_096, 8_192, 16_384, 32_768] as const;\nconst DEFAULT_HEADROOM_FRACTION = 0.8;\nconst F16_BYTES = 2;\nconst Q8_BYTES = 1;\n\nexport interface ContextSizingModelInfo {\n\tmodelInfo: Record<string, unknown>;\n\tweightsBytes: number;\n}\n\nexport interface ContextSizingHostProfile {\n\ttotalMemBytes: number;\n\theadroomFraction?: number;\n}\n\nexport interface ContextSizingRuntimeCaps {\n\tsupportsKvQuantization?: boolean;\n}\n\nexport interface ContextSizingDecision {\n\tnumCtx: number;\n\tkvCacheType?: \"q8_0\";\n\tkvBytesPerToken: number;\n\tbudgetBytes: number;\n\testimatedBytes: number;\n}\n\nexport function deriveLocalContextSizing(args: {\n\thost: ContextSizingHostProfile;\n\tmodel: ContextSizingModelInfo;\n\truntime?: ContextSizingRuntimeCaps;\n\trungs?: readonly number[];\n}): ContextSizingDecision | undefined {\n\tconst kvShape = deriveKvShape(args.model.modelInfo);\n\tif (!kvShape) return undefined;\n\tconst headroomFraction = args.host.headroomFraction ?? DEFAULT_HEADROOM_FRACTION;\n\tconst budgetBytes = Math.floor(args.host.totalMemBytes * headroomFraction);\n\tconst availableKvBytes = budgetBytes - args.model.weightsBytes;\n\tif (availableKvBytes <= 0) return undefined;\n\tconst rungs = args.rungs ?? CONTEXT_RUNGS;\n\tconst f16Decision = chooseContextRung({ availableKvBytes, bytesPerElement: F16_BYTES, budgetBytes, kvShape, rungs });\n\tif (!args.runtime?.supportsKvQuantization) return f16Decision;\n\tconst q8Decision = chooseContextRung({\n\t\tavailableKvBytes,\n\t\tbytesPerElement: Q8_BYTES,\n\t\tbudgetBytes,\n\t\tkvCacheType: \"q8_0\",\n\t\tkvShape,\n\t\trungs,\n\t});\n\tif (q8Decision && (!f16Decision || q8Decision.numCtx > f16Decision.numCtx)) return q8Decision;\n\treturn f16Decision;\n}\n\nexport function renderOllamaContextModelfile(args: { from: string; numCtx: number }): string {\n\treturn `FROM ${args.from}\\nPARAMETER num_ctx ${args.numCtx}\\n`;\n}\n\nexport function sizedLocalModelRef(sourceRef: string, numCtx: number): string {\n\tconst safeSource = sourceRef.replace(/[^a-zA-Z0-9_.-]+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"local-model\";\n\treturn `pi-${safeSource}:ctx${numCtx}`;\n}\n\nfunction chooseContextRung(args: {\n\tavailableKvBytes: number;\n\tbytesPerElement: number;\n\tbudgetBytes: number;\n\tkvCacheType?: \"q8_0\";\n\tkvShape: { elementsPerToken: number };\n\trungs: readonly number[];\n}): ContextSizingDecision | undefined {\n\tconst kvBytesPerToken = args.kvShape.elementsPerToken * args.bytesPerElement;\n\tfor (const numCtx of [...args.rungs].sort((a, b) => b - a)) {\n\t\tconst estimatedBytes = kvBytesPerToken * numCtx;\n\t\tif (estimatedBytes <= args.availableKvBytes) {\n\t\t\treturn {\n\t\t\t\tnumCtx,\n\t\t\t\tkvCacheType: args.kvCacheType,\n\t\t\t\tkvBytesPerToken,\n\t\t\t\tbudgetBytes: args.budgetBytes,\n\t\t\t\testimatedBytes,\n\t\t\t};\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction deriveKvShape(modelInfo: Record<string, unknown>): { elementsPerToken: number } | undefined {\n\tconst layers = firstPositiveNumber(modelInfo, [\"llama.block_count\", \"qwen2.block_count\", \"gemma3.block_count\"]);\n\tif (!layers) return undefined;\n\tconst headCountKv = firstPositiveNumber(modelInfo, [\n\t\t\"llama.attention.head_count_kv\",\n\t\t\"qwen2.attention.head_count_kv\",\n\t\t\"gemma3.attention.head_count_kv\",\n\t]);\n\tconst keyLength = firstPositiveNumber(modelInfo, [\n\t\t\"llama.attention.key_length\",\n\t\t\"qwen2.attention.key_length\",\n\t\t\"gemma3.attention.key_length\",\n\t]);\n\tconst valueLength = firstPositiveNumber(modelInfo, [\n\t\t\"llama.attention.value_length\",\n\t\t\"qwen2.attention.value_length\",\n\t\t\"gemma3.attention.value_length\",\n\t]);\n\tif (headCountKv && keyLength && valueLength) {\n\t\treturn { elementsPerToken: layers * headCountKv * (keyLength + valueLength) };\n\t}\n\tconst embeddingLength = firstPositiveNumber(modelInfo, [\n\t\t\"llama.embedding_length\",\n\t\t\"qwen2.embedding_length\",\n\t\t\"gemma3.embedding_length\",\n\t]);\n\treturn embeddingLength ? { elementsPerToken: layers * embeddingLength * 2 } : undefined;\n}\n\nfunction firstPositiveNumber(record: Record<string, unknown>, keys: readonly string[]): number | undefined {\n\tfor (const key of keys) {\n\t\tconst value = record[key];\n\t\tif (typeof value === \"number\" && Number.isFinite(value) && value > 0) return value;\n\t}\n\treturn undefined;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"default-model-suggestions.d.ts","sourceRoot":"","sources":["../../../src/core/models/default-model-suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6DAA6D,CAAC;AAE/F;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC/B,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,oGAAoG;IACpG,WAAW,EAAE,OAAO,CAAC;IACrB,mGAAmG;IACnG,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,yBAAyB,EAAE,SAAS,eAAe,EA8E/D,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,sBAAsB,CAAC,WAAW,GAAE,SAAS,eAAe,EAA8B,GAAG,MAAM,EAAE,CAcpH","sourcesContent":["import type { FitnessRole } from \"../../modes/interactive/components/fitness-role-selector.ts\";\n\n/**\n * Curated local-model suggestions: a starting roster validated during pi-adaptative's own\n * small-model research so a user does not have to know WHICH model fits WHICH role. Each entry is\n * an install ref (accepted by /models add) plus the role it was validated for and whether it can use\n * tools. Native tool-calling remains preferred when it works; text-protocol models are listed only\n * when their runtime is pi-managed and the fitness/probe path can calibrate that fallback.\n *\n * Honesty: these are SUGGESTIONS, not guarantees. Fitness is a property of the model AND the host\n * hardware, so no per-model score is baked in here — `/models add` auto-probes on the actual\n * machine, and the roster only encodes what role each model was SHAPED for. `assignRole` is the\n * /fitness role the suggestion maps to (undefined = no single-setting slot yet; use it as a\n * research/worker lane model).\n */\n\nexport interface ModelSuggestion {\n\t/** Display name in the suggestions list. */\n\tname: string;\n\t/** Ref accepted by /models add and the source normalizer. */\n\tpullRef: string;\n\t/** Role this model was validated/shaped for. */\n\trole: string;\n\t/** True if the model can use tools after host-local probing/calibration (required for executor). */\n\ttoolCalling: boolean;\n\t/** The /fitness assignment target this suggestion maps to, when there is a single-setting slot. */\n\tassignRole?: FitnessRole;\n\t/** One-line rationale from the validation work; never a fabricated numeric score. */\n\trationale: string;\n\t/** Optional caveat (quant selection, RAM needs). */\n\tnote?: string;\n}\n\nexport const DEFAULT_MODEL_SUGGESTIONS: readonly ModelSuggestion[] = [\n\t{\n\t\tname: \"qwen3:1.7b\",\n\t\tpullRef: \"qwen3:1.7b\",\n\t\trole: \"Toolkit executor / reflex muscle\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Validated as the executor: reliable native tool-calling and low latency, so direct toolkit commands run without retries.\",\n\t},\n\t{\n\t\tname: \"qwen3:0.6b\",\n\t\tpullRef: \"qwen3:0.6b\",\n\t\trole: \"Minimal executor (fastest)\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Fastest local option; on harder requests it can narrate without executing, so prefer it only when speed dominates and requests are simple.\",\n\t},\n\t{\n\t\tname: \"MiniCPM5-1B (full-base)\",\n\t\tpullRef: \"hf.co/openbmb/MiniCPM5-1B\",\n\t\trole: \"Full-base Transformers executor / tiny local muscle\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Full-base Hugging Face MiniCPM5-1B target: pi installs an isolated Transformers runtime and probes native tool-calling first, then calibrates the text protocol only if native tool calls do not work.\",\n\t\tnote: \"Not GGUF/quantized: weights and Python deps land under pi-owned runtime/cache directories, separate from Ollama and system Python.\",\n\t},\n\t{\n\t\tname: \"FastContext-1.0-4B\",\n\t\tpullRef: \"hf.co/KikoCis/FastContext-1.0-4B-longctx-imatrix-GGUF:fastcontext4b.Q4_K_M.imx.gguf\",\n\t\trole: \"Repository scout (context_scout)\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"scout\",\n\t\trationale:\n\t\t\t\"The model the context_scout lane is built around (Plan 9): 4B Qwen3, 256K context, trained for read-only repo exploration returning file:line citations. /fitness must pass the tool-calls + research lanes before assignment.\",\n\t\tnote: \"Q4 ≈ 2.5 GB; ~5-6 GB peak with 32-64K KV. Qwen3 template caveat: if tool calls fail the probe, use the Modelfile recipe in docs/scout.md.\",\n\t},\n\t{\n\t\tname: \"Ornith-1.0-9B\",\n\t\tpullRef: \"hf.co/deepreinforce-ai/Ornith-1.0-9B-GGUF:Q4_K_M\",\n\t\trole: \"Agentic-coding worker / router cheap tier\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"router-cheap\",\n\t\trationale:\n\t\t\t\"External candidate (not from pi's own validation research): MIT, Qwen 3.5 base, RL-trained for agentic coding with native tool-calling — the strongest local worker SHAPE in the roster. /fitness on your hardware is the validator.\",\n\t\tnote: \"Q4_K_M ≈ 5.6 GB weights, ~7-8 GB peak with KV — on a 10 GB box run it as the ONLY local model. Qwen 3.5 arch: confirm pi's pinned Ollama supports it and probe tool-calls with /fitness before assigning (template derives from the GGUF). Larger boxes: consider router-medium after a passed worker lane.\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-1.7B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-1.7B-gguf\",\n\t\trole: \"Search scout (heavy-lifter)\",\n\t\ttoolCalling: false,\n\t\trationale:\n\t\t\t\"Ternary weights, very fast; strong at structured search plans. No tool-calling template — use it as a research/worker lane model, never an executor.\",\n\t\tnote: \"Pick a GGUF quant the runtime accepts (e.g. :Q8_0). The ternary Q2_0 build needs prism-ml's patched llama.cpp.\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-4B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-4B-gguf\",\n\t\trole: \"Context curator / reflex brain / lane analyst\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"curator\",\n\t\trationale:\n\t\t\t\"Validated as the 'brain': strict-JSON interpretation and faithful digests. Drives context curation and the toolkit reflex interpreter. Not a tool-caller.\",\n\t\tnote: \"Pick a GGUF quant the runtime accepts (e.g. :Q8_0).\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-8B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-8B-gguf\",\n\t\trole: \"Routing judge (larger machines)\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"judge\",\n\t\trationale:\n\t\t\t\"A judge candidate for machines with more headroom — too slow on ~16GB-class hardware in this research, kept for a bigger box.\",\n\t\tnote: \"Heavy: confirm tok/s with /fitness before committing. Ternary quant may need prism-ml's patched llama.cpp.\",\n\t},\n];\n\n/** Bounded plain-text roster for `/models suggest` and the empty-store hint. */\nexport function formatModelSuggestions(suggestions: readonly ModelSuggestion[] = DEFAULT_MODEL_SUGGESTIONS): string[] {\n\tconst lines = [\n\t\t\"Suggested local models (validated roles from pi's own small-model research; probe on YOUR hardware with /fitness):\",\n\t];\n\tfor (const suggestion of suggestions) {\n\t\tlines.push(\n\t\t\t` - ${suggestion.name} → ${suggestion.role}${suggestion.toolCalling ? \"\" : \" [no tool-calling]\"}`,\n\t\t\t` /models add ${suggestion.pullRef}`,\n\t\t\t` ${suggestion.rationale}`,\n\t\t);\n\t\tif (suggestion.note) lines.push(` note: ${suggestion.note}`);\n\t}\n\tlines.push(\"Add one with /models add <ref> — pi pulls it, probes it, and offers a role in one step.\");\n\treturn lines;\n}\n"]}
1
+ {"version":3,"file":"default-model-suggestions.d.ts","sourceRoot":"","sources":["../../../src/core/models/default-model-suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6DAA6D,CAAC;AAE/F;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC/B,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,oGAAoG;IACpG,WAAW,EAAE,OAAO,CAAC;IACrB,mGAAmG;IACnG,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,yBAAyB,EAAE,SAAS,eAAe,EAwF/D,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,sBAAsB,CAAC,WAAW,GAAE,SAAS,eAAe,EAA8B,GAAG,MAAM,EAAE,CAcpH","sourcesContent":["import type { FitnessRole } from \"../../modes/interactive/components/fitness-role-selector.ts\";\n\n/**\n * Curated local-model suggestions: a starting roster validated during pi-adaptative's own\n * small-model research so a user does not have to know WHICH model fits WHICH role. Each entry is\n * an install ref (accepted by /models add) plus the role it was validated for and whether it can use\n * tools. Native tool-calling remains preferred when it works; text-protocol models are listed only\n * when their runtime is pi-managed and the fitness/probe path can calibrate that fallback.\n *\n * Honesty: these are SUGGESTIONS, not guarantees. Fitness is a property of the model AND the host\n * hardware, so no per-model score is baked in here — `/models add` auto-probes on the actual\n * machine, and the roster only encodes what role each model was SHAPED for. `assignRole` is the\n * /fitness role the suggestion maps to (undefined = no single-setting slot yet; use it as a\n * research/worker lane model).\n */\n\nexport interface ModelSuggestion {\n\t/** Display name in the suggestions list. */\n\tname: string;\n\t/** Ref accepted by /models add and the source normalizer. */\n\tpullRef: string;\n\t/** Role this model was validated/shaped for. */\n\trole: string;\n\t/** True if the model can use tools after host-local probing/calibration (required for executor). */\n\ttoolCalling: boolean;\n\t/** The /fitness assignment target this suggestion maps to, when there is a single-setting slot. */\n\tassignRole?: FitnessRole;\n\t/** One-line rationale from the validation work; never a fabricated numeric score. */\n\trationale: string;\n\t/** Optional caveat (quant selection, RAM needs). */\n\tnote?: string;\n}\n\nexport const DEFAULT_MODEL_SUGGESTIONS: readonly ModelSuggestion[] = [\n\t{\n\t\tname: \"qwen3:1.7b\",\n\t\tpullRef: \"qwen3:1.7b\",\n\t\trole: \"Toolkit executor / reflex muscle\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Validated as the executor: reliable native tool-calling and low latency, so direct toolkit commands run without retries.\",\n\t},\n\t{\n\t\tname: \"qwen3:0.6b\",\n\t\tpullRef: \"qwen3:0.6b\",\n\t\trole: \"Minimal executor (fastest)\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Fastest local option; on harder requests it can narrate without executing, so prefer it only when speed dominates and requests are simple.\",\n\t},\n\t{\n\t\tname: \"MiniCPM5-1B (full-base)\",\n\t\tpullRef: \"hf.co/openbmb/MiniCPM5-1B\",\n\t\trole: \"Full-base Transformers executor / tiny local muscle\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Full-base Hugging Face MiniCPM5-1B target: pi installs an isolated Transformers runtime and probes native tool-calling first, then calibrates the text protocol only if native tool calls do not work.\",\n\t\tnote: \"Not GGUF/quantized: weights and Python deps land under pi-owned runtime/cache directories, separate from Ollama and system Python.\",\n\t},\n\t{\n\t\tname: \"FastContext-1.0-4B\",\n\t\tpullRef: \"hf.co/KikoCis/FastContext-1.0-4B-longctx-imatrix-GGUF:fastcontext4b.Q4_K_M.imx.gguf\",\n\t\trole: \"Repository scout (context_scout)\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"scout\",\n\t\trationale:\n\t\t\t\"The model the context_scout lane is built around (Plan 9): 4B Qwen3, 256K context, trained for read-only repo exploration returning file:line citations. /fitness must pass the tool-calls + research lanes before assignment.\",\n\t\tnote: \"Q4 ≈ 2.5 GB; ~5-6 GB peak with 32-64K KV. Qwen3 template caveat: if tool calls fail the probe, use the Modelfile recipe in docs/scout.md.\",\n\t},\n\t{\n\t\tname: \"Ornith-1.0-9B\",\n\t\tpullRef: \"hf.co/deepreinforce-ai/Ornith-1.0-9B-GGUF:Q4_K_M\",\n\t\trole: \"Agentic-coding worker / router cheap tier\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"router-cheap\",\n\t\trationale:\n\t\t\t\"External candidate (not from pi's own validation research): MIT, Qwen 3.5 base, RL-trained for agentic coding with native tool-calling — the strongest local worker SHAPE in the roster. /fitness on your hardware is the validator.\",\n\t\tnote: \"Q4_K_M ≈ 5.6 GB weights, ~7-8 GB peak with KV — on a 10 GB box run it as the ONLY local model. Qwen 3.5 arch: confirm pi's pinned Ollama supports it and probe tool-calls with /fitness before assigning (template derives from the GGUF). Larger boxes: consider router-medium after a passed worker lane.\",\n\t},\n\t{\n\t\tname: \"Bonsai-4B (GGUF Q1_0)\",\n\t\tpullRef: \"hf.co/prism-ml/Bonsai-4B-gguf:Q1_0\",\n\t\trole: \"Context curator / reflex brain / lighter lane analyst\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"curator\",\n\t\trationale:\n\t\t\t\"Lighter Bonsai lane model for context curation and structured local analysis; the exact Q1_0 artifact keeps /models suggest one-step.\",\n\t\tnote: \"Recommended when Bonsai-8B is too heavy. Pi pulls this exact GGUF through managed Ollama, probes /fitness on the host, then offers the curator role.\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-1.7B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-1.7B-gguf\",\n\t\trole: \"Search scout (heavy-lifter)\",\n\t\ttoolCalling: false,\n\t\trationale:\n\t\t\t\"Ternary weights, very fast; strong at structured search plans. No tool-calling template — use it as a research/worker lane model, never an executor.\",\n\t\tnote: \"Pick a GGUF quant the runtime accepts (e.g. :Q8_0). The ternary Q2_0 build needs prism-ml's patched llama.cpp.\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-4B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-4B-gguf\",\n\t\trole: \"Context curator / reflex brain / lane analyst\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"curator\",\n\t\trationale:\n\t\t\t\"Validated as the 'brain': strict-JSON interpretation and faithful digests. Drives context curation and the toolkit reflex interpreter. Not a tool-caller.\",\n\t\tnote: \"Pick a GGUF quant the runtime accepts (e.g. :Q8_0).\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-8B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-8B-gguf\",\n\t\trole: \"Routing judge (larger machines)\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"judge\",\n\t\trationale:\n\t\t\t\"A judge candidate for machines with more headroom — too slow on ~16GB-class hardware in this research, kept for a bigger box.\",\n\t\tnote: \"Heavy: confirm tok/s with /fitness before committing. Ternary quant may need prism-ml's patched llama.cpp.\",\n\t},\n];\n\n/** Bounded plain-text roster for `/models suggest` and the empty-store hint. */\nexport function formatModelSuggestions(suggestions: readonly ModelSuggestion[] = DEFAULT_MODEL_SUGGESTIONS): string[] {\n\tconst lines = [\n\t\t\"Suggested local models (validated roles from pi's own small-model research; probe on YOUR hardware with /fitness):\",\n\t];\n\tfor (const suggestion of suggestions) {\n\t\tlines.push(\n\t\t\t` - ${suggestion.name} → ${suggestion.role}${suggestion.toolCalling ? \"\" : \" [no tool-calling]\"}`,\n\t\t\t` /models add ${suggestion.pullRef}`,\n\t\t\t` ${suggestion.rationale}`,\n\t\t);\n\t\tif (suggestion.note) lines.push(` note: ${suggestion.note}`);\n\t}\n\tlines.push(\"Add one with /models add <ref> — pi pulls it, probes it, and offers a role in one step.\");\n\treturn lines;\n}\n"]}
@@ -42,6 +42,15 @@ export const DEFAULT_MODEL_SUGGESTIONS = [
42
42
  rationale: "External candidate (not from pi's own validation research): MIT, Qwen 3.5 base, RL-trained for agentic coding with native tool-calling — the strongest local worker SHAPE in the roster. /fitness on your hardware is the validator.",
43
43
  note: "Q4_K_M ≈ 5.6 GB weights, ~7-8 GB peak with KV — on a 10 GB box run it as the ONLY local model. Qwen 3.5 arch: confirm pi's pinned Ollama supports it and probe tool-calls with /fitness before assigning (template derives from the GGUF). Larger boxes: consider router-medium after a passed worker lane.",
44
44
  },
45
+ {
46
+ name: "Bonsai-4B (GGUF Q1_0)",
47
+ pullRef: "hf.co/prism-ml/Bonsai-4B-gguf:Q1_0",
48
+ role: "Context curator / reflex brain / lighter lane analyst",
49
+ toolCalling: false,
50
+ assignRole: "curator",
51
+ rationale: "Lighter Bonsai lane model for context curation and structured local analysis; the exact Q1_0 artifact keeps /models suggest one-step.",
52
+ note: "Recommended when Bonsai-8B is too heavy. Pi pulls this exact GGUF through managed Ollama, probes /fitness on the host, then offers the curator role.",
53
+ },
45
54
  {
46
55
  name: "Ternary-Bonsai-1.7B",
47
56
  pullRef: "hf.co/prism-ml/Ternary-Bonsai-1.7B-gguf",
@@ -1 +1 @@
1
- {"version":3,"file":"default-model-suggestions.js","sourceRoot":"","sources":["../../../src/core/models/default-model-suggestions.ts"],"names":[],"mappings":"AAiCA,MAAM,CAAC,MAAM,yBAAyB,GAA+B;IACpE;QACC,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,YAAY;QACrB,IAAI,EAAE,kCAAkC;QACxC,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,UAAU;QACtB,SAAS,EACR,0HAA0H;KAC3H;IACD;QACC,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,YAAY;QACrB,IAAI,EAAE,4BAA4B;QAClC,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,UAAU;QACtB,SAAS,EACR,4IAA4I;KAC7I;IACD;QACC,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,2BAA2B;QACpC,IAAI,EAAE,qDAAqD;QAC3D,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,UAAU;QACtB,SAAS,EACR,wMAAwM;QACzM,IAAI,EAAE,oIAAoI;KAC1I;IACD;QACC,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,qFAAqF;QAC9F,IAAI,EAAE,kCAAkC;QACxC,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,OAAO;QACnB,SAAS,EACR,gOAAgO;QACjO,IAAI,EAAE,6IAA2I;KACjJ;IACD;QACC,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,kDAAkD;QAC3D,IAAI,EAAE,2CAA2C;QACjD,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,cAAc;QAC1B,SAAS,EACR,wOAAsO;QACvO,IAAI,EAAE,iTAA6S;KACnT;IACD;QACC,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,yCAAyC;QAClD,IAAI,EAAE,6BAA6B;QACnC,WAAW,EAAE,KAAK;QAClB,SAAS,EACR,wJAAsJ;QACvJ,IAAI,EAAE,gHAAgH;KACtH;IACD;QACC,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,uCAAuC;QAChD,IAAI,EAAE,+CAA+C;QACrD,WAAW,EAAE,KAAK;QAClB,UAAU,EAAE,SAAS;QACrB,SAAS,EACR,2JAA2J;QAC5J,IAAI,EAAE,qDAAqD;KAC3D;IACD;QACC,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,uCAAuC;QAChD,IAAI,EAAE,iCAAiC;QACvC,WAAW,EAAE,KAAK;QAClB,UAAU,EAAE,OAAO;QACnB,SAAS,EACR,iIAA+H;QAChI,IAAI,EAAE,4GAA4G;KAClH;CACD,CAAC;AAEF,gFAAgF;AAChF,MAAM,UAAU,sBAAsB,CAAC,WAAW,GAA+B,yBAAyB,EAAY;IACrH,MAAM,KAAK,GAAG;QACb,oHAAoH;KACpH,CAAC;IACF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CACT,OAAO,UAAU,CAAC,IAAI,QAAM,UAAU,CAAC,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,oBAAoB,EAAE,EAClG,qBAAqB,UAAU,CAAC,OAAO,EAAE,EACzC,SAAS,UAAU,CAAC,SAAS,EAAE,CAC/B,CAAC;QACF,IAAI,UAAU,CAAC,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,eAAe,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,2FAAyF,CAAC,CAAC;IACtG,OAAO,KAAK,CAAC;AAAA,CACb","sourcesContent":["import type { FitnessRole } from \"../../modes/interactive/components/fitness-role-selector.ts\";\n\n/**\n * Curated local-model suggestions: a starting roster validated during pi-adaptative's own\n * small-model research so a user does not have to know WHICH model fits WHICH role. Each entry is\n * an install ref (accepted by /models add) plus the role it was validated for and whether it can use\n * tools. Native tool-calling remains preferred when it works; text-protocol models are listed only\n * when their runtime is pi-managed and the fitness/probe path can calibrate that fallback.\n *\n * Honesty: these are SUGGESTIONS, not guarantees. Fitness is a property of the model AND the host\n * hardware, so no per-model score is baked in here — `/models add` auto-probes on the actual\n * machine, and the roster only encodes what role each model was SHAPED for. `assignRole` is the\n * /fitness role the suggestion maps to (undefined = no single-setting slot yet; use it as a\n * research/worker lane model).\n */\n\nexport interface ModelSuggestion {\n\t/** Display name in the suggestions list. */\n\tname: string;\n\t/** Ref accepted by /models add and the source normalizer. */\n\tpullRef: string;\n\t/** Role this model was validated/shaped for. */\n\trole: string;\n\t/** True if the model can use tools after host-local probing/calibration (required for executor). */\n\ttoolCalling: boolean;\n\t/** The /fitness assignment target this suggestion maps to, when there is a single-setting slot. */\n\tassignRole?: FitnessRole;\n\t/** One-line rationale from the validation work; never a fabricated numeric score. */\n\trationale: string;\n\t/** Optional caveat (quant selection, RAM needs). */\n\tnote?: string;\n}\n\nexport const DEFAULT_MODEL_SUGGESTIONS: readonly ModelSuggestion[] = [\n\t{\n\t\tname: \"qwen3:1.7b\",\n\t\tpullRef: \"qwen3:1.7b\",\n\t\trole: \"Toolkit executor / reflex muscle\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Validated as the executor: reliable native tool-calling and low latency, so direct toolkit commands run without retries.\",\n\t},\n\t{\n\t\tname: \"qwen3:0.6b\",\n\t\tpullRef: \"qwen3:0.6b\",\n\t\trole: \"Minimal executor (fastest)\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Fastest local option; on harder requests it can narrate without executing, so prefer it only when speed dominates and requests are simple.\",\n\t},\n\t{\n\t\tname: \"MiniCPM5-1B (full-base)\",\n\t\tpullRef: \"hf.co/openbmb/MiniCPM5-1B\",\n\t\trole: \"Full-base Transformers executor / tiny local muscle\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Full-base Hugging Face MiniCPM5-1B target: pi installs an isolated Transformers runtime and probes native tool-calling first, then calibrates the text protocol only if native tool calls do not work.\",\n\t\tnote: \"Not GGUF/quantized: weights and Python deps land under pi-owned runtime/cache directories, separate from Ollama and system Python.\",\n\t},\n\t{\n\t\tname: \"FastContext-1.0-4B\",\n\t\tpullRef: \"hf.co/KikoCis/FastContext-1.0-4B-longctx-imatrix-GGUF:fastcontext4b.Q4_K_M.imx.gguf\",\n\t\trole: \"Repository scout (context_scout)\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"scout\",\n\t\trationale:\n\t\t\t\"The model the context_scout lane is built around (Plan 9): 4B Qwen3, 256K context, trained for read-only repo exploration returning file:line citations. /fitness must pass the tool-calls + research lanes before assignment.\",\n\t\tnote: \"Q4 ≈ 2.5 GB; ~5-6 GB peak with 32-64K KV. Qwen3 template caveat: if tool calls fail the probe, use the Modelfile recipe in docs/scout.md.\",\n\t},\n\t{\n\t\tname: \"Ornith-1.0-9B\",\n\t\tpullRef: \"hf.co/deepreinforce-ai/Ornith-1.0-9B-GGUF:Q4_K_M\",\n\t\trole: \"Agentic-coding worker / router cheap tier\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"router-cheap\",\n\t\trationale:\n\t\t\t\"External candidate (not from pi's own validation research): MIT, Qwen 3.5 base, RL-trained for agentic coding with native tool-calling — the strongest local worker SHAPE in the roster. /fitness on your hardware is the validator.\",\n\t\tnote: \"Q4_K_M ≈ 5.6 GB weights, ~7-8 GB peak with KV — on a 10 GB box run it as the ONLY local model. Qwen 3.5 arch: confirm pi's pinned Ollama supports it and probe tool-calls with /fitness before assigning (template derives from the GGUF). Larger boxes: consider router-medium after a passed worker lane.\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-1.7B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-1.7B-gguf\",\n\t\trole: \"Search scout (heavy-lifter)\",\n\t\ttoolCalling: false,\n\t\trationale:\n\t\t\t\"Ternary weights, very fast; strong at structured search plans. No tool-calling template — use it as a research/worker lane model, never an executor.\",\n\t\tnote: \"Pick a GGUF quant the runtime accepts (e.g. :Q8_0). The ternary Q2_0 build needs prism-ml's patched llama.cpp.\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-4B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-4B-gguf\",\n\t\trole: \"Context curator / reflex brain / lane analyst\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"curator\",\n\t\trationale:\n\t\t\t\"Validated as the 'brain': strict-JSON interpretation and faithful digests. Drives context curation and the toolkit reflex interpreter. Not a tool-caller.\",\n\t\tnote: \"Pick a GGUF quant the runtime accepts (e.g. :Q8_0).\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-8B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-8B-gguf\",\n\t\trole: \"Routing judge (larger machines)\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"judge\",\n\t\trationale:\n\t\t\t\"A judge candidate for machines with more headroom — too slow on ~16GB-class hardware in this research, kept for a bigger box.\",\n\t\tnote: \"Heavy: confirm tok/s with /fitness before committing. Ternary quant may need prism-ml's patched llama.cpp.\",\n\t},\n];\n\n/** Bounded plain-text roster for `/models suggest` and the empty-store hint. */\nexport function formatModelSuggestions(suggestions: readonly ModelSuggestion[] = DEFAULT_MODEL_SUGGESTIONS): string[] {\n\tconst lines = [\n\t\t\"Suggested local models (validated roles from pi's own small-model research; probe on YOUR hardware with /fitness):\",\n\t];\n\tfor (const suggestion of suggestions) {\n\t\tlines.push(\n\t\t\t` - ${suggestion.name} → ${suggestion.role}${suggestion.toolCalling ? \"\" : \" [no tool-calling]\"}`,\n\t\t\t` /models add ${suggestion.pullRef}`,\n\t\t\t` ${suggestion.rationale}`,\n\t\t);\n\t\tif (suggestion.note) lines.push(` note: ${suggestion.note}`);\n\t}\n\tlines.push(\"Add one with /models add <ref> — pi pulls it, probes it, and offers a role in one step.\");\n\treturn lines;\n}\n"]}
1
+ {"version":3,"file":"default-model-suggestions.js","sourceRoot":"","sources":["../../../src/core/models/default-model-suggestions.ts"],"names":[],"mappings":"AAiCA,MAAM,CAAC,MAAM,yBAAyB,GAA+B;IACpE;QACC,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,YAAY;QACrB,IAAI,EAAE,kCAAkC;QACxC,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,UAAU;QACtB,SAAS,EACR,0HAA0H;KAC3H;IACD;QACC,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,YAAY;QACrB,IAAI,EAAE,4BAA4B;QAClC,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,UAAU;QACtB,SAAS,EACR,4IAA4I;KAC7I;IACD;QACC,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,2BAA2B;QACpC,IAAI,EAAE,qDAAqD;QAC3D,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,UAAU;QACtB,SAAS,EACR,wMAAwM;QACzM,IAAI,EAAE,oIAAoI;KAC1I;IACD;QACC,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,qFAAqF;QAC9F,IAAI,EAAE,kCAAkC;QACxC,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,OAAO;QACnB,SAAS,EACR,gOAAgO;QACjO,IAAI,EAAE,6IAA2I;KACjJ;IACD;QACC,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,kDAAkD;QAC3D,IAAI,EAAE,2CAA2C;QACjD,WAAW,EAAE,IAAI;QACjB,UAAU,EAAE,cAAc;QAC1B,SAAS,EACR,wOAAsO;QACvO,IAAI,EAAE,iTAA6S;KACnT;IACD;QACC,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,oCAAoC;QAC7C,IAAI,EAAE,uDAAuD;QAC7D,WAAW,EAAE,KAAK;QAClB,UAAU,EAAE,SAAS;QACrB,SAAS,EACR,uIAAuI;QACxI,IAAI,EAAE,sJAAsJ;KAC5J;IACD;QACC,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,yCAAyC;QAClD,IAAI,EAAE,6BAA6B;QACnC,WAAW,EAAE,KAAK;QAClB,SAAS,EACR,wJAAsJ;QACvJ,IAAI,EAAE,gHAAgH;KACtH;IACD;QACC,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,uCAAuC;QAChD,IAAI,EAAE,+CAA+C;QACrD,WAAW,EAAE,KAAK;QAClB,UAAU,EAAE,SAAS;QACrB,SAAS,EACR,2JAA2J;QAC5J,IAAI,EAAE,qDAAqD;KAC3D;IACD;QACC,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,uCAAuC;QAChD,IAAI,EAAE,iCAAiC;QACvC,WAAW,EAAE,KAAK;QAClB,UAAU,EAAE,OAAO;QACnB,SAAS,EACR,iIAA+H;QAChI,IAAI,EAAE,4GAA4G;KAClH;CACD,CAAC;AAEF,gFAAgF;AAChF,MAAM,UAAU,sBAAsB,CAAC,WAAW,GAA+B,yBAAyB,EAAY;IACrH,MAAM,KAAK,GAAG;QACb,oHAAoH;KACpH,CAAC;IACF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CACT,OAAO,UAAU,CAAC,IAAI,QAAM,UAAU,CAAC,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,oBAAoB,EAAE,EAClG,qBAAqB,UAAU,CAAC,OAAO,EAAE,EACzC,SAAS,UAAU,CAAC,SAAS,EAAE,CAC/B,CAAC;QACF,IAAI,UAAU,CAAC,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,eAAe,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,2FAAyF,CAAC,CAAC;IACtG,OAAO,KAAK,CAAC;AAAA,CACb","sourcesContent":["import type { FitnessRole } from \"../../modes/interactive/components/fitness-role-selector.ts\";\n\n/**\n * Curated local-model suggestions: a starting roster validated during pi-adaptative's own\n * small-model research so a user does not have to know WHICH model fits WHICH role. Each entry is\n * an install ref (accepted by /models add) plus the role it was validated for and whether it can use\n * tools. Native tool-calling remains preferred when it works; text-protocol models are listed only\n * when their runtime is pi-managed and the fitness/probe path can calibrate that fallback.\n *\n * Honesty: these are SUGGESTIONS, not guarantees. Fitness is a property of the model AND the host\n * hardware, so no per-model score is baked in here — `/models add` auto-probes on the actual\n * machine, and the roster only encodes what role each model was SHAPED for. `assignRole` is the\n * /fitness role the suggestion maps to (undefined = no single-setting slot yet; use it as a\n * research/worker lane model).\n */\n\nexport interface ModelSuggestion {\n\t/** Display name in the suggestions list. */\n\tname: string;\n\t/** Ref accepted by /models add and the source normalizer. */\n\tpullRef: string;\n\t/** Role this model was validated/shaped for. */\n\trole: string;\n\t/** True if the model can use tools after host-local probing/calibration (required for executor). */\n\ttoolCalling: boolean;\n\t/** The /fitness assignment target this suggestion maps to, when there is a single-setting slot. */\n\tassignRole?: FitnessRole;\n\t/** One-line rationale from the validation work; never a fabricated numeric score. */\n\trationale: string;\n\t/** Optional caveat (quant selection, RAM needs). */\n\tnote?: string;\n}\n\nexport const DEFAULT_MODEL_SUGGESTIONS: readonly ModelSuggestion[] = [\n\t{\n\t\tname: \"qwen3:1.7b\",\n\t\tpullRef: \"qwen3:1.7b\",\n\t\trole: \"Toolkit executor / reflex muscle\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Validated as the executor: reliable native tool-calling and low latency, so direct toolkit commands run without retries.\",\n\t},\n\t{\n\t\tname: \"qwen3:0.6b\",\n\t\tpullRef: \"qwen3:0.6b\",\n\t\trole: \"Minimal executor (fastest)\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Fastest local option; on harder requests it can narrate without executing, so prefer it only when speed dominates and requests are simple.\",\n\t},\n\t{\n\t\tname: \"MiniCPM5-1B (full-base)\",\n\t\tpullRef: \"hf.co/openbmb/MiniCPM5-1B\",\n\t\trole: \"Full-base Transformers executor / tiny local muscle\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"executor\",\n\t\trationale:\n\t\t\t\"Full-base Hugging Face MiniCPM5-1B target: pi installs an isolated Transformers runtime and probes native tool-calling first, then calibrates the text protocol only if native tool calls do not work.\",\n\t\tnote: \"Not GGUF/quantized: weights and Python deps land under pi-owned runtime/cache directories, separate from Ollama and system Python.\",\n\t},\n\t{\n\t\tname: \"FastContext-1.0-4B\",\n\t\tpullRef: \"hf.co/KikoCis/FastContext-1.0-4B-longctx-imatrix-GGUF:fastcontext4b.Q4_K_M.imx.gguf\",\n\t\trole: \"Repository scout (context_scout)\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"scout\",\n\t\trationale:\n\t\t\t\"The model the context_scout lane is built around (Plan 9): 4B Qwen3, 256K context, trained for read-only repo exploration returning file:line citations. /fitness must pass the tool-calls + research lanes before assignment.\",\n\t\tnote: \"Q4 ≈ 2.5 GB; ~5-6 GB peak with 32-64K KV. Qwen3 template caveat: if tool calls fail the probe, use the Modelfile recipe in docs/scout.md.\",\n\t},\n\t{\n\t\tname: \"Ornith-1.0-9B\",\n\t\tpullRef: \"hf.co/deepreinforce-ai/Ornith-1.0-9B-GGUF:Q4_K_M\",\n\t\trole: \"Agentic-coding worker / router cheap tier\",\n\t\ttoolCalling: true,\n\t\tassignRole: \"router-cheap\",\n\t\trationale:\n\t\t\t\"External candidate (not from pi's own validation research): MIT, Qwen 3.5 base, RL-trained for agentic coding with native tool-calling — the strongest local worker SHAPE in the roster. /fitness on your hardware is the validator.\",\n\t\tnote: \"Q4_K_M ≈ 5.6 GB weights, ~7-8 GB peak with KV — on a 10 GB box run it as the ONLY local model. Qwen 3.5 arch: confirm pi's pinned Ollama supports it and probe tool-calls with /fitness before assigning (template derives from the GGUF). Larger boxes: consider router-medium after a passed worker lane.\",\n\t},\n\t{\n\t\tname: \"Bonsai-4B (GGUF Q1_0)\",\n\t\tpullRef: \"hf.co/prism-ml/Bonsai-4B-gguf:Q1_0\",\n\t\trole: \"Context curator / reflex brain / lighter lane analyst\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"curator\",\n\t\trationale:\n\t\t\t\"Lighter Bonsai lane model for context curation and structured local analysis; the exact Q1_0 artifact keeps /models suggest one-step.\",\n\t\tnote: \"Recommended when Bonsai-8B is too heavy. Pi pulls this exact GGUF through managed Ollama, probes /fitness on the host, then offers the curator role.\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-1.7B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-1.7B-gguf\",\n\t\trole: \"Search scout (heavy-lifter)\",\n\t\ttoolCalling: false,\n\t\trationale:\n\t\t\t\"Ternary weights, very fast; strong at structured search plans. No tool-calling template — use it as a research/worker lane model, never an executor.\",\n\t\tnote: \"Pick a GGUF quant the runtime accepts (e.g. :Q8_0). The ternary Q2_0 build needs prism-ml's patched llama.cpp.\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-4B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-4B-gguf\",\n\t\trole: \"Context curator / reflex brain / lane analyst\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"curator\",\n\t\trationale:\n\t\t\t\"Validated as the 'brain': strict-JSON interpretation and faithful digests. Drives context curation and the toolkit reflex interpreter. Not a tool-caller.\",\n\t\tnote: \"Pick a GGUF quant the runtime accepts (e.g. :Q8_0).\",\n\t},\n\t{\n\t\tname: \"Ternary-Bonsai-8B\",\n\t\tpullRef: \"hf.co/prism-ml/Ternary-Bonsai-8B-gguf\",\n\t\trole: \"Routing judge (larger machines)\",\n\t\ttoolCalling: false,\n\t\tassignRole: \"judge\",\n\t\trationale:\n\t\t\t\"A judge candidate for machines with more headroom — too slow on ~16GB-class hardware in this research, kept for a bigger box.\",\n\t\tnote: \"Heavy: confirm tok/s with /fitness before committing. Ternary quant may need prism-ml's patched llama.cpp.\",\n\t},\n];\n\n/** Bounded plain-text roster for `/models suggest` and the empty-store hint. */\nexport function formatModelSuggestions(suggestions: readonly ModelSuggestion[] = DEFAULT_MODEL_SUGGESTIONS): string[] {\n\tconst lines = [\n\t\t\"Suggested local models (validated roles from pi's own small-model research; probe on YOUR hardware with /fitness):\",\n\t];\n\tfor (const suggestion of suggestions) {\n\t\tlines.push(\n\t\t\t` - ${suggestion.name} → ${suggestion.role}${suggestion.toolCalling ? \"\" : \" [no tool-calling]\"}`,\n\t\t\t` /models add ${suggestion.pullRef}`,\n\t\t\t` ${suggestion.rationale}`,\n\t\t);\n\t\tif (suggestion.note) lines.push(` note: ${suggestion.note}`);\n\t}\n\tlines.push(\"Add one with /models add <ref> — pi pulls it, probes it, and offers a role in one step.\");\n\treturn lines;\n}\n"]}
@@ -25,6 +25,9 @@ export interface InstalledLocalModel {
25
25
  name: string;
26
26
  sizeBytes: number;
27
27
  }
28
+ export interface OllamaModelInfo {
29
+ modelInfo: Record<string, unknown>;
30
+ }
28
31
  /** Pinned ollama release for the managed installer below. Bump here when needed — one constant. */
29
32
  export declare const OLLAMA_PINNED_VERSION = "0.31.1";
30
33
  export type OllamaAssetKind = "tar-zst" | "tar-gz" | "zip";
@@ -161,6 +164,22 @@ export declare class OllamaRuntime {
161
164
  stopped: boolean;
162
165
  };
163
166
  list(): Promise<InstalledLocalModel[]>;
167
+ show(ref: string): Promise<{
168
+ ok: true;
169
+ info: OllamaModelInfo;
170
+ } | {
171
+ ok: false;
172
+ error: string;
173
+ }>;
174
+ createFromModelfile(args: {
175
+ name: string;
176
+ modelfile: string;
177
+ }): Promise<{
178
+ ok: true;
179
+ } | {
180
+ ok: false;
181
+ error: string;
182
+ }>;
164
183
  /** Pull a model through the server API (weights land in the SERVER's models dir). */
165
184
  pull(ref: string, onProgress?: (status: string) => void): Promise<{
166
185
  ok: boolean;