Skip to content

bug: buildTaskPrompt hardcodes workerMode: 'ephemeral' — persistent workers receive ephemeral role instructions #120

@komoreka

Description

@komoreka

Summary

DispatchDaemon.buildTaskPrompt hardcodes workerMode: 'ephemeral' when loading the worker role prompt, regardless of the spawned worker's actual workerMode. Persistent workers therefore receive ephemeral-mode operating instructions, including the explicit directive that contradicts persistent semantics:

Auto-shutdown: After completing or handing off a task, your session ends automatically

Do not check the task queue. Your session ends after closing or handing off your task.

The infrastructure for persistent-worker prompts is already in place. loadRolePrompt correctly routes to persistent-worker.md when workerMode: 'persistent' is passed, and that prompt file exists (packages/smithy/src/prompts/persistent-worker.md). The bug is that the caller doesn't pass the worker's actual mode.

Root cause

// packages/smithy/src/services/dispatch-daemon.ts:2999 (master 0a7052a)
private async buildTaskPrompt(task: Task, workerId: EntityId): Promise<string> {
  const parts: string[] = [];
  const roleResult = loadRolePrompt('worker', undefined, {
    projectRoot: this.config.projectRoot,
    workerMode: 'ephemeral',   // ← hardcoded, never reads agent's actual mode
  });
  // ...
}

For comparison, loadRolePrompt already does the right thing once told:

// packages/smithy/src/prompts/index.ts:184-185
const filename = role === 'worker' && workerMode === 'persistent'
  ? PROMPT_FILES['persistent-worker']
  : PROMPT_FILES[role];

And persistent-worker.md exists alongside worker.md in the same directory.

Steps to reproduce

  1. Register a persistent worker: sf agent register worker --name CodeDelivery --worker-mode persistent
  2. Assign it a task: sf task assign --to <worker-id> <task-id>
  3. Once the daemon spawns its session (requires PR Version Packages #8 / komoreka fork for persistent workers to get auto-spawned; or use any path that calls buildTaskPrompt), inspect the spawn args:
    ps -ef | grep claude | grep -v grep
    # The initial prompt contains: "You are an **Ephemeral Worker** ..."
    # and "your session ends after closing or handing off your task"

The worker now operates with ephemeral semantics: completes the task, runs sf task complete, and shuts down — defeating the whole point of registering it as persistent.

Proposed fix

Read the worker's workerMode from agent metadata and pass it through:

private async buildTaskPrompt(task: Task, workerId: EntityId): Promise<string> {
  const worker = await this.agentRegistry.getAgent(workerId);
  const workerMeta = worker ? getAgentMetadata(worker) : undefined;
  const workerMode = workerMeta?.agentRole === 'worker'
    ? (workerMeta as WorkerMetadata).workerMode
    : 'ephemeral';

  const roleResult = loadRolePrompt('worker', undefined, {
    projectRoot: this.config.projectRoot,
    workerMode,
  });
  // ... rest unchanged
}

Other callsites of loadRolePrompt('worker', ...) should be audited the same way. Only the dispatch-daemon hardcodes it; the prompts module itself is correct.

Why this matters

Persistent workers are intended to stay alive across multiple task cycles, picking up newly assigned tasks via inbox messages (processPersistentAgentMessage). Telling them via the role prompt to auto-shutdown after each task defeats that design — they exit normally, then sit idle waiting for either a manual restart or (post-PR #8) the next orphan-recovery cycle to respawn them. The persistent-worker mode loses its main benefit (context continuity) and becomes a more expensive ephemeral worker.

This was uncovered while dogfooding a single-persistent-worker setup (CodeDelivery) against cloudboostup-website. The daemon-spawn fix in komoreka PR #8 made the worker actually start; this issue is the next gap: it starts, but it thinks it's ephemeral.

Environment

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions