הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

שבתות וחגים - מה קורה כשהמודל מנסה יותר מדי

11/10/2026

נתתי ל Sol את הפרומפט הבא כדי לבנות תוכנית פייתון שמדלגת על שבתות וחגים, למשל בשביל לא לשלוח מייל או לחייב לקוחות בשבת:

create a new python project with uv
  the app is a cron scheduler we should start it via a CLI
  cron.py cronfile

  cronfile is in the same format as any regular crontab EXCEPT it has a twist
  each line also has a boolean indicating if the request should execute in Jewish holidays and Saturdays
  (called "skip_shabatot_vehagim")

  If skip_shabatot_vehagim is set that line shouldn't execute on Jewish holidays and Saturdays with religious date
  boundaries

התוצאה היתה מרשימה. זה קובץ ה calendar.py שהוא יצר:

"""Protected intervals, starting the evening before each sacred civil date."""

import math
from dataclasses import dataclass
from datetime import UTC, date, datetime, timedelta
from functools import lru_cache
from zoneinfo import ZoneInfo

from astral import Observer, SunDirection
from astral.sun import sunset, time_at_elevation
from pyluach.dates import HebrewDate


class BoundaryError(ValueError):
    """The location has no calculable sunset or nightfall for a required date."""


@dataclass(frozen=True)
class ReligiousCalendar:
    timezone: ZoneInfo = ZoneInfo("Asia/Jerusalem")
    latitude: float = 31.778
    longitude: float = 35.235
    israel: bool = True
    start_offset_minutes: float = 0
    nightfall_degrees: float = 8.5
    nightfall_minutes: float | None = None

    def __post_init__(self) -> None:
        for name, value, lower, upper in (
            ("latitude", self.latitude, -90, 90),
            ("longitude", self.longitude, -180, 180),
            ("start offset", self.start_offset_minutes, 0, 180),
            ("nightfall degrees", self.nightfall_degrees, 1, 30),
        ):
            if not math.isfinite(value) or not lower <= value <= upper:
                raise ValueError(f"{name} must be between {lower} and {upper}")
        if self.nightfall_minutes is not None and (
            not math.isfinite(self.nightfall_minutes)
            or not 0 < self.nightfall_minutes <= 240
        ):
            raise ValueError("nightfall minutes must be greater than 0 and at most 240")

    @lru_cache(maxsize=1024)  # noqa: B019 - bounded cache of immutable configurations
    def protected_date(self, day: date) -> bool:
        if day.weekday() == 5:
            return True
        hebrew = HebrewDate.from_pydate(day)
        # Pyluach numbers Nissan as 1 and Tishrei as 7. Only holidays on
        # which work is prohibited are included, rather than minor holidays.
        sacred = {
            (7, 1),
            (7, 2),  # Rosh Hashanah (two days also in Israel)
            (7, 10),  # Yom Kippur
            (7, 15),
            (7, 22),  # Sukkot and Shemini Atzeret / Simchat Torah
            (1, 15),
            (1, 21),  # First and last days of Pesach
            (3, 6),  # Shavuot
        }
        if not self.israel:
            sacred |= {(7, 16), (7, 23), (1, 16), (1, 22), (3, 7)}
        return (hebrew.month, hebrew.day) in sacred

    @lru_cache(maxsize=1024)  # noqa: B019 - bounded cache of immutable configurations
    def interval(self, day: date) -> tuple[datetime, datetime]:
        observer = Observer(self.latitude, self.longitude)
        try:
            start = sunset(observer, day - timedelta(days=1), self.timezone)
            start = start.astimezone(UTC) - timedelta(minutes=self.start_offset_minutes)
            if self.nightfall_minutes is not None:
                end = sunset(observer, day, self.timezone).astimezone(UTC)
                end += timedelta(minutes=self.nightfall_minutes)
            else:
                end = time_at_elevation(
                    observer,
                    -self.nightfall_degrees,
                    day,
                    SunDirection.SETTING,
                    self.timezone,
                    with_refraction=False,
                ).astimezone(UTC)
        except ValueError as error:
            raise BoundaryError(
                f"cannot calculate religious boundaries for {day}"
            ) from error
        if end <= start:
            raise BoundaryError(f"invalid religious boundaries for {day}")
        return start, end

    def is_protected(self, when: datetime) -> bool:
        if when.tzinfo is None or when.utcoffset() is None:
            raise ValueError(
                "religious boundary checks require a timezone-aware datetime"
            )
        local_day = when.astimezone(self.timezone).date()
        instant = when.astimezone(UTC)
        # Yesterday covers a nightfall after midnight at high latitudes.
        # Tomorrow covers Friday and holiday-eve evenings. Intersecting
        # intervals naturally keep consecutive holy days protected.
        for delta in (-1, 0, 1):
            day = local_day + timedelta(days=delta)
            if self.protected_date(day):
                start, end = self.interval(day)
                if start <= instant < end:
                    return True
        return False

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

