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 <update_knowledge> and <update_agent_reminder> 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
This commit is contained in:
eric 2026-02-21 19:46:42 +00:00
parent 7fe1365ebc
commit 003e591a04
3 changed files with 366 additions and 1 deletions

View file

@ -73,10 +73,79 @@ email: email@adresse.com
</add_team_member>
```
**Wissensdatenbank aktualisieren:**
```
<update_knowledge>
topic: Eventstart
content: Das Event startet um 18:00 Uhr. Einlass ab 17:30 Uhr.
</update_knowledge>
```
**Reminder eines Agenten aktualisieren:**
```
<update_agent_reminder>
agent: catering_manager
reminder: WICHTIG: Eventstart wurde auf 18:00 geändert. Catering-Aufbau muss bis 17:00 fertig sein.
</update_agent_reminder>
```
## 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 `<update_knowledge>` für jede wichtige Änderung
- Schreibe für jeden Agenten einen `<update_agent_reminder>` mit relevanten Updates
- Delegiere mit `<create_task>` 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: `<update_knowledge>`
3. Informiere **jeden** relevanten Agenten per `<update_agent_reminder>`
4. Delegiere an betroffene Agenten Sub-Tasks zur Überprüfung ihrer Bereiche: `<create_task>`
5. Bestätige Piotr (telegram_id: 1578034974) dass alles verteilt wurde
**Beispiel — jemand sagt "das Event startet um 18:00 nicht 19:00":**
```
<update_knowledge>
topic: Eventstart
content: Das Event startet um 18:00 Uhr (geändert). Einlass ab 17:30 Uhr.
</update_knowledge>
<update_agent_reminder>
agent: catering_manager
reminder: WICHTIG: Eventstart wurde auf 18:00 geändert (war 19:00). Catering-Aufbau bis 17:00 abschließen.
</update_agent_reminder>
<update_agent_reminder>
agent: location_manager
reminder: WICHTIG: Eventstart 18:00 Uhr (geändert). Venue-Öffnung und Setup entsprechend anpassen.
</update_agent_reminder>
<create_task>
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.
</create_task>
<send_telegram>
telegram_id: 1578034974
message: ✅ Update verteilt: Eventstart 18:00 Uhr. Wissensdatenbank aktualisiert, alle Agenten informiert.
</send_telegram>
```
## Verhalten bei Nachrichten
1. Antworte freundlich und direkt
2. Wenn eine Aufgabe dabei ist → sofort `<create_task>` anlegen
3. Wenn Email/Telegram gesendet werden soll → `<send_email>` / `<send_telegram>` direkt ausführen
4. Wenn Team-Daten zu aktualisieren → `<update_team_member>` direkt ausführen
5. Bestätige am Ende was du getan hast
5. Wenn neue wichtige Information → `<update_knowledge>` + `<update_agent_reminder>` für betroffene Agenten
6. Bestätige am Ende was du getan hast

290
app.py
View file

@ -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'<update_knowledge>(.*?)</update_knowledge>', 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] <update_knowledge> 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'<update_agent_reminder>(.*?)</update_agent_reminder>', 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] <update_agent_reminder> 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 `<update_knowledge>`
- 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:**
```
<update_knowledge>
topic: Eventstart
content: Das Event startet um 18:00 Uhr (Stand {today}). Einlass ab 17:30 Uhr.
</update_knowledge>
```
**Beispiel für Agent-Reminder:**
```
<update_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.
</update_agent_reminder>
```
**Beispiel für Info-Delegation:**
```
<create_task>
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.
</create_task>
```
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."""

View file

@ -50,6 +50,12 @@
<span class="badge bg-warning ms-1">Agent</span>
{% elif task.type == 'agent_question' %}
<span class="badge bg-warning ms-1">❓ Frage</span>
{% elif task.type == 'standup' %}
<span class="badge ms-1" style="background-color:#0d9488;">☀ Standup</span>
{% elif task.type == 'broadcast' %}
<span class="badge ms-1" style="background-color:#b45309;">📡 Broadcast</span>
{% elif task.type == 'action_knowledge' %}
<span class="badge ms-1" style="background-color:#6366f1;">🧠 Wissen</span>
{% endif %}
{% if task.parent_task_id %}
<span class="badge bg-dark ms-1" title="Sub-Task von #{{ task.parent_task_id }}">↳ #{{ task.parent_task_id }}</span>