7. מערכת משימות

"פרקו מטרות גדולות למשימות קטנות, סדרו אותן, שמרו לדיסק"

25 דקות קריאה
💡חדש בנושא?

מה זה גרף משימות (DAG)?

גרף מכוון ללא מחזורים שבו כל משימה יכולה לרשום משימות אחרות שהיא תלויה בהן (blockedBy). משימה 4 לא יכולה להתחיל עד שמשימות 2 ו-3 הסתיימו. זה מדגים תלויות בין פריטי עבודה בעולם האמיתי.

למה לשמור משימות לדיסק במקום לזיכרון?

דחיסת הקשר (סשן דחיסת הקשר) יכולה למחוק את רשימת ה-todo בזיכרון. משימות שנשמרות כקבצי JSON על הדיסק שורדות דחיסה, קריסות, ואפילו העברות בין סוכנים — כל סוכן יכול להמשיך מהנקודה שבה אחר עצר.

מה פירוש 'ready' עבור משימה?

משימה היא ready כשהסטטוס שלה הוא 'pending' וכל המשימות ברשימת ה-blockedBy שלה הן 'completed'. ה-TaskManager מחשב משימות מוכנות אוטומטית כך שהסוכן פשוט שואל 'מה אני יכול לעשות עכשיו?'

הבעיה

ה-TodoManager של סשן TodoWrite הוא רשימת תיוגים שטוחה בזיכרון: ללא סדר, ללא תלויות, ללא סטטוס מעבר ל-done-or-not. למטרות אמיתיות יש מבנה — משימה B תלויה במשימה A, משימות C ו-D יכולות לרוץ במקביל, משימה E מחכה לשתי C ו-D.

ללא קשרים מפורשים, הסוכן לא יכול לדעת מה מוכן, מה חסום, או מה יכול לרוץ בו-זמנית. ומכיוון שהרשימה חיה רק בזיכרון, דחיסת ה-context (סשן דחיסת הקשר) מוחקת אותה לחלוטין.

הפתרון

קדמו את רשימת התיוגים לגרף משימות השמור לדיסק. כל משימה היא קובץ JSON עם סטטוס ותלויות (blockedBy). הגרף עונה על שלוש שאלות בכל רגע:

  • מה מוכן? — משימות עם סטטוס pending ו-blockedBy ריק
  • מה חסום? — משימות שמחכות לתלויות שלא הסתיימו
  • מה נסגר? — משימות completed, שסיומן מבטל אוטומטית חסימת תלויות
.tasks/
  task_1.json  {"id":1, "status":"completed"}
  task_2.json  {"id":2, "blockedBy":[1], "status":"pending"}
  task_3.json  {"id":3, "blockedBy":[1], "status":"pending"}
  task_4.json  {"id":4, "blockedBy":[2,3], "status":"pending"}

Task graph (DAG):
  1 --> 2 --> 4
  1 --> 3 --> 4

When task 1 completes: 2 and 3 become ready.
When 2 and 3 complete: 4 becomes ready.

איך זה עובד

  1. כל משימה היא קובץ JSON. ה-TaskManager קורא את כל הקבצים ובונה את הגרף בזיכרון.
import json
from pathlib import Path

class TaskManager:
    def __init__(self, tasks_dir: str = ".tasks"):
        self.dir = Path(tasks_dir)
        self.dir.mkdir(exist_ok=True)

    def _load_all(self) -> list:
        tasks = []
        for f in sorted(self.dir.glob("task_*.json")):
            tasks.append(json.loads(f.read_text()))
        return tasks

    def create(self, title: str, blocked_by: list = None) -> dict:
        tasks = self._load_all()
        new_id = max((t["id"] for t in tasks), default=0) + 1
        task = {"id": new_id, "title": title,
                "status": "pending", "blockedBy": blocked_by or []}
        path = self.dir / f"task_{new_id}.json"
        path.write_text(json.dumps(task, indent=2))
        return task

    def complete(self, task_id: int) -> str:
        path = self.dir / f"task_{task_id}.json"
        task = json.loads(path.read_text())
        task["status"] = "completed"
        path.write_text(json.dumps(task, indent=2))
        return f"Task {task_id} completed."

    def ready(self) -> list:
        tasks = self._load_all()
        done_ids = {t["id"] for t in tasks if t["status"] == "completed"}
        return [
            t for t in tasks
            if t["status"] == "pending"
            and all(dep in done_ids for dep in t.get("blockedBy", []))
        ]
  1. הסוכן מקבל שלושה כלים: create_task, complete_task ו-list_ready_tasks.
