think
16px
820px

Completion notes — Guided procedural flows (public)

Date: 2026-07-18 · Spec: specs/2026-07-18-public-conversation-features.md (feature 4) · Plan: plans/2026-07-18-status-tracking-and-guided-flows.md
Feature 3 (gated-SQL status tracking) was dropped same-day after discovery — see the plan's "Discovery 2026-07-18" section.

What shipped (3 commits, all TDD, pnpm check green)

1. Server side — flow-mode compose + suggestions channel (public-web)

  • lib/orchestrator/suggest.tsSuggestStreamSplitter: streaming splitter for the model's machine line SUGGEST: ["…","…"]. Marker-safe across arbitrary chunk splits; only line-start SUGGEST: counts (SUGGESTION… / mid-line passes through as prose); malformed JSON → line stripped, no suggestions; cap 3, strings only. 8 unit tests.
  • steps/compose.ts — guided mode activates ONLY when a retrieved RAG passage contains a [FLOW: marker: stepwise instruction ("Langkah N dari M", ringkasan alur, next action) + the SUGGEST last-line format (user's language, ≤6 words each). The splitter runs on every answer (flow or not) so a stray SUGGEST line can never leak as prose. New ComposeEvent kind suggestions.
  • sse.ts + orchestrate.ts — new event: suggestions SSE frame (checkpointed for SSE-resume like all frames), emitted between content and references.

2. Client side — server-driven chips

  • packages/streams: StreamEvent + Message.suggestions types; OrchestratorProvider maps event: suggestions{type:"suggestions", items} (non-string items dropped; empty frames suppressed). New provider test file.
  • packages/ui: chatStore.setSuggestions (stamps last assistant message) + useAgentStream case.
  • SuggestedFollowups.tsx: server chips take precedence; the static category fixtures remain the fallback — exactly the "phase-2" evolution the fixture's own comment anticipated.

3. Content — docs/knowledge-sources/gen_flows.json

3 flow articles (chunk ids gen3_flow_*_0, corpus generated-help), grounded strictly in the existing §1/§1b articles (no new facts):
- [FLOW:pendirian-pt-perorangan] — 6 langkah (PP priority)
- [FLOW:permohonan-apostille] — 7 langkah (incl. spesimen-ditangguhkan caveat, 7-day payment window, Kanwil pickup)
- [FLOW:pendirian-pt-notaris] — 5 langkah (pesan nama 60 hari → akta → PNBP modal-tiered → SK)
README §1c documents the artifact + ingest (idempotent chunk_id upsert, same as §1).

Verification

  • pnpm check fully green: typecheck all packages, 181 public-web + 15 internal-web + 27 streams tests, conventions OK.
  • Splitter/compose/orchestrate/provider behavior all pinned by failing-first tests (RED verified at each step).

Deployed + verified live (same day)

  1. Deploy — images tag b849488 built in dind (in-image agent pytest 42+64 green), shipped in chunks (scp resumed across the 10-min ceiling), loaded on Server 2, all four stacks up. Health: /api/health ok on :3500, staff :3510 up, staging URL 200. Agent images dedup'd to the previous build (no agent changes — expected); both web images recreated. Quirks hit: no local rsync (tar fallback for infra sync), docker exec needs -i for stdin scripts.
  2. Ingestgen_flows.json upserted via the still-present /tmp/ingest_*.py pattern inside ai-ahu-rag: upserted: 3, collection 800 rows.
  3. E2E (staging, real turns)
    - "Bagaimana cara mendirikan PT perorangan?" → ringkasan alur + Langkah detail; event: suggestions frame: ["Siapkan 3 kata nama PT","Cek ketersediaan nama PT","Daftar akun di ptp.ahu.go.id"]; 0 SUGGEST leakage across 115 content frames.
    - Chip-click simulation ("Lanjut ke langkah berikutnya" + history) → "Anda sedang berada pada Langkah 1 dari 6 … Langkah 2: Menyiapkan Nama Perseroan" + chips ["Lanjut ke Langkah 3","Cara pesan voucher","Syarat nama perseroan"]. The conversational wizard advances correctly with no server-side state.

Ops note: /home/efran/ahu-ai-staging/inbox on Server 2 still holds ~24 GB of old ahu-ai-ocr-*.tar.gz (host disk was already 92% full) — candidate cleanup.

Design notes

  • Chip flow state is conversational, not server-side: "Lanjut ke langkah 2" is just the next user message; compose re-retrieves the flow article and details step 2. No thread-state machinery (v2 idea if flows grow branches).
  • Suggestions are ephemeral (live stream only) — thread reload shows the conversation without chips; acceptable for v1 and consistent with chips being an input affordance, not content.
  • English users get English chips automatically: the SUGGEST instruction says "dalam bahasa pengguna" and sits alongside the lang=en compose rule.