Skip to content

feat: replace PlanningTool with project-scoped Goal & Task management#44

Merged
cyzus merged 13 commits into
mainfrom
feat/task-and-goal
May 31, 2026
Merged

feat: replace PlanningTool with project-scoped Goal & Task management#44
cyzus merged 13 commits into
mainfrom
feat/task-and-goal

Conversation

@cyzus
Copy link
Copy Markdown
Owner

@cyzus cyzus commented May 31, 2026

Summary

  • Replaces the old PlanningTool/PlanModel/TaskModel system with a new project-scoped Goal and Task architecture
  • Backend: new GoalModel, TaskModel, CRUD routes (/project/goal, /project/tasks, /project/kanban), and agent tools (goal_tool, create_tasks, update_task, list_tasks). Goals and tasks are scoped to project_id with chat_id as the ownership/assignee marker
  • Frontend: replaces PlanView/usePlan with GoalTaskView + ProjectKanbanView + useGoalTasks context. The Goal tab shows current chat's checklist; the full project Kanban opens as a fullscreen overlay. Human-operable board with inline add, click-to-cycle status, and delete

Key design decisions

  • blocked status is derived at serialisation time from blocked_by array — not stored in DB or accepted as tool input
  • assignee = chat_id; displayed in kanban as shortened ID + chat title via deterministic unicode shape hash
  • plan_refresh SSE event triggers frontend refresh after stream ends
  • Right sidebar icon strip always visible at narrow viewports; hides entirely in new-chat state

Test plan

  • Agent creates goal and tasks via tools; they appear in Goal tab after stream
  • Project board opens fullscreen via "Board" button; shows all project tasks across chats
  • Human can add (Todo column only), cycle status, and delete tasks on the board
  • Switching chats closes the board overlay
  • New chat state hides the sidebar icon strip with no transition artifacts
  • Narrow viewport resize keeps icon strip visible

cyzus added 11 commits May 31, 2026 13:32
- Added goal_task_routes.py to handle API routes for goals and tasks, including fetching project goals, tasks, and kanban views.
- Removed plan_routes.py as its functionality is now covered by the new goal and task routes.
- Updated server.py to route requests to the new goal and task endpoints.
- Refactored streaming.py to remove plan-related logic and ensure it focuses on goal and task management.
- Created goal_tool.py for managing goals, including setting, pausing, resuming, and clearing goals.
- Implemented task_create_tool.py, task_list_tool.py, and task_update_tool.py for creating, listing, and updating tasks respectively.
- Updated plan_hooks.py to inject active goal and task information into system reminders.
- Removed planning_tool.py as its functionality has been replaced by the new goal and task tools.
- Updated registry.py to include new tools for goal and task management.
…grate project board functionality with task management, including task creation, updates, and deletions; enhance UI for better user experience and accessibility
…n): enhance button variants, integrate project task count, and improve UI components for better user experience
…button components for improved UI consistency and functionality
Copilot AI review requested due to automatic review settings May 31, 2026 17:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Replaces the legacy PlanningTool / PlanModel / TaskModel system with a project-scoped Goal + Task architecture. Backend introduces new ORM models, CRUD routes, and four agent tools (manage_goal, create_tasks, update_task, list_tasks), while the frontend swaps PlanView/PlanProgress/usePlan for a GoalTaskView sidebar plus a fullscreen ProjectKanbanView overlay backed by a new useGoalTasks context. Goals/tasks are keyed on project_id with chat_id used as owner/assignee; blocked is derived from blocked_by at serialization time. A destructive migration drops the old plans table and recreates tasks if it lacks project_id.

