דלגו לתוכן

סביבות עבודה ובידוד

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

סביבת עבודה טיפוסית ממופה לפרויקט אחד או לבסיס קוד אחד. ייתכן שיהיו לכם סביבות עבודה נפרדות עבור “api-service”, “frontend-v2” ו-“infrastructure”.

נתונים בהיקף סביבת העבודה

קטע בשם “נתונים בהיקף סביבת העבודה”

השורות של סביבת עבודה יושבות בקובץ ה-SQLite של אותה סביבת עבודה עצמה, בנתיב <dataDir>/<workspaceId>/workspace.db. כל סביבת עבודה מקבלת תיקייה ולא קובץ חשוף, מפני שסביבת עבודה צוברת יותר ממסד נתונים — עצי עבודה לכל שיחה, קובצי הסוכנים והמיומנויות ופרטי הגישה של בוט הצ’אט — ומתן תיקייה לה פירושו שכל זה חי ונמחק יחד.

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

רוב השורות האלה נושאות עמודת workspaceId; קבצי מסד נתונים נפרדים אוכפים את הבידוד. ראו את אינואריאנטת הבידוד בהמשך.

מאגרים הם בהיקף סביבת עבודה; זהות בין סביבות עבודה היא לפי נתיב

קטע בשם “מאגרים הם בהיקף סביבת עבודה; זהות בין סביבות עבודה היא לפי נתיב”

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

מוסיפים מאגר לסביבת עבודה בהגדרות ← סביבת עבודה ← מאגרים. רישום של אותו checkout בשתי סביבות עבודה יוצר שתי שורות בלתי תלויות עם שני מזהים; זהותו של מאגר בין סביבות עבודה נקבעת לפי נתיב במערכת הקבצים (או לפי owner/name ב-GitHub), ולעולם לא לפי מזהה.

הרישום קפדני: הנתיב חייב להיות בתוך עץ עבודה של git, לחיבור חייב להיות remote בשם origin, וה-remote הזה חייב להצביע על מארח קוד נתמך (github.com, gitlab.com או bitbucket.org). מארח הקוד נקרא מה-remote ונשמר על המאגר, כך שסביבת עבודה אחת יכולה להחזיק מאגרים מכל השלושה. כל דבר אחר נדחה. דפדפן התיקיות שמתוכו בוחרים מציג את מערכת הקבצים של מארח השרת, והוא מוגבל לרשימת ההיתרים --repo-roots של השרת (שברירת המחדל שלה היא תיקיית הבית של משתמש השרת).

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

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

דליפות נתונים בין סביבות עבודה מטופלות כבאגים. הבידוד הוא מבני, והוא נאכף בכמה רבדים:

  • שכבת מסד הנתונים. לכל סביבת עבודה יש קובץ SQLite משלה, המסופק על ידי WorkspaceDatabaseManager.of(workspaceId). מסד נתונים של סביבת עבודה אינו מצהיר על users, workspaces או טבלאות של סביבת עבודה אחרת, ולכן קריאה חוצת סביבות עבודה אינה באג שאפשר לכתוב — היא לא מתקמפלת. מאגרים מפענחים את ה-DAO שלהם בכל קריאה במקום לשמור אחד במטמון, ומכיוון שכל מתודה של מאגר מקבלת workspaceId חובה, אי אפשר להסיק את סביבת העבודה ולא להחיל עליה ברירת מחדל.
  • מזהה סביבת העבודה הוא מקטע נתיב. מכיוון שהמזהה הופך לשם תיקייה, עליו להתאים לתבנית בטוחה של מקטע יחיד (התחלה בתו אלפאנומרי, ואחריו תווים אלפאנומריים, נקודות, קווים תחתונים ומקפים; בלי ..). המזהים הם UUID בכל המוצר. כל דבר אחר זורק חריגה במקום לעבור ניקוי — זהו אמצעי ההגנה מפני מעבר בנתיב (path traversal), והוא נכשל בקול רם במקום לכתוב את הנתונים של סביבת עבודה לנתיב שמישהו אחר בחר.
  • הגנה בעומק. עמודות ה-workspaceId עדיין שם ועדיין נכתבות. מיותרות בתוך קובץ, הן משאירות את טריגרי פיד הסנכרון, אינדקסי FTS וצורות השורות הקיימות ללא שינוי, והן הופכות קובץ לכזה שמתאר את עצמו כאשר בוחנים אותו לבדו — וזה מה שייצוא וייבוא נשענים עליו.
  • שכבת התחום. שירותים שמשנים ישויות לפי מזהה מאמתים entity.workspaceId == workspaceId בצוואר בקבוק יחיד לפני שהם ממשיכים, וזורקים WorkspaceMismatchException באי-התאמה — הכשל הרועש עבור מזהה שהגיע מההקשר הלא נכון. סירוב בקול רם חשוב: פעולה שקטה שאינה עושה דבר מסתירה את הבאג, והמשך הפעולה מדליף.
  • שכבת ה-RPC. ה-dispatcher של repo-RPC הוא צוואר הבקבוק. כל פעולה בהיקף סביבת עבודה חייבת לשאת workspace_id בארגומנטים שלה עצמה — אין “סביבת עבודה נוכחית” לפי סשן שאפשר להדליף, ומזהה לא מוכר נדחה כ“לא נמצא” לפני שמשהו פותח מסד נתונים, כך שמזהה שגוי אינו יכול להפיק workspace.db רפאים. פעולות גלובליות באמת (עדכוני החדשות, תור הצי) מצהירות על עצמן כלא-תחומות כהחלטה מפורשת שנבדקה. כלי MCP נוהגים לפי אותו כלל: כל כלי שנוגע בנתונים בהיקף סביבת עבודה דורש workspace_id.
  • הפיזור בין סביבות עבודה ניתן למנייה. מענה על שאלה לגבי כמה סביבות עבודה פירושו פתיחה של כמה מסדי נתונים. הפיזור הזה מצומצם לעוזר אחד, CrossWorkspaceQueries, כך שהרשימה המלאה של הדברים שחוצים את הגבול באופן לגיטימי יושבת בנקודות הקריאה של קובץ אחד במקום להתפזר מאחורי הערות תיעוד. נקודות הקריאה האלה הן: צבירה ברמת השרת, מפייסי אתחול, סריקות שימור ו-GC, גיבוי, מנתבי אירועים — ובדיקת החברות שלהלן.
  • גישה לפי מזהה בלבד אינה מספקת. שליפת ישות לפי ה-UUID שלה אינה מוכיחה שהיא שייכת לסביבת העבודה של הקורא. פיצול הקבצים הופך את הקובץ הלא נכון לבלתי נגיש; אימות שכבת התחום תופס מזהה שגוי שמגיע דרך קובץ לגיטימי.

החברות, ולא מפתח הצימוד, היא שמעניקה גישה

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

החזקה של פרטי גישה של מכשיר מאמתת מכשיר. היא אינה מקנה הרשאה לשום דבר בתוך סביבת עבודה. כל פעולה בהיקף סביבת עבודה מפענחת את תפקיד החברות של הקורא ואוכפת רצפה הנגזרת מסוג הפעולה — קריאה דורשת guest, שינוי דורש member, ופעולה הרסנית דורשת admin — ופעולות שחושפות תוכן של מאגר בודקות בנוסף את ההענקה של הקורא לפי מאגר. מפתח צימוד תקף בלי חברות מקבל Not a member of this workspace.

המשמעות לבידוד אלגנטית: שורות החברות יושבות בקובץ של סביבת העבודה עצמה, ולכן “מי נמצא בסביבת העבודה הזו?” היא קריאה תחומה רגילה. השאלה ההפוכה — “באילו סביבות עבודה אני נמצא?” — פרושה מעצם הגדרתה על פני קבצים, ולכן היא אחד הפיזורים הבודדים המאושרים של CrossWorkspaceQueries, והיא מה שבורר סביבות העבודה מריץ לפני שנבחרה סביבת עבודה כלשהי.

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

לא הכול בהיקף סביבת עבודה. מסד נתונים שני, <dataDir>/global.db, מחזיק את מה שהוא באמת ברמת השרת:

  • רישום סביבות העבודה, כך שהמחליף יכול להציג כל סביבת עבודה בלי לפתוח ולו קובץ סביבת עבודה אחד — וכדי שהשם, הלוגו, הבעלים, הסדר הידני וסמן המחיקה הרכה של סביבת עבודה יימצאו מחוץ לקובץ שהם מתארים;
  • זהות: משתמשים, העדפות משתמש ומכשירים מצומדים, מפני שאדם אחד הוא משתמש אחד בכל סביבות העבודה, ומכשיר מצומד שורד מחיקה של סביבת עבודה;
  • תור הצי (עובדים, עבודות ויומן שיבוץ), שהמתזמן שלו מתאים את כל התור מול כל עובד בכל פעימה;
  • עדכוני החדשות, שהם לפי משתמש (ולא לפי סביבת עבודה) — הוספה של פיד מלווה אתכם אל כל סביבת עבודה;
  • workspace_routes, אינדקס טרום־אימות שעונה על “איזו סביבת עבודה היא הבעלים של המפתח הזה?” עבור נקודות כניסה שמגיעות עם סוד בלבד או עם מזהה אטום (גיבוב של הזמנה, אסימון webhook, קישור עמוק) ובלי סביבת עבודה. החמצה בו היא “לא נמצא”; אין במכוון נפילה חזרה לסריקה;
  • הגדרות וזהות בהיקף ההתקנה: מזהה ההתקנה, הגדרות ברמת המארח, חיבורי SSO ומדיניות פעולות מנוהלות (הגבלה בהיקף ההתקנה שיכולה רק להדק את מעקות הבטיחות של סביבות עבודה).

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

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

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

  • סוכן CEO וארבעה מומחים — qa, architect, engineer, librarian — כשהמומחים מדווחים ל-CEO;
  • תבניות הפייפליין המובנות והטריגרים שלהן (טריגרים של אירוע וטריגרים ידניים מגיעים דלוקים; טריגרים מתוזמנים דורשים הצטרפות מבחירה, למעט סריקת ה-GC היומית של עצי העבודה);
  • חבילות ההערכה ההתחלתיות.

זה קורה ל_כל_ סביבת עבודה, לא רק לראשונה. הסוכנים הנזרעים נוצרים בלי מתאם, ולכן הם רצים על Control Center (מובנה) עם מודל ברירת המחדל של Anthropic (claude-opus-4-8 אלא אם תצמידו אחר) עד שתגדירו מתאם ומודל לכל סוכן בהגדרות ← סביבת עבודה ← סוכנים. ראו מודל הסוכן.

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

מחיקה של סביבת עבודה מסמנת את שורת הרישום ומבטלת את הקישור לתיקייה. סביבת העבודה נעלמת מכל רשימה ומכל שליפה. שורת הרישום נשארת (במחיקה רכה) כך שהמזהה עדיין מוכר לגיבוי ולתחזוקה, אבל workspace.delete מפיל מיד את התיקייה של אותה סביבת עבודה — מסד הנתונים, עצי העבודה, קובצי הסוכנים והשאר — דרך WorkspaceDatabaseManager.dropAndClose. רק הבעלים של סביבת העבודה יכול למחוק אותה.

מכיוון שסביבת עבודה אחת היא קובץ אחד, העברה של סביבת עבודה היא פעולת קובץ ולא ייצוא טבלה־אחר־טבלה:

  • workspace.export הוא VACUUM INTO יחיד — תמונת מצב עקבית ומאוחה שנלקחת תחת טרנזקציית קריאה בזמן שהשרת ממשיך לשרת.
  • workspace.import מאמץ קובץ כזה, ומחליף כל מה שסביבת העבודה היעד מחזיקה כעת. ה-workspace_meta המוטבע מאפשר לייבוא להבחין בין “הקובץ שלי עצמי, שאומץ מחדש” לבין “קובץ מהתקנה אחרת” (האחרון מותר, אך נרשם ביומן במקום להיות בלתי נראה).
  • server.backupNow יוצר תמונת מצב של ההתקנה כולה לתיקייה עם חותמת זמן שמשקפת את תיקיית הנתונים החיה — manifest.json, global.db וקובץ <workspaceId>/workspace.db אחד לכל סביבת עבודה — כך ששחזור הוא העתקה שלה חזרה.
  • server.listBackups קורא את התיקייה הזו חזרה, מהחדש לישן, עם הגודל ורשימת סביבות העבודה של כל תמונת מצב. תמונת מצב שחסר בה ה-manifest או שמציינת קבצים שאינם שם עדיין מדווחת, מסומנת כלא שלמה — העלמתה היא הדרך שבה מפעיל מגיע להאמין שיש לו גיבוי שאין לו.

פעולות הגיבוי נמצאות תחת הגדרות ← שרת ← גיבוי ושחזור: גיבוי עכשיו לוקח תמונת מצב של ההתקנה, הרשימה מציגה אילו תמונות מצב שלמות ומאפשרת לבעלים של ההתקנה למחוק אחת לאחר אישור, וכל סביבת עבודה מציעה ייצוא וייבוא. הפייפליין המובנה Workspace backup לוקח רק את מסד הנתונים של אותה סביבת עבודה בלוח זמנים שבועי ומוחק את הגיבויים שלו עצמו שישנים מגיל השימור המוגדר. שחזור של סביבת עבודה מתמונת מצב של ההתקנה הוא workspace.import שמופנה אל <workspaceId>/workspace.db של תמונת המצב; שחזור של התקנה שלמה נשאר העתקה חזרה של תיקיית תמונת המצב כשהשרת כבוי.

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

העברת גיבוי בין השרת למכשיר שלכם

קטע בשם “העברת גיבוי בין השרת למכשיר שלכם”

כל פעולה שלמעלה מדברת בנתיבים על השרת, וזו תשובה מלאה רק כשהשרת הוא המחשב שלכם. שלושה נתיבי HTTP חתומים — /backup/workspace, /backup/snapshot ו-/backup/restore — נושאים את הבתים עבור כל טופולוגיה אחרת, על אותו מסלול מאומת שכבר משתמשים בו פרוקסי המדיה ומאגר התמונות. בדף זהו כפתור הורדה לצד כל ייצוא וכל תמונת מצב (אתם בוחרים היכן היא נוחתת), ובחירת קובץ והעלאה כדי לשחזר מקובץ שנמצא במחשב שאתם עובדים עליו. ההעלאה היא זו שעובדת כשהשרת אינו המחשב שלכם: workspace.import צריך שהקובץ כבר יהיה על המארח, וזו הדרך שבה הוא מגיע לשם.

שלושה מאפיינים שכדאי להכיר:

  • הורדה מנפיקה עותק טרי והשרת לא שומר ממנו דבר. הורדה אינה “שליפת הקובץ שכפתור הייצוא יצר” — היא מייצאת שוב, מזרימה את התוצאה ומוחקת אותה. התשובה היא no-store עם Accept-Ranges: none מאותו נימוק: טווח שממשיך מנקודת עצירה היה מלחים שני ייצואים שונים לקובץ אחד שנראה תקין ואינו.
  • העלאה מוזרמת לדיסק, ולעולם אינה נשמרת במאגר זמני, בשני הקצוות, והעותק הזמני נמחק בכל מסלול יציאה — כולל סירוב. מסד נתונים של סביבת עבודה אינו צילום מסך.
  • לחיבור שעובר בממסר אין מקור HTTP, ולכן פקדי ההעברה מושבתים בו, עם הערה שמסבירה למה. כל מה שמדבר בנתיבים בצד השרת ממשיך לעבוד דרך הממסר; רק מסלולי הבתים דורשים חיבור ישיר.

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

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

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