סוכן AI הוא תהליך שרץ בשרת WizzoAI (ה-stream), חושב בלולאת LLM וקורא לכלים של האתר דרך שרת ה-MCP. הדף הזה מסביר מה בדיוק יושב ב-CMS ומה לא, איך סוכן מקבל זהות והרשאות, איך עובד תור האישורים, ואיך קוד של האתר יכול להפעיל סוכן רקע.
חלוקת האחריות#
ה-CMS לא מריץ מודל ולא מתזמן. מגדירים סוכן במסך WizzoAI במרקט (נפתח מ-{admin}/wizzo_market?tool=wizzoai), וה-stream מפעיל אותו. האתר מספק שלושה דברים: כלים, הרשאות ואישורים.
| נושא | ה-stream (stream.ai.wizzo.media) | ה-CMS |
|---|---|---|
| הגדרת סוכן, builder, הפעלה וכיבוי | כן | שורת shadow ב-CRM_ai_agents, נכתבת בכל mint |
תזמון, לולאת LLM, web_search, fetch_url, תקציבים, kill switch | כן | לא |
| כלים | מגלה דרך tools/list | POST /system/mcp ו-AGENT_TOOLS |
| הרשאות | לא | bot admin וקבוצה לכל סוכן, וטוקן ריצה קצר עם allowed_tools |
| אישורים | מציג | התור, ה-snapshot והביצוע |
| זיכרון | לסוכני stream | רק ל-chat jobs, ועוד הכלים remember, recall, update_playbook |
| ריצות וצעדים | כן | shadow ב-CRM_ai_agents_runs, וצעדים ב-CRM_ai_agents_run_steps ל-chat jobs |
אין צורך לגעת בטבלאות הסוכנים ידנית. ai_agents_lib::ensure_schema() יוצרת אותן (CREATE TABLE IF NOT EXISTS) בקריאה הראשונה של ה-stream לאתר.
מחזור חיים של ריצה#
- ה-stream מחליט להריץ סוכן (cron, אירוע, ידני, או chat job) וקורא ל-
POST /system/ai_agent_runner/mintעם הגדרת הסוכן. - ה-CMS מעדכן את שורת ה-shadow, דואג ל-bot admin של הסוכן, ואם הוגדר
precheck_sqlואין עבודה מחזיר{ok, skip}והריצה מסתיימת בלי להפעיל מודל. - אחרת נפתחת שורת ריצה ונוצר טוקן ריצה:
{ok, token, url, cms_run_id, agent_id}. הטוקן תקף שעתיים (RUN_TOKEN_TTL_HOURS) ומוגבל לרשימת הכלים של הסוכן. - ה-stream קורא ל-
/system/mcpעם הטוקן. כל קריאה רצה בזהות ה-bot admin, לכן הסוכן לא יכול לעשות יותר ממה שקבוצת ההרשאות שלו מאפשרת. - בסיום ה-stream קורא ל-
finish(statusהואdone,failedאוcancelled), והטוקן נמחק.
ה-bot admin#
לכל סוכן נוצר מנהל agent_<slug> בקבוצה AI Agent: <slug>. ההרשאות של הקבוצה נגזרות מרשימת ה-panels של הסוכן, ומתעדכנות בכל mint. מכאן שהדרך להגביל סוכן היא לצמצם את רשימת הפאנלים שלו, לא להסתמך על ההנחיות בפרומפט.
נקודות הקצה של ai_agent_runner#
כל הפעולות הן POST /system/ai_agent_runner/<action>, פרט ל-run, tick ו-reconcile (GET).
| action | גוף או פרמטרים | אימות | מה עושה |
|---|---|---|---|
catalog | - | מפתח WizzoAI | מחזיר {tools, panels, site}: כלי הליבה האופציונליים, כלי האתר (custom:true) ופאנלים שאינם למפתחים בלבד |
mint | {agent:{id, slug, title, goal, tools[], panels[], trigger_type, event_key, precheck_sql, approval_mode, notify}, run:{id, trigger_src, dry_run, precheck}} | מפתח WizzoAI | פותח ריצה ומנפיק טוקן, כמתואר למעלה |
finish | {cms_run_id, status, summary, error, tokens_in, tokens_out, wizzo_tokens, steps_count} | מפתח WizzoAI | סוגר את הריצה |
approvals | {count_only?} | מפתח WizzoAI | רשימת אישורים ממתינים (עד 100) או {total, by_slug} |
approval_decide | {id, approve, reason} | מפתח WizzoAI | מחליט באישור |
run | ?id=<shadow id>&trigger=event|manual|chat&tk= | cron_runner_token או סשן אדמין | מעביר את ההפעלה ל-stream, מחזיר טקסט OK: ... או ERROR: ... |
report | {run_secret, steps[], phase?, status, summary, error, tokens_*} | run_secret של ה-chat job | כותב צעדים וסוגר ריצה |
tick, reconcile | ?tk= | טוקן cron או אדמין | מסמן ריצות תקועות (ישנות מ-120 דקות) ככושלות |
register | - | אדמין או מפתח WizzoAI | רושם את האתר ב-stream |
migrate | - | אדמין או מפתח WizzoAI | מייצא סוכנים ישנים (stream_agent_id=0) ל-stream |
מפתח WizzoAI מגיע ב-Authorization, ב-X-Wizzo-Key או בגוף (_key, api_key, _token), ומושווה למפתח האתר ב-hash_equals.
כלים של סוכן#
לכל טוקן ריצה מתווספים כלי הבסיס, ai_agents_lib::BASELINE_TOOLS: cms:list_panels, cms:panel_schema, cms:list_rows, cms:search_media, cms:db_schema, cms:db_query, cms:remember, cms:recall, cms:update_playbook, cms:notify_admin. כלים נוספים מגדירים בהגדרת הסוכן, מתוך OPTIONAL_TOOLS: cms:save_record, cms:set_field, cms:delete_record, cms:claim_items, cms:generate_image, cms:import_media. עוד אפשר: builtin:web_search, builtin:fetch_url, כלי מוצרים (<product>:*) וכלי האתר שרשמתם ב-כלי MCP מותאמים (כ-cms:<name>).
cms:db_query נמצא בכלי הבסיס של כל סוכן, ולכן כל סוכן יכול לקרוא כל טבלה שה-bot admin שלו מורשה אליה בשכבת ה-SQL. תכננו את panels וההנחיות בהתאם, ואל תשמרו סודות בטבלאות שסוכן עשוי לקרוא. פירוט הכלים ב-כלי ה-MCP.סוגי הפעלה#
trigger_type | משמעות |
|---|---|
schedule | לפי crontime, מתזמן ה-stream |
event | אחרי שמירת טופס מוצלחת, ראו למטה |
manual | כפתור הפעלה, או קריאה ל-run |
continuous | ריצה מתמשכת בצד ה-stream |
טריגר אירוע#
כשטופס נשמר בהצלחה, Form::save() מחפש סוכנים עם trigger_type='event', is_active=1, status='active' ו-event_key שווה ל-form_save:<table> (שם הטבלה בלי CRM_). לכל סוכן כזה נשלח MISC::ping("system/ai_agent_runner/run", ...), אסינכרוני, כך שהשמירה לא מתעכבת ולא נכשלת בגלל הסוכן. שמירה שביצע ה-bot admin של אותו סוכן מדולגת כדי למנוע לולאות.
precheck_sql#
הוא SELECT יחיד שמחזיר שורות רק כשיש עבודה. כשהוא ריק מהתוצאה הריצה מדולגת בלי עלות מודל. אם הבדיקה עצמה נכשלת, הריצה כן יוצאת לדרך (fail-open).
מצבי אישור#
השדה approval_mode של הסוכן הוא auto, draft או approve_writes.
רק approve_writes נאכף בצד ה-CMS. draft ו-auto מתנהגים אותו דבר באתר: הכלים כותבים ישירות. אל תסתמכו על draft כמנגנון הגנה.
ב-approve_writes, כלים כותבים (save_record, set_field, delete_record, form_action, delete_media, כלי אתר עם write=true וכלי מוצר כותבים) לא מתבצעים. נכתבת שורה ב-CRM_ai_agents_approvals והכלי מחזיר {queued_for_approval:true, approval_id, note}.
payloadמכיל את הארגומנטים ואת__before, הערכים הנוכחיים של השדות שישתנו.record_snapshotהואmd5של הרשומה בזמן ההצעה.- ריצה שנגמרה עם אישורים ממתינים מקבלת את הסטטוס
waiting_approval.
אישור מריץ את הכלי בזהות ה-bot admin של הסוכן, אבל רק אם הרשומה לא השתנתה מאז: אם ה-md5 שונה, הסטטוס הופך ל-stale ושום דבר לא נכתב. דחייה מסמנת rejected. שתי ההחלטות נשמרות כזיכרון מסוג feedback, כך שהסוכן לומד מהן.
SELECT id, agent_id, action_type, panel, record_id, status, created_at
FROM CRM_ai_agents_approvals
WHERE status = 'pending'
ORDER BY id DESC
LIMIT 50
סטטוסי אישור אפשריים: pending, approved, rejected, expired, stale.
זיכרון#
הטבלה CRM_ai_agents_memory מסווגת לפי kind:
| kind | מה נשמר |
|---|---|
work | רשימות עבודה, remember, recall ו-claim_items |
episode | סיכום ריצה (20 אחרונות), רק ל-chat jobs |
insight | תובנות שה-stream כותב |
feedback | אישור או דחייה מתור האישורים |
playbook | מדריך אחד לסוכן, דרך update_playbook |
באתר ישן שה-ENUM שלו לא כולל playbook, הכלי update_playbook מזהה זאת ומחזיר שגיאה ברורה במקום להיכשל בשקט.
Chat jobs: משימות רקע מהקוד#
ai_agents_lib::dispatch_chat_job מפעיל משימת רקע בשם מנהל נתון, על סוכן נסתר בשם chat_jobs_<admin_id> (או $opts["slug"]). ההגבלות ברירת המחדל: 60 צעדים ו-600,000 טוקנים. הצ'אט משתמש בזה בכלי start_background_job (צ'אט AI), והמודול של SEO המאוחסן משתמש בו עם approve_writes.
$this->load->library("ai_agents_lib");
$res = ai_agents_lib::dispatch_chat_job(
ADMIN::get_id(),
"עבור על כל הכתבות בלי תיאור מטא והצע תיאור לכל אחת",
["articles"] // רשימת פאנלים: גבול ההרשאות של הריצה
);
if (!$res["ok"]) {
// $res["error"] מכיל את הסיבה
} else {
$run_id = $res["run_id"];
}
מצב הריצה נבדק דרך GET {admin}/ai_agents/chat_job_status?run_id=<id>&pmode=empg, והתראות סיום מגיעות ל-CRM_admin_chat לפי agent.notify (on_fail פעיל כברירת מחדל, on_success לא).
ai_agents_lib: API סטטי#
המחלקה נטענת עם $this->load->library("ai_agents_lib").
| חתימה | מחזיר | הערות |
|---|---|---|
full_tool_list($agent) | מערך ייחודי של שמות כלים | BASELINE_TOOLS בתוספת ה-tools של הסוכן |
get_agent($id) | שורת סוכן | |
available_external_servers() | מפה של שרתי מוצרים | מעטפת ל-external_mcp::servers() |
external_tool_servers($agent) | רשימת {name, url, token} | לפי הקידומות <product>: ב-tools, ל-chat jobs |
ensure_schema() | - | יוצרת את טבלאות הסוכנים |
catalog() | {tools, panels} | מה שה-builder מציע |
shadow_upsert($def) | שורה או null | upsert לפי slug ([a-z0-9_]) |
shadow_by_stream_id($id) | שורה | |
open_shadow_run($agent, $run) | cms_run_id | idempotency_key ייחודי לכל ריצה |
ensure_agent_bot(&$agent) | bot_admin_id | יוצרת או מעדכנת מנהל וקבוצה |
precheck_has_work($agent) | bool | fail-open |
mint_run_token($agent, $run_id) | טוקן | תקף RUN_TOKEN_TTL_HOURS (2) שעות |
drop_run_token($run_id) | - | מוחק גם טוקנים שפגו |
dispatch_chat_job($admin_id, $instruction, $panels = [], $opts = []) | {ok, run_id} או {ok:false, error} | ראו למעלה |
close_run($run, $status, $summary, $error, $tokens = [], $remember = true) | סטטוס סופי | done עם אישורים ממתינים הופך ל-waiting_approval |
notify_owner($agent, $msg) | - | הודעה בצ'אט המנהלים בשם ה-bot |
pending_approvals($with_rows = true) | שורות, או {total, by_slug} | |
decide_approval($id, $approve, $reason, $decided_by) | {ok, status} | status הוא approved, rejected, stale או error |
export_legacy(), mark_migrated($results) | - | העברת סוכנים ישנים ל-stream |
reconcile_run($run_id), reconcile_stuck() | - | סוגרים ריצות תקועות |
stream_api($path, $data = null, $method = "POST") | תשובה מפוענחת, {error, http, body} או null | קריאה ל-API של ה-stream עם מפתח האתר |
stream_request_ok() | bool |
מלכודות#
מפתח WizzoAI של האתר מוזרק לדפדפן של כל מנהל שנכנס לאדמין כשהצ'אט פעיל (הממצא AI-01 בביקורת). מי שמחזיק בו יכול לדבר עם ה-stream בשם האתר. התייחסו למפתח כמפתח רגיש, סובבו אותו כשמנהל עוזב, ושקלו MISC::$GLOBALS["allow_ai_chat"] = false באתרים שבהם אין צורך בצ'אט. ראו אבטחת ה-MCP והצ'אט.
- ביטול סוכן מתבצע ב-WizzoAI. מצד ה-CMS אפשר רק לשנות
is_activeבשורת ה-shadow (משפיע על טריגרי אירוע) או למחוק טוקנים. - ריצה שנתקעה (
pendingאוrunningיותר מ-120 דקות) נסגרת ככושלת על ידיreconcile_stuck. - שינוי
panelsשל סוכן משפיע רק בריצה הבאה, כי הקבוצה מתעדכנת ב-mint.