אם הייתי נותן ל AI להריץ איטרציית שיפור על הקוד הוא כנראה היה מבין שאין צורך להגדיר את רשימת החגים בתוך הפונקציה protected_date ואפשר להוציא אותה למשתנה של המודול כדי לשפר ביצועים ולקצר קצת את הקוד.

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

תיקון טוב במקרה הזה היה הפרומפט:

replace sunset calculation with a healthy safe buffer

זה הקובץ אחרי התיקון:

"""Conservative local-clock buffers around Shabbat and Jewish holidays."""

from dataclasses import dataclass
from datetime import UTC, date, datetime, time, timedelta
from functools import lru_cache
from zoneinfo import ZoneInfo

from pyluach.dates import HebrewDate


@dataclass(frozen=True)
class ReligiousCalendar:
    timezone: ZoneInfo = ZoneInfo("Asia/Jerusalem")
    israel: bool = True
    protection_start: time = time(15, 0)
    protection_end: time = time(23, 0)

    def __post_init__(self) -> None:
        for boundary in (self.protection_start, self.protection_end):
            if boundary.tzinfo is not None or boundary.second or boundary.microsecond:
                raise ValueError("protection times must be local HH:MM times")
        if self.protection_end < self.protection_start:
            raise ValueError("protection end must not be earlier than protection start")

    @lru_cache(maxsize=1024)  # noqa: B019 - bounded cache of immutable configurations
    def protected_date(self, day: date) -> bool:
        if day.weekday() == 5:
            return True
        hebrew = HebrewDate.from_pydate(day)
        # Pyluach numbers Nissan as 1 and Tishrei as 7. Only holidays on
        # which work is prohibited are included, rather than minor holidays.
        sacred = {
            (7, 1),
            (7, 2),  # Rosh Hashanah (two days also in Israel)
            (7, 10),  # Yom Kippur
            (7, 15),
            (7, 22),  # Sukkot and Shemini Atzeret / Simchat Torah
            (1, 15),
            (1, 21),  # First and last days of Pesach
            (3, 6),  # Shavuot
        }
        if not self.israel:
            sacred |= {(7, 16), (7, 23), (1, 16), (1, 22), (3, 7)}
        return (hebrew.month, hebrew.day) in sacred

    @lru_cache(maxsize=1024)  # noqa: B019 - bounded cache of immutable configurations
    def interval(self, day: date) -> tuple[datetime, datetime]:
        start = datetime.combine(
            day - timedelta(days=1), self.protection_start, self.timezone
        )
        end = datetime.combine(day, self.protection_end, self.timezone)
        return start.astimezone(UTC), end.astimezone(UTC)

    def is_protected(self, when: datetime) -> bool:
        if when.tzinfo is None or when.utcoffset() is None:
            raise ValueError(
                "religious boundary checks require a timezone-aware datetime"
            )
        local_day = when.astimezone(self.timezone).date()
        instant = when.astimezone(UTC)
        # Tomorrow covers the buffered eve. Overlapping intervals keep
        # consecutive holy days continuously protected.
        for delta in (0, 1):
            day = local_day + timedelta(days=delta)
            if self.protected_date(day):
                start, end = self.interval(day)
                if start <= instant < end:
                    return True
        return False

גם קוד פשוט יותר וגם עונה טוב יותר למטרה האמיתית של הפרומפט הראשון.

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

לקחים מפרק ג׳וניורים תעסוקה ואפוקליפסת AI של עושים תוכנה

10/10/2026

הקשבתי לעמית ודנה השבוע בעושים תוכנה, רוב הפרק היה מאוד מעניין ואני ממליץ להאזין. אלה הנקודות המרכזיות שאני מסכם לעצמי:

  1. המרחק בין ג'וניורים לסניורים התקצר - תעשיית התוכנה אף פעם לא היתה חזקה בלתגמל נסיון (הבדיחה כשגדלתי היתה שצריך לעשות אקזיט עד גיל 40 כי אחרי זה לא תהיה לך עבודה). עמית ודנה שיתפו, וגם אני שמתי לב לזה, שהיום נסיון נהיה אפילו הרבה פחות חשוב. דברים שפעם היית צריך שנתיים כדי ללמוד אנשים מצליחים להכיר ולהיות פרודוקטיביים בהם כבר אחרי כמה שבועות. ה AI מקצר את תהליך הלמידה ומקצר את הדרך מהיכרות שטחית להכנסת קוד פרודקשן למערכות.

  2. הרף עלה - לכו לקלוד ותגידו לו ליצור לכם מערכת X. התוצאה היא ה Baseline החדש. עמית אמר את זה בבירור בפרק, הוא לא היה מגייס בן אדם שחוזר אליו עם תוצאה ברמה שקלוד היה נותן. אז מה הציפייה? להיות ספציפיים, להכיר טכנולוגיה, לדחוף את ה AI לקצה, כשמגישים פיתרון להראות את עצמכם ואת האופי שלכם והבחירות שלכם בתוך קוד הפיתרון.

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

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

לפעמים קוד טוב עדיף על קוד ידידותי לסוכן

09/10/2026

אם החלטתם על Refactor כדי שהקוד יהיה יותר ברור ואחרי השינוי אתם מגלים שגם ל AI הרבה יותר קל לבנות פיצ'רים למערכת והוא עושה פחות טעויות אין שום בעיה.

אבל מה קורה אם עשיתם את אותו Refactor וגיליתם שהוא הביא את המערכת שלכם למצב שלכם יותר קל לקרוא אותה אבל לסוכן יותר קשה?

ניסוי שהראיתי לאחרונה בקורס חשף בדיוק את התופעה הזאת:

  1. הקוד החדש מכיל קבצים יותר קטנים, אפיון יותר נקי, בדיקות טובות יותר.

  2. עבודה על הקוד החדש לקחה יותר טוקנים.

לא משנה כמה הוספנו הסברים ב AGENTS.md או בסקילים בסוף קלוד היה צריך לעבור על יותר קבצים, לחפש ביותר מקומות וספציפית התיקון שהיינו צריכים היה יותר קשה לביצוע עם הקוד החדש.

האם זה אומר שהריפקטור מוטעה? אני חושב שלא. כל מבנה של קוד שנבחר יגרום לפיצ'רים מסוימים להיות קלים יותר ואחרים להיות קשים יותר. זה נכון גם לקוד של בני אדם. השיקולים בבחירת ארכיטקטורה לא נגמרים ב"מה קל ל AI".

רעיונות מסדרים, פתרונות נקודתיים

08/10/2026

האם יש אמת אחת שמתאימה לכל מערכות התוכנה או שלכל מערכת יש את הכיוון והאופי שלה?

אם אתם מאלה שחושבים שיש אמת אחת, דרך אחת שמתאימה לכולם, המשיכו לקרוא. הפוסט הזה לא בשבילכם.

נשארתם? יופי. אז אם אנחנו מסכימים שלמערכות שונות יש אופי שונה למה כולנו משתמשים באותם מודלים ושואלים אותם את אותן שאלות? איך המודל אמור לדעת מה האופי של המערכת לפני שמייצר את התשובה?

סוכני קידוד מנסים לעזור. הם נותנים למודל דרך לקרוא חלק מהקוד. הם מריצים Linters שאולי שומרים על Code Style מסוים. הם מריצים את הבדיקות. אבל זה רק חלק מהתמונה.

החלק השני הוא הרעיונות המסדרים. רעיונות כמו:

  1. מינימליזם - במערכת שלנו אנחנו בונים את הקוד הכי קטן שפותר את הבעיה. אנחנו לא רוצים לפתור בעיות נוספות ולא מוכנים לשלם אפילו מחיר קטן על התאמת הקוד לבעיות מהעתיד.

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

  3. תכנות דפנסיבי - המשתמשים שלנו משוגעים והמערכת שלנו צריכה להתאים לכל מצב. אנחנו כותבים קוד גמיש שתמיד מוצא את הדרך לעבוד ולא להתרסק.

  4. מהירות פיתוח - אנחנו סטארט-אפ שנאבק למצוא התאמת מוצר לשוק. זה ה Pivot השלישי שלנו. אנחנו בונים קוד שאפשר לשנות מהר, בלי סכימות, לא 100% עובד אבל בעיקר לא מסבך אותנו עם מיגרציות יקרות כשצריך לקחת צעד אחורה ולשנות מסלול.

  5. המפתח הכי צעיר שלנו עובד פה 8 שנים. כולם מכירים את הקוד לעומק ומבינים כל שורה וכל פסיק. אנחנו כותבים קוד קצר ומדויק ולא מבזבזים תווים על הערות או הסברים ארוכים.

הרשימה היא רק דוגמה - אתם בוחרים את הפילוסופיה של המערכת שלכם. הדבר החשוב שתהיה לכם אחת.

ואחרי שבחרתם תוכלו לשמור אותה בקובץ AGENTS.md תחת סעיף Philosophy או Coding Approach כדי לקבל מהסוכן הערות שמתאימות לאותה פילוסופיה לדוגמה:

This commit mostly matches the Philosophy section in AGENTS.md. The coupon list is stored as a JSON column instead of a new table, which fits "Development speed": no migration to undo if we pivot again. One deviation: the new CouponValidator handles currency mismatches and expired-but-grace-period coupons, neither of which exists in the product today. Per "Minimalism," I'd remove both branches and keep only the expiry check.

כשהגישה שלכם כתובה בצורה ברורה הרבה יותר קל לקלוד לענות על שאלות כמו "מה עשיתי לא בסדר" או "באיזו מידה הקוד החדש תואם את הפילוסופיה של המערכת".

ממצב תכנון למצב למידה

07/10/2026

בעקבות כמה המלצות הפסקתי לפני כמה חודשים להשתמש ב Plan Mode. במבחן התוצאה הניסוי היה מוצלח, התוצאות שקיבלתי היו ברמה דומה לתוצאות עם Plan Mode. יותר מזה, שמתי לב שעבורי סוכן חכם "מתכנן" שמעביר לסוכן טיפש למימוש לא ממש עובד. התוצאה תמיד יוצאת פחות טוב מאשר אם אני נותן לסוכן החכם גם לממש.

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

אבל ככל שהפסקתי להשתמש במצב תכנון שמתי לב שאני גם מפסיק לתכנן וכך כל משקל העבודה של הפיצ'ר עבר לשלב ה Code Review. פעם ראשונה שהגעתי ל Code Review עוד קראתי כל שורה אבל עם הזמן עברתי לקרוא פחות ופחות עד שכבר לא הכרתי את המוצר.

"מצב תכנון" ככפתור בתוך הסוכן הוא די מיותר. מספיק לכתוב בכותרת של הפרומפט "עכשיו מתכננים" והסוכן יודע לא לשנות קוד. אבל מצב תכנון כשיטת עבודה, עם פרומפט נכון, יכול להיות חלק מאוד חשוב מהתהליך:

  1. מתחילים ב grill me - מבקשים מהסוכן שישאל אתכם שאלות טכניות על הפיצ'ר כדי לוודא שכולם מיושרים. אפשר להתקין את הסקיל או לבקש בעצמכם.

  2. ממשיכים ב"סכם את כל מה שדיברנו לתוכנית", קוראים את התוכנית ורואים שאנחנו מבינים כל מקרה קצה. אפשר ורצוי לקחת את התוכנית לסוכן אחר או למודל אחר ולחפש עוד מקרי קצה והשפעות על כלל המערכת.

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

ביקשתי מ ChatGPT לכתוב לי כמה פרומפטים שאוכל להשתמש כדי להפוך את מצב תכנון למצב למידה, הנה ההצעות שקיבלתי:

“Build this plan with me in Learn mode. As you make each important decision, explain the concept behind it, the alternatives you considered, and why you chose this approach. Don’t just give me the finished plan.”

“Don’t immediately make the whole plan for me. At important decision points, ask me what I think we should do and why. Then critique my reasoning, teach me anything I’m missing, and continue building the plan.”

“Whenever we reach an important choice in the plan, ask me to predict what you would choose before telling me. After I answer, explain the correct reasoning and update the plan.”

“For each major section of the plan: first teach me the relevant concept briefly, then ask me 1–2 questions to check my understanding, then use it to make the actual planning decision.”

“Make the plan, but every few major decisions stop and test whether I understand what we’ve decided, why we decided it, and what tradeoffs we accepted. If I misunderstand something, teach it again before continuing.”

“While creating the plan, occasionally ask me to explain or defend a decision in my own words. Push back on weak reasoning like a good teacher would.”

“Start by explaining your reasoning in detail. As I demonstrate that I understand, give me more of the planning decisions to make myself. Step in when my reasoning has gaps.”

“After we finish the plan, don’t just summarize it. Test me on it: ask why the major choices were made, what alternatives we rejected, what assumptions the plan depends on, and what I’d change if those assumptions changed.”

“Teach me this plan by showing me the common mistakes someone might make at each stage. Ask me to identify the mistake before explaining it.”

“Use Learn mode. Help me produce the plan, but optimize equally for the quality of the plan and for my understanding. Teach concepts just before they become relevant, ask me to predict important decisions, periodically test my understanding, correct misconceptions, and gradually make me take over more of the reasoning. At the end, test whether I could reconstruct and adapt the plan without your help.”

“Plan this with me, not for me. Make sure I could explain every important decision by the end.”

אפשר להשתמש בפרומפט אחד מכאן או שילוב של כמה באותה תוכנית. הדבר החשוב הוא לצאת משלב התכנון עם הבנה מלאה של הבעיה כדי להגיע מוכנים וביקורתיים ל Code Review שאחרי.

נראה בסדר

06/10/2026

ביקשתי מ AI ליצור תוכנית דוגמה באיזה קורס וקיבלתי את הקוד הזה:

https://github.com/ynonp/googledics-ai-demo

יש פה כמה דברים שקל ממש לראות למה הם שבורים:

  1. כל הקוד יושב בקובץ App.tsx במקום חלוקה לקבצים

  2. מספרים קסומים מפוזרים באפליקציה (5 צבעים, 20 מסמכים אחרונים, הצגת מקסימום 4 משתמשים מחוברים, אורך מקסימלי של שם 32 תווים).

  3. טקסטים מפוזרים בקוד ולא בקובץ תרגום.

יותר קשה לזהות שהאפליקציה בנתה מנגנון Router קטן משלה שכנראה לא יגדל יפה, ש person.name מגיע מ localStorage וכולנו בטוחים שהוא מחרוזת אבל אם הוא מתקלקל שם אין מנגנון התאוששות והסוכן לקח עורך טקסט קוד פתוח בשם tiptap ומימש עליו שכבת עריכה משותפת במקום לקחת את ספריית העריכה המשותפת של טיפטפ שגם היא בקוד פתוח ומתוחזקת על ידי אותם אנשים.

דוגמה נוספת מאפליקציה אחרת היא השורה:

void this.local.whenSynced.then(()=>{if(!this.destroyed){if(navigator.onLine)this.provider.connect();else this.setStatus('offline');}});

כן שורה אחת במקור, ככה זה נראה מחולק לשורות:

void this.local.whenSynced.then(() => {
  if (!this.destroyed) {
    if (navigator.onLine) {
      this.provider.connect();
    } else {
      this.setStatus('offline');
    }
  }
});

פקודת void לפני הקריאה ל whenSynced תופסת את העין - פקודה זו מבטלת את כלל ה lint שתופס Promises שאין להן catch, כלומר הסוכן אומר פה לטייפסקריפט "עזוב אותי אני יודע מה אני עושה שום דבר לא יישבר פה". בתוך קובץ קוד כשה linter לא צועק קשה יותר לשים לב שאין טיפול בשגיאות.

כשלא מבינים מה זה עושה כל קוד "נראה בסדר".

תן לי רגע לעקוף את המגבלה

05/10/2026

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

הנה דוגמה טפשית משיחה שהיתה לי היום עם קלוד. אני ביקשתי:

  • הי קלוד תקרא את הדף בקישור ותסביר לי איך המנגנון עובד

קלוד נותן תשובה שמבוססת על גרסה ישנה של הדף. אז דחפתי אותו קצת:

  • לא זה עודכן מאז. אולי יש לך caching או משהו

ופה קלוד שמח על ההזדמנות להשתולל. הוא ענה:

"Working around a tool's restriction on fetching fresh content."

נשים לב שאנתרופיק החביאו את המשפט הזה בתוך האקורדיון של Ran 4 commands. הם לא רוצים שנדע מה עובר לקלוד בראש. אני הייתי שמח לקרוא גם את כל טוקני החשיבה שהיו שם אבל אותם אנתרופיק בכלל לא משתפים. ההמשך היה אפילו יותר מעניין:

cd /tmp && curl -sL -H "Cache-Control: no-cache" -H "Pragma: no-cache" "https://demo.ynonperek.com/test?nocache=$(date +%s)" -o page.html -w "%{http_code}\n" && python3 -c "
import re,html
s=open('page.html',encoding='utf-8').read()
s=re.sub(r'<(script|style)[^>]*>.*?</\1>','',s,flags=re.S)
s=re.sub(r'<svg.*?</svg>','[SVG]',s,flags=re.S)
s=re.sub(r'<br\s*/?>|</(p|h\d|li|div|section|label|button|a)>','\n',s)
s=re.sub(r'<[^>]+>','',s)
s=html.unescape(s)
s='\n'.join(l.strip() for l in s.splitlines() if l.strip())
print(s)
"

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

מה אפשר ללמוד כאן?

  1. כשאנחנו בונים סוכן חשוב שהכלים ישלימו אחד את השני ולא יתנגשו. אם החלטתם שהמערכת שלכם מאפשרת קריאה רק של דפי אינטרנט מתוך cache תקפידו שכל קריאת רשת תעבוד דרך אותו cache. אם אתם דווקא בסדר עם זה שהסוכן יקרא גרסאות עדכניות של דפים תוסיפו פרמטר של "מוכן לקבל עותק מ cache" לכלי של קריאת הרשת. בצורה כזאת תחסכו טוקנים וזמן עבודה.

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

איפה זה משאיר אותנו? מיומנויות ליבה בעבודה עם סוכני קידוד

04/10/2026

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

סט מיומנויות ראשון קשור לארכיטקטורה של מערכות תוכנה. ספר התכנות הראשון שהתבקשתי לקנות באוניברסיטה נקרא Structure and Interpretation of Computer Programs והוא כולו עסק בשאלות האלה, מהו המבנה הנכון של מערכות תוכנה. בשנים מאז יצא מערכות תוכנה הלכו והשתכללו, הלכו והסתבכו והשאלות לגבי המבנה הנכון שלהן רק הפכו קשות יותר. אלה שאלות שאין להן תשובה אחת נכונה והמפתח לגשת אליהן הוא לחפש את האיזונים, מה מרוויחים ומה מפסידים בכל בחירה.

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

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

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

אני מחלק את מיומנויות ה AI שאנחנו רוצים לשפר ל-4 קבוצות. כל קבוצה מורכבת מאוסף גדול של מיומנויות והמיומנויות בכל קבוצה משתנות עם הזמן ככל שמודלים וכלי העבודה משתפרים. ציר ה-X לא נעלם, הוא השתנה. היום לתרגם מבנה מערכת לתוכנית מחשב פירושו לגרום לסוכן לעשות את זה נכון, וזה מה שארבע הקבוצות מתארות.

המשך קריאה

האם הסוכן הבודק שלכם הוא רק התחמקות מהחלטה?

03/10/2026

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

מנגנון אחד משתמש בכלים דטרמניסטיים - מריצים בדיקת אבטחה סטטית על הקוד, Linter, בדיקות אוטומטיות, מטריקות של איכות קוד, תקציב לגודל ה JavaScript, ונותנים לסוכן לרוץ בלולאה עד שה CI עובר. סוכנים אוהבים לולאות ואחרי כמה שעות יש סיכוי טוב שתקבלו קוד שעובר את כל הכללים שהגדרתם. מיטיבי לכת יגבילו את מספר הנסיונות של הסוכן כדי שלא יגמור לכם את המנוי על פיצ'ר אחד שהוא לא יודע לבנות.

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

  1. אי אפשר לדעת מתי PR יאושר או מה תהיה האיכות שלו בסוף.

  2. אי אפשר לדעת כמה טוקנים זה יעלה.

  3. כמעט תמיד נדרשת התערבות אנושית כדי לצאת מהלופ.

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

ניסוי paseo - הפעלת סוכנים ב Docker Sandbox

02/10/2026

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

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

מה עושים? כאן paseo.sh נכנס לתמונה. פסאו הוא כלי לניהול סוכנים שתוך בסוכנים שרצים על מכונות מרוחקות או בסביבות וירטואליות ומראה את כל הסוכנים במסך ניהול אחד. בדיוק מה שאנחנו צריכים בשביל לתכנת בצורה נוחה.

פסאו גם יוצר git worktree אוטומטית דרך כפתור "הקמת סביבה" המובנה שלו מה שנותן לנו שיטת עבודה קלה ובטוחה:

  1. מתוך paseo כל פעם שרוצים סביבה מקבילית נוספת לוחצים על יצירת סביבה.
  2. פסאו יוצר worktree חדש.
  3. נכנסים לסוכן בתוך הסביבה החדשה ומבקשים מ paseo שהסוכן הזה ירוץ בתוך Docker Sandbox.

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

מה צריך? בסך הכל לא הרבה.

מתקינים Docker Sandbox מכאן: https://docs.docker.com/ai/sandboxes/

מתקינים paseo מכאן: https://paseo.sh/

ומוסיפים לקובץ ההגדרות של paseo שנקרא ~/.paseo/config.json את הבלוק הבא שמגדיר סוכן שרץ בתוך ארגז חול של דוקר:

  "agents": {
    "providers": {
      "sandbox-codex": {
        "extends": "codex",
        "label": "Codex in sandbox",
        "command": [
          "bash",
          "-c",
          "set -eu; if [ -z \"${PASEO_AGENT_CWD:-}\" ]; then exec sbx exec -i paseo-codex-discovery codex \"$@\"; fi; workspace=\"$PASEO_AGENT_CWD\"; sandbox=\"paseo-$(printf '%s' \"$workspace\" | cksum | awk '{print $1}')\"; sbx run --detached --name \"$sandbox\" codex \"$workspace\" >&2; exec sbx exec -i --workdir \"$workspace\" \"$sandbox\" codex \"$@\"",
          "paseo-sandbox-codex"       
        ]
      }
    }
  }

קצת מכוער אני יודע כי כל החיבורים אצלנו אבל עובד ממש יופי.

חסרונות? גם יש:

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

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

  3. עבודה עם git worktree יכולה להיות מבלבלת. ככל שעובדים יותר במקביל קל לאבד ריכוז ולשכוח על איזה ענף אתם. פסאו עוזר בכך שכל סביבת עבודה היא worktree והיא יושבת בטאב אחר, אבל עדיין צריך לזכור שפיצ'ר מסוים נבנה בסביבה אחרת ולא נעלם פתאום.