TASK_MANAGER = TaskManager()

TOOL_HANDLERS = {
    # ...base tools...
    "create_task": lambda **kw: str(TASK_MANAGER.create(
        kw["title"], kw.get("blocked_by", []))),
    "complete_task": lambda **kw: TASK_MANAGER.complete(kw["task_id"]),
    "list_ready_tasks": lambda **kw: json.dumps(TASK_MANAGER.ready(), indent=2),
}
  1. תהליך העבודה של הסוכן הופך ל: יצירת גרף המשימות, ואז עבודה דרך משימות מוכנות אחת אחת.
Agent: "I need to refactor auth module."

1. create_task("Read current auth code")            -> id=1
2. create_task("Write new AuthManager",  blocked_by=[1]) -> id=2
3. create_task("Update tests", blocked_by=[2])      -> id=3
4. create_task("Update docs",  blocked_by=[2])      -> id=4

list_ready_tasks() -> [task 1]

[works on task 1]
complete_task(1)

list_ready_tasks() -> [task 2]
...

מה השתנה מ-דחיסת הקשר

רכיבלפני (דחיסת הקשר)אחרי (מערכת משימות)
תכנוןרשימת תיוגים בזיכרוןגרף משימות שמור לדיסק
תלויותאיןרשימת blockedBy
מקביליותעוקבת בלבדזיהוי מפורש של מקביל-מוכן
שורדכלוםדחיסה, קריסות, העברות

מסקנה מרכזית

שמירת משימות לדיסק היא מה שהופך את תוכניות הסוכן לעמידות. גרף המשימות מקודד לא רק מה לעשות אלא באיזה סדר ומה יכול לרוץ במקביל. בשילוב עם דחיסת הקשר, הסוכן יכול לעבוד על מטרות גדולות באמת לאורך סיבובים רבים מבלי לאבד עקב.

מדריך קוד אינטראקטיבי

CRUD משימות ופתרון תלויות
1class TaskManager:
2 def __init__(self, tasks_dir: str = ".tasks"):
3 self.dir = Path(tasks_dir)
4 self.dir.mkdir(exist_ok=True)
5 
6 def _load_all(self) -> list:
7 tasks = []
8 for f in sorted(self.dir.glob("task_*.json")):
9 tasks.append(json.loads(f.read_text()))
10 return tasks
11 
12 def create(self, title: str, blocked_by: list = None) -> dict:
13 tasks = self._load_all()
14 new_id = max((t["id"] for t in tasks), default=0) + 1
15 task = {"id": new_id, "title": title,
16 "status": "pending", "blockedBy": blocked_by or []}
17 path = self.dir / f"task_{new_id}.json"
18 path.write_text(json.dumps(task, indent=2))
19 return task
20 
21 def ready(self) -> list:
22 tasks = self._load_all()
23 done_ids = {t["id"] for t in tasks if t["status"] == "completed"}
24 return [
25 t for t in tasks
26 if t["status"] == "pending"
27 and all(dep in done_ids for dep in t.get("blockedBy", []))
28 ]
29 
כל מופע TaskManager מצביע לתיקיית .tasks/. mkdir(exist_ok=True) פירושו שהקריאה הראשונה יוצרת את התיקייה אוטומטית — אין צורך בשלב הגדרה נפרד.
שלב 1 מתוך 4
🧪 נסו בעצמכם

צרו גרף משימות עם 5+ משימות ולפחות 2 שרשראות תלויות. הריצו get_ready_tasks כדי לראות מה יכול לרוץ.

רמז

השתמשו ב-add_dependency לקישור משימות, ואז בדקו לאילו כל התלויות מסופקות

מצאתם טעות? דווחו ←