Skip to content

[RFC] Claude Code-style /goal with evaluator-driven continuation #3834

Description

@hetaoBackend

Summary

Add a first-class /goal command to DeerFlow, modeled after Claude Code's goal feature: a user sets a verifiable completion condition, DeerFlow runs the lead agent, and after each completed turn a separate lightweight evaluator decides whether the condition is satisfied. If not, DeerFlow automatically starts the next continuation turn with the evaluator reason as guidance. If yes, the goal is cleared/recorded as achieved.

This should be implemented as a thread/session-level completion contract, not as a skill and not as a stronger prompt inside the main agent.

External reference

Claude Code's documented behavior is the reference design:

  • /goal sets a completion condition and keeps the current session running across turns until that condition is met.
  • After each turn, a small fast model evaluates the condition against the surfaced conversation evidence.
  • A failed evaluation starts another turn and feeds the evaluator reason back as guidance.
  • A successful evaluation clears the goal and records the achieved entry.
  • One goal can be active per session; /goal shows status; /goal clear and aliases cancel it.
  • The evaluator does not run tools or read files independently, so conditions should be written as verifiable outcomes the agent can demonstrate in the transcript.
  • The implementation is described as a session-scoped prompt-based Stop hook.

References:

Motivation

DeerFlow already supports long-running agent work, plan mode, todos, subagents, sandboxed execution, thread persistence, and streaming. However, users still need to manually decide when to send the next prompt for long-horizon work. A Claude Code-style /goal gives DeerFlow a higher-level workflow primitive:

  • "Keep working until this check passes."
  • "Continue triaging this queue until it is empty."
  • "Implement this design until these acceptance criteria hold."
  • "Refactor this module until the file-size/test constraints are satisfied."

The important distinction is that completion is judged by a separate evaluator at run finalization time, not by the same agent that is doing the work.

Current DeerFlow fit

Useful existing pieces:

  • ThreadState already persists structured state such as todos, artifacts, and title through the checkpointer.
  • TodoMiddleware already gives multi-step progress visibility when is_plan_mode is true.
  • SkillActivationMiddleware already owns /skill-name ... semantics, so /goal must be a reserved built-in command, not a skill.
  • The TUI already has a pure built-in slash command registry in backend/packages/harness/deerflow/tui/command_registry.py.
  • Web submission flows through frontend/src/components/workspace/input-box.tsx and frontend/src/core/threads/hooks.ts.
  • Gateway runs funnel through app.gateway.services.create_run_record_and_task() and deerflow.runtime.runs.worker.run_agent().
  • run_agent() has a finalization point after success/error/interruption, token usage persistence, title sync, thread-meta status update, and bridge.publish_end().
  • RunManager.create_or_reject() already serializes active runs per thread and supports reject/interrupt/rollback strategies.

Proposed behavior

Command semantics

Add /goal as a built-in command across Web, TUI, and eventually IM/headless clients.

Supported forms:

  • /goal <condition>: set or replace the active goal, then immediately start a turn using the condition as the directive.
  • /goal: show active goal status, or the most recently achieved/cleared goal for the current thread/session.
  • /goal clear: clear the active goal without marking it achieved.
  • Aliases for clear: stop, off, reset, none, cancel.

Initial non-goals:

  • Do not support multiple concurrent goals per thread.
  • Do not make /goal a DeerFlow skill.
  • Do not let the main agent self-certify completion with a tool call.
  • Do not let the evaluator run arbitrary tools in v1.

Thread state

Extend ThreadState with a reducer-backed goal channel. Suggested shape:

class GoalState(TypedDict, total=False):
    id: str
    condition: str
    status: Literal["active", "achieved", "cleared", "failed"]
    created_at: str
    updated_at: str
    achieved_at: str | None
    cleared_at: str | None
    clear_reason: str | None
    evaluated_turns: int
    continuation_turns: int
    consecutive_blocks: int
    token_baseline: int
    token_spend: int
    last_evaluated_run_id: str | None
    last_decision: Literal["pass", "fail", "error"] | None
    last_reason: str | None
    last_guidance: str | None

Reducer semantics should mirror merge_todos: None preserves existing goal state; an explicit goal update replaces it.

Questions for implementation:

  • Should achieved/cleared goals remain in checkpoint state for /goal status, or move to run/thread metadata after completion?
  • Should Web threads treat goals as thread-scoped, while TUI/CLI treat them as session-scoped with counters reset on resume?

Recommended v1 answer: persist the active goal in ThreadState so Web, TUI, and Gateway agree. Keep the latest achieved/cleared entry in the same state for status display. Revisit a historical goal log later.

Slash command ownership

Add goal to RESERVED_SLASH_SKILL_NAMES in deerflow.skills.slash so /goal cannot be interpreted as /goal skill activation.

Create a shared command parser rather than duplicating Web/TUI behavior:

  • Move or mirror TUI command registry concepts into a reusable harness module, e.g. deerflow.commands.registry.
  • Built-ins: goal, help, new, resume, etc.
  • Skills remain dynamically appended as slash-skill activations.
  • Web autocomplete should list /goal as a built-in command alongside enabled skills.

Goal run flow

The core loop should live outside the agent graph, near run finalization:

  1. User submits /goal <condition>.
  2. Gateway creates/updates ThreadState.goal as active.
  3. Gateway starts a normal lead-agent run with the condition as the user directive.
  4. The lead agent runs with normal tools, skills, todos, sandbox, memory, and subagents.
  5. run_agent() reaches finalization with status success.
  6. A Goal coordinator checks whether the thread has an active goal and whether this run should be evaluated.
  7. The coordinator collects bounded evidence from the completed run and current checkpoint state.
  8. The evaluator model returns structured JSON: pass/fail/error + reason + optional next guidance.
  9. If pass: update goal.status=achieved, store reason, publish a goal event, and leave thread idle.
  10. If fail: update goal.last_reason, increment counters, and enqueue one continuation run with a hidden/system control prompt containing the reason.
  11. If evaluation fails or continuation limits trip: mark goal failed or pause it with a visible reason rather than looping indefinitely.

The continuation run should use multitask_strategy="reject" and should only be scheduled after the completed run fully exits, so it does not race the previous run's checkpoint writes.

Evaluator

Introduce deerflow.runtime.goals.evaluator or similar.

Inputs:

  • Goal condition.
  • Recent visible transcript, including the latest user/AI turn.
  • Tool summaries or run events from the latest run.
  • Current todos and their statuses.
  • Artifacts written during the run.
  • Token/turn counters.
  • The evaluator's previous reason, if any.

Output schema:

{
  "ok": true,
  "reason": "The transcript shows `pytest backend/tests/test_goal.py` exited 0 and the goal condition only required that check."
}

or

{
  "ok": false,
  "reason": "The implementation changed backend files, but no test result has been surfaced yet.",
  "guidance": "Run the relevant backend tests and surface the exact command and exit status."
}

The evaluator should use a configured small/fast model, similar in spirit to Claude Code defaulting goal evaluation to the configured small fast model. DeerFlow can follow existing patterns from title generation and suggestions:

goals:
  enabled: true
  evaluator_model_name: null  # null = default model, can be overridden
  max_condition_chars: 4000
  max_consecutive_blocks: 8
  max_continuation_turns: 20
  max_eval_context_messages: 20
  max_eval_context_chars: 24000
  require_trusted_workspace: false  # future: relevant for local/TUI hook-like behavior

The evaluator should not call tools in v1. This keeps it cheap and avoids giving an automated stop-condition checker its own side effects. A later v2 could add an agent-based verifier, but only with explicit config and limits.

Continuation prompt

Continuation prompts should be hidden from normal chat UI but persisted enough for reproducibility. Example content:

<goal_continuation>
Active goal: {condition}
The previous turn did not satisfy the goal.
Evaluator reason: {reason}
Guidance for the next turn: {guidance}
Continue working toward the active goal. Surface concrete evidence for any checks you run.
</goal_continuation>

Display rules:

  • Hide raw control messages from the normal transcript, like slash-skill activation reminders.
  • Show compact goal status/event rows: active, evaluator reason, continuing, achieved, cleared, failed.
  • Ensure memory extraction ignores hidden goal-control messages unless explicitly useful.

Safety and loop limits

Borrow the same failure posture as Claude Code Stop hooks:

  • Cap consecutive failed evaluator blocks, default 8.
  • Cap total continuation turns, default configurable.
  • Do not continue after a run error, interruption, rollback, or safety termination unless explicitly configured.
  • Do not continue while another run is pending/running for the same thread.
  • User input wins: if a user submits a new message while a goal continuation is being considered, do not auto-schedule over it.
  • Do not continue if there are unresolved clarification interrupts.
  • Do not continue if active background subagents/tool tasks are still running and their output has not returned.
  • Publish a visible warning when the cap trips, including the last evaluator reason.

Frontend

Add a GoalProgressRow or GoalPanel near the composer, parallel to the existing To-do panel.

It should show:

  • Active condition.
  • Runtime duration.
  • Evaluated turn count.
  • Token spend since activation or resume baseline.
  • Latest evaluator reason.
  • Actions: clear/cancel, possibly copy condition.

Web command UX:

  • Autocomplete /goal as a built-in slash command.
  • Submitting /goal <condition> should not be sent as a literal user-visible task if the backend command API can set the state directly; alternatively, v1 can submit the condition as the user directive but mark the slash command metadata for display cleanup.
  • /goal status should open/show the goal panel or append a compact status message.
  • /goal clear should call a goal API and update thread state without starting a normal agent turn.

APIs

Possible Gateway endpoints:

  • GET /api/threads/{thread_id}/goal
  • PUT /api/threads/{thread_id}/goal with { condition }
  • DELETE /api/threads/{thread_id}/goal
  • Optional: POST /api/threads/{thread_id}/goal/evaluate for tests/debug only.

However, the LangGraph-compatible path still needs to surface goal updates through thread state snapshots so the existing useStream frontend state model works.

Backend integration points

Recommended module placement:

  • backend/packages/harness/deerflow/agents/thread_state.py: GoalState, merge_goal, goal field.
  • backend/packages/harness/deerflow/commands/: shared slash command registry and parser.
  • backend/packages/harness/deerflow/runtime/goals/: goal evaluator, coordinator, prompt builder, schemas.
  • backend/packages/harness/deerflow/runtime/runs/worker.py: invoke goal coordinator after successful run finalization and before bridge.publish_end() or immediately after checkpoint-safe finalization.
  • backend/app/gateway/routers/goals.py: explicit Web/API goal operations.
  • backend/app/gateway/services.py: command-aware run creation or context metadata for /goal submissions.
  • frontend/src/components/workspace/input-box.tsx: command autocomplete and submit routing.
  • frontend/src/core/threads/types.ts: goal?: GoalState.
  • frontend/src/components/workspace/goal-progress-row.tsx: visual status and clear action.

Observability

Add run events for:

  • goal.set
  • goal.evaluate.started
  • goal.evaluate.completed
  • goal.continuation.scheduled
  • goal.achieved
  • goal.cleared
  • goal.failed

Token attribution should separate main-agent tokens from evaluator tokens, similar to how middleware/title usage is already tracked separately.

Testing plan

Backend unit tests:

  • Slash parser reserves /goal and does not route it to skill activation.
  • merge_goal preserves existing state on None and replaces on explicit updates.
  • Evaluator parses strict JSON and handles malformed output safely.
  • Coordinator marks achieved on ok=true.
  • Coordinator schedules continuation on ok=false.
  • Coordinator stops at consecutive block cap.
  • Coordinator does not evaluate interrupted/error runs.
  • Coordinator does not schedule if thread already has an active run.
  • Hidden goal-control messages are excluded from memory/title-visible user message logic.

Backend integration tests:

  • Set goal -> agent run -> evaluator fail -> continuation run is created.
  • Set goal -> evaluator pass -> goal becomes achieved and no continuation run is created.
  • /goal clear cancels active goal and no continuation is scheduled.
  • Resume/checkpointer restores an active goal.

Frontend tests:

  • /goal appears in slash autocomplete.
  • /goal <condition> routes through goal command flow.
  • Active goal row renders condition, turn count, token spend, and evaluator reason.
  • Clear action updates the UI.
  • Existing /skill-name ... behavior continues to work.

E2E tests:

  • Mock backend stream with values.goal and verify UI updates.
  • Mock a fail evaluation followed by continuation status.
  • Mock achieved state and verify no active indicator remains.

Rollout plan

Phase 1: Data model and command plumbing

  • Add GoalState to ThreadState.
  • Reserve /goal against slash-skill activation.
  • Add shared built-in command parsing.
  • Add explicit goal API and frontend state types.

Phase 2: Evaluator and coordinator

  • Implement evaluator model call with strict JSON parsing.
  • Implement post-run coordinator and continuation scheduling.
  • Add loop caps and safety guards.
  • Add run events and token attribution.

Phase 3: UI polish and cross-surface parity

  • Add GoalProgressRow in Web.
  • Add TUI /goal status and indicator.
  • Add IM/channel behavior or document unsupported surfaces.
  • Add docs in README/frontend/backend AGENTS as appropriate.

Phase 4: Optional advanced verification

  • Consider command/agent-based deterministic verifiers for projects that want tool-access evaluation.
  • Consider goal history beyond the latest achieved/cleared goal.
  • Consider scheduled/background goals independent of an open thread, which is out of scope for this RFC.

Open questions

  1. Should Web goals be strictly thread-scoped, while TUI goals are session-scoped, or should all DeerFlow surfaces share thread-scoped semantics?
  2. Should the evaluator inspect persisted run events or only serialized visible transcript + state snapshots?
  3. Should evaluator tokens count against normal thread token budgets, a separate goal-evaluator budget, or both?
  4. Should /goal <condition> be available in IM channels, where repeated auto-continuation may surprise users?
  5. Should active goals survive server restart by default when using SQLite/Postgres checkpointers?
  6. Is a model-only evaluator enough for v1, or do we need a deterministic command-hook equivalent for CI/test-heavy goals?

Acceptance criteria for the RFC implementation

  • Users can set, inspect, and clear one active /goal per thread/session.
  • /goal cannot conflict with slash-skill activation.
  • A successful agent run with an active goal triggers evaluator execution.
  • Evaluator ok=false schedules a bounded continuation run with visible rationale.
  • Evaluator ok=true marks the goal achieved and stops continuation.
  • Loop caps prevent indefinite continuation.
  • Frontend displays active/achieved/cleared goal state.
  • Tests cover parser, state reducer, evaluator parsing, coordinator decisions, and frontend rendering.

Metadata

Metadata

Assignees

Labels

P1Major priorityRFCRequest for commentsarea:agentsAgents, subagents, graph wiring, prompts, langgraph.jsonarea:backendGateway / runtime / core backend under backend/area:frontendNext.js frontend under frontend/enhancementNew feature or requestneeds-validationTouches front/back contract surface; needs real-path validationrisk:highHigh risk: backend API, agents, sandbox, auth, deps, CI

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions