From 003e591a0491dacf02479ac390922be732f2c6e4 Mon Sep 17 00:00:00 2001 From: eric Date: Sat, 21 Feb 2026 19:46:42 +0000 Subject: [PATCH] Add daily standup + knowledge broadcast system - DailyStandupBeat thread fires at 09:00 every day - trigger_daily_standup(): messages all team members, orchestrator updates KB + agent reminders - broadcast_knowledge_update(): distributes any new info to all agents immediately - parse_agent_commands(): add and XML handlers - /api/standup/trigger and /api/broadcast routes for manual triggering - orchestrator systemprompt: standup + broadcast instructions with examples - tasks.html: badges for standup / broadcast / action_knowledge task types --- agents/orchestrator/systemprompt.md | 71 ++++++- app.py | 290 ++++++++++++++++++++++++++++ templates/tasks.html | 6 + 3 files changed, 366 insertions(+), 1 deletion(-) diff --git a/agents/orchestrator/systemprompt.md b/agents/orchestrator/systemprompt.md index 9add563..d044d0e 100644 --- a/agents/orchestrator/systemprompt.md +++ b/agents/orchestrator/systemprompt.md @@ -73,10 +73,79 @@ email: email@adresse.com ``` +**Wissensdatenbank aktualisieren:** +``` + +topic: Eventstart +content: Das Event startet um 18:00 Uhr. Einlass ab 17:30 Uhr. + +``` + +**Reminder eines Agenten aktualisieren:** +``` + +agent: catering_manager +reminder: WICHTIG: Eventstart wurde auf 18:00 geändert. Catering-Aufbau muss bis 17:00 fertig sein. + +``` + +## Tägliches Standup + +Jeden Morgen um 09:00 Uhr wirst du automatisch aktiviert, um: +1. Alle Team-Members nach ihren täglichen Updates zu fragen +2. Neue Informationen in die Wissensdatenbank einzupflegen +3. Jeden Agenten über relevante Änderungen zu informieren + +Wenn du für ein Standup aktiviert wirst: +- Prüfe aktuelle Tasks und Erinnerungen auf neue Informationen +- Aktualisiere `` für jede wichtige Änderung +- Schreibe für jeden Agenten einen `` mit relevanten Updates +- Delegiere mit `` an jeden Agenten, damit er seinen Bereich prüft + +## Wissens-Broadcasts + +Wenn jemand eine neue wichtige Information mitteilt (z.B. "das Event startet um 18:00 statt 19:00"): +1. Erkenne, dass dies ein Update ist das alle betrifft +2. Aktualisiere sofort die Wissensdatenbank: `` +3. Informiere **jeden** relevanten Agenten per `` +4. Delegiere an betroffene Agenten Sub-Tasks zur Überprüfung ihrer Bereiche: `` +5. Bestätige Piotr (telegram_id: 1578034974) dass alles verteilt wurde + +**Beispiel — jemand sagt "das Event startet um 18:00 nicht 19:00":** + +``` + +topic: Eventstart +content: Das Event startet um 18:00 Uhr (geändert). Einlass ab 17:30 Uhr. + + + +agent: catering_manager +reminder: WICHTIG: Eventstart wurde auf 18:00 geändert (war 19:00). Catering-Aufbau bis 17:00 abschließen. + + + +agent: location_manager +reminder: WICHTIG: Eventstart 18:00 Uhr (geändert). Venue-Öffnung und Setup entsprechend anpassen. + + + +title: Eventstart-Änderung prüfen: 18:00 statt 19:00 +agent: program_manager +details: Der Eventstart wurde auf 18:00 geändert. Bitte überprüfe den Programmablauf und passe alle Zeitangaben an. + + + +telegram_id: 1578034974 +message: ✅ Update verteilt: Eventstart 18:00 Uhr. Wissensdatenbank aktualisiert, alle Agenten informiert. + +``` + ## Verhalten bei Nachrichten 1. Antworte freundlich und direkt 2. Wenn eine Aufgabe dabei ist → sofort `` anlegen 3. Wenn Email/Telegram gesendet werden soll → `` / `` direkt ausführen 4. Wenn Team-Daten zu aktualisieren → `` direkt ausführen -5. Bestätige am Ende was du getan hast +5. Wenn neue wichtige Information → `` + `` für betroffene Agenten +6. Bestätige am Ende was du getan hast diff --git a/app.py b/app.py index 402e344..2f2722d 100644 --- a/app.py +++ b/app.py @@ -1176,6 +1176,64 @@ def parse_agent_commands(agent_key, response_text, task_id=None): update_task_db(action_id, status='completed' if success else 'error', response=status_msg) logger.info(f"[AgentCmd] {status_msg}") + # ── UPDATE_KNOWLEDGE ───────────────────────────────────────────────────── + # Agenten können damit einen neuen Abschnitt in der Wissensdatenbank anlegen/aktualisieren + for block in re.findall(r'(.*?)', response_text, re.DOTALL | re.IGNORECASE): + topic = get_field(block, 'topic') + content = get_field(block, 'content') + if not topic or not content: + logger.warning("[AgentCmd] ohne topic/content ignoriert") + continue + kb_file = os.path.join(os.path.dirname(__file__), 'agents', 'orchestrator', 'knowledge', 'diversityball_knowledge.md') + os.makedirs(os.path.dirname(kb_file), exist_ok=True) + # Existierenden Abschnitt ersetzen oder neuen anhängen + section_header = f"## {topic}" + new_section = f"{section_header}\n\n{content.strip()}\n" + if os.path.exists(kb_file): + with open(kb_file, 'r', encoding='utf-8') as f: + kb_text = f.read() + # Ersetze bestehenden Abschnitt (## Topic ... bis zum nächsten ##) + pattern = rf'(^## {re.escape(topic)}\s*\n)(.*?)(?=\n## |\Z)' + if re.search(pattern, kb_text, re.MULTILINE | re.DOTALL): + kb_text = re.sub(pattern, new_section, kb_text, flags=re.MULTILINE | re.DOTALL) + else: + kb_text = kb_text.rstrip() + f"\n\n{new_section}" + else: + kb_text = f"# Diversity Ball Wien 2026 — Wissensdatenbank\n\n{new_section}" + with open(kb_file, 'w', encoding='utf-8') as f: + f.write(kb_text) + action_id = create_task( + title=f"Wissen aktualisiert: {topic}", + description=f"**Topic:** {topic}\n\n{content[:200]}{'...' if len(content) > 200 else ''}", + agent_key=agent_key, task_type='action_knowledge', created_by=agent_key, parent_task_id=task_id, + ) + update_task_db(action_id, status='completed', response=f"✓ Wissensdatenbank aktualisiert: {topic}") + logger.info(f"[AgentCmd] Wissensdatenbank aktualisiert: {topic}") + + # ── UPDATE_AGENT_REMINDER ──────────────────────────────────────────────── + # Orchestrator kann damit die reminders.md eines beliebigen Agenten aktualisieren + for block in re.findall(r'(.*?)', response_text, re.DOTALL | re.IGNORECASE): + target_agent = get_field(block, 'agent') + reminder = get_field(block, 'reminder') + if not target_agent or not reminder: + logger.warning("[AgentCmd] ohne agent/reminder ignoriert") + continue + reminder_file = os.path.join(os.path.dirname(__file__), 'agents', target_agent, 'reminders.md') + if not os.path.exists(os.path.dirname(reminder_file)): + logger.warning(f"[AgentCmd] Agent-Verzeichnis nicht gefunden: {target_agent}") + continue + timestamp = datetime.now().strftime('%d.%m.%Y %H:%M') + entry = f"\n## Update {timestamp}\n\n{reminder.strip()}\n" + with open(reminder_file, 'a', encoding='utf-8') as f: + f.write(entry) + action_id = create_task( + title=f"Reminder aktualisiert: {target_agent}", + description=f"**Agent:** {target_agent}\n\n{reminder[:200]}{'...' if len(reminder) > 200 else ''}", + agent_key=agent_key, task_type='action_knowledge', created_by=agent_key, parent_task_id=task_id, + ) + update_task_db(action_id, status='completed', response=f"✓ reminders.md aktualisiert für {target_agent}") + logger.info(f"[AgentCmd] reminders.md aktualisiert für {target_agent}") + def create_new_agent(agent_key, role, skills): """Erstellt dynamisch einen neuen Agenten.""" agent_dir = os.path.join(AGENTS_BASE_DIR, agent_key) @@ -2286,10 +2344,218 @@ def start_orchestrator_beat(): logger.info("[OrchestratorBeat] Daemon-Thread gestartet.") +# ── DAILY STANDUP ───────────────────────────────────────────────────────────── + +def trigger_daily_standup(): + """ + Tägliches Standup: Orchestrator fragt alle Team-Members nach Updates + und delegiert anschließend Wissensupdates an alle Agenten. + """ + logger.info("[DailyStandup] Starte tägliches Standup...") + + # Team-Members aus DB holen + try: + con = sqlite3.connect(EMAIL_JOURNAL_DB) + con.row_factory = sqlite3.Row + members = con.execute("SELECT name, email, role, telegram_id FROM team_members").fetchall() + con.close() + except Exception as e: + logger.error("[DailyStandup] Fehler beim Laden der Team-Members: %s", e) + members = [] + + today = datetime.now().strftime('%d.%m.%Y') + + # ── 1. Orchestrator-Haupttask: plant den Standup ────────────────────────── + standup_task_id = create_task( + title=f"Daily Standup {today}", + description=( + f"Täglicher Status-Check vom {today}.\n\n" + "Der Orchestrator koordiniert:\n" + "1. Alle Team-Members werden nach Updates gefragt (Telegram + Email)\n" + "2. Eingegangene Updates werden an alle Agenten weitergegeben\n" + "3. Wissensdatenbank wird bei Bedarf aktualisiert" + ), + agent_key='orchestrator', + task_type='standup', + created_by='system', + ) + + # ── 2. Pro Team-Member einen Sub-Task: frag nach Updates ───────────────── + for m in members: + name = m['name'] + email = m['email'] + telegram_id = m['telegram_id'] + role = m['role'] or 'Team-Member' + + msg = ( + f"Guten Morgen {name}! 👋\n\n" + f"Täglicher Status-Check ({today}):\n\n" + f"Bitte teile uns mit:\n" + f"• Was hat sich seit gestern in deinem Bereich geändert?\n" + f"• Gibt es neue Informationen, Termine oder Entscheidungen?\n" + f"• Benötigst du Unterstützung von anderen?\n\n" + f"Du kannst direkt hier per Telegram antworten oder eine Email senden." + ) + + # Telegram bevorzugen, Fallback auf Email + if telegram_id: + create_task( + title=f"Standup-Frage an {name} (Telegram)", + description=f"Telegram-ID: {telegram_id}\nNachricht: {msg}", + agent_key='orchestrator', + task_type='action_telegram', + created_by='system', + parent_task_id=standup_task_id, + ) + send_telegram_message(telegram_id, msg) + logger.info("[DailyStandup] Telegram-Standup gesendet an %s (%s)", name, telegram_id) + elif email: + subject = f"Daily Standup {today} — Was gibt's Neues?" + create_task( + title=f"Standup-Frage an {name} (Email)", + description=f"An: {email}\nBetreff: {subject}\n\n{msg}", + agent_key='orchestrator', + task_type='action_email', + created_by='system', + parent_task_id=standup_task_id, + ) + send_email(email, subject, msg, triggered_by='system:standup', task_id=standup_task_id) + logger.info("[DailyStandup] Email-Standup gesendet an %s (%s)", name, email) + + # ── 3. Orchestrator-Prompt: sammle & verteile Wissen ───────────────────── + agent_list = ', '.join(k for k in AGENTS.keys() if k != 'orchestrator') + orchestrator_prompt = f"""## Daily Standup — {today} + +Du hast soeben alle Team-Members nach ihren täglichen Updates gefragt. + +**Deine Aufgabe jetzt:** + +1. Prüfe ob es aktuelle Informationen oder Änderungen gibt (aus deiner Erinnerung, aus Tasks der letzten 24h, oder aus eingegangenen Nachrichten). + +2. Falls es wichtige Updates gibt (z.B. Terminänderungen, neue Entscheidungen, Budget-Anpassungen): + - Aktualisiere die Wissensdatenbank mit `` + - Delegiere an **jeden** der folgenden Agenten einen Sub-Task damit sie ihre reminders.md aktualisieren: + {agent_list} + +3. Falls keine konkreten Updates vorliegen: schreibe eine kurze Zusammenfassung des aktuellen Status und schicke sie per Telegram an Piotr (telegram_id: 1578034974). + +**Beispiel für Wissens-Update:** +``` + +topic: Eventstart +content: Das Event startet um 18:00 Uhr (Stand {today}). Einlass ab 17:30 Uhr. + +``` + +**Beispiel für Agent-Reminder:** +``` + +agent: catering_manager +reminder: WICHTIG ({today}): Eventstart wurde auf 18:00 geändert. Catering-Aufbau muss spätestens um 17:00 abgeschlossen sein. + +``` + +**Beispiel für Info-Delegation:** +``` + +title: Wissensupdate: Eventstart 18:00 Uhr +agent: budget_manager +details: Bitte aktualisiere deinen Wissensstand: Das Event startet am Diversity Ball Wien 2026 um 18:00 Uhr (nicht 19:00). Prüfe ob sich dadurch Änderungen für deinen Bereich ergeben. + +``` + +Führe alle notwendigen Aktionen aus und bestätige am Ende was du getan hast. +""" + + response = execute_agent_task('orchestrator', orchestrator_prompt) + if response: + parse_agent_commands('orchestrator', response, task_id=standup_task_id) + update_task_db(standup_task_id, status='completed', response=response[:500]) + logger.info("[DailyStandup] Orchestrator-Standup abgeschlossen.") + else: + update_task_db(standup_task_id, status='error', response='Keine Antwort vom Orchestrator') + + +def broadcast_knowledge_update(info: str, source: str = 'manual'): + """ + Verteilt eine neue Information an alle Agenten: + - Wissensdatenbank aktualisieren (via Orchestrator) + - Jeden Agenten per Sub-Task informieren + - Piotr per Telegram bestätigen + """ + logger.info("[Broadcast] Starte Knowledge-Broadcast: %s", info[:80]) + today = datetime.now().strftime('%d.%m.%Y %H:%M') + + broadcast_task_id = create_task( + title=f"Wissens-Broadcast: {info[:60]}", + description=f"Neue Information vom {today}:\n\n{info}\n\nQuelle: {source}", + agent_key='orchestrator', + task_type='broadcast', + created_by='system', + ) + + agent_list = ', '.join(k for k in AGENTS.keys() if k != 'orchestrator') + prompt = f"""## Wissens-Broadcast — {today} + +Eine neue wichtige Information wurde eingegeben und muss an das gesamte Team verteilt werden: + +**Neue Information:** +{info} + +**Deine Aufgaben:** + +1. Aktualisiere die Wissensdatenbank mit dem passenden Topic. + +2. Aktualisiere die reminders.md für **jeden** dieser Agenten: + {agent_list} + +3. Lege für jeden Agenten einen Sub-Task an, damit er die neue Information in seinem Fachbereich berücksichtigt. + +4. Sende Piotr (telegram_id: 1578034974) eine Bestätigung dass die Information verteilt wurde. + +Führe alle Aktionen jetzt aus. +""" + + response = execute_agent_task('orchestrator', prompt) + if response: + parse_agent_commands('orchestrator', response, task_id=broadcast_task_id) + update_task_db(broadcast_task_id, status='completed', response=response[:500]) + logger.info("[Broadcast] Knowledge-Broadcast abgeschlossen.") + else: + update_task_db(broadcast_task_id, status='error', response='Keine Antwort') + + +def daily_standup_beat(): + """Hintergrund-Thread: Führt täglich um 09:00 das Standup aus.""" + logger.info("[DailyStandup] Hintergrund-Thread gestartet.") + # Beim Start: nächste 09:00 berechnen + while True: + try: + now = datetime.now() + target = now.replace(hour=9, minute=0, second=0, microsecond=0) + if now >= target: + target += timedelta(days=1) + sleep_secs = (target - now).total_seconds() + logger.info("[DailyStandup] Nächstes Standup um %s (in %.0f Minuten)", target.strftime('%d.%m.%Y %H:%M'), sleep_secs / 60) + time.sleep(sleep_secs) + trigger_daily_standup() + except Exception as e: + logger.error("[DailyStandup] Fehler: %s", e) + time.sleep(60) + + +def start_daily_standup(): + """Startet den Daily-Standup-Thread als Daemon.""" + t = threading.Thread(target=daily_standup_beat, name='DailyStandup', daemon=True) + t.start() + logger.info("[DailyStandup] Daemon-Thread gestartet.") + + # Poller beim App-Start starten start_email_poller() start_task_beat() start_orchestrator_beat() +start_daily_standup() @app.route('/login', methods=['GET', 'POST']) @@ -3471,6 +3737,30 @@ def distribute_tasks(): }) +@app.route('/api/standup/trigger', methods=['POST']) +@login_required +def api_trigger_standup(): + """Löst das Daily Standup manuell aus (für Tests oder on-demand).""" + def run(): + trigger_daily_standup() + threading.Thread(target=run, daemon=True).start() + return jsonify({'success': True, 'message': 'Daily Standup wurde gestartet.'}) + + +@app.route('/api/broadcast', methods=['POST']) +@login_required +def api_broadcast(): + """Verteilt eine neue Information sofort an alle Agenten.""" + data = request.get_json() + info = data.get('info', '').strip() + if not info: + return jsonify({'error': 'Kein info-Text übergeben'}), 400 + def run(): + broadcast_knowledge_update(info, source='manual_broadcast') + threading.Thread(target=run, daemon=True).start() + return jsonify({'success': True, 'message': f'Broadcast gestartet: {info[:80]}'}) + + @app.route('/api/webhook/deploy', methods=['POST']) def webhook_deploy(): """Gitea Webhook: git pull + restart service on push to main.""" diff --git a/templates/tasks.html b/templates/tasks.html index 6bdc710..8b50dea 100644 --- a/templates/tasks.html +++ b/templates/tasks.html @@ -50,6 +50,12 @@ Agent {% elif task.type == 'agent_question' %} ❓ Frage + {% elif task.type == 'standup' %} + ☀ Standup + {% elif task.type == 'broadcast' %} + 📡 Broadcast + {% elif task.type == 'action_knowledge' %} + 🧠 Wissen {% endif %} {% if task.parent_task_id %} ↳ #{{ task.parent_task_id }}