דלגו לתוכן

פייפליינים ואוטומציה

פייפליינים הם החצי הדטרמיניסטי של Control Center. סוכן מחליט מה לומר; פייפליין מחליט מה יקרה בהמשך, באותו אופן בכל פעם. פייפליין הוא DAG של שלבים שיכולים לשלוח סוכנים, להריץ סקריפטים, להסתעף לפי תנאי, להתפצל על פני אוסף ולהעביר נתונים בין שלבים — נשמר, ניתן לחידוש ומתומחר.

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

תבנית (PipelineDefinition) היא ה-DAG הדקלרטיבי: שלבים, קלטים מוצהרים ותצורה. היא מתגוררת במסד הנתונים של סביבת העבודה עצמה. הרצה (PipelineRun) היא ביצוע אחד של אותה תבנית, והיא נושאת מאגר מצב ניתן לשינוי, סכומי עלות וטוקנים וסטטוס לכל שלב.

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

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

  • שלב שנמצא במצב suspended יותר מ-suspendedStepTimeout (24 שעות כברירת מחדל) ממתין לכרטיסים שלעולם לא יגיעו למצב סופי, ולכן הוא מוכשל כדי לשחרר את ההרצה. הפטור הוא extras['awaitApproval'] == true — זהו שער האישור החלקי orchestration.awaitApproval. human.gate אינו פטור.
  • שלב שהוצהר עם extras: {'idempotent': false} והיה באמצע ריצה כשהשרת נעצר עשוי כבר להחיל את תוצאת הלוואי שלו (מיזוג squash, ניקוי עץ עבודה). הרצה חוזרת שלו עלולה להחיל אותה פעמיים, ולכן המנוע מכשיל אותו ומשאיר את הניסיון החוזר לכם.

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

הסוג (StepKind) אומר למנוע מתי שלב נעשה בר-הרצה ביחס לשלבים שבמעלה הזרם — זה הדבר היחיד שהמתזמן צריך לדעת. ישנם שישה: trigger, listen, join, router, forEach ו-terminal. הגוף (bodyKey) הוא ה_עבודה_ שהשלב מבצע לאחר שתוזמן. גופים הם סגירות הרשומות לפי מפתח עם עליית השרת, כך ששלבים רבים מסוגים שונים יכולים לחלוק גוף אחד: כמה שלבי listen שמריצים כולם conversation.promptAgent, למשל.

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

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

  • שלב שה-bodyKey שלו אינו רשום מפיק אזהרה בעת טעינת התבנית ואז לעולם אינו מבוצע. שום דבר לא נכשל בקול רם בזמן ריצה. שלבי trigger ו-terminal פטורים מהאזהרה הזו: הטריגר עדיין מריץ את גוף ה-pipeline.trigger שאינו עושה דבר, כדי שמאזינים במורד הזרם יופעלו; שלב terminal אינו בעל גוף והוא זקיף של המתזמן.
  • מפתחות במפת ה-extras של צומת הם בהתאמה מדויקת, ואחרת מתעלמים מהם בשקט. רק extras['spaceId'] מציב את עבודתו של שלב סוכן בשיחה קיימת. extras['mode'] (chat / plan / review) נקרא בצומת שפותח את החדר (messaging.createSpace, ברירת מחדל review) — שלב סוכן מאוחר יותר אינו יכול להעניק מחדש הרשאה לשיחה שהוא הצטרף אליה. כל איות אחר של מי מהמפתחות מתעלמים ממנו.

שלבים לא קוראים זה לזה. הם קוראים ממאגר מצב משותף של ההרצה וכותבים אליו: שלב קורא את הקלטים שלו לפי מפתח, מבצע, וכותב את הפלט שלו חזרה תחת ה-outputKey שלו. StateReducer ממזג כתיבות מקבילות כשענפים מקבילים מכוונים לאותו מפתח, ו-TemplateRenderer מחליף מחזיקי מקום {{...}} בהנחיות ובסקריפטים מאותו מצב.

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

רשומות PipelineTrigger מצהירות מתי תבנית מופעלת אוטומטית. ישנם ארבעה סוגים:

טריגר מפעיל הרצה כאשר
אירוע תחום אירוע תחום תואם נורה — למשל PullRequestPublished או TicketAssigned
לוח זמנים מגיע מועד של ביטוי cron/מרווח
ידני אתם מריצים אותו ביד; נוכחותו של טריגר ידני מופעל היא מה שמציג את התבנית בבורר ההרצה
Webhook מערכת חיצונית שולחת POST אל /webhooks/<token> בפורט ה-HTTP של השרת

טריגרי אירוע הם קבוצה סגורה: 16 סוגי אירועים מוכרים למפעיל ומוצעים בבורר (EventPayloadMapper.knownEventTypes), ושום דבר אחר אינו יכול להתחיל פייפליין. לארבעה-עשר מתוך 16 האלה יש מיפוי מטען. לשניים — TicketCreated ו-TicketStatusChanged — אין כזה, ולכן טריגר על מי מהם נשאר מופעל ולעולם אינו נורה. ראו אירועי תחום לרשימה המלאה.

אסימון ה-webhook משמש גם כסוד ה-HMAC: הכותרת X-Hub-Signature-256: sha256=<hex> של הבקשה מאומתת מול HMAC-SHA256(body, token), משלוחים שאינם מאומתים נדחים ואינם ניתנים לשליחה חוזרת, וכפילויות נזרקות על ידי dedupe_key. מכיוון שרשומות טריגר ייחודיות לפי (סביבת עבודה, סוג אירוע, תבנית), תבנית יכולה להחזיק לכל היותר טריגר webhook אחד וטריגר אחד לכל סוג אירוע תחום.

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

התבניות שמגיעות מושבתות

קטע בשם “התבניות שמגיעות מושבתות”

שלוש-עשרה תבניות פייפליין נזרעות לכל סביבת עבודה, ושמונה מהן מגיעות מושבתות: external_pr_welcome, cross_review, ticket_to_pr, pr_triage, pre_merge_gate, release_notes, dep_audit ו-pr_digest.

זה החלק שמפתיע אנשים. הטריגרים שלהן נזרעים מופעלים, אבל PipelineEngine.start מסרב לתבנית מושבתת ומחזיר null — כך שהטריגר נורה אל תוך כלום ולא מופיעה שום הרצה. התבנית, ולא הטריגר, היא המתג. הפעילו אותה במסך הגדרות ← סביבת עבודה ← תבניות פייפליין לפני שתצפו שמי מהשמונה האלה יעשה משהו.

זריעה מחדש של סביבת עבודה משמרת את מה שבחרתם: הגדרת ה-isEnabled שלכם ורשומות הטריגר הקיימות שורדות. יש חריג מכוון אחד בדיוק — לוח הזמנים היומי של pr_merged_cleanup מופעל בכוח גם אם היה כבוי קודם, מפני שזהו איסוף אשפה. רשומות עצי עבודה, מחיצות של גרף הקוד ועותקים copy-on-write מצטברים בלעדיו, וזה מעולם לא נועד להיות בחירה של המשתמש.

עוד א-סימטריה אחת שכדאי להכיר: הפייפליין המובנה pr_review כבר לא מפרסם ל-GitHub, במכוון. פרסום סקירה הוא שלב נפרד שעובר בשער של משתמש, ופייפליין שמפרסם ישירות יפרסם פעמיים. cross_review, ticket_to_pr ו-pr_triage עדיין מפרסמים הערה ישירות ל-GitHub כשהם רצים.

PipelineEngine יוצר את רשומת ההרצה, מתזמן את צומת ההתחלה שמתאים לסוג האירוע של ההרצה, ואז צועד ב-DAG: כששלבים מסתיימים הוא מעריך טריגרים במורד הזרם, מכריע ענפי router, ממתין ל-join, מיישם מדיניות ניסיון חוזר ו-continue-on-fail, ושומר מצב, עלות ושגיאות בכל שלב. קשתות שיוצאות מ-start אחר מתעלמים מהן בהרצה הזו, כך שהפעלה מתוזמנת אינה ממתינה לתנאי המיועד להרצה ידנית בלבד.

מה שהוא לא יודע הוא שום דבר הקשור לכרטוס, שליחה, GitHub או הודעות. נקודת ההרחבה היחידה שלו היא ה-PipelineBodyRegistry. הגופים הרשומים שם הם שמחזיקים את AgentDispatchPort, MessagingPort ואחיהם — ולכן הוספת יכולת לפייפליינים לעולם אינה מחייבת שינוי של המתזמן.

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