diff --git a/Assets/assistent.js b/Assets/assistent.js
index 5090a8c..0925fc2 100644
--- a/Assets/assistent.js
+++ b/Assets/assistent.js
@@ -1,6 +1,6 @@
/**
* Swarm Assistent — Krea 2 collaborative chat (Ollama via Swarm API).
- * v0.7.0: Config presets, persona folders, vector memory, chat|memory model roles.
+ * v0.7.0: Config presets, persona folders, exact KV + vector memory, chat|memory model roles.
*/
(function () {
const LS_BASE = 'swarm_assistent_base_url';
@@ -99,6 +99,9 @@
history: [],
packsLoaded: false,
config: null,
+ exact: null,
+ sessionExact: {},
+ lastUserParamIntent: false,
enabledSkills: [],
kreaProfiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 }, raw: { steps: 28, cfg: 4.5 } },
preferredEmbed: null,
@@ -424,6 +427,158 @@
el.dispatchEvent(new Event('change', { bubbles: true }));
}
+ function isEmptyParamField(raw, { treatZeroEmpty = false } = {}) {
+ if (raw == null) {
+ return true;
+ }
+ const s = String(raw).trim();
+ if (s === '') {
+ return true;
+ }
+ if (treatZeroEmpty && (s === '0' || Number(s) === 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ function userTextMentionsParams(text) {
+ return /\b(steps?|cfg|seed|sigma|размер|aspect|ширин|высот|resolution|batch|турбо|turbo|raw)\b/i.test(String(text || ''));
+ }
+
+ function applyAspectTableFrom(obj) {
+ if (!obj || typeof obj !== 'object') {
+ return false;
+ }
+ const next = {};
+ for (const [k, v] of Object.entries(obj)) {
+ if (Array.isArray(v) && v.length >= 2) {
+ next[k] = [Number(v[0]), Number(v[1])];
+ }
+ }
+ if (!Object.keys(next).length) {
+ return false;
+ }
+ ASPECT_TABLE = next;
+ return true;
+ }
+
+ function resolveExactBundle() {
+ const exact = state.exact || state.config?.exact || {};
+ const profiles = exact.profiles || state.kreaProfiles || {};
+ return { exact, profiles };
+ }
+
+ function detectKreaProfileName() {
+ try {
+ const model = resolveCurrentCheckpoint();
+ const blob = `${model?.name || ''} ${model?.title || ''}`.toLowerCase();
+ const hasRaw = /\braw\b/.test(blob);
+ const hasTurbo = /\bturbo\b/.test(blob);
+ return hasRaw && !hasTurbo ? 'raw' : 'turbo';
+ } catch (e) {
+ return (state.exact?.generation?.profile) || 'turbo';
+ }
+ }
+
+ function mergedGenerationDefaults(profileName) {
+ const { exact, profiles } = resolveExactBundle();
+ const gen = exact.generation && typeof exact.generation === 'object' ? { ...exact.generation } : {};
+ const profile = profileName || gen.profile || detectKreaProfileName();
+ const fromProfile = profiles[profile] && typeof profiles[profile] === 'object' ? { ...profiles[profile] } : {};
+ const session = state.sessionExact && typeof state.sessionExact === 'object' ? { ...state.sessionExact } : {};
+ // Profile (turbo/raw) overrides generation defaults; session overrides both.
+ return { ...gen, ...fromProfile, profile, ...session };
+ }
+
+ function exactDefaultFor(key, profileName) {
+ const { exact, profiles } = resolveExactBundle();
+ const profile = profileName || exact.generation?.profile || detectKreaProfileName();
+ const fromProfile = profiles[profile]?.[key];
+ if (fromProfile != null) {
+ return fromProfile;
+ }
+ return exact.generation?.[key];
+ }
+
+ function rememberSessionExact(partial) {
+ if (!partial || typeof partial !== 'object') {
+ return;
+ }
+ const keys = ['steps', 'cfg', 'sigma_shift', 'aspect', 'width', 'height', 'images', 'batch', 'seed', 'sampler', 'scheduler'];
+ for (const k of keys) {
+ if (partial[k] != null) {
+ state.sessionExact[k] = partial[k];
+ }
+ }
+ if (partial.images == null && partial.batch != null) {
+ state.sessionExact.images = partial.batch;
+ }
+ }
+
+ function fillEmptyParamsFromExact() {
+ const defaults = mergedGenerationDefaults();
+ if (isEmptyParamField(val('input_steps'), { treatZeroEmpty: true }) && defaults.steps != null) {
+ setVal('input_steps', String(defaults.steps));
+ }
+ const cfgRaw = val('input_cfgscale') || val('input_cfg');
+ if (isEmptyParamField(cfgRaw, { treatZeroEmpty: true }) && defaults.cfg != null) {
+ if (document.getElementById('input_cfgscale')) {
+ setVal('input_cfgscale', String(defaults.cfg));
+ } else if (document.getElementById('input_cfg')) {
+ setVal('input_cfg', String(defaults.cfg));
+ }
+ }
+ if (isEmptyParamField(val('input_sigmashift')) && defaults.sigma_shift != null) {
+ setVal('input_sigmashift', String(defaults.sigma_shift));
+ }
+ const wEmpty = isEmptyParamField(val('input_width'), { treatZeroEmpty: true });
+ const hEmpty = isEmptyParamField(val('input_height'), { treatZeroEmpty: true });
+ if ((wEmpty || hEmpty) && defaults.aspect) {
+ const size = sizeFromAspect(defaults.aspect);
+ if (size) {
+ if (wEmpty) {
+ setVal('input_width', String(size[0]));
+ }
+ if (hEmpty) {
+ setVal('input_height', String(size[1]));
+ }
+ }
+ } else {
+ if (wEmpty && defaults.width != null) {
+ setVal('input_width', String(defaults.width));
+ }
+ if (hEmpty && defaults.height != null) {
+ setVal('input_height', String(defaults.height));
+ }
+ }
+ const batchId = document.getElementById('input_images') ? 'input_images' : (document.getElementById('input_batchsize') ? 'input_batchsize' : null);
+ if (batchId && isEmptyParamField(val(batchId), { treatZeroEmpty: true })) {
+ const batch = defaults.images != null ? defaults.images : defaults.batch;
+ if (batch != null) {
+ setVal(batchId, String(batch));
+ }
+ }
+ }
+
+ function shouldSkipSessionRollback(key, patchValue) {
+ if (state.lastUserParamIntent) {
+ return false;
+ }
+ if (state.sessionExact[key] == null) {
+ return false;
+ }
+ const sessionVal = state.sessionExact[key];
+ if (String(sessionVal) === String(patchValue)) {
+ return false;
+ }
+ const exactVal = exactDefaultFor(key);
+ if (exactVal == null) {
+ return false;
+ }
+ // Model trying to restore file exact while session override differs — keep session.
+ return String(patchValue) === String(exactVal);
+ }
+
function openAssistentTab() {
const tab = document.getElementById(TAB_BUTTON_ID);
if (tab) {
@@ -1143,6 +1298,8 @@
function onPersonaChanged() {
const id = $('sa_persona')?.value || 'neutral';
+ state.sessionExact = {};
+ state.lastUserParamIntent = false;
saveSettings();
loadConfig(id, (data) => {
const title = data?.personas?.find((p) => p.id === id)?.title
@@ -1159,6 +1316,7 @@
$('sa_pack').value = packId;
}
}
+ fillEmptyParamsFromExact();
});
}
@@ -1480,15 +1638,32 @@
const blob = `${model.name || ''} ${model.title || ''}`.toLowerCase();
const hasRaw = /\braw\b/.test(blob);
const hasTurbo = /\bturbo\b/.test(blob);
- ctx.krea_profile = hasRaw && !hasTurbo ? 'raw' : 'turbo';
- ctx.recommended_params = ctx.krea_profile === 'raw'
- ? { steps: 28, cfg: 4.5 }
- : { steps: 8, cfg: 1, sigma_shift: 1.15 };
+ const profile = hasRaw && !hasTurbo ? 'raw' : 'turbo';
+ ctx.krea_profile = profile;
+ const defaults = mergedGenerationDefaults(profile);
+ ctx.recommended_params = {
+ steps: defaults.steps ?? 8,
+ cfg: defaults.cfg ?? 1,
+ sigma_shift: defaults.sigma_shift ?? 1.15,
+ };
+ if (defaults.aspect) {
+ ctx.recommended_params.aspect = defaults.aspect;
+ }
} catch (e) {
ctx.krea_profile = 'turbo';
- ctx.recommended_params = { steps: 8, cfg: 1, sigma_shift: 1.15 };
+ const defaults = mergedGenerationDefaults('turbo');
+ ctx.recommended_params = {
+ steps: defaults.steps ?? 8,
+ cfg: defaults.cfg ?? 1,
+ sigma_shift: defaults.sigma_shift ?? 1.15,
+ };
}
+ ctx.exact = state.exact || state.config?.exact || null;
+ ctx.session_exact = state.sessionExact && Object.keys(state.sessionExact).length
+ ? { ...state.sessionExact }
+ : {};
+
return ctx;
}
@@ -1901,7 +2076,7 @@
if (/\b(inpaint|замажь|закрась|руки|лицо|маск|mask|img2img|init\s*image)\b/i.test(t)) {
return 'inpaint_edit';
}
- if (/\b(steps?|cfg|seed|sigma|размер|aspect|ширин|высот|resolution|batch)\b/i.test(t)) {
+ if (userTextMentionsParams(t)) {
return 'fix_params';
}
if (/\b(сцен|moodboard|атмосфер|compose|scene|мизансцен)\b/i.test(t)) {
@@ -2010,27 +2185,68 @@
}
if (doParams) {
+ const defaults = mergedGenerationDefaults();
+ const capture = state.lastUserParamIntent;
const aspectSize = sizeFromAspect(patch.aspect);
- if (aspectSize) {
- setVal('input_width', String(aspectSize[0]));
- setVal('input_height', String(aspectSize[1]));
+ if (patch.aspect != null && !shouldSkipSessionRollback('aspect', patch.aspect)) {
+ if (aspectSize) {
+ setVal('input_width', String(aspectSize[0]));
+ setVal('input_height', String(aspectSize[1]));
+ }
+ if (capture) {
+ rememberSessionExact({ aspect: patch.aspect });
+ }
+ } else if (patch.aspect == null && isEmptyParamField(val('input_width'), { treatZeroEmpty: true })
+ && isEmptyParamField(val('input_height'), { treatZeroEmpty: true }) && defaults.aspect) {
+ const fill = sizeFromAspect(defaults.aspect);
+ if (fill) {
+ setVal('input_width', String(fill[0]));
+ setVal('input_height', String(fill[1]));
+ }
} else {
- if (patch.width != null) {
+ if (patch.width != null && !shouldSkipSessionRollback('width', patch.width)) {
setVal('input_width', String(patch.width));
+ if (capture) {
+ rememberSessionExact({ width: patch.width });
+ }
+ } else if (patch.width == null && isEmptyParamField(val('input_width'), { treatZeroEmpty: true }) && defaults.width != null) {
+ setVal('input_width', String(defaults.width));
}
- if (patch.height != null) {
+ if (patch.height != null && !shouldSkipSessionRollback('height', patch.height)) {
setVal('input_height', String(patch.height));
+ if (capture) {
+ rememberSessionExact({ height: patch.height });
+ }
+ } else if (patch.height == null && isEmptyParamField(val('input_height'), { treatZeroEmpty: true }) && defaults.height != null) {
+ setVal('input_height', String(defaults.height));
}
}
- if (patch.steps != null) {
+ if (patch.steps != null && !shouldSkipSessionRollback('steps', patch.steps)) {
setVal('input_steps', String(patch.steps));
+ if (capture) {
+ rememberSessionExact({ steps: patch.steps });
+ }
+ } else if (patch.steps == null && isEmptyParamField(val('input_steps'), { treatZeroEmpty: true }) && defaults.steps != null) {
+ setVal('input_steps', String(defaults.steps));
}
- if (patch.cfg != null) {
+ if (patch.cfg != null && !shouldSkipSessionRollback('cfg', patch.cfg)) {
if (document.getElementById('input_cfgscale')) {
setVal('input_cfgscale', String(patch.cfg));
} else {
setVal('input_cfg', String(patch.cfg));
}
+ if (capture) {
+ rememberSessionExact({ cfg: patch.cfg });
+ }
+ } else if (patch.cfg == null) {
+ const cfgRaw = val('input_cfgscale') || val('input_cfg');
+ if (isEmptyParamField(cfgRaw, { treatZeroEmpty: true }) && defaults.cfg != null) {
+ if (document.getElementById('input_cfgscale')) {
+ setVal('input_cfgscale', String(defaults.cfg));
+ } else if (document.getElementById('input_cfg')) {
+ setVal('input_cfg', String(defaults.cfg));
+ }
+ }
}
if (patch.vary === true) {
setVal('input_seed', '-1');
@@ -2039,27 +2255,50 @@
if (cur && String(cur) !== '-1') {
setVal('input_seed', cur);
}
- } else if (patch.seed != null) {
+ } else if (patch.seed != null && !shouldSkipSessionRollback('seed', patch.seed)) {
setVal('input_seed', String(patch.seed));
+ if (capture) {
+ rememberSessionExact({ seed: patch.seed });
+ }
}
- if (patch.sigma_shift != null) {
+ if (patch.sigma_shift != null && !shouldSkipSessionRollback('sigma_shift', patch.sigma_shift)) {
setVal('input_sigmashift', String(patch.sigma_shift));
+ if (capture) {
+ rememberSessionExact({ sigma_shift: patch.sigma_shift });
+ }
+ } else if (patch.sigma_shift == null && isEmptyParamField(val('input_sigmashift')) && defaults.sigma_shift != null) {
+ setVal('input_sigmashift', String(defaults.sigma_shift));
}
if (patch.sampler != null) {
if (document.getElementById('input_sampler')) {
setVal('input_sampler', String(patch.sampler));
}
+ if (capture) {
+ rememberSessionExact({ sampler: patch.sampler });
+ }
}
if (patch.scheduler != null && document.getElementById('input_scheduler')) {
setVal('input_scheduler', String(patch.scheduler));
+ if (capture) {
+ rememberSessionExact({ scheduler: patch.scheduler });
+ }
}
const batch = patch.images != null ? patch.images : patch.batch;
- if (batch != null) {
+ if (batch != null && !shouldSkipSessionRollback('images', batch)) {
if (document.getElementById('input_images')) {
setVal('input_images', String(batch));
} else if (document.getElementById('input_batchsize')) {
setVal('input_batchsize', String(batch));
}
+ if (capture) {
+ rememberSessionExact({ images: batch });
+ }
+ } else if (batch == null) {
+ const batchId = document.getElementById('input_images') ? 'input_images' : (document.getElementById('input_batchsize') ? 'input_batchsize' : null);
+ const defBatch = defaults.images != null ? defaults.images : defaults.batch;
+ if (batchId && isEmptyParamField(val(batchId), { treatZeroEmpty: true }) && defBatch != null) {
+ setVal(batchId, String(defBatch));
+ }
}
}
@@ -2835,19 +3074,16 @@
return;
}
state.config = data;
- if (data.model?.aspect_table && typeof data.model.aspect_table === 'object') {
- const next = {};
- for (const [k, v] of Object.entries(data.model.aspect_table)) {
- if (Array.isArray(v) && v.length >= 2) {
- next[k] = [Number(v[0]), Number(v[1])];
- }
- }
- if (Object.keys(next).length) {
- ASPECT_TABLE = next;
- }
+ if (data.exact && typeof data.exact === 'object') {
+ state.exact = data.exact;
}
- if (data.model?.profiles) {
- state.kreaProfiles = data.model.profiles;
+ const aspectSource = data.exact?.aspect_table || data.model?.aspect_table;
+ if (aspectSource && typeof aspectSource === 'object') {
+ applyAspectTableFrom(aspectSource);
+ }
+ const profileSource = data.exact?.profiles || data.model?.profiles;
+ if (profileSource && typeof profileSource === 'object') {
+ state.kreaProfiles = profileSource;
}
if (data.ui?.pack_aliases) {
PACK_ALIASES = { ...PACK_ALIASES, ...data.ui.pack_aliases };
@@ -2879,6 +3115,9 @@
if (data.assistant?.embed_model && !state.preferredEmbed) {
state.preferredEmbed = data.assistant.embed_model;
}
+ if (applyDefaults || data.exact) {
+ fillEmptyParamsFromExact();
+ }
}
function renderPersonaOptions(personas, selected) {
@@ -3958,7 +4197,10 @@
if (!Array.isArray(withActions.actions) && patchHasGenTrigger(withActions)) {
withActions.actions = ['generate'];
}
+ const prevIntent = state.lastUserParamIntent;
+ state.lastUserParamIntent = true;
await applyPatch(withActions, 'all');
+ state.lastUserParamIntent = prevIntent;
setStatus(note || 'Applied');
if ($('sa_auto_generate')?.checked) {
await runGenerateFromPatch(withActions);
@@ -4209,6 +4451,9 @@
if (!text) {
return;
}
+ if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop) {
+ state.lastUserParamIntent = userTextMentionsParams(text);
+ }
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.skipSlash) {
if (rawInput.startsWith('/')) {
@@ -4745,6 +4990,8 @@
state.visionHopUsed = false;
state.packUserTouched = false;
state.pendingPersonaNote = null;
+ state.sessionExact = {};
+ state.lastUserParamIntent = false;
clearPersistedHistory();
const box = $('sa_messages');
if (box) {
@@ -4786,11 +5033,11 @@
} else if (vary) {
await applyQuickPatch({ vary: true, seed: -1, actions: ['generate'] }, 'Vary');
} else if (profile === 'turbo') {
- const p = state.kreaProfiles?.turbo || { steps: 8, cfg: 1, sigma_shift: 1.15 };
+ const p = state.kreaProfiles?.turbo || mergedGenerationDefaults('turbo');
await applyQuickPatch({ steps: p.steps ?? 8, cfg: p.cfg ?? 1, sigma_shift: p.sigma_shift ?? 1.15, actions: ['generate'] }, 'Turbo');
} else if (profile === 'raw') {
- const p = state.kreaProfiles?.raw || { steps: 28, cfg: 4.5 };
- await applyQuickPatch({ steps: p.steps ?? 28, cfg: p.cfg ?? 4.5, actions: ['generate'] }, 'RAW');
+ const p = state.kreaProfiles?.raw || mergedGenerationDefaults('raw');
+ await applyQuickPatch({ steps: p.steps ?? 28, cfg: p.cfg ?? 4.5, sigma_shift: p.sigma_shift, actions: ['generate'] }, 'RAW');
}
renderLoraChips();
});
diff --git a/AssistentConfig.cs b/AssistentConfig.cs
index ea862ce..138e903 100644
--- a/AssistentConfig.cs
+++ b/AssistentConfig.cs
@@ -319,6 +319,9 @@ public sealed class AssistentConfig
public JObject LoadUi(string personaId) => MergeJsonLayers("ui.json", LayerRoots(personaId));
+ /// Exact (KV) memory: bundled base → persona overlays → disk overlays. Persona keys overwrite base.
+ public JObject LoadExact(string personaId) => MergeJsonLayers("exact.json", LayerRoots(personaId));
+
public JObject LoadModelProfile(string personaId)
{
JObject assistant = LoadAssistant(personaId);
@@ -712,6 +715,7 @@ public sealed class AssistentConfig
JObject assistant = LoadAssistant(id);
JObject ui = LoadUi(id);
JObject model = LoadModelProfile(id);
+ JObject exact = LoadExact(id);
var packs = ListPacks(id);
var skills = ListSkills(id);
var personas = ListPersonaCatalog();
@@ -724,6 +728,7 @@ public sealed class AssistentConfig
["assistant"] = assistant,
["ui"] = ui,
["model"] = model,
+ ["exact"] = exact,
["packs"] = new JArray(packs.Select(p => new JObject
{
["id"] = p.id,
diff --git a/Config/_base/assistant.json b/Config/_base/assistant.json
index 07f5820..690eb02 100644
--- a/Config/_base/assistant.json
+++ b/Config/_base/assistant.json
@@ -10,7 +10,7 @@
"default_persona": "neutral",
"embed_model": "nomic-embed-text",
"memory_top_k": 10,
- "seed_version": 1,
+ "seed_version": 2,
"gate": {
"architecture": "krea2",
"keywords": ["krea"]
diff --git a/Config/_base/core/core.md b/Config/_base/core/core.md
index 1bd5757..9e4c685 100644
--- a/Config/_base/core/core.md
+++ b/Config/_base/core/core.md
@@ -2,13 +2,28 @@
You are **Swarm Assistent**, a collaborative art director for image generation inside SwarmUI.
+## Priority (mandatory)
+
+When instructions conflict, apply this order (highest wins):
+
+1. **This core contract** — output format, never invent LoRA/checkpoint names, never use CFG 0.
+2. **Current user message** — explicit “use steps 20 / aspect 16:9 now” wins for that turn.
+3. **Live `session_exact`** — prior user overrides this chat (until persona change / clear chat).
+4. **Exact memory** (`## Exact memory` JSON) — canonical defaults (steps/CFG/aspect/facts). Persona overlays are already merged into it.
+5. **Filled live SwarmUI fields** — respect what is already set unless the user or pack asks to change.
+6. **`memory_hits` (vector RAG)** — notes, pitfalls, LoRA blurbs. Never override exact numbers or the user’s param request.
+7. Guesses — last resort only.
+
+Exact = encyclopedia of defaults. RAG = soft notes. Do **not** re-emit `steps` / `cfg` / `sigma_shift` / `aspect` when they already match exact (or session_exact) and the user did not ask to change them.
+
## Live context
A JSON block named "Live SwarmUI context" is attached. Treat it as ground truth — refreshed every chat turn:
- Use only LoRAs listed in `available_loras` / `enabled_loras` (exact `name`), or Civitai search candidates.
- Prefer listed `trigger_phrase` / `triggers` — **never invent** trigger words.
-- `memory_hits` are retrieved facts (model knowledge, LoRA notes, pitfalls). Trust them over guesses.
+- `exact` / `session_exact` / `recommended_params` — generation defaults; see Priority.
+- `memory_hits` are retrieved notes (LoRA tips, pitfalls). Trust them over guesses, but **not** over exact or the user.
- `model_cards` for **enabled** models beat generic blurbs — follow `when` / `avoid` / `prompt_hint` / `triggers`.
- `taste_profile` is the user's remembered preferences — bias toward it unless they override.
- Prefer `krea_likely` / Krea architecture entries; ignore FLUX/SDXL LoRAs.
@@ -67,6 +82,7 @@ A JSON block named "Live SwarmUI context" is attached. Treat it as ground truth
### Patch rules
- Omit keys you are not changing.
+- Prefer omitting `steps` / `cfg` / `sigma_shift` / `aspect` when they already match Exact memory (or `session_exact`) and the user did not request a change — the UI fills empties from exact.
- `loras` replaces the intended LoRA set for Apply (list all that should be on).
- Prefer `aspect` over raw width/height when framing changes.
- `vary: true` — new random seed. `lock_seed: true` — reuse current seed.
diff --git a/Config/_base/exact.json b/Config/_base/exact.json
new file mode 100644
index 0000000..9e55a90
--- /dev/null
+++ b/Config/_base/exact.json
@@ -0,0 +1,38 @@
+{
+ "generation": {
+ "profile": "turbo",
+ "steps": 8,
+ "cfg": 1,
+ "sigma_shift": 1.15,
+ "images": 1
+ },
+ "profiles": {
+ "turbo": {
+ "steps": 8,
+ "cfg": 1,
+ "sigma_shift": 1.15
+ },
+ "raw": {
+ "steps": 28,
+ "cfg": 4.5,
+ "sigma_shift": 1.15
+ }
+ },
+ "aspect_table": {
+ "1:1": [1024, 1024],
+ "4:3": [1184, 896],
+ "3:2": [1248, 832],
+ "16:9": [1376, 768],
+ "2.35:1": [1568, 672],
+ "4:5": [928, 1152],
+ "2:3": [832, 1248],
+ "9:16": [768, 1376]
+ },
+ "facts": {
+ "architecture": "Krea 2 is a 12B DiT architecture. Not FLUX, not SDXL, not FLUX.1-Krea. Text encoder: Qwen3-VL 4B. VAE: Qwen Image VAE. Use only Krea2-trained LoRAs — never suggest FLUX/SDXL LoRAs.",
+ "negatives": "Negative prompts are nearly useless with Qwen3-VL. Prefer positives (sharp focus, empty street) over no blur / no people. Built-in NSFW text-refiner may strip risque words; LoRAs/finetunes may restore — stay practical.",
+ "prompt_images": "Prompt Images (refs in the prompt box) often overpower text — use sparingly and warn. Init Image = structure (img2img). Mask = local fix. They are not interchangeable. Cloud-only features (moodboards, Generative Sliders) are not in Swarm — emulate with prompt language + board refs.",
+ "turbo": "Krea 2 Turbo defaults live in exact.generation / exact.profiles.turbo. Never use CFG 0 — broken output. Side ~1024 (128–4096 OK).",
+ "raw": "Krea 2 RAW/Base: prefer exact.profiles.raw when checkpoint name/title looks like RAW (not Turbo). If a turbo LoRA exists, weight ~0.6 for photoreal (1.0 ≈ full turbo). Swarm Generate cannot run dual-sampler Comfy graphs — only suggest LoRA weight + steps/CFG the UI can set."
+ }
+}
diff --git a/Config/_base/memory-seed/aspect.json b/Config/_base/memory-seed/aspect.json
index 8ff9792..fb404a8 100644
--- a/Config/_base/memory-seed/aspect.json
+++ b/Config/_base/memory-seed/aspect.json
@@ -1,50 +1,8 @@
[
{
"kind": "aspect",
- "key": "1:1",
+ "key": "table",
"tags": ["aspect", "1k"],
- "text": "Aspect 1:1 maps to 1024×1024 on the official Krea 1K table. Prefer patch field aspect over raw width/height."
- },
- {
- "kind": "aspect",
- "key": "4:3",
- "tags": ["aspect", "1k"],
- "text": "Aspect 4:3 maps to 1184×896."
- },
- {
- "kind": "aspect",
- "key": "3:2",
- "tags": ["aspect", "1k"],
- "text": "Aspect 3:2 maps to 1248×832."
- },
- {
- "kind": "aspect",
- "key": "16:9",
- "tags": ["aspect", "1k", "widescreen"],
- "text": "Aspect 16:9 maps to 1376×768."
- },
- {
- "kind": "aspect",
- "key": "2.35:1",
- "tags": ["aspect", "1k", "cinematic"],
- "text": "Aspect 2.35:1 (cinematic ultrawide) maps to 1568×672."
- },
- {
- "kind": "aspect",
- "key": "4:5",
- "tags": ["aspect", "1k", "portrait"],
- "text": "Aspect 4:5 maps to 928×1152 — good for portrait."
- },
- {
- "kind": "aspect",
- "key": "2:3",
- "tags": ["aspect", "1k", "portrait"],
- "text": "Aspect 2:3 maps to 832×1248."
- },
- {
- "kind": "aspect",
- "key": "9:16",
- "tags": ["aspect", "1k", "stories"],
- "text": "Aspect 9:16 maps to 768×1376 — vertical / stories."
+ "text": "Official Krea 1K aspect → width/height table lives in Exact memory aspect_table. Prefer patch field aspect over raw width/height. UI fills empties from Exact."
}
]
diff --git a/Config/_base/memory-seed/krea_facts.json b/Config/_base/memory-seed/krea_facts.json
index 4529f7d..977029b 100644
--- a/Config/_base/memory-seed/krea_facts.json
+++ b/Config/_base/memory-seed/krea_facts.json
@@ -3,30 +3,30 @@
"kind": "model",
"key": "krea2_architecture",
"tags": ["krea", "architecture"],
- "text": "Krea 2 is a 12B DiT architecture. Not FLUX, not SDXL, not FLUX.1-Krea. Text encoder: Qwen3-VL 4B. VAE: Qwen Image VAE. Use only Krea2-trained LoRAs — never suggest FLUX/SDXL LoRAs."
+ "text": "Krea 2 architecture facts live in Exact memory (exact.facts.architecture). Prefer Exact KV over this note. Never suggest FLUX/SDXL LoRAs."
},
{
"kind": "model",
"key": "krea2_turbo",
"tags": ["krea", "turbo", "params"],
- "text": "Krea 2 Turbo defaults: steps 8 (min 4), CFG 1 (never CFG 0 — broken output), sigma shift 1.15, side ~1024 (128–4096 OK)."
+ "text": "Turbo numeric defaults (steps/CFG/sigma) live in Exact memory profiles.turbo / generation — do not invent numbers; never use CFG 0."
},
{
"kind": "model",
"key": "krea2_raw",
"tags": ["krea", "raw", "params"],
- "text": "Krea 2 RAW/Base: steps ~20–52, CFG ~4–4.5. If checkpoint name/title looks like RAW (not Turbo), prefer RAW settings. If a turbo LoRA exists, weight ~0.6 for photoreal (1.0 ≈ full turbo). Swarm Generate cannot run dual-sampler Comfy graphs — only suggest LoRA weight + steps/CFG the UI can set."
+ "text": "RAW numeric defaults live in Exact memory profiles.raw. If checkpoint looks like RAW (not Turbo), use that profile. Turbo LoRA weight ~0.6 for photoreal when needed."
},
{
"kind": "model",
"key": "krea2_negatives",
"tags": ["krea", "prompting"],
- "text": "Negative prompts are nearly useless with Qwen3-VL. Prefer positives (sharp focus, empty street) over no blur / no people. Built-in NSFW text-refiner may strip risque words; LoRAs/finetunes may restore — stay practical."
+ "text": "See Exact memory facts.negatives: negatives are nearly useless with Qwen3-VL — prefer positive phrasing."
},
{
"kind": "model",
"key": "krea2_prompt_images",
"tags": ["krea", "board"],
- "text": "Prompt Images (refs in the prompt box) often overpower text — use sparingly and warn. Init Image = structure (img2img). Mask = local fix. They are not interchangeable. Cloud-only features (moodboards, Generative Sliders) are not in Swarm — emulate with prompt language + board refs."
+ "text": "See Exact memory facts.prompt_images: Prompt Images overpower text; Init ≠ Mask ≠ Prompt Images."
}
]
diff --git a/Config/_base/models/krea2.json b/Config/_base/models/krea2.json
index 36405cb..dc9749c 100644
--- a/Config/_base/models/krea2.json
+++ b/Config/_base/models/krea2.json
@@ -1,26 +1,4 @@
{
"id": "krea2",
- "gate_keywords": ["krea"],
- "profiles": {
- "turbo": {
- "steps": 8,
- "cfg": 1,
- "sigma_shift": 1.15
- },
- "raw": {
- "steps": 28,
- "cfg": 4.5,
- "sigma_shift": 1.15
- }
- },
- "aspect_table": {
- "1:1": [1024, 1024],
- "4:3": [1184, 896],
- "3:2": [1248, 832],
- "16:9": [1376, 768],
- "2.35:1": [1568, 672],
- "4:5": [928, 1152],
- "2:3": [832, 1248],
- "9:16": [768, 1376]
- }
+ "gate_keywords": ["krea"]
}
diff --git a/Config/_base/packs/fix_params.md b/Config/_base/packs/fix_params.md
index fdb442c..e27f3ca 100644
--- a/Config/_base/packs/fix_params.md
+++ b/Config/_base/packs/fix_params.md
@@ -4,16 +4,16 @@ Goal: adjust **generation parameters** for Krea 2 Turbo (or RAW if context says
## Guidelines
-- **Turbo:** steps 4–12 (default **8**), CFG **1** (never 0), sigma shift ~**1.15**.
-- **RAW/base:** steps 20–52, CFG ~4–4.5 — only if checkpoint/context indicates Raw. If a turbo-distill LoRA is available, weight **0.6** is the usual photoreal compromise (UI LoRA only — no dual-sampler).
-- Prefer live context field `krea_profile` (`turbo` | `raw`) and `recommended_params` when present.
-- **Aspect:** prefer patch field `aspect` (`1:1`, `4:5`, `2:3`, `16:9`, `9:16`, `4:3`, `3:2`, `2.35:1`) — UI maps to official 1K sizes. Else set width/height near 1024.
+- Prefer **Exact memory** (`profiles.turbo` / `profiles.raw`), live `recommended_params`, and `session_exact` over invented numbers. Never CFG 0.
+- Prefer live context field `krea_profile` (`turbo` | `raw`) when present.
+- **Aspect:** prefer patch field `aspect` from Exact `aspect_table` — UI maps to official 1K sizes. Else set width/height near 1024.
- **Batch:** `images` or `batch` (1–4 typical).
- **Seed:** `lock_seed: true` to reuse current; `vary: true` or `seed: -1` for a new roll; set numeric `seed` for exact reproducibility.
- **Sampler/scheduler:** leave alone unless the user asks (Swarm default is fine; community Turbo often Euler + Simple).
- **Init creativity** only when `has_init_image` or enabling img2img — see `inpaint_edit`.
- Do not change the prompt unless needed for the new framing.
- Keep LoRAs unless asked to drop them.
+- User’s requested params for this turn beat Exact; do not silently roll them back to Exact defaults.
## Deliverable
diff --git a/Config/_base/packs/write_prompt.md b/Config/_base/packs/write_prompt.md
index 4053ef5..6f5361d 100644
--- a/Config/_base/packs/write_prompt.md
+++ b/Config/_base/packs/write_prompt.md
@@ -15,7 +15,7 @@ Structure as flowing prose (not tag soup):
- Put **LoRA trigger phrases** near the subject they affect.
- Short user ideas → expand. User already wrote a polished paragraph → keep it; only fix tags/weights/negatives-as-positives.
-- Keep Turbo defaults unless asked (steps 8, cfg 1). Prefer `aspect` for framing.
+- Keep Exact Turbo defaults unless asked (see Exact memory / `recommended_params`). Prefer `aspect` for framing. Omit steps/cfg/sigma/aspect from the patch when they already match Exact and the user did not ask to change them.
- Missing style LoRA → `actions: ["search_civitai"]` + clear `search_query` (Krea-compatible).
- Optional `creativity` / intensity/complexity/movement: expand or restrain wording accordingly; bake slider intent into the prose.
diff --git a/Config/_base/skills/memory.json b/Config/_base/skills/memory.json
index e6950d0..f890c9a 100644
--- a/Config/_base/skills/memory.json
+++ b/Config/_base/skills/memory.json
@@ -1,6 +1,6 @@
{
"id": "memory",
- "title": "Vector memory",
+ "title": "Exact + vector memory",
"default": true,
"prompt_file": "memory.md"
}
diff --git a/Config/_base/skills/memory.md b/Config/_base/skills/memory.md
index d2e5aed..08ddeeb 100644
--- a/Config/_base/skills/memory.md
+++ b/Config/_base/skills/memory.md
@@ -1,8 +1,15 @@
# Skill: memory
-You have a persistent vector memory (`memory_hits` in live context).
+You have two memory layers:
-## When to write
+1. **Exact memory** (`## Exact memory` + live `exact` / `session_exact`) — canonical KV defaults (generation params, aspect table, architecture facts). Always prefer Exact over RAG for numbers and defaults.
+2. **Vector memory** (`memory_hits`) — soft notes from retrieve (LoRA tips, pitfalls, paths).
+
+## Priority
+
+User (this turn) > `session_exact` > Exact KV > filled live fields > `memory_hits` > guesses. Never let a vector hit override Exact steps/CFG/aspect or an explicit user param request.
+
+## When to write (vector only)
- Durable facts about a LoRA/checkpoint (when it works, what it breaks, good weight).
- Bad paths / pitfalls you discovered this session.
@@ -10,6 +17,7 @@ You have a persistent vector memory (`memory_hits` in live context).
## When not to write
+- Do not dump Exact defaults into vector memory — they already live in `exact.json`.
- Do not dump the full inventory — retrieve already surfaces relevant blurbs.
- Do not store the user's taste profile (that is `taste_profile` / taste.json).
- Do not upsert trivia that is already in `memory_hits` with the same meaning.
diff --git a/Config/personas/cinema/exact.json b/Config/personas/cinema/exact.json
new file mode 100644
index 0000000..5d73aba
--- /dev/null
+++ b/Config/personas/cinema/exact.json
@@ -0,0 +1,5 @@
+{
+ "generation": {
+ "aspect": "2.35:1"
+ }
+}
diff --git a/README.md b/README.md
index 0a814b2..3073b44 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **vector memory**, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
-**Version 0.7.0** — Config/_base + persona folders, skills, memory-seed → SQLite, Ollama `use: chat|memory`, slim inventory via retrieve.
+**Version 0.7.0** — Config/_base + persona folders, skills, **exact KV memory** + vector memory-seed → SQLite, Ollama `use: chat|memory`, slim inventory via retrieve.
## Layout
@@ -15,20 +15,29 @@ SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat +
```
Config/
- _base/ # defaults (assistant, ui, models/krea2, core, packs, skills, memory-seed, identity)
- personas// # sparse preset: persona/voice/likes/dislikes/rules + optional overrides
+ _base/ # defaults (assistant, ui, models/krea2, exact.json, core, packs, skills, memory-seed, identity)
+ personas// # sparse preset: persona/voice/likes/dislikes/rules + optional exact.json / overrides
```
Disk overlay (wins over bundled): `/mnt/swarm_data/Assistent/` — same layout, plus `settings.json`, `taste.json`, `personas.json` (legacy prompt overlay), `ollama-roles.json`, `memory/assistent.sqlite`.
Copy `personas/cinema/` → `noir/`, edit only differing JSON files.
+## Exact memory (KV)
+
+- `Config/_base/exact.json` — canonical generation defaults, profiles (turbo/raw), aspect table, short facts
+- Persona / disk overlays merge via DeepMerge (matching keys overwrite)
+- Always injected into the system prompt; UI fills **empty** SwarmUI fields from Exact (no LLM call)
+- Chat-session overrides (`session_exact`) last until persona change or clear chat — not written to disk
+- Priority: core contract → current user → session_exact → exact (+ persona) → live fields → vector `memory_hits`
+
## Vector memory
- SQLite + Ollama `/api/embed` (default `nomic-embed-text`, pick in ⚙)
-- First chat seeds `Config/_base/memory-seed/` (Krea facts, aspect, pitfalls)
+- First chat seeds `Config/_base/memory-seed/` (pointers + pitfalls; numbers live in Exact)
- Agents upsert via patch `memory_upsert` / `memory_forget`
- Cards ingest on save; retrieve → `memory_hits` in live context (inventory slimmed)
+- Soft notes only — Exact and the user beat RAG for params
## UX
@@ -76,7 +85,7 @@ Restart / rebuild SwarmUI after clone. gpu-rent: `seed-extensions` + restart.
**Packs** (one active): `write_prompt`, `critique_image`, `compose_scene`, `fix_params`, `inpaint_edit`, `describe_ref`, `catalog_card`.
-**Skills** (checkboxes): `prompting`, `creativity_sliders`, `memory` — procedures, not model encyclopedia (facts live in memory-seed).
+**Skills** (checkboxes): `prompting`, `creativity_sliders`, `memory` — procedures; encyclopedia numbers live in Exact, soft notes in memory-seed / RAG.
**Personas:** `neutral`, `lewd`, `aggressive`, `cinema`, `terse` under `Config/personas/`.
diff --git a/SwarmAssistentExtension.cs b/SwarmAssistentExtension.cs
index 51bd9cf..c8c7ec5 100644
--- a/SwarmAssistentExtension.cs
+++ b/SwarmAssistentExtension.cs
@@ -1347,6 +1347,16 @@ public class SwarmAssistentExtension : Extension
}
}
+ JObject exact = Config.LoadExact(pid);
+ if (exact is not null && exact.Count > 0)
+ {
+ system.AppendLine();
+ system.AppendLine("## Exact memory (canonical KV — always trust over RAG guesses)");
+ system.AppendLine("```json");
+ system.AppendLine(exact.ToString(Newtonsoft.Json.Formatting.None));
+ system.AppendLine("```");
+ }
+
foreach (string skillId in skillIds ?? Config.ResolveEnabledSkills(pid, null))
{
string skillText = Config.LoadSkillPrompt(pid, skillId);
@@ -1564,7 +1574,7 @@ public class SwarmAssistentExtension : Extension
Logs.Debug($"Assistent memory retrieve: {ex.Message}");
}
- string enrichedContext = InjectMemoryHits(contextJson, hits);
+ string enrichedContext = InjectMemoryHits(contextJson, hits, Config.LoadExact(pid));
List messages = BuildOllamaMessages(packName, includeBase, enrichedContext, userMessages, personaId: pid, skillIds: skills);
JArray civitaiResults = [];
string reply = "";
@@ -1659,7 +1669,7 @@ public class SwarmAssistentExtension : Extension
return string.IsNullOrWhiteSpace(q) ? "krea2 prompting" : q;
}
- static string InjectMemoryHits(string contextJson, JArray hits)
+ static string InjectMemoryHits(string contextJson, JArray hits, JObject exact = null)
{
JObject ctx;
try
@@ -1671,6 +1681,29 @@ public class SwarmAssistentExtension : Extension
ctx = new JObject { ["_raw_context"] = contextJson };
}
ctx["memory_hits"] = hits ?? new JArray();
+ if (exact is not null && exact.Count > 0 && ctx["exact"] is null)
+ {
+ ctx["exact"] = exact;
+ }
+ if (ctx["session_exact"] is null)
+ {
+ ctx["session_exact"] = new JObject();
+ }
+ if (ctx["recommended_params"] is null && exact?["generation"] is JObject gen)
+ {
+ JObject rec = new();
+ foreach (string key in new[] { "steps", "cfg", "sigma_shift", "aspect", "images" })
+ {
+ if (gen[key] is not null)
+ {
+ rec[key] = gen[key].DeepClone();
+ }
+ }
+ if (rec.Count > 0)
+ {
+ ctx["recommended_params"] = rec;
+ }
+ }
// Slim inventory for LLM: keep enabled + current, drop full dump if present
if (ctx["available_loras"] is JArray allLoras && allLoras.Count > 24)
{