diff --git a/routes/task_routes.py b/routes/task_routes.py
index 5734fcb22..5698353bf 100644
--- a/routes/task_routes.py
+++ b/routes/task_routes.py
@@ -151,6 +151,7 @@ class TaskCreate(BaseModel):
endpoint_url: Optional[str] = None
then_task_id: Optional[str] = None # chain: run this task after success
notifications_enabled: Optional[bool] = None # None lets action-specific defaults apply
+ character_id: Optional[str] = None # built-in persona id (PERSONAS) — biases output voice
class TaskUpdate(BaseModel):
@@ -171,6 +172,7 @@ class TaskUpdate(BaseModel):
endpoint_url: Optional[str] = None
then_task_id: Optional[str] = None
notifications_enabled: Optional[bool] = None
+ character_id: Optional[str] = None
def _display_task_name(t: ScheduledTask) -> str:
@@ -203,6 +205,7 @@ def _task_to_dict(t: ScheduledTask, include_last_run_result: bool = False) -> di
"output_target": t.output_target,
"session_id": t.session_id,
"crew_member_id": getattr(t, "crew_member_id", None),
+ "character_id": getattr(t, "character_id", None),
"model": t.model,
"endpoint_url": t.endpoint_url,
"run_count": t.run_count or 0,
@@ -552,6 +555,7 @@ def setup_task_routes(task_scheduler) -> APIRouter:
then_task_id=then_task_id,
webhook_token=webhook_token,
notifications_enabled=notifications_enabled,
+ character_id=(req.character_id or None),
)
db.add(task)
db.commit()
@@ -705,6 +709,9 @@ def setup_task_routes(task_scheduler) -> APIRouter:
task.then_task_id = _validate_then_task_id(db, req.then_task_id, user, current_task_id=task.id)
if req.notifications_enabled is not None:
task.notifications_enabled = bool(req.notifications_enabled)
+ if req.character_id is not None:
+ # Empty string clears the persona; non-empty stores the id.
+ task.character_id = req.character_id or None
if req.cron_expression is not None:
if req.cron_expression:
try:
diff --git a/src/task_scheduler.py b/src/task_scheduler.py
index 999a0699d..eb4617531 100644
--- a/src/task_scheduler.py
+++ b/src/task_scheduler.py
@@ -1335,11 +1335,24 @@ class TaskScheduler:
return await self._execute_checkin(task, crew, db, session_id, endpoint_url, model)
# Build system prompt: crew member persona overrides the default.
+ # Built-in character_id (Socrates, Razor, etc.) further biases the
+ # voice — it prepends to whichever base prompt we landed on so the
+ # task still knows it's executing a scheduled task but in that
+ # character's tone.
system_prompt = (
(crew.personality or "").strip()
if crew and crew.personality
else "You are a helpful assistant executing a scheduled task. Use available tools to complete the task thoroughly."
)
+ char_id = (getattr(task, "character_id", None) or "").strip()
+ if char_id:
+ try:
+ from src.reminder_personas import PERSONAS as _PERSONAS
+ char_prompt = _PERSONAS.get(char_id.lower())
+ if char_prompt:
+ system_prompt = f"{char_prompt}\n\n{system_prompt}"
+ except Exception:
+ pass
# Inject current time so the model knows what's past vs upcoming
tz_name = _resolve_task_timezone(db, task)
try:
diff --git a/static/js/tasks.js b/static/js/tasks.js
index 8437b4250..a74bde151 100644
--- a/static/js/tasks.js
+++ b/static/js/tasks.js
@@ -1077,9 +1077,23 @@ function _showForm(existing, initTaskType, initTriggerType) {
typeOpts.innerHTML = '';
if (taskType === 'llm' || taskType === 'research') {
const placeholder = taskType === 'research' ? 'What should be researched?' : 'What should the AI do?';
+ const _personaOpts = [
+ ['', 'Default (no persona)'],
+ ['socrates', 'Socrates'],
+ ['razor', 'Razor'],
+ ['nietzsche', 'Nietzsche'],
+ ['spark', 'Spark'],
+ ['odysseus', 'Odysseus'],
+ ];
+ const _curPersona = (existing?.character_id || '').toLowerCase();
+ const _personaOptsHtml = _personaOpts.map(([v, label]) =>
+ ``).join('');
typeOpts.innerHTML = `
+
+
+
`;
} else {
typeOpts.innerHTML = `
@@ -1437,7 +1451,11 @@ function _showForm(existing, initTaskType, initTriggerType) {
return;
}
payload.prompt = prompt;
+ const personaVal = document.getElementById('task-form-persona')?.value || '';
+ payload.character_id = personaVal;
} else {
+ // Non-llm/research tasks: explicitly clear any persona on switch.
+ payload.character_id = '';
const action = document.getElementById('task-form-action')?.value;
if (!action) {
if (uiModule) uiModule.showError('Select an action');