Keep journal openings from treating finished days as open plans.
A 400-character entry prefix plus plan regex could reopen a completed yesterday as if it were still pending. Openings now take plans only from yesterday's user dialogue, never from saved entries, and skip space recency in the first call. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e42f751fdc
commit
ec0f8bad55
|
|
@ -70,6 +70,7 @@ def build_internal_context(
|
||||||
space_id: str | None = None,
|
space_id: str | None = None,
|
||||||
journal_day_id: str | None = None,
|
journal_day_id: str | None = None,
|
||||||
purpose: str = "dialogue_turn",
|
purpose: str = "dialogue_turn",
|
||||||
|
include_space_recency: bool = True,
|
||||||
include_existing: bool = False,
|
include_existing: bool = False,
|
||||||
conversation_ids: list[str] | None = None,
|
conversation_ids: list[str] | None = None,
|
||||||
existing_text: str = "",
|
existing_text: str = "",
|
||||||
|
|
@ -118,7 +119,7 @@ def build_internal_context(
|
||||||
day_messages = read("conversation_messages", profile_id=profile_id, context={"conversation_id": conversation_id})
|
day_messages = read("conversation_messages", profile_id=profile_id, context={"conversation_id": conversation_id})
|
||||||
items.append({"type": "day_messages", "messages": day_messages})
|
items.append({"type": "day_messages", "messages": day_messages})
|
||||||
|
|
||||||
if purpose == "dialogue_turn" and space_id:
|
if purpose == "dialogue_turn" and space_id and include_space_recency:
|
||||||
prior = retrieve(
|
prior = retrieve(
|
||||||
profile_id,
|
profile_id,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
"""Journal-specific first impulse. Not a generic continuation or intent engine.
|
"""Journal-specific first impulse. Not a generic continuation or intent engine.
|
||||||
|
|
||||||
Uses the existing Privacy Gateway, dialogue prompt, guards and request-scoped
|
Uses the existing Privacy Gateway, dialogue prompt, guards and request-scoped
|
||||||
trace. Attested plans and open day points come only from user-role sources.
|
trace. Attested plans come only from yesterday's user-role dialogue. Open day
|
||||||
Assistant text is conversation context, never a user fact. Recency is not a pattern.
|
points stay on the same journal day. Saved journal entries are completed
|
||||||
|
narrative, never an opening plan. Assistant text is conversation context, never
|
||||||
|
a user fact. Recency is not a pattern.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
from context_builder import assemble_text, build_internal_context
|
from context_builder import assemble_text, build_internal_context
|
||||||
from debug_store import persist_engine_error, persist_step
|
from debug_store import persist_engine_error, persist_step
|
||||||
|
|
@ -20,11 +23,14 @@ from retrieval import retrieve
|
||||||
NEUTRAL_OPENING = "Wenn du magst, fang einfach an – ich höre zu."
|
NEUTRAL_OPENING = "Wenn du magst, fang einfach an – ich höre zu."
|
||||||
OPENING_POLICY = (
|
OPENING_POLICY = (
|
||||||
"Dies ist der erste Impuls dieses Gesprächs. Es gibt hier noch keine Nutzerzeile. "
|
"Dies ist der erste Impuls dieses Gesprächs. Es gibt hier noch keine Nutzerzeile. "
|
||||||
"Nur belegte Vorhaben aus user-Quellen oder offene user-Punkte desselben Journal Day aufgreifen. "
|
"Nur belegte Vorhaben aus dem user-Dialog des unmittelbar vorigen Kalendertags "
|
||||||
|
"oder offene user-Punkte desselben Journal Day aufgreifen. "
|
||||||
|
"Ein gespeicherter Journal-Eintrag ist abgeschlossene Erzählung, kein offenes Vorhaben. "
|
||||||
"Ein Recency-Treffer ist kein bewiesenes wiederkehrendes Muster. "
|
"Ein Recency-Treffer ist kein bewiesenes wiederkehrendes Muster. "
|
||||||
"Nicht erwähnt ist nicht geschehen. Ein Plan ist kein Vollzug. "
|
"Nicht erwähnt ist nicht geschehen. Ein Plan ist kein Vollzug. "
|
||||||
"Assistententext ist Gesprächskontext, kein Nutzerfakt. "
|
"Assistententext ist Gesprächskontext, kein Nutzerfakt. "
|
||||||
"Keine Behauptung, ein Vorhaben sei ausgeführt worden."
|
"Keine Behauptung, ein Vorhaben sei ausgeführt worden. "
|
||||||
|
"Keine bereits erzählte, abgeschlossene Episode eines früheren Tags als offenen Faden führen."
|
||||||
)
|
)
|
||||||
|
|
||||||
FUTURE_INTENT = re.compile(
|
FUTURE_INTENT = re.compile(
|
||||||
|
|
@ -46,6 +52,22 @@ UNEARNED_PATTERN = re.compile(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_calendar_date(value: str | None) -> date | None:
|
||||||
|
raw = (value or "").strip()
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return date.fromisoformat(raw)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_previous_calendar_day(source: date | None, current: date | None) -> bool:
|
||||||
|
if source is None or current is None:
|
||||||
|
return False
|
||||||
|
return source == current - timedelta(days=1)
|
||||||
|
|
||||||
|
|
||||||
def is_attested_plan(text: str) -> bool:
|
def is_attested_plan(text: str) -> bool:
|
||||||
"""User wording that marks a plan or intention. Not past completion, not overlap."""
|
"""User wording that marks a plan or intention. Not past completion, not overlap."""
|
||||||
body = (text or "").strip()
|
body = (text or "").strip()
|
||||||
|
|
@ -88,7 +110,8 @@ def collect_opening_context(profile_id: str, conversation_id: str) -> dict:
|
||||||
journal_day_id = conversation.get("journal_day_id")
|
journal_day_id = conversation.get("journal_day_id")
|
||||||
if not space_id or not journal_day_id:
|
if not space_id or not journal_day_id:
|
||||||
raise StoreError("not_journal_conversation", "Erster Impuls nur für einen Journal-Dialog.")
|
raise StoreError("not_journal_conversation", "Erster Impuls nur für einen Journal-Dialog.")
|
||||||
get_day(profile_id, journal_day_id)
|
day = get_day(profile_id, journal_day_id)
|
||||||
|
current_date = parse_calendar_date(day.get("calendar_date"))
|
||||||
|
|
||||||
day_conversations = list_conversations_for_day(profile_id, journal_day_id)
|
day_conversations = list_conversations_for_day(profile_id, journal_day_id)
|
||||||
open_points: list[str] = []
|
open_points: list[str] = []
|
||||||
|
|
@ -114,6 +137,7 @@ def collect_opening_context(profile_id: str, conversation_id: str) -> dict:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
recency_user: list[str] = []
|
recency_user: list[str] = []
|
||||||
|
yesterday_user: list[str] = []
|
||||||
for entry in prior_entries:
|
for entry in prior_entries:
|
||||||
excerpt = (entry.get("excerpt") or "").strip()
|
excerpt = (entry.get("excerpt") or "").strip()
|
||||||
if excerpt:
|
if excerpt:
|
||||||
|
|
@ -122,15 +146,19 @@ def collect_opening_context(profile_id: str, conversation_id: str) -> dict:
|
||||||
excerpt = (source.get("excerpt") or "").strip()
|
excerpt = (source.get("excerpt") or "").strip()
|
||||||
if excerpt:
|
if excerpt:
|
||||||
recency_user.append(excerpt)
|
recency_user.append(excerpt)
|
||||||
|
if excerpt and is_previous_calendar_day(
|
||||||
|
parse_calendar_date(source.get("calendar_date")),
|
||||||
|
current_date,
|
||||||
|
):
|
||||||
|
yesterday_user.append(excerpt)
|
||||||
|
|
||||||
user_pool = [*open_points, *recency_user]
|
plans = attested_plans_from_user_texts(yesterday_user)
|
||||||
plans = attested_plans_from_user_texts(user_pool)
|
|
||||||
has_relevant = bool(plans or open_points)
|
has_relevant = bool(plans or open_points)
|
||||||
return {
|
return {
|
||||||
"space_id": space_id,
|
"space_id": space_id,
|
||||||
"journal_day_id": journal_day_id,
|
"journal_day_id": journal_day_id,
|
||||||
"conversation_id": conversation_id,
|
"conversation_id": conversation_id,
|
||||||
"user_texts": user_pool,
|
"user_texts": [*open_points, *yesterday_user],
|
||||||
"assistant_texts": day_assistant,
|
"assistant_texts": day_assistant,
|
||||||
"attested_plans": plans,
|
"attested_plans": plans,
|
||||||
"open_day_points": open_points,
|
"open_day_points": open_points,
|
||||||
|
|
@ -230,13 +258,15 @@ def start_journal_opening(profile_id: str, conversation_id: str) -> dict:
|
||||||
journal_day_id=conversation.get("journal_day_id"),
|
journal_day_id=conversation.get("journal_day_id"),
|
||||||
purpose="dialogue_turn",
|
purpose="dialogue_turn",
|
||||||
conversation_ids=day_ids,
|
conversation_ids=day_ids,
|
||||||
|
include_space_recency=False,
|
||||||
)
|
)
|
||||||
assembled = assemble_text(context)
|
assembled = assemble_text(context)
|
||||||
assembled = dict(assembled)
|
assembled = dict(assembled)
|
||||||
assembled["opening_hint"] = format_opening_hint(facts)
|
assembled["opening_hint"] = format_opening_hint(facts)
|
||||||
assembled["register_hint"] = (
|
assembled["register_hint"] = (
|
||||||
"Erster Impuls. Noch keine Nutzerzeile in diesem Gespräch. "
|
"Erster Impuls. Noch keine Nutzerzeile in diesem Gespräch. "
|
||||||
"Nur belegte Vorhaben oder offene user-Punkte. Kein Muster aus Recency."
|
"Nur Vorhaben aus dem user-Dialog von gestern oder offene user-Punkte dieses Tags. "
|
||||||
|
"Kein Muster aus Recency. Keine abgeschlossene frühere Episode wieder öffnen."
|
||||||
)
|
)
|
||||||
prompt = load_active_prompt("mvp.dialogue_turn")
|
prompt = load_active_prompt("mvp.dialogue_turn")
|
||||||
calls = 0
|
calls = 0
|
||||||
|
|
|
||||||
|
|
@ -190,8 +190,9 @@ def _space_recent_sources(profile_id: str, spec: dict[str, Any]) -> list[dict]:
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
conversations = conn.execute(
|
conversations = conn.execute(
|
||||||
f"""
|
f"""
|
||||||
SELECT c.id, c.updated, c.journal_day_id
|
SELECT c.id, c.updated, c.journal_day_id, d.calendar_date
|
||||||
FROM conversations c
|
FROM conversations c
|
||||||
|
LEFT JOIN journal_days d ON d.id = c.journal_day_id AND d.profile_id = c.profile_id
|
||||||
WHERE c.profile_id = ? AND c.space_id = ? {extra}
|
WHERE c.profile_id = ? AND c.space_id = ? {extra}
|
||||||
ORDER BY c.updated DESC, c.created DESC
|
ORDER BY c.updated DESC, c.created DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
|
|
@ -217,6 +218,7 @@ def _space_recent_sources(profile_id: str, spec: dict[str, Any]) -> list[dict]:
|
||||||
{
|
{
|
||||||
"conversation_id": conv["id"],
|
"conversation_id": conv["id"],
|
||||||
"journal_day_id": conv["journal_day_id"],
|
"journal_day_id": conv["journal_day_id"],
|
||||||
|
"calendar_date": conv["calendar_date"],
|
||||||
"excerpt": excerpt,
|
"excerpt": excerpt,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
|
@ -23,6 +25,8 @@ from journal_opening import (
|
||||||
attested_plans_from_user_texts,
|
attested_plans_from_user_texts,
|
||||||
collect_opening_context,
|
collect_opening_context,
|
||||||
is_attested_plan,
|
is_attested_plan,
|
||||||
|
is_previous_calendar_day,
|
||||||
|
parse_calendar_date,
|
||||||
)
|
)
|
||||||
from journal_reconstruct import assign_source_ids
|
from journal_reconstruct import assign_source_ids
|
||||||
from retrieval import pair_user_priority_messages
|
from retrieval import pair_user_priority_messages
|
||||||
|
|
@ -58,6 +62,9 @@ def main() -> None:
|
||||||
expect(not is_attested_plan("Heute war der Hafen ruhig. Kirschen am Stand."), "past report is not a plan")
|
expect(not is_attested_plan("Heute war der Hafen ruhig. Kirschen am Stand."), "past report is not a plan")
|
||||||
expect(not is_attested_plan("Wir standen vor dem Hafen und aßen Kirschen."), "vor without haben is not a plan")
|
expect(not is_attested_plan("Wir standen vor dem Hafen und aßen Kirschen."), "vor without haben is not a plan")
|
||||||
expect(not is_attested_plan("Hafen Kirschen Wind"), "word overlap is not a plan")
|
expect(not is_attested_plan("Hafen Kirschen Wind"), "word overlap is not a plan")
|
||||||
|
expect(parse_calendar_date("2026-08-29") == date(2026, 8, 29), "calendar date parses")
|
||||||
|
expect(is_previous_calendar_day(date(2026, 8, 29), date(2026, 8, 30)), "yesterday is previous")
|
||||||
|
expect(not is_previous_calendar_day(date(2026, 8, 29), date(2026, 9, 3)), "older than yesterday is not previous")
|
||||||
plans = attested_plans_from_user_texts(
|
plans = attested_plans_from_user_texts(
|
||||||
["Heute war der Hafen ruhig.", "Morgen wollen wir nach Cres fahren.", "Hafen Kirschen"]
|
["Heute war der Hafen ruhig.", "Morgen wollen wir nach Cres fahren.", "Hafen Kirschen"]
|
||||||
)
|
)
|
||||||
|
|
@ -157,6 +164,48 @@ def main() -> None:
|
||||||
expect(overlap_open.json().get("opening", {}).get("kind") == "local_neutral", "overlap falls back to neutral")
|
expect(overlap_open.json().get("opening", {}).get("kind") == "local_neutral", "overlap falls back to neutral")
|
||||||
expect("Muster" not in (overlap_open.json().get("opening") or {}).get("assistant", {}).get("body", ""), "neutral impulse does not claim a pattern")
|
expect("Muster" not in (overlap_open.json().get("opening") or {}).get("assistant", {}).get("body", ""), "neutral impulse does not claim a pattern")
|
||||||
|
|
||||||
|
done_space, done_day = open_space_day(client, headers, "Abgeschlossen", "2026-08-20")
|
||||||
|
done_conv = start_conv(client, headers, done_day["day"]["id"])
|
||||||
|
client.post(
|
||||||
|
f"/api/journal/conversations/{done_conv.json()['id']}/turn",
|
||||||
|
headers=headers,
|
||||||
|
json={"body": "Nachmittag am Strand, abends Eis, dann schlafen."},
|
||||||
|
)
|
||||||
|
client.post(
|
||||||
|
"/api/journal/entries",
|
||||||
|
headers=headers,
|
||||||
|
json={
|
||||||
|
"journal_day_id": done_day["day"]["id"],
|
||||||
|
"title": "Hitze",
|
||||||
|
"body": (
|
||||||
|
"Schon um 6 Uhr schlug mir eine drückende Luft entgegen. "
|
||||||
|
"Ich hatte überlegt, die Roller auf den nächsten Tag zu schieben. "
|
||||||
|
"Wir haben sie dann doch geholt und sind den Tag gefahren."
|
||||||
|
),
|
||||||
|
"origin": "user_edit",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
later_done = client.post(
|
||||||
|
f"/api/journal/spaces/{done_space['id']}/days",
|
||||||
|
headers=headers,
|
||||||
|
json={"calendar_date": "2026-08-21"},
|
||||||
|
)
|
||||||
|
done_open = start_conv(client, headers, later_done.json()["day"]["id"])
|
||||||
|
done_facts = collect_opening_context(setup.json()["profile_id"], done_open.json()["id"])
|
||||||
|
expect(not done_facts["attested_plans"], "completed journal entry is not an opening plan")
|
||||||
|
expect(not done_facts["has_relevant_context"], "a closed earlier day does not keep the next opening relevant")
|
||||||
|
expect(done_open.json().get("opening", {}).get("kind") == "local_neutral", "completed story falls back to neutral")
|
||||||
|
|
||||||
|
stale = client.post(
|
||||||
|
f"/api/journal/spaces/{plan_space['id']}/days",
|
||||||
|
headers=headers,
|
||||||
|
json={"calendar_date": "2026-08-04"},
|
||||||
|
)
|
||||||
|
stale_open = start_conv(client, headers, stale.json()["day"]["id"])
|
||||||
|
stale_facts = collect_opening_context(setup.json()["profile_id"], stale_open.json()["id"])
|
||||||
|
expect(not any("Cres" in item for item in stale_facts["attested_plans"]), "plan from two days ago is no longer an opening mandate")
|
||||||
|
expect(stale_open.json().get("opening", {}).get("kind") == "local_neutral", "stale plan uses local neutral")
|
||||||
|
|
||||||
asst_space, asst_day = open_space_day(client, headers, "Assistent", "2026-08-05")
|
asst_space, asst_day = open_space_day(client, headers, "Assistent", "2026-08-05")
|
||||||
asst_conv = start_conv(client, headers, asst_day["day"]["id"])
|
asst_conv = start_conv(client, headers, asst_day["day"]["id"])
|
||||||
from dialogue_store import append_message
|
from dialogue_store import append_message
|
||||||
|
|
|
||||||
|
|
@ -388,7 +388,11 @@ Der erste Gesprächsimpuls unterliegt derselben Dialog-Policy wie spätere Folge
|
||||||
|
|
||||||
**Status: Code vorhanden, journalspezifisch, keine universelle Continuation-Engine.**
|
**Status: Code vorhanden, journalspezifisch, keine universelle Continuation-Engine.**
|
||||||
|
|
||||||
Ein neu gestarteter Journal-Dialog erhält einen ersten Kanshō-Impuls. Belegte Vorhaben und offene user-Punkte desselben Journal Day dürfen den Einstieg stützen. Recency allein ist kein Muster. Assistententext ist Kontext, kein Nutzerfakt. Ohne relevanten Kontext: kurzer neutraler lokaler Einstieg. Fehlgeschlagener Modelloutput speichert keine beschädigte Nachricht.
|
Ein neu gestarteter Journal-Dialog erhält einen ersten Kanshō-Impuls. Belegte Vorhaben dürfen den Einstieg stützen, wenn sie aus dem **user-Dialog des unmittelbar vorigen Kalendertags** stammen. Offene user-Punkte gelten nur für denselben Journal Day. Recency allein ist kein Muster. Ein gespeicherter Journal-Eintrag ist abgeschlossene Erzählung, kein offenes Vorhaben; ein 400-Zeichen-Entry-Ausschnitt darf keinen Folgetag als offenen Faden führen. Assistententext ist Kontext, kein Nutzerfakt. Ohne relevanten Kontext: kurzer neutraler lokaler Einstieg. Fehlgeschlagener Modelloutput speichert keine beschädigte Nachricht.
|
||||||
|
|
||||||
|
**Additiv 2026-09-03:** Der Opening-Pfad lädt Space-Recency nicht in den Modellkontext. Gestern angekündigte Vorhaben bleiben ein Folgetag lang zulässig; ältere oder nur im Entry vorkommende Formulierungen („auf den nächsten Tag“) erzeugen den neutralen Einstieg.
|
||||||
|
|
||||||
|
**Additiv 2026-09-07 (Fehlerbild, ohne Thread-Engine):** Ein nachgetragener Morgenanker (späte user-Zeile mit früher Uhrzeit) kann im Journal am Textanfang landen. Ein festes Prefix (400 Zeichen) liest dann Überlegung ohne späteren Vollzug und erzeugt tagelang denselben Einstieg. Das ist kein Primär-Thread, sondern Recency plus Plan-Regex. **Bewusst offen:** dieselben 400-Zeichen-Ausschnitte bleiben im *laufenden* Dialogzug (`dialogue_turn`) als Hinweis geladen; sie sind kein Opening-Mandat mehr. Entfernen oder Relevanz statt Prefix ist Slice-2-/Memory-Thema, nicht Opening-Regression.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -439,10 +439,10 @@ Foundation-Verletzungen, die ein Refactor erzwingen würden, sind **nicht** gefu
|
||||||
| Kein Fragebogen; Reflexionspartner | **Teilweise** | Prompt und Stimme zielen darauf. Qualität ist modellabhängig. Lokale Wächter gegen Recap, erfundenen Vollzug, erfundene Empfindung, Systemfloskeln, Schluss-Wiederöffnung. `local_hold` bittet um Fortsetzung statt Floskel. |
|
| Kein Fragebogen; Reflexionspartner | **Teilweise** | Prompt und Stimme zielen darauf. Qualität ist modellabhängig. Lokale Wächter gegen Recap, erfundenen Vollzug, erfundene Empfindung, Systemfloskeln, Schluss-Wiederöffnung. `local_hold` bittet um Fortsetzung statt Floskel. |
|
||||||
| Sechs Operationen, keine Intent-Engines | **Erfüllt** | Ein Call, JSON `{operation, impulse}`; sichtbar ist `impulse` |
|
| Sechs Operationen, keine Intent-Engines | **Erfüllt** | Ein Call, JSON `{operation, impulse}`; sichtbar ist `impulse` |
|
||||||
| Register lokal, kein zweites Modell | **Erfüllt** | `infer_register` |
|
| Register lokal, kein zweites Modell | **Erfüllt** | `infer_register` |
|
||||||
| Bekannter Tagesplan (Cres-Beispiel) | **Teilweise / Ungenauigkeit** | Plan kann aus begrenzten Recency-Ausschnitten **gespeicherter Entries** oder **jüngerer Original-Conversations** desselben Space kommen. Kein Plan-Objekt, keine zeitliche Vor/Nach-Logik, kein semantisches Retrieval. |
|
| Bekannter Tagesplan (Cres-Beispiel) | **Teilweise / Ungenauigkeit** | **Opening (seit 2026-09-03):** nur user-Dialog des vorigen Kalendertags, nicht gespeicherte Entries. **Laufender Dialogzug:** Recency-Ausschnitte (Entries und jüngere Original-Conversations) bleiben Hinweis. Kein Plan-Objekt, keine Vor/Nach-Logik über den Folgetag hinaus, kein semantisches Retrieval. |
|
||||||
| Wiederkehrende Aktivität (Japanisch-Beispiel) | **Gap** | Keine Zählung über Morgenbeschreibungen. Recency kann den Treffer zufällig enthalten oder nicht. |
|
| Wiederkehrende Aktivität (Japanisch-Beispiel) | **Gap** | Keine Zählung über Morgenbeschreibungen. Recency kann den Treffer zufällig enthalten oder nicht. |
|
||||||
| Keine triviale Gefühlsfrage nach selbst benannter Emotion | **Teilweise** | Prompt + `is_unearned_stance`. Kein Dialogzustand „bereits beantwortet“. |
|
| Keine triviale Gefühlsfrage nach selbst benannter Emotion | **Teilweise** | Prompt + `is_unearned_stance`. Kein Dialogzustand „bereits beantwortet“. |
|
||||||
| Kontextsensitiver Einstieg statt „Erzähl von deinem Tag“ | **Erfüllt** als begrenzte Keimzelle | Erster Impuls aus belegtem Plan oder offenem Tagesfaden; sonst neutral. Keine Pattern-Engine. |
|
| Kontextsensitiver Einstieg statt „Erzähl von deinem Tag“ | **Erfüllt** als begrenzte Keimzelle | Erster Impuls aus belegtem *gestrigem* user-Plan oder offenem Tagesfaden; sonst neutral. Entries und Prefix-Recency sind kein Einstieg. Keine Pattern-Engine. |
|
||||||
| Unterbrechen und Fortsetzen am selben Day | **Erfüllt** | Transcript bleibt. **Teilweise:** keine lokale Karte „welche Tagesabschnitte schon da sind“. |
|
| Unterbrechen und Fortsetzen am selben Day | **Erfüllt** | Transcript bleibt. **Teilweise:** keine lokale Karte „welche Tagesabschnitte schon da sind“. |
|
||||||
| Folgetag-Fortsetzung dem ursprünglichen Day zuordnen | **Erfüllt** | Nutzer öffnet den Day; Message-Zeit ist unabhängig. |
|
| Folgetag-Fortsetzung dem ursprünglichen Day zuordnen | **Erfüllt** | Nutzer öffnet den Day; Message-Zeit ist unabhängig. |
|
||||||
|
|
||||||
|
|
@ -518,7 +518,7 @@ Foundation-Verletzungen, die ein Refactor erzwingen würden, sind **nicht** gefu
|
||||||
1. **`mvp.md` §1 und §5.1** versprechen „relevante frühere Aussagen“ und „wiederkehrende Aktivitäten“. Der Code liefert Recency-Ausschnitte finaler Entries plus begrenzte Original-Ausschnitte jüngerer Space-Conversations. Der klarstellende Absatz in §5.1 und §8.2 ist der gültige Implementierungsstand; die Beispiele §5.4–5.5 und Case C bleiben **schärfer als der Code** (kein Plan-Objekt, keine Wiederholungszählung).
|
1. **`mvp.md` §1 und §5.1** versprechen „relevante frühere Aussagen“ und „wiederkehrende Aktivitäten“. Der Code liefert Recency-Ausschnitte finaler Entries plus begrenzte Original-Ausschnitte jüngerer Space-Conversations. Der klarstellende Absatz in §5.1 und §8.2 ist der gültige Implementierungsstand; die Beispiele §5.4–5.5 und Case C bleiben **schärfer als der Code** (kein Plan-Objekt, keine Wiederholungszählung).
|
||||||
2. **`mvp.md` §17** sprach vom „nächsten Schritt: technischen Plan ableiten“. Der Slice ist gebaut. Dieses Dokument ist der Abgleich; die Formulierung in §17 bleibt historisch.
|
2. **`mvp.md` §17** sprach vom „nächsten Schritt: technischen Plan ableiten“. Der Slice ist gebaut. Dieses Dokument ist der Abgleich; die Formulierung in §17 bleibt historisch.
|
||||||
3. **`mvp.md` §4.1 Konsolidierung** beschreibt narrative Kriterien. Der Slice nutzt lokale Signale derselben Dimensionen, keine LLM-Typklassifikation.
|
3. **`mvp.md` §4.1 Konsolidierung** beschreibt narrative Kriterien. Der Slice nutzt lokale Signale derselben Dimensionen, keine LLM-Typklassifikation.
|
||||||
4. **`mvp.md` §6 Einstieg** verlangt einen kontextsensitiven ersten Impuls. Seit 2026-08-25: journalspezifischer Opening-Adapter; ohne belegten Plan oder offenen Tagesfaden lokal neutral.
|
4. **`mvp.md` §6 Einstieg** verlangt einen kontextsensitiven ersten Impuls. Seit 2026-08-25: journalspezifischer Opening-Adapter; ohne belegten Plan oder offenen Tagesfaden lokal neutral. **Additiv 2026-09-03/07:** Plan nur noch aus dem user-Dialog des unmittelbar vorigen Kalendertags, nicht aus Journal-Entries oder älterer Recency. Opening-Call ohne Space-Recency. Ein 400-Zeichen-Entry-Prefix (typisch: nachgetragener Morgenanker ohne späteren Vollzug) ist kein Opening-Mandat. Der laufende Dialogzug lädt Recency-Ausschnitte weiterhin; das ist kein Slice-Gap des Openings, sondern bewusst offene Heuristik bis zu Relevanz/Memory.
|
||||||
5. **`mvp.md` §8.2 Punkt 2** Space-Kontext bei der Generierung: Generate sieht Prior-Entries nicht. Für diesen Freeze bewusst so begrenzt (`mvp.md` §8.2.1).
|
5. **`mvp.md` §8.2 Punkt 2** Space-Kontext bei der Generierung: Generate sieht Prior-Entries nicht. Für diesen Freeze bewusst so begrenzt (`mvp.md` §8.2.1).
|
||||||
6. **`mvp.md` §9.3** listet Lernsignale, von denen nur ein Teil heuristisch existiert.
|
6. **`mvp.md` §9.3** listet Lernsignale, von denen nur ein Teil heuristisch existiert.
|
||||||
7. **`mvp.md` §11.3 / §13.1–13.2** Papierkorb-UI und Entry-Liste sind vorhanden. Dialog-Medien bleiben bewusst aus (`mvp.md` §10.2).
|
7. **`mvp.md` §11.3 / §13.1–13.2** Papierkorb-UI und Entry-Liste sind vorhanden. Dialog-Medien bleiben bewusst aus (`mvp.md` §10.2).
|
||||||
|
|
@ -549,6 +549,7 @@ Zielmodell und Non-Goals, nicht „vergessen im MVP“:
|
||||||
- Contextual Continuation als Ziel-IA (der Freeze hat nur eine journalspezifische Startkeimzelle)
|
- Contextual Continuation als Ziel-IA (der Freeze hat nur eine journalspezifische Startkeimzelle)
|
||||||
- Browser-Zurück als harter Unsaved-Blocker (Data-Router)
|
- Browser-Zurück als harter Unsaved-Blocker (Data-Router)
|
||||||
- Dialogmedien als direkte Dialogquelle
|
- Dialogmedien als direkte Dialogquelle
|
||||||
|
- Prefix-Recency (400 Zeichen) als Hinweis im *laufenden* Dialogzug; Relevanz statt Prefix bleibt Zielmodell (`resurfacing_and_saturation.md`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -558,7 +559,7 @@ Keine neue Horizontal-Konzeption. Getrennt halten, sonst baut ein Agent das Targ
|
||||||
|
|
||||||
## 8.1 Distanz zum **freigegebenen MVP-Text** (`mvp.md`)
|
## 8.1 Distanz zum **freigegebenen MVP-Text** (`mvp.md`)
|
||||||
|
|
||||||
1. **Case C Wiederholung** – wiederkehrende Aktivität bleibt Recency, keine Zählung. Der erste Impuls greift nur belegte Pläne oder offene Tagesfäden auf.
|
1. **Case C Wiederholung** – wiederkehrende Aktivität bleibt Recency, keine Zählung. Der erste Impuls greift nur belegte Pläne aus dem user-Dialog von gestern oder offene Tagesfäden desselben Journal Day auf; Journal-Entries nicht.
|
||||||
2. **Generate und Space-Ausschnitte** – für diesen Freeze entschieden: Day-Conversations plus Verified Artifact, keine Prior-Entries als Generate-Fakten (`mvp.md` §8.2.1).
|
2. **Generate und Space-Ausschnitte** – für diesen Freeze entschieden: Day-Conversations plus Verified Artifact, keine Prior-Entries als Generate-Fakten (`mvp.md` §8.2.1).
|
||||||
3. **Dialog-Medien** – bewusst gestrichen für den Slice (`mvp.md` §10.2); Editor-Medien bleiben.
|
3. **Dialog-Medien** – bewusst gestrichen für den Slice (`mvp.md` §10.2); Editor-Medien bleiben.
|
||||||
4. **Lokales Detect-Modell** – Klartext an externe Detect-Provider ist in Production blockiert; Ollama als Ablösung der Dev/Test-Phase bleibt offen.
|
4. **Lokales Detect-Modell** – Klartext an externe Detect-Provider ist in Production blockiert; Ollama als Ablösung der Dev/Test-Phase bleibt offen.
|
||||||
|
|
@ -597,6 +598,7 @@ Ein Agent, der 8.2 in diesem Slice „schließt“, verletzt Foundation-Regeln 2
|
||||||
|---|---|
|
|---|---|
|
||||||
| `backend/privacy_gateway.py`, `entity_detect.py`, `pronoun_bind.py`, `identity_store.py` | Egress, Maskierung, Dialog-Pronomen |
|
| `backend/privacy_gateway.py`, `entity_detect.py`, `pronoun_bind.py`, `identity_store.py` | Egress, Maskierung, Dialog-Pronomen |
|
||||||
| `backend/context_builder.py`, `retrieval.py`, `conversation_signals.py` | Interner Kontext, Selection-Specs, lokale Dialogsignale |
|
| `backend/context_builder.py`, `retrieval.py`, `conversation_signals.py` | Interner Kontext, Selection-Specs, lokale Dialogsignale |
|
||||||
|
| `backend/journal_opening.py` | Erster Journal-Impuls; Plan nur gestriger user-Dialog; Opening ohne Space-Recency |
|
||||||
| `backend/dialogue_turn.py` | Ein Call, Operation/Impuls, lokale Wächter; kein Writing-Profile-Kernel |
|
| `backend/dialogue_turn.py` | Ein Call, Operation/Impuls, lokale Wächter; kein Writing-Profile-Kernel |
|
||||||
| `backend/journal_generate.py`, `journal_shape.py`, `journal_body.py` | Entwurf, Shape, Titel/Markdown |
|
| `backend/journal_generate.py`, `journal_shape.py`, `journal_body.py` | Entwurf, Shape, Titel/Markdown |
|
||||||
| `backend/journal_store.py`, `journal_policy.py` | Entries, Versionen, Scratch, explizites Generate |
|
| `backend/journal_store.py`, `journal_policy.py` | Entries, Versionen, Scratch, explizites Generate |
|
||||||
|
|
@ -618,6 +620,7 @@ Ein Agent, der 8.2 in diesem Slice „schließt“, verletzt Foundation-Regeln 2
|
||||||
| `backend/tests/test_journal_shape.py` | Keine Pronomenersetzung nach Demask; akzeptierter Wortlaut bleibt | Modellqualität, Stil |
|
| `backend/tests/test_journal_shape.py` | Keine Pronomenersetzung nach Demask; akzeptierter Wortlaut bleibt | Modellqualität, Stil |
|
||||||
| `backend/tests/test_writing_profile.py` | Quellenpriorität | „klingt wie die Person“ |
|
| `backend/tests/test_writing_profile.py` | Quellenpriorität | „klingt wie die Person“ |
|
||||||
| `backend/tests/test_frame.py` | Auth, Prompts, Rahmen | Journal-IA |
|
| `backend/tests/test_frame.py` | Auth, Prompts, Rahmen | Journal-IA |
|
||||||
|
| `backend/tests/test_journal_opening.py` | Neutral, Folgetag-Plan, abgeschlossener Entry kein Mandat, Plan älter als gestern entfällt | Opening-Qualität des Modells |
|
||||||
| `frontend/src/journal/document.test.js` | Markdown-Runde | Editor-UX, Dirty-Flag |
|
| `frontend/src/journal/document.test.js` | Markdown-Runde | Editor-UX, Dirty-Flag |
|
||||||
|
|
||||||
Laufzeit lokal: Frontend Port **5188**, Backend **8018**, SQLite `backend/data/kansho.sqlite`. Prompt-Templates in der DB werden bei unangetasteten Seeds durch `init_db` aktualisiert; nach Prompt-Änderungen Backend neu starten.
|
Laufzeit lokal: Frontend Port **5188**, Backend **8018**, SQLite `backend/data/kansho.sqlite`. Prompt-Templates in der DB werden bei unangetasteten Seeds durch `init_db` aktualisiert; nach Prompt-Änderungen Backend neu starten.
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ Zusätzlich zu Layer 0:
|
||||||
|
|
||||||
`derived_records` bleibt die generische Layer-0-Hülle und ist nicht der Journal-Store. `handoffs.target='journal'` ist kein Schreibpfad für Einträge.
|
`derived_records` bleibt die generische Layer-0-Hülle und ist nicht der Journal-Store. `handoffs.target='journal'` ist kein Schreibpfad für Einträge.
|
||||||
|
|
||||||
Nicht enthalten: Threads-UI, Related Spaces, semantisches Retrieval, Session-Summaries, Voice, Space Content Memory. Der Dialogzug sieht denselben Journal Day vollständig und dazu die letzten Journal Entries des Spaces als Recency-Ausschnitte. Die Journalgenerierung bekommt denselben Tagesdialog inklusive Kanshō-Impulse als Gesprächskontext, nicht als Erlebtes.
|
Nicht enthalten: Threads-UI, Related Spaces, semantisches Retrieval, Session-Summaries, Voice, Space Content Memory. Der Dialogzug sieht denselben Journal Day vollständig und dazu die letzten Journal Entries des Spaces als Recency-Ausschnitte. **Additiv 2026-09-03:** Der *erste* Impuls eines neuen Gesprächs nutzt diese Ausschnitte nicht als Vorhaben und lädt sie nicht in den Opening-Prompt. Die Journalgenerierung bekommt denselben Tagesdialog inklusive Kanshō-Impulse als Gesprächskontext, nicht als Erlebtes.
|
||||||
|
|
||||||
## 7. Offene Fragen
|
## 7. Offene Fragen
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ Lokal, eine Instanz, ein Profil-Nutzer nach Setup:
|
||||||
|
|
||||||
Proxy: Vite leitet `/api` an 8018. CORS erlaubt `localhost:5188`.
|
Proxy: Vite leitet `/api` an 8018. CORS erlaubt `localhost:5188`.
|
||||||
|
|
||||||
**Abweichung vom technischen Zielrahmen:** `product_frame_and_stack.md` nennt PostgreSQL. Der Slice läuft auf SQLite. Lokal zulässig; Prod-Pfad offen. Siehe Fit-Gap.
|
**Abweichung vom technischen Zielrahmen:** `product_frame_and_stack.md` nennt PostgreSQL. Der Slice läuft auf SQLite. Lokal zulässig; Prod-Pfad offen. Transfer der Urlaubsinstanz: zuerst SQLite-Restore auf dem Heimrechner, danach Compose/Postgres. Siehe `environment_handover.md` und `runtime_and_deploy.md` §7.2.
|
||||||
|
|
||||||
Health: `GET /api/health`.
|
Health: `GET /api/health`.
|
||||||
|
|
||||||
|
|
@ -278,7 +278,7 @@ Additiv zum Slice. Fachliche Abnahme: `../functional/mvp_freeze_candidate.md`.
|
||||||
- Lokaler Testeinstieg `scripts/test-mvp.ps1` (UTF-8, isolierte Temp-Daten, keine Live-Keys; Fake nur in den Suites, die ihn setzen).
|
- Lokaler Testeinstieg `scripts/test-mvp.ps1` (UTF-8, isolierte Temp-Daten, keine Live-Keys; Fake nur in den Suites, die ihn setzen).
|
||||||
- Backup: `backend/local_backup.py`, Einstieg `scripts/backup-local.ps1`. SQLite-Backup-API, Medien, Manifest, keine Secrets. Restore bestätigt, prüft Checksummen, legt Sicherheitsbackup an, bricht bei geöffneter DB ab.
|
- Backup: `backend/local_backup.py`, Einstieg `scripts/backup-local.ps1`. SQLite-Backup-API, Medien, Manifest, keine Secrets. Restore bestätigt, prüft Checksummen, legt Sicherheitsbackup an, bricht bei geöffneter DB ab.
|
||||||
- Papierkorb: `GET /spaces/{id}/entries`, `GET /spaces/{id}/trash`, `POST /entries/{id}/undelete`, `POST /entries/{id}/purge`.
|
- Papierkorb: `GET /spaces/{id}/entries`, `GET /spaces/{id}/trash`, `POST /entries/{id}/undelete`, `POST /entries/{id}/purge`.
|
||||||
- Erster Impuls: `journal_opening.py`, derselbe Gateway-Pfad, `{{opening_hint}}`.
|
- Erster Impuls: `journal_opening.py`, derselbe Gateway-Pfad, `{{opening_hint}}`. **Additiv 2026-09-03:** Vorhaben nur aus dem user-Dialog des vorigen Kalendertags (`calendar_date` an `space_recent_sources`); Journal-Entries und ältere Recency sind kein Opening-Mandat. Der Opening-Call setzt `include_space_recency=False`. Tests: abgeschlossener Entry mit „nächsten Tag“ und Plan von vorgestern → `local_neutral`; „Morgen wollen wir …“ nur am Folgetag → Modellpfad. **Additiv 2026-09-07:** Prefix-Recency (5×400 Entries, 3×400 Source-Conversations) bleibt im *laufenden* Dialogzug; das ist kein Opening-Mandat. Ein Nachsatz mit Morgenuhrzeit darf den Folgetag nicht über den Entry-Anfang steuern.
|
||||||
- Home: `GET /continuity`.
|
- Home: `GET /continuity`.
|
||||||
|
|
||||||
Bewusst nicht: Docker, Postgres, Ollama, Verschlüsselung, Dialogmedien als Quelle, Space-Entries als Generate-Fakten.
|
Bewusst nicht: Docker, Postgres, Ollama, Verschlüsselung, Dialogmedien als Quelle, Space-Entries als Generate-Fakten.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user