From 6ad7597db85b80e839204e87fcd83b6c65d4700c Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 8 May 2026 21:53:35 -0400 Subject: [PATCH] feat: per-role inject_datetime toggle for system prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each role can now disable the current date/time header injected into the system prompt. Default is true (all existing roles unchanged). Useful for pure processing roles (summarizer, classifier, translator) where temporal context is irrelevant or could cause unexpected model behavior. Changes: - model_registry: set_role_config/get_role_config gain inject_datetime field - context_loader: load_context gains inject_datetime param (default True) - orchestrator router: passes inject_datetime from role_cfg to load_context - local_llm router: reads inject_datetime from POST body, passes to registry; role_config_data_js includes the field - local_llm.html: checkbox in role config panel; populate on open, save on submit Session logs still timestamp every turn (HH:MM header in YYYY-MM-DD.md files) regardless of this setting — the toggle only affects the system prompt header. Co-Authored-By: Claude Sonnet 4.6 --- cortex/context_loader.py | 8 +++++--- cortex/model_registry.py | 26 ++++++++++++++++++-------- cortex/routers/local_llm.py | 31 +++++++++++++++++++++---------- cortex/routers/orchestrator.py | 1 + cortex/static/local_llm.html | 10 ++++++++-- 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/cortex/context_loader.py b/cortex/context_loader.py index df3c67d..f1a1a7f 100644 --- a/cortex/context_loader.py +++ b/cortex/context_loader.py @@ -20,6 +20,7 @@ def load_context( include_mid: bool = True, include_short: bool = True, role_append: str = "", + inject_datetime: bool = True, ) -> str: """ Build the system-prompt context block for a given tier and memory toggles. @@ -38,9 +39,10 @@ def load_context( inara_dir = persona_path() parts = [] - # ── 0. Current date and time (always — injected first so it's prominent) ── - now = datetime.now().astimezone() - parts.append(f"--- System ---\nCurrent date and time: {now.strftime('%A, %Y-%m-%d at %I:%M %p %Z')}") + # ── 0. Current date and time (per-role toggle — injected first so it's prominent) ── + if inject_datetime: + now = datetime.now().astimezone() + parts.append(f"--- System ---\nCurrent date and time: {now.strftime('%A, %Y-%m-%d at %I:%M %p %Z')}") # ── 1. Core identity (always) ────────────────────────────────── for filename in _CORE: diff --git a/cortex/model_registry.py b/cortex/model_registry.py index 0d52b05..04698ae 100644 --- a/cortex/model_registry.py +++ b/cortex/model_registry.py @@ -416,17 +416,25 @@ def get_best_local_model(username: str, role: str = "chat") -> dict | None: return None -def set_role_config(username: str, role: str, system_append: str, tools: list[str] | None) -> None: - """Save system_append and tools allow-list for a role. +def set_role_config( + username: str, + role: str, + system_append: str, + tools: list[str] | None, + inject_datetime: bool = True, +) -> None: + """Save system_append, tools allow-list, and inject_datetime flag for a role. tools=None clears the allow-list (role uses all accessible tools). - tools=[] would mean no tools at all — validate in the caller if that's undesired. + inject_datetime=False suppresses the current date/time from the system prompt + for this role — useful for pure processing roles (summarizer, classifier, etc.). """ data = _load(username) roles = data.setdefault("roles", {}) if role not in roles: roles[role] = {} roles[role]["system_append"] = system_append.strip() + roles[role]["inject_datetime"] = inject_datetime if tools is None: roles[role].pop("tools", None) else: @@ -436,17 +444,19 @@ def set_role_config(username: str, role: str, system_append: str, tools: list[st def get_role_config(username: str, role: str) -> dict: """ - Return supplemental config for a role: system_append and tools. + Return supplemental config for a role: system_append, tools, and inject_datetime. - Both keys are optional in the registry — missing means "use defaults": - system_append: str — appended to the system prompt for this role + All keys are optional in the registry — missing means "use defaults": + system_append: str — appended to the system prompt for this role tools: list[str] | None — explicit tool allow-list (None = no restriction) + inject_datetime: bool — whether to inject current date/time (default True) """ registry = _load(username) role_cfg = registry.get("roles", {}).get(role, {}) return { - "system_append": role_cfg.get("system_append", ""), - "tools": role_cfg.get("tools") or None, + "system_append": role_cfg.get("system_append", ""), + "tools": role_cfg.get("tools") or None, + "inject_datetime": role_cfg.get("inject_datetime", True), } diff --git a/cortex/routers/local_llm.py b/cortex/routers/local_llm.py index 7b168dd..e91a296 100644 --- a/cortex/routers/local_llm.py +++ b/cortex/routers/local_llm.py @@ -313,6 +313,14 @@ def _render(username: str, success: str = "", error: str = "") -> str: f'' f'' + f'
' + f'' + f'' + f'Disable for pure processing roles (summarizer, classifier, translator)' + f'
' f'
' f'' @@ -332,8 +340,9 @@ def _render(username: str, success: str = "", error: str = "") -> str: role_config_data_js = _json.dumps({ role: { - "system_append": roles.get(role, {}).get("system_append", ""), - "tools": roles.get(role, {}).get("tools") or None, + "system_append": roles.get(role, {}).get("system_append", ""), + "tools": roles.get(role, {}).get("tools") or None, + "inject_datetime": roles.get(role, {}).get("inject_datetime", True), } for role in app_settings.get_defined_roles() }) @@ -574,10 +583,11 @@ async def set_role(request: Request) -> JSONResponse: @router.post("/api/models/role-config") async def set_role_config(request: Request) -> JSONResponse: - """AJAX: save system_append and tool allow-list for a role. + """AJAX: save system_append, tool allow-list, and inject_datetime flag for a role. - Body: {"role": "coder", "system_append": "...", "tools": ["web_search", ...] | null} + Body: {"role": "coder", "system_append": "...", "tools": [...] | null, "inject_datetime": true} tools=null clears the allow-list (role uses all accessible tools). + inject_datetime=false suppresses the date/time header for pure processing roles. """ username = _get_user(request) if not username: @@ -587,18 +597,19 @@ async def set_role_config(request: Request) -> JSONResponse: except Exception: return JSONResponse({"error": "Invalid JSON"}, status_code=400) - role = body.get("role", "").strip() - system_append = body.get("system_append", "") - tools = body.get("tools") # list[str] or None + role = body.get("role", "").strip() + system_append = body.get("system_append", "") + tools = body.get("tools") # list[str] or None + inject_datetime = body.get("inject_datetime", True) if not role: return JSONResponse({"error": "role is required"}, status_code=400) if tools is not None and not isinstance(tools, list): return JSONResponse({"error": "tools must be a list or null"}, status_code=400) - reg.set_role_config(username, role, system_append, tools) - logger.info("role config saved: %s %s (tools=%s)", username, role, - len(tools) if tools is not None else "all") + reg.set_role_config(username, role, system_append, tools, inject_datetime=bool(inject_datetime)) + logger.info("role config saved: %s %s (tools=%s inject_datetime=%s)", + username, role, len(tools) if tools is not None else "all", inject_datetime) return JSONResponse({"ok": True}) diff --git a/cortex/routers/orchestrator.py b/cortex/routers/orchestrator.py index 87bdedc..0e5d315 100644 --- a/cortex/routers/orchestrator.py +++ b/cortex/routers/orchestrator.py @@ -203,6 +203,7 @@ async def _run_job(job_id: str, req: OrchestrateRequest, user: str) -> None: include_mid=req.include_mid, include_short=req.include_short, role_append=role_cfg.get("system_append", ""), + inject_datetime=role_cfg.get("inject_datetime", True), ) session_id = req.session_id or generate_session_id() diff --git a/cortex/static/local_llm.html b/cortex/static/local_llm.html index 1ff865a..52aa2a9 100644 --- a/cortex/static/local_llm.html +++ b/cortex/static/local_llm.html @@ -627,6 +627,9 @@ if (!panel) return; // Populate textarea panel.querySelector('.rcp-textarea').value = cfg.system_append || ''; + // Inject datetime checkbox (default true if not set) + const dtCb = panel.querySelector('.rcp-datetime-cb'); + if (dtCb) dtCb.checked = cfg.inject_datetime !== false; // Build tool checklist buildToolChecklist(role, cfg.tools || null); panel.classList.add('open'); @@ -665,7 +668,9 @@ const role = btn.dataset.role; const panel = document.getElementById(`rcp-${role}`); const ta = panel.querySelector('.rcp-textarea'); - const checks = [...panel.querySelectorAll('.rcp-check input[type=checkbox]')]; + const dtCb = panel.querySelector('.rcp-datetime-cb'); + const inject_datetime = dtCb ? dtCb.checked : true; + const checks = [...panel.querySelectorAll('.rcp-tools input[type=checkbox]')]; const allChecked = checks.every(c => c.checked); const someChecked = checks.some(c => c.checked); const tools = allChecked ? null : (someChecked ? checks.filter(c => c.checked).map(c => c.value) : []); @@ -675,7 +680,7 @@ const res = await fetch('/api/models/role-config', { method: 'POST', headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({role, system_append: ta.value, tools}), + body: JSON.stringify({role, system_append: ta.value, tools, inject_datetime}), }); const data = await res.json(); if (data.ok) { @@ -683,6 +688,7 @@ if (!ROLE_CONFIG_DATA[role]) ROLE_CONFIG_DATA[role] = {}; ROLE_CONFIG_DATA[role].system_append = ta.value; ROLE_CONFIG_DATA[role].tools = tools; + ROLE_CONFIG_DATA[role].inject_datetime = inject_datetime; showToast(`${role} config saved`); closeRolePanel(role); } else {