STATEK
Harness Reminders

STATEK Harness Reminders

Looping reminders are system-level messages that STATEK can inject back into a DialogAgent job when the model gives a text-only dialog response that would otherwise finish the step.

Use them as a lightweight agent harness mechanism: keep the model in the loop until it satisfies a required condition, such as reporting a durable outcome. This is useful when a text-only response might otherwise let the job complete before the application has recorded the result it needs.

⚠️

Reminders are guidance plus loop control. They do not enforce security, sandboxing, authorization, deterministic behavior, or external side-effect policy. Production code still needs application-level controls around tools, credentials, execution, and side effects.

LLM textresponseDialoghandlerReminder.fire_ready(job)ready?ReminderLogItem +console reminderNext modelturnJobcontinuesNot ready and nopending notificationJob completesreadynot ready

The reminder checks job state and can keep the job running until the harness condition is satisfied.

Harness example

The pattern below keeps a dialog job active until application code has recorded an outcome. The prompt and tool contract tell the model what to do; the reminder provides the loop control if the model replies with text instead.

from dataclasses import dataclass
 
import dbzero as db0
from statek import SubTaskHandler, SubTaskState, complete_sub_task, tool
from statek.agents.dialog_agent import DialogAgent, RecurringReminder
 
 
@db0.memo
class OutcomeHandler(SubTaskHandler):
    """Durable handler used by the parent job to inspect the reported outcome."""
 
    def get_log_message(self) -> str:
        if self.state == SubTaskState.COMPLETED:
            return f"Outcome reported: {self.result}"
        return super().get_log_message()
 
 
@tool
def report_outcome(summary: str, **kwargs) -> None:
    """Tool called by the agent when the task is complete."""
    complete_sub_task(result=summary)
 
 
@db0.memo
@dataclass
class ReportOutcomeReminder(RecurringReminder):
    def fire_ready(self, job):
        local_state = getattr(job.py_env, "local_state", None) or {}
        handler = local_state.get("sub_task_handler")
 
        if isinstance(handler, OutcomeHandler) and handler.state in (
            SubTaskState.COMPLETED,
            SubTaskState.ERROR,
        ):
            return False
 
        return super().fire_ready(job)
 
 
def send_message(body: str, media=None):
    print(body)
 
 
class NegotiatorAgent(DialogAgent):
    def __init__(self):
        super().__init__(
            send_message=send_message,
            tools=[report_outcome],
            _metadata={"MODEL": "openai/gpt-5-mini"},
        )
 
 
agent = NegotiatorAgent()
agent.set_reminder(
    ReportOutcomeReminder(
        text=(
            'Before ending, call report_outcome("<summary>") exactly once '
            "when the task is complete."
        ),
        min_dialog_len=2,
    )
)

Add the same requirement to the agent instructions:

Call report_outcome("<summary>") exactly once when the task is complete.
Do not finish with only a text response if the outcome has not been reported.

report_outcome(...) is application code. In this example it calls complete_sub_task(...), which marks the active durable handler complete. When the dialog job is running as a subtask, STATEK stores that handler in job.py_env.local_state["sub_task_handler"]; the reminder checks that state before deciding whether to fire.

The tool should validate the reported data and make repeated calls idempotent or explicitly rejected. Once the handler state says the outcome is complete or failed, ReportOutcomeReminder.fire_ready(...) returns False and stops extending the job.

The reminder does not prove the outcome is correct. It only prevents a ready reminder from letting a text-only MD_DIALOG or DIRECT response end the job before the harness condition has been satisfied.

Recurring reminders

Use RecurringReminder directly when you only need repeated model-visible guidance:

agent.set_new_reminder(
    "Use the selected locale in every user-visible response.",
    type="RECURRING",
    min_dialog_len=2,
)

RecurringReminder(text, min_dialog_len=None) is ready immediately. Passing min_dialog_len=N waits until job.get_dialog() has at least N dialog entries.

Custom reminders

Subclass RecurringReminder when the reminder should stop, delay, or change behavior based on job or handler state. A custom reminder owns its own fire_ready(job) condition:

class ReportOutcomeReminder(RecurringReminder):
    def fire_ready(self, job):
        local_state = getattr(job.py_env, "local_state", None) or {}
        handler = local_state.get("sub_task_handler")
 
        if isinstance(handler, OutcomeHandler) and handler.is_completed:
            return False
 
        return super().fire_ready(job)

Attach prebuilt reminders with agent.set_reminder(reminder). The reminder must be a Reminder instance stored under the same dbzero prefix as the agent.

API behavior and limits

  • DialogAgent.reminder is None until a reminder is configured.
  • A DialogAgent stores one reminder policy at a time.
  • DialogAgent.set_new_reminder(text, type="RECURRING", **kwargs) creates and stores a RecurringReminder.
  • agent.set_reminder(reminder) stores a prebuilt Reminder instance.
  • set_reminder(...) rejects non-reminders and reminders created under another dbzero prefix.
  • Job.handle_reminder(...) calls reminder.fire_ready(job) before adding a ReminderLogItem.
  • Fired reminders are appended to the job console and sent to the model as SYSTEM chat history on the next model request.
  • In MD_DIALOG and DIRECT dialog flows, a fired reminder can keep a text-only response from completing the job immediately.
  • The deprecated RecursiveReminder class alias and type="RECURSIVE" remain accepted for compatibility. Prefer RecurringReminder and type="RECURRING" in new code.

When not to use reminders

Do not use reminders as the only protection for behavior that must be enforced. Put critical constraints in application code:

  • authorization checks before exposing tools or objects
  • validation inside tools such as report_outcome(...)
  • confirmation gates before irreversible side effects
  • sandboxing and resource limits around Python execution
  • durable state checks before sending or committing external changes

Use reminders for model-visible guidance and loop control. Use tools, callbacks, job state, and application controls for behavior that must be enforced.

Related pages: Agents, Chat Styles, Callbacks and Interruptions, Security, and Agents API Reference.