Skip to content

Commit e90f733

Browse files
authored
Auto-clear completed task lists on new prompt (#512)
1 parent 84c313c commit e90f733

5 files changed

Lines changed: 73 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Auto-clear completed task lists: when a new prompt is sent and all tasks are done, the list is automatically cleared.
6+
57
## 0.142.2
68

79
- `/context` now shows where auto-compaction triggers: a `🔲` marker on the threshold cell of the grid plus an `Auto-compaction at N%` line.

resources/prompts/tools/task.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,3 @@ Workflow & Strict Execution Rules:
3636
4. SUBAGENTS & PARALLEL WORK: For sequential work, keep only one task `in_progress`; later tasks must stay pending until you start them. Multiple tasks may be `in_progress` only for concurrent separate workstreams (e.g. separate subagents). Only the main agent updates the task list.
3737
5. COMPLETION TIMING: Once a task is verified, close it as soon as possible. Do not delay completion just to align with other tasks.
3838
6. ADAPTABILITY: If completing tasks reveals follow-up work, use 'add' to append new tasks.
39-
7. CLEANUP: When all tasks are done and no further work remains, use 'clear'.

src/eca/features/chat.clj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
[eca.features.skills :as f.skills]
2222
[eca.features.tools :as f.tools]
2323
[eca.features.tools.mcp :as f.mcp]
24+
[eca.features.tools.task :as f.tools.task]
2425
[eca.llm-api :as llm-api]
2526
[eca.llm-providers.errors :as llm-providers.errors]
2627
[eca.llm-util :as llm-util]
@@ -1653,6 +1654,13 @@
16531654
_ (when (and seeded-default-trust? provided-chat-id)
16541655
(config/notify-fields-changed-only! {:chat {:select-trust true}} messenger db* chat-id))]
16551656
(logger/with-chat-context chat-id (:parent-chat-id base-chat-ctx)
1657+
(when-let [cleared-details (f.tools.task/auto-clear-completed! db* chat-id)]
1658+
(logger/info logger-tag "Auto-cleared completed task list" {:chat-id chat-id})
1659+
(lifecycle/send-content! base-chat-ctx :assistant
1660+
{:type :toolCalled
1661+
:server "eca"
1662+
:name "task"
1663+
:details cleared-details}))
16561664
(try
16571665
(prompt* params base-chat-ctx)
16581666
(catch Exception e

src/eca/features/tools/task.clj

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,23 @@
256256
[_name _arguments before-details result _ctx]
257257
(or (:details result) before-details))
258258

259+
;; --- Auto-clear ---
260+
261+
(defn ^:private all-tasks-done?
262+
"Returns true if the task list has tasks and all are :done."
263+
[state]
264+
(let [tasks (:tasks state)
265+
{:keys [done pending in-progress]} (status-counts tasks)]
266+
(and (pos? done) (zero? pending) (zero? in-progress))))
267+
268+
(defn auto-clear-completed!
269+
"Clear the task list when all tasks are done.
270+
Returns task-details of the cleared state if clearing occurred, nil otherwise."
271+
[db* chat-id]
272+
(when (all-tasks-done? (get-task @db* chat-id))
273+
(mutate-task! db* chat-id (fn [_] {:state empty-task}))
274+
(task-details empty-task)))
275+
259276
;; --- Operations ---
260277

261278
(defn ^:private op-read [_arguments {:keys [db chat-id]}]
@@ -502,7 +519,7 @@
502519
:items {:type "integer"}
503520
:description "Task IDs (required for start/complete/delete)"}
504521
:active_summary {:type "string"
505-
:description "Summary of what will be done in the current active session. Required for start operation."}
522+
:description "Summary of what will be done in the current active session. Required for start operation."}
506523
:task {:type "object"
507524
:description "Single task data (for add/update)"
508525
:properties {:subject {:type "string" :description "Task subject/title (required)"}

test/eca/features/tools/task_test.clj

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,48 @@
482482
result
483483
nil)]
484484
(is (= (:details result) details)))))
485+
486+
(deftest auto-clear-completed-test
487+
(testing "clears task list when all tasks are done"
488+
(let [db* (atom {:chats {"c1" {:task {:next-id 3
489+
:active-summary nil
490+
:tasks [{:id 1 :subject "T1" :description "D1" :status :done :priority :medium :blocked-by #{}}
491+
{:id 2 :subject "T2" :description "D2" :status :done :priority :medium :blocked-by #{}}]}}}})
492+
result (task/auto-clear-completed! db* "c1")]
493+
(is (some? result))
494+
(is (= :task (:type result)))
495+
(is (empty? (:tasks result)))
496+
(is (= {:next-id 1 :active-summary nil :tasks []}
497+
(task/get-task @db* "c1")))))
498+
499+
(testing "does not clear when tasks are still pending"
500+
(let [db* (atom {:chats {"c1" {:task {:next-id 3
501+
:active-summary nil
502+
:tasks [{:id 1 :subject "T1" :description "D1" :status :done :priority :medium :blocked-by #{}}
503+
{:id 2 :subject "T2" :description "D2" :status :pending :priority :medium :blocked-by #{}}]}}}})
504+
result (task/auto-clear-completed! db* "c1")]
505+
(is (nil? result))
506+
(is (= 2 (count (:tasks (task/get-task @db* "c1")))))))
507+
508+
(testing "does not clear when tasks are in progress"
509+
(let [db* (atom {:chats {"c1" {:task {:next-id 3
510+
:active-summary "working"
511+
:tasks [{:id 1 :subject "T1" :description "D1" :status :done :priority :medium :blocked-by #{}}
512+
{:id 2 :subject "T2" :description "D2" :status :in-progress :priority :medium :blocked-by #{}}]}}}})
513+
result (task/auto-clear-completed! db* "c1")]
514+
(is (nil? result))
515+
(is (= 2 (count (:tasks (task/get-task @db* "c1")))))))
516+
517+
(testing "does nothing when task list is empty"
518+
(let [db* (atom {:chats {"c1" {:task {:next-id 1 :active-summary nil :tasks []}}}})
519+
result (task/auto-clear-completed! db* "c1")]
520+
(is (nil? result))
521+
(is (= {:next-id 1 :active-summary nil :tasks []}
522+
(task/get-task @db* "c1")))))
523+
524+
(testing "does nothing when chat has no task list"
525+
(let [db* (atom {})
526+
result (task/auto-clear-completed! db* "c1")]
527+
(is (nil? result))
528+
(is (= {:next-id 1 :active-summary nil :tasks []}
529+
(task/get-task @db* "c1"))))))

0 commit comments

Comments
 (0)