Changes:

  • New GoalModel/TaskModel + goals/tasks DB mixins, /project/goal, /project/tasks, /project/kanban routes, and goal/task agent tools.
  • Frontend rewrites: GoalTaskView, ProjectKanbanView, useGoalTasks provider, RightSidebar refactor (icon strip always visible, hidden in new-chat state), plan_refresh SSE just triggers refetch.
  • Destructive migration drops legacy plans/tasks tables; removes plan watcher and auto-completion in streaming.py; updates default_tools config.

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/suzent/tools/{goal_tool,task_create_tool,task_update_tool,task_list_tool}.py New agent tools for managing project goals/tasks
src/suzent/tools/planning_tool.py Removed legacy planning tool
src/suzent/tools/plan_hooks.py Rewrites reminder hook to surface goal + active tasks and increment turn counter
src/suzent/tools/registry.py Registers new tools, drops PlanningTool
src/suzent/streaming.py Removes plan watcher and auto-complete; plan_refresh becomes a bare refresh signal
src/suzent/server.py Replaces /plans and /plan with /project/goal, /project/tasks, /project/kanban
src/suzent/routes/plan_routes.py Removed
src/suzent/routes/goal_task_routes.py New CRUD endpoints for goals and tasks
src/suzent/plan.py Removed
src/suzent/database/{models,facade,init,chats,migrations}.py Replaces Plan/Task ORM with Goal/Task; adds destructive migration
src/suzent/database/{goals,tasks}.py New ORM mixins
src/suzent/database/plans.py Removed
src/suzent/config/init.py & config/default.example.yaml Updated default tool list
frontend/src/types/api.ts Adds Goal/Task types, drops Plan/PlanPhase
frontend/src/hooks/{usePlan,useGoalTasks}.ts Replaces plan context with goal/task context (chat+project scoped)
frontend/src/components/sidebar/{PlanView,GoalTaskView,ProjectKanbanView}.tsx Replaces PlanView with goal/task sidebar + fullscreen kanban
frontend/src/components/{PlanProgress,ChatWindow}.tsx Removes PlanProgress, wires new context, fullscreen board overlay
frontend/src/components/chat/RightSidebar.tsx Restructures sidebar layout, always-visible icon strip, hides in new chat
frontend/src/components/BrutalButton.tsx Adds dark variant
frontend/src/App.tsx Switches PlanProvider → GoalTasksProvider, refreshes both contexts on chat change
frontend/src/i18n/messages/{en,zh-CN}.ts Translation updates for new goal/task/board UI

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +130 to +153
async def create_project_task(request: Request) -> JSONResponse:
"""Create a task on a project (human-operated board).

Body JSON: { project_id, title, description?, status?, chat_id? }
"""
try:
body = await request.json()
project_id = body.get("project_id")
title = (body.get("title") or "").strip()
if not project_id or not title:
return JSONResponse(
{"error": "project_id and title are required"}, status_code=400
)
db = get_database()
task = db.create_task(
project_id=project_id,
title=title,
description=body.get("description") or "",
chat_id=body.get("chat_id"),
assignee=body.get("chat_id") or "human",
)
return JSONResponse(_task_to_dict(task), status_code=201)
except Exception as e:
return JSONResponse({"error": str(e)}, status_code=500)
Comment on lines +37 to +42
tasks: Annotated[
List[TaskInput],
Field(
description="List of tasks to create. Each task has title, description, and optional assignee/blocks/blocked_by."
),
],
Comment on lines +222 to +238
table_names = set(inspector.get_table_names())
needs_recreate = False
with self.engine.connect() as conn:
if "plans" in table_names:
conn.execute(text("DROP TABLE plans"))
conn.commit()
needs_recreate = True
if "tasks" in table_names:
task_cols = [col["name"] for col in inspector.get_columns("tasks")]
if "project_id" not in task_cols:
conn.execute(text("DROP TABLE tasks"))
conn.commit()
needs_recreate = True
if needs_recreate:
from sqlmodel import SQLModel

SQLModel.metadata.create_all(self.engine)
Comment on lines +51 to +56
if status is not None:
updates["status"] = status
if status == "completed":
updates["completed_at"] = datetime.now(timezone.utc)
elif status in ("pending", "in_progress"):
updates["completed_at"] = None
Comment on lines +127 to +128
const hasGoalContent = Boolean(goal !== null || tasks.length > 0);
const hasKanbanContent = Boolean(onProjectBoardChange);
Comment on lines +22 to +32
turns_info = ""
if goal.max_turns:
remaining = goal.max_turns - goal.turns_elapsed
turns_info = f" ({goal.turns_elapsed}/{goal.max_turns} turns used, {remaining} remaining)"
parts.append(f"[ACTIVE GOAL] {goal.objective}{turns_info}")
for sg in goal.subgoals:
parts.append(f" - {sg}")
parts.append(
"Evaluate: if the goal is achieved call manage_goal(action='clear'). Otherwise keep working."
)
db.update_goal(goal.id, turns_elapsed=goal.turns_elapsed + 1)
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 965b2b2838

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ctx: RunContext[AgentDeps],
status: Annotated[
Optional[
Literal["pending", "in_progress", "completed", "blocked", "cancelled"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return tasks when filtering for blocked status

When the agent calls list_tasks(status='blocked'), this schema advertises blocked as a valid filter value, but the database never stores that status; the API derives it from pending plus a non-empty blocked_by list in goal_task_routes._task_to_dict. Because the tool forwards the value to db.list_tasks, the query becomes TaskModel.status == 'blocked' and real blocked tasks are returned as “No tasks found.” Handle blocked specially or remove it from the accepted filter values.

Useful? React with 👍 / 👎.

] = None,
) -> ToolResult:
db = get_database()
task = db.get_task(task_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate task ownership before updating by id

In a chat linked to one project, a model can still call update_task with a task id from another project, for example from stale conversation history or a copied id. This fetches by global id and applies updates without comparing task.project_id to the current chat’s resolved project, so project-scoped task management can mutate another project’s board. Resolve the current project here and reject tasks whose project_id differs before calling db.update_task.

Useful? React with 👍 / 👎.

cyzus added 2 commits May 31, 2026 18:19
- goal_task_routes: honour status field from POST body on task creation
- task_create_tool: remove misleading 'assignee' mention from field description
- task_update_tool: clear completed_at on any non-completed status transition;
  add project ownership check before applying updates
- task_list_tool: handle derived 'blocked' status filter correctly
- plan_hooks: emit hard stop warning when max_turns budget is exhausted
- RightSidebar: remove unused hasKanbanContent variable
@cyzus cyzus merged commit 06e5d7e into main May 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants