feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
# Taskboard — Aufgaben statt Delay-Schleifen
|
||||
|
||||
Setzt A1 aus der [Roadmap](Roadmap.md) um. Das Taskboard ist das Fundament, auf dem
|
||||
Audit (A3), Staging (A2), Marktkalender (C1) und das Ergebnisregister (C7/C8)
|
||||
aufsetzen. Es löst zugleich sechs Altpunkte auf einmal (F-A5 Queue, F-A4 Run-Historie,
|
||||
B8 Rekursion, B6 `Task.Delay`-Überlauf, B7 Cron in Lokalzeit, T7 `RunAsync` vs.
|
||||
`ChatAsync`).
|
||||
|
||||
Aufbau analog zum [Memory-Konzept](Memory-Konzept.md): erst warum die vorhandenen
|
||||
Mechanismen nicht reichen, dann Dateiformat, Wahrheitsaufteilung, Scanner-Verhalten,
|
||||
Invarianten, Migration.
|
||||
|
||||
## Das Problem
|
||||
|
||||
Heute gibt es zwei getrennte, je für sich unzureichende Wege, einen Agenten Arbeit
|
||||
tun zu lassen:
|
||||
|
||||
- **Der Cron-Scheduler** ([`AgentScheduler`](../src/ClawdDotNet.Core/Scheduling/AgentScheduler.cs),
|
||||
[`ToolJobScheduler`](../src/ClawdDotNet.Core/Scheduling/ToolJobScheduler.cs)) hängt starr
|
||||
am Agenten: eine Cron-Zeile je Agent, ausgeführt über ein `Task.Delay` bis zum
|
||||
nächsten Termin. Ein jährlicher Termin bedeutet ein `Task.Delay` über Monate (B6).
|
||||
Cron läuft in Lokalzeit ohne explizite Zone (B7). Ob mit oder ohne Kontext gelaufen
|
||||
wird, entscheidet ein implizites Flag (`UseChatContext`, T7).
|
||||
- **Die `coordination/*.md`-Dateien** im SharedWorkspace sind die improvisierte
|
||||
Antwort der Agenten darauf, dass es kein Aufgabenmodell gibt: `task_*`-, `status_*`-
|
||||
und `broadcast`-Dateien, per Konvention beschrieben, ohne Schema, ohne Claiming,
|
||||
ohne Zustandsübergänge. Zwei Agenten, die dieselbe Datei „übernehmen", tun das ohne
|
||||
jede Absicherung.
|
||||
|
||||
Delegation läuft heute über rekursives `send_message`/`spawn` — ein Agent ruft
|
||||
synchron einen anderen, der wieder einen dritten (B8: Zyklengefahr, deshalb ein
|
||||
`LoopGuard` als Notbremse). Es gibt keine Run-Historie am Auftrag und keine Queue.
|
||||
|
||||
## Warum ein neues Subsystem, nicht der vorhandene State-Store
|
||||
|
||||
Dieselbe Überlegung wie beim Gedächtnis: `IStateStore` ist eine Schlüssel-Wert-Tabelle
|
||||
für kleine Marker. Ein Aufgabenmodell mit Status, Zuweisung, Abhängigkeiten und
|
||||
Terminen darin abzulegen hieße, JSON in eine `Value`-Spalte zu schreiben — nicht
|
||||
filterbar, nicht atomar claimbar, nicht auswertbar.
|
||||
|
||||
Der Kern ist eine **atomare Anspruchsnahme** (Claim). Genau das kann ein Dateisystem
|
||||
nicht verlässlich und der Schlüssel-Wert-Store nicht ausdrücken, eine SQL-Zeile mit
|
||||
einem bedingten `UPDATE` aber sehr wohl. Deshalb eine eigene Tabelle auf dem
|
||||
vorhandenen [`SqliteStorage`](../src/ClawdDotNet.Core/Storage/SqliteStorage.cs) (WAL,
|
||||
`busy_timeout`, prozessweite Schreib-Warteschlange) — dieselbe Grundlage, die schon
|
||||
Gedächtnis, Zustand und Verbrauch teilen.
|
||||
|
||||
## Wahrheitsaufteilung — Datei ist Definition, DB ist Koordination
|
||||
|
||||
Die eine Entscheidung, an der alles hängt:
|
||||
|
||||
| Ebene | Wahrheit über | Wer schreibt |
|
||||
|---|---|---|
|
||||
| **Markdown-Datei** (Frontmatter + Rumpf) | die *Definition* der Aufgabe: Titel, Priorität, Assignee, Termin, Abnahme, Abhängigkeiten. Menschen- und agentenlesbar. | Mensch (Editor), Agent (`task_*`-Tool) |
|
||||
| **SQLite-Tabelle `Tasks`** | den *Ausführungszustand*: Status, Claim, Lease, Last-Fired-Marker je Termin, Blocker-Auflösung. | ausschließlich das Taskboard selbst (Importer, Scanner, Tool) |
|
||||
|
||||
**Regel:** Für die Definition ist die Datei die Wahrheit. Für jede
|
||||
Ausführungsentscheidung ist die DB die Wahrheit. Weichen beide ab (Absturz zwischen
|
||||
DB-Claim und Datei-Schreiben), gewinnt die DB, und die Datei wird bei der
|
||||
Reconciliation nachgezogen.
|
||||
|
||||
Warum nicht alles nur in die DB und die Datei als reine Projektion? Weil die Datei der
|
||||
Bedienpunkt ist: Ein Mensch soll eine Aufgabe im Editor anlegen und ändern können, ein
|
||||
Agent über sein Tool, und beides soll im SharedWorkspace sichtbar und versionierbar
|
||||
bleiben. Warum nicht alles nur in Dateien? Weil das Claiming dort nicht atomar geht —
|
||||
siehe oben. Die Aufteilung nimmt von beidem das Belastbare.
|
||||
|
||||
Der **Importer** ist die Brücke: Er liest die Frontmatter-Definition und spiegelt sie
|
||||
idempotent in die DB-Zeile (`UPSERT` auf `task_id`). Er läuft beim Start (alle Dateien),
|
||||
nach jeder `task_*`-Änderung (die betroffene Datei) und optional per
|
||||
`FileSystemWatcher` mit Debounce (~500 ms, wie bei A4), damit von Hand editierte
|
||||
Dateien zeitnah einfließen.
|
||||
|
||||
## Dateiformat
|
||||
|
||||
Aufgaben liegen als eine Datei je Aufgabe unter `SharedWorkspace/tasks/`. Der
|
||||
Dateiname ist beschreibend (`recherche-nvda-earnings.md`); die stabile Identität ist
|
||||
die `id` im Frontmatter, nicht der Name — so überlebt eine Aufgabe das Umbenennen.
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: t-8f3a2c # stabil, beim Anlegen vergeben; Wahrheit der Identität
|
||||
title: NVDA Earnings recherchieren
|
||||
status: todo # backlog | todo | in_progress | in_review | done | canceled | blocked
|
||||
type: work # work | approval | human_input
|
||||
priority: 3 # 1 (niedrig) .. 5 (hoch)
|
||||
assignee: "@crawler" # @new | @<agentId> | @human
|
||||
when: # optional; fehlt = einmalige Aufgabe, sofort fällig
|
||||
kind: cron # at | every | cron
|
||||
value: "0 7 * * 1-5"
|
||||
tz: Europe/Berlin # PFLICHT, wenn when gesetzt ist — kein Termin ohne Zone
|
||||
require_approval: false # true = gilt erst nach Review (in_review) als done
|
||||
acceptance: | # Abnahmekriterien, gegen die das Ergebnis geprüft wird
|
||||
Aktuelle Zahlen mit Datum und Quelle, in SharedWorkspace/data/nvda.json abgelegt.
|
||||
blocked_by: [t-4b1e] # diese Aufgabe startet erst, wenn alle Blocker done sind
|
||||
onlyWhenMarketOpen: false # C1: Termin nur auslösen, wenn der Markt offen ist
|
||||
---
|
||||
|
||||
Freitext-Rumpf: Auftragsbeschreibung, Kontext, Verweise. Geht als Aufgabenstellung
|
||||
an den Agenten. Kommentare (Ergebnisse, Kritik, Reopen) werden unten angehängt.
|
||||
```
|
||||
|
||||
Feldregeln:
|
||||
|
||||
- **`when.kind`**: `at` (einmaliger Zeitpunkt, ISO 8601), `every` (Intervall, z. B.
|
||||
`30m`), `cron` (5-Felder-Ausdruck wie bisher). `tz` ist bei allen dreien Pflicht —
|
||||
das ist die Antwort auf B7. Die vorhandene
|
||||
[`CronExpression`](../src/ClawdDotNet.Core/Scheduling/CronExpression.cs) rechnet
|
||||
heute zonen-blind in `DateTime.Now`; sie wird in einen zonen-bewussten Aufruf
|
||||
gekapselt (nächsten Termin in `tz` bestimmen, dann in UTC vergleichen). Das
|
||||
verzahnt sich mit R2 (`TimeProvider`) aus der [Teststrategie](Teststrategie.md).
|
||||
- **`assignee`** ersetzt das `UseChatContext`-Flag durch eine explizite Angabe (T7):
|
||||
- `@new` bzw. `@new:<agentId>` → frischer Lauf ohne Historie (`AgentEngine.RunAsync`).
|
||||
Bloßes `@new` ist nur eindeutig, wenn die Instanz genau **einen** Agenten hat; sonst
|
||||
benennt `@new:<agentId>` den Ziel-Agenten. Ist er nicht auflösbar, scheitert der
|
||||
Dispatch mit klarer Meldung, statt einen falschen Agenten zu raten.
|
||||
- `@<agentId>` → bestehender Agent mit seinem Kontext (`AgentEngine.ChatAsync`)
|
||||
- `@human` → wartet auf einen Menschen; kein Modell-Lauf
|
||||
- **`type`**: `work` (Standard), `approval` und `human_input`. Bei den letzten beiden
|
||||
ist ein Mensch der Assignee; seine Antwort **ist** das Ergebnis und Input für
|
||||
Folgeaufgaben. Kein Sonderpfad — nur ein Assignee, der kein Modell ist.
|
||||
|
||||
## Scanner-Verhalten
|
||||
|
||||
Ein einziger Takt (~60 s) statt vieler langer `Task.Delay`. Damit kennt das System
|
||||
keine Monats-Delays mehr (B6), und ein verpasster Takt ist ein verpasster Termin, kein
|
||||
Zeitbombe.
|
||||
|
||||
Je Takt:
|
||||
|
||||
1. **Fällige Termine bestimmen.** Aus jeder aktiven Task-Zeile den nächsten Termin in
|
||||
ihrer `tz` berechnen und gegen „jetzt" prüfen. Ein Termin ist durch einen
|
||||
**Occurrence-Key** eindeutig: `task_id` + geplante Feuerzeit (bei `at`/`cron`/`every`).
|
||||
2. **Claim vor Lauf (at-most-once).** Bevor gelaufen wird, wird der Occurrence-Key
|
||||
atomar beansprucht:
|
||||
|
||||
```sql
|
||||
UPDATE Tasks
|
||||
SET claim_token = @token, claimed_at = @now, last_occurrence = @occ, status = 'in_progress'
|
||||
WHERE id = @id
|
||||
AND status IN ('todo','backlog')
|
||||
AND (last_occurrence IS NULL OR last_occurrence < @occ)
|
||||
AND (claim_token IS NULL OR claimed_at < @leaseCutoff);
|
||||
```
|
||||
|
||||
Genau eine Zeile betroffen = Anspruch gewonnen. Ein zweiter, gleichzeitiger Takt
|
||||
findet die Bedingung nicht mehr erfüllt (Rowcount 0) und läuft **nicht** — so wird
|
||||
ein doppelter Tick zu einem Lauf.
|
||||
3. **Dispatch nach Assignee.** `@new` → `RunAsync`; `@<agent>` → `ChatAsync`; `@human`
|
||||
→ kein Lauf, Status bleibt/wird `in_review` bzw. `todo`, die UI zeigt die Aufgabe
|
||||
als wartend. Der Rumpf (+ Abnahmekriterien) ist die Nachricht an den Agenten.
|
||||
4. **Abschluss verbuchen.** Last-Fired-Marker setzen (`last_occurrence = @occ`), Status
|
||||
fortschreiben, Claim lösen. Bei `require_approval` → `in_review` statt `done`. Der
|
||||
Lauf wird am Task verknüpft (Grundlage für A3-Receipts und die Run-Historie F-A4).
|
||||
|
||||
**Kein Retry-Sturm.** Der Marker wird auch bei einem **fehlgeschlagenen** Lauf gesetzt:
|
||||
ein fehlgeschlagener Lauf bleibt der einzige Versuch für diesen Termin. Wiederholung
|
||||
ist eine bewusste Entscheidung (neuer Termin oder Reopen), kein Automatismus. Das
|
||||
schützt vor einem Agenten, der bei jedem Takt erneut in denselben Fehler läuft und
|
||||
Budget verbrennt.
|
||||
|
||||
**Serialisierung mit den Chat-Läufen (B2).** Der Scanner ruft die Engine über
|
||||
dieselben Einstiegspunkte wie WebView, ToolJob und AgentComm. `ChatAsync` ist bereits
|
||||
je Agent über ein `SemaphoreSlim`-Gate serialisiert
|
||||
([`AgentEngine`](../src/ClawdDotNet.Core/Engine/AgentEngine.cs)) — der Scanner fügt
|
||||
sich dort ein, statt einen zweiten, konkurrierenden Pfad in den geteilten
|
||||
Konversationskontext aufzumachen. Der Claim ist eine **DB**-Grenze (welcher Termin wird
|
||||
behandelt), das Agent-Gate eine **Kontext**-Grenze (kein verschränkter Nachrichtenstrom).
|
||||
Beide werden gebraucht; keine ersetzt die andere.
|
||||
|
||||
### Startup-Reconciliation
|
||||
|
||||
Beim Start wird Soll (Frontmatter) gegen Ist (DB-Marker/Claims) abgeglichen, statt
|
||||
verpasste Läufe still zu überspringen:
|
||||
|
||||
- Alle Task-Dateien importieren (UPSERT), gelöschte Dateien in der DB als `archived`
|
||||
markieren.
|
||||
- **Stale Claims freigeben:** Ein Claim, dessen `claimed_at` älter ist als die
|
||||
Lease-Dauer (ein während des Laufs abgestürzter Prozess), wird verworfen — die
|
||||
Bedingung `claimed_at < @leaseCutoff` im Claim-`UPDATE` erledigt das ohnehin, die
|
||||
Reconciliation stellt den Status zusätzlich von `in_progress` auf `todo` zurück.
|
||||
- **Verpasste Termine erkennen:** Liegt der letzte planmäßige Termin nach dem
|
||||
Last-Fired-Marker, ist ein Lauf ausgefallen. Er wird als solcher gemeldet (Log, später
|
||||
A5), nicht heimlich verschluckt. Ob nachgeholt wird, ist Politik — Standard: einmal
|
||||
nachholen, sonst würde ein über Nacht ausgeschalteter Rechner beim Start eine Welle
|
||||
auslösen.
|
||||
|
||||
## Die drei Invarianten
|
||||
|
||||
Der Scanner-Kern ist in der Roadmap als der heikle Teil markiert (die Fehlerklasse, die
|
||||
hier schon einmal schiefging). Er wird strikt gegen diese Invarianten gebaut und mit
|
||||
Property-Tests (FsCheck, siehe Teststrategie) abgesichert:
|
||||
|
||||
1. **Nie zwei Claims auf einen Task-Termin.** Garantiert durch das bedingte `UPDATE`:
|
||||
Rowcount ≤ 1 pro Occurrence-Key. Test: N parallele Claims auf denselben Key → genau
|
||||
einer gewinnt.
|
||||
2. **Kein Dispatch bei offenem Blocker.** Eine Aufgabe mit unerfüllten `blocked_by`
|
||||
ist nicht `todo`, sondern `blocked`, und die Claim-Bedingung (`status IN
|
||||
('todo','backlog')`) greift nicht. Test: Blocker offen → kein Lauf; letzter Blocker
|
||||
`done` → genau ein Auto-Dispatch.
|
||||
3. **Doppelter Tick = ein Lauf.** Zwei Takte im selben Fenster konkurrieren um denselben
|
||||
Occurrence-Key; der Claim lässt nur einen durch. Test: zwei gleichzeitige
|
||||
`ScanOnce` → ein Lauf, ein Marker.
|
||||
|
||||
Diese drei sind keine Kür, sondern die Bedingung dafür, dass der Kern mit einem
|
||||
schwächeren Modell umgesetzt werden darf.
|
||||
|
||||
## Abhängigkeiten und Eskalation
|
||||
|
||||
- **`blocked_by` mit Auto-Dispatch:** Wird eine Aufgabe `done`, sucht das Board alle
|
||||
Aufgaben, deren `blocked_by` sie enthält. Sind für eine davon **alle** Blocker `done`,
|
||||
wechselt sie `blocked → todo` und der Scanner nimmt sie beim nächsten Takt auf.
|
||||
- **Blocker-Eskalation:** Meldet ein Agent einen Blocker (`task_update status=blocked`
|
||||
mit Begründung), fällt die Aufgabe und der Zuständige (Lead/Benutzer) wird
|
||||
benachrichtigt. Bis A5 (Matrix) steht, geht das über den vorhandenen Log-/UI-Weg.
|
||||
|
||||
## Zusammenspiel mit Staging (A2) und Reopen
|
||||
|
||||
- **`require_approval` / `in_review`:** Eine Aufgabe mit `require_approval: true` gilt
|
||||
nach dem Lauf nicht als `done`, sondern als `in_review`. Das ist der natürliche
|
||||
Andockpunkt für A2: Die Freigabe erzeugt einen Folge-Task, der den Agenten mit dem
|
||||
eingefrorenen Aufruf weckt (so bleibt der Lauf regulär beendet, kein pausierter
|
||||
In-Memory-Zustand — genau die Architektur-Vorgabe aus der Roadmap-Einstufung für A2).
|
||||
- **Reopen/Feedback:** Ergebnis + Kritik gehen per `task_comment` an **denselben**
|
||||
Agenten zurück (`ChatAsync` in dessen Kontext), statt eine neue Aufgabe von vorn zu
|
||||
beginnen. Die Aufgabe kehrt nach `todo`/`in_progress` zurück, der Verlauf am Task
|
||||
bleibt erhalten.
|
||||
|
||||
## Agenten-Tool
|
||||
|
||||
Ein Tool `Taskboard` im Muster von `MemoryTool` (eine Aktion je Aufruf), das über einen
|
||||
neuen `ITaskRepository` auf dem `AgentToolContext` arbeitet (analog `IMemoryRepository?
|
||||
Memory`):
|
||||
|
||||
| Aktion | Zweck |
|
||||
|---|---|
|
||||
| `task_create` | Aufgabe anlegen — schreibt Datei **und** DB-Zeile (über den Importer). Vergibt die `id`. |
|
||||
| `task_list` | Aufgaben filtern (Status, Assignee, Betreff). Gekappte, kontextschonende Ausgabe wie bei Memory. |
|
||||
| `task_update` | Status/Felder ändern; `blocked` melden; Ergebnis eintragen. |
|
||||
| `task_comment` | Kommentar/Kritik anhängen; mit Reopen den Zuständigen erneut wecken. |
|
||||
|
||||
Agent-zu-Agent-Delegation läuft künftig hierüber: Statt rekursivem `send_message` legt
|
||||
ein Agent eine Aufgabe mit `assignee: @<other>` an. Das ist strukturell zyklenfrei (B8) —
|
||||
eine Aufgabe ist ein Datensatz, kein synchroner Aufruf-Stack.
|
||||
|
||||
**Sicherheit:** Ein Task-Rumpf ist Prompt-Input für den Assignee. Fremdbestimmte Inhalte
|
||||
(Ergebnisse anderer Tools, die in einen Task fließen) werden als Daten gerahmt, nicht als
|
||||
Anweisung — dieselbe Linie wie K2. Irreversibles, das ein Task auslöst, läuft über A2.
|
||||
|
||||
## Migration
|
||||
|
||||
Die `coordination/*.md`-Dateien gehen im Taskboard auf. Die Altdateien haben kein Schema
|
||||
(freies Markdown wie `# Task: …`, `## Status: ASSIGNED`), deshalb bewusst konservativ
|
||||
(`CoordinationMigration`, beim Start ausgeführt):
|
||||
|
||||
- **`task_*`-Dateien** → einmalig als `backlog`-Aufgaben übernommen: Titel aus der
|
||||
`# Task:`-Überschrift (sonst erste Überschrift, sonst Dateiname), das ganze Markdown
|
||||
als Rumpf, `assignee: @human` als sicherer Default, bis ein Mensch sie zuordnet.
|
||||
`backlog` (nicht `todo`), damit der Scanner nichts unbesehen ausführt.
|
||||
- **`status_*`, `broadcast`, Incident-Berichte, `*.json`-Artefakte** → keine Aufgaben;
|
||||
bleiben unangetastet (später nach A5/Matrix bzw. verfallen als Altbestand).
|
||||
|
||||
Idempotent durch **Verschieben statt Löschen**: eine übernommene `task_*`-Datei wandert
|
||||
nach `coordination/migrated/` — die Historie bleibt, ein zweiter Start findet sie nicht
|
||||
mehr. Vor der Migration greift die übliche Regel: frisches Backup.
|
||||
|
||||
## Ein Takt für alles — Ablösung der Alt-Scheduler
|
||||
|
||||
Der Scanner ist der **einzige** periodische Treiber. Die früheren `AgentScheduler` und
|
||||
`ToolJobScheduler` (zwei `Task.Delay`-Schleifen mit B6/B7, ungetestet) sind **gelöscht** —
|
||||
alles Periodische ist jetzt ein Task:
|
||||
|
||||
- **Geplanter Agent-Lauf** (früher `scheduler`-Config) → ein normaler Task mit
|
||||
`when: cron` und Assignee `@new:<agent>`.
|
||||
- **Tool-Job-Poll** (früher `toolJobs`-Config, z. B. `telegram_poll`) → ein Task vom Typ
|
||||
**`tool_job`** mit `tool_name`/`job_type`. Beim fälligen Termin tickt der Dispatcher den
|
||||
`IToolJobProvider` und weckt den Zielagenten (Assignee) nur, wenn der Tick etwas meldet —
|
||||
mit oder ohne Kontext, je nach `ToolJobResult`.
|
||||
|
||||
Zwei Feinheiten, die dabei geradegezogen wurden:
|
||||
|
||||
- **Wiederkehrende Tasks** (`cron`/`every`/`tool_job`) kehren nach dem Feuern auf `todo`
|
||||
zurück statt auf `done` — sonst liefe ein Cron-Task nur ein einziges Mal. Der
|
||||
Last-Fired-Marker verhindert weiterhin, dass **derselbe** Termin doppelt feuert.
|
||||
- **`backlog` ist ein Halte-Status**: Der Scanner claimt nur `todo`. Eine Aufgabe in
|
||||
`backlog` (frisch importiert, migriert, oder ein deaktivierter Poll) ruht, bis ein
|
||||
Mensch sie auf `todo` setzt.
|
||||
|
||||
Die Alt-Konfiguration (`scheduler`, `toolJobs`) wird beim Start einmalig und
|
||||
nicht-destruktiv in Tasks migriert (`SchedulerTaskMigration`, stabile Ids
|
||||
`sched-<agent>` / `tj-<agent>-<job>`). Ein manuelles „Jetzt ausführen" im Host läuft über
|
||||
`TaskScanner.RunTaskNowAsync`.
|
||||
|
||||
## Verzahnung
|
||||
|
||||
- **C1 Marktkalender:** `onlyWhenMarketOpen` gehört ins Frontmatter, nicht in einen
|
||||
eigenen Mechanismus — der Scanner überspringt einen Termin, wenn der Markt zu ist.
|
||||
- **A3 Audit/Receipts:** Jeder Lauf wird am Task verknüpft; der Abschluss-Beleg (Schritte,
|
||||
Tokens, Kosten) fällt daraus ab und macht C7 weitgehend zum Abfallprodukt.
|
||||
- **F-A5/F-A4:** Das Board **ist** die Queue; die verknüpften Läufe **sind** die Historie.
|
||||
|
||||
## Umsetzungsreihenfolge
|
||||
|
||||
Bewusst so geschnitten, dass der heikle Kern zuletzt und gegen grüne Invarianten kommt:
|
||||
|
||||
1. **Modelle + `ITaskRepository` + Schema + Frontmatter-Parser.** Reine, testbare
|
||||
Bausteine. Der Parser wird eng gebaut (die Frontmatter ist ein kleiner, flacher
|
||||
Satz aus Skalaren und kurzen Listen) — keine YAML-Bibliothek, passend zum
|
||||
dependency-armen Stil des Projekts. 4.6-tauglich.
|
||||
2. **`Taskboard`-Tool + Importer.** Datei ↔ DB, `task_*`-Aktionen. 4.6-tauglich.
|
||||
3. **Scanner-Kern** (Claim, Dispatch, Reconciliation, Auto-Dispatch) — strikt gegen die
|
||||
drei Invarianten, mit Property-Tests. Der in der Roadmap für Opus 5/Fable markierte
|
||||
Teil; mit Opus 4.8 nur streng nach diesem Dokument und mit den Invarianten-Tests als
|
||||
Netz.
|
||||
4. **Migration** der `coordination/*.md`.
|
||||
|
||||
Neue Subsysteme kommen mit Tests nach der [Teststrategie](Teststrategie.md); die
|
||||
Invarianten-Tests sind bei Punkt 3 die Absicherung, kein Nice-to-have.
|
||||
|
||||
## Offen
|
||||
|
||||
- **Nachhol-Politik verpasster Termine** — umgesetzt als „höchstens einmal nachholen":
|
||||
der Scanner nimmt den jüngsten verpassten Termin, nicht jeden einzelnen. Eine frische
|
||||
Aufgabe holt zudem keinen Termin von **vor** ihrer Anlage nach. Ob das je Task
|
||||
abschaltbar sein soll (`catchUp: true|false`), ist offen.
|
||||
- **Marktkalender (C1)** — der Scanner fragt eine `IMarketCalendar` (derzeit Platzhalter
|
||||
„immer offen"). C1 liefert später den echten Kalender; die Verzahnung steht.
|
||||
- **DST-Randfall** — eine bei der Zeitumstellung nicht existierende Ortszeit
|
||||
(Frühjahr, „02:30") wird derzeit übersprungen statt verschoben. Für die geplanten
|
||||
Termine unkritisch; die Härtung gehört zur Scheduler-Nacharbeit (Teststrategie R4/R5).
|
||||
- **Priorität als Reihenfolge** — bei mehreren fälligen Aufgaben desselben Agenten
|
||||
bestimmt `priority` die Reihenfolge; ob strikt oder gewichtet, ist noch offen.
|
||||
- **Aufräumen** — `done`/`canceled`-Aufgaben nach einer Frist archivieren, damit
|
||||
`tasks/` nicht zuwächst (dieselbe Überlegung wie „Verfall" beim Gedächtnis).
|
||||
Reference in New Issue
Block a user