refactor(management): replace LangGraph with native PostgreSQL state runner #113

Merged
yuris merged 4 commits from refactor/112-remove-langgraph into main 2026-09-28 20:04:00 +00:00
Owner

Closes #112.

What changed

  • Added bot_sessions table in schema.ts and migration 0014_safe_arachne.sql dropping LangGraph checkpoint tables.
  • Replaced LangGraph StateGraph, checkpointer, and message annotations with direct native PostgreSQL state runner in runner.ts and pure domain types in state.ts.
  • Direct OpenRouter query loop with 8 native workshop tools in lib/bot/tools/.
  • Confirmation state machine in bot_sessions with 60-minute session expiry.
  • Pruned 6 LangChain packages (@langchain/core, @langchain/langgraph, @langchain/langgraph-checkpoint-postgres, @langchain/openrouter, langchain, langfuse-langchain) and 21 transitive dependencies.
  • Deleted obsolete graph.ts, checkpointer.ts, cleanup-turn-state.ts, and reply.ts.

Verified

  • pnpm --filter=garage exec tsc --noEmit: 0 errors
  • pnpm --filter=garage test: 19 test files passed (431 tests)
  • pnpm --filter=garage test lib/bot/: 2 test files passed (11 tests)
  • .forgejo/scripts/biome-gate.sh: PASSED (32 findings vs 34 baseline)

Deviations

None.

Closes #112. ## What changed - Added `bot_sessions` table in `schema.ts` and migration `0014_safe_arachne.sql` dropping LangGraph checkpoint tables. - Replaced LangGraph StateGraph, checkpointer, and message annotations with direct native PostgreSQL state runner in `runner.ts` and pure domain types in `state.ts`. - Direct OpenRouter query loop with 8 native workshop tools in `lib/bot/tools/`. - Confirmation state machine in `bot_sessions` with 60-minute session expiry. - Pruned 6 LangChain packages (`@langchain/core`, `@langchain/langgraph`, `@langchain/langgraph-checkpoint-postgres`, `@langchain/openrouter`, `langchain`, `langfuse-langchain`) and 21 transitive dependencies. - Deleted obsolete `graph.ts`, `checkpointer.ts`, `cleanup-turn-state.ts`, and `reply.ts`. ## Verified - `pnpm --filter=garage exec tsc --noEmit`: 0 errors - `pnpm --filter=garage test`: 19 test files passed (431 tests) - `pnpm --filter=garage test lib/bot/`: 2 test files passed (11 tests) - `.forgejo/scripts/biome-gate.sh`: PASSED (32 findings vs 34 baseline) ## Deviations None.
refactor(management): replace LangGraph with native PostgreSQL state runner
All checks were successful
Checks / test garage-store (pull_request) Successful in 1m17s
Checks / test @garage/catalog (pull_request) Successful in 1m13s
Checks / test garage (pull_request) Successful in 1m22s
Checks / typecheck apps/management (pull_request) Successful in 1m36s
Checks / lint (biome) (pull_request) Successful in 12s
Checks / typecheck apps/store (pull_request) Successful in 1m22s
1b1d30844b
yuris left a comment

VERDICT: request-changes

apps/management/lib/bot/runner.ts:L116: blocker: Two distinct confirmation messages can both read the same AWAITING_CONFIRM session before either clears it, executing the purchase/order twice; webhook deduplication only covers the same message ID. Atomically claim/lock the session before executing an action.
apps/management/lib/bot/nodes/classify-intent.ts:L285: blocker: The new order filter accepts a missing selling price as 0, so an LLM extraction without price can confirm and save a free sale while depleting stock. Require a positive price for every item and clarify rather than silently omitting it.
apps/management/lib/bot/nodes/classify-intent.ts:L297: should: Casting any model-provided orderStatus to OrderStatus allows invalid values into the unconstrained orders.status column; status-based revenue reports omit those orders. Validate against OrderStatusEnum and fall back to menunggu as before.
apps/management/lib/bot/runner.ts:L246: should: A clarification reply is followed by a fresh classification using only the next message; the former pendingIntent and conversation are no longer persisted, so replies like an item name after a purchase clarification lose the command context. Persist the pending intent/context in bot_sessions and supply it to classification.
apps/management/lib/bot/runner.ts:L116: should: A receipt image with caption ya during purchase confirmation takes the confirmation branch and executes the old action before the media branch sees the image. Prioritize receipt media before interpreting the caption as confirmation.
apps/management/lib/db/schema.ts:L278: should: The new state varchar accepts arbitrary values rather than the specified IDLE/AWAITING_CONFIRM states, and the runner does not repair unknown states on conflict. Add a DB enum or CHECK constraint in schema and migration.

The migration and six dependency removals are present. PR metadata reports 19 passing test files and a passing baseline biome gate, but does not show a pnpm verify:management run; I did not rerun tests or checks in this read-only review.

VERDICT: request-changes apps/management/lib/bot/runner.ts:L116: blocker: Two distinct confirmation messages can both read the same AWAITING_CONFIRM session before either clears it, executing the purchase/order twice; webhook deduplication only covers the same message ID. Atomically claim/lock the session before executing an action. apps/management/lib/bot/nodes/classify-intent.ts:L285: blocker: The new order filter accepts a missing selling price as 0, so an LLM extraction without price can confirm and save a free sale while depleting stock. Require a positive price for every item and clarify rather than silently omitting it. apps/management/lib/bot/nodes/classify-intent.ts:L297: should: Casting any model-provided orderStatus to OrderStatus allows invalid values into the unconstrained orders.status column; status-based revenue reports omit those orders. Validate against OrderStatusEnum and fall back to menunggu as before. apps/management/lib/bot/runner.ts:L246: should: A clarification reply is followed by a fresh classification using only the next message; the former pendingIntent and conversation are no longer persisted, so replies like an item name after a purchase clarification lose the command context. Persist the pending intent/context in bot_sessions and supply it to classification. apps/management/lib/bot/runner.ts:L116: should: A receipt image with caption ya during purchase confirmation takes the confirmation branch and executes the old action before the media branch sees the image. Prioritize receipt media before interpreting the caption as confirmation. apps/management/lib/db/schema.ts:L278: should: The new state varchar accepts arbitrary values rather than the specified IDLE/AWAITING_CONFIRM states, and the runner does not repair unknown states on conflict. Add a DB enum or CHECK constraint in schema and migration. The migration and six dependency removals are present. PR metadata reports 19 passing test files and a passing baseline biome gate, but does not show a pnpm verify:management run; I did not rerun tests or checks in this read-only review.
fix(bot): address review round 1 on concurrency, order pricing, and session state
Some checks failed
Checks / test garage (pull_request) Successful in 1m21s
Checks / typecheck apps/store (pull_request) Has been cancelled
Checks / test @garage/catalog (pull_request) Successful in 1m14s
Checks / test garage-store (pull_request) Successful in 1m17s
Checks / typecheck apps/management (pull_request) Successful in 1m35s
Checks / lint (biome) (pull_request) Successful in 11s
a2e3f3edda
Author
Owner

round 1 addressed

round 1 addressed
yuris left a comment

VERDICT: request-changes

[P2] Preserve a recoverable claim until execution is resolved (apps/management/lib/bot/runner.ts:142). The new confirmation claim sets state: IDLE and pendingAction: null before executeAction. If execution fails before writing (for example, executePurchaseNode calls findInventoryByPrefix outside its try/catch and that lookup rejects), the webhook sends a generic error but the confirmed purchase is now permanently absent from bot_sessions; another "ya" is treated as a new message, so the operator must reconstruct/resend the purchase or receipt. Previously that failure left the pending action available. Keep the claim exclusive without discarding the action until execution has reached a recoverable outcome, and ensure failures before a write can be retried safely.

The price/status guards, media-first ordering, and matching schema/migration CHECK constraint address the other Round 1 requests.

VERDICT: request-changes [P2] Preserve a recoverable claim until execution is resolved (`apps/management/lib/bot/runner.ts:142`). The new confirmation claim sets `state: IDLE` and `pendingAction: null` before `executeAction`. If execution fails before writing (for example, `executePurchaseNode` calls `findInventoryByPrefix` outside its try/catch and that lookup rejects), the webhook sends a generic error but the confirmed purchase is now permanently absent from `bot_sessions`; another "ya" is treated as a new message, so the operator must reconstruct/resend the purchase or receipt. Previously that failure left the pending action available. Keep the claim exclusive without discarding the action until execution has reached a recoverable outcome, and ensure failures before a write can be retried safely. The price/status guards, media-first ordering, and matching schema/migration CHECK constraint address the other Round 1 requests.
fix(bot): preserve recoverable session on execution failure
Some checks failed
Checks / test @garage/catalog (pull_request) Successful in 1m14s
Checks / test garage-store (pull_request) Successful in 1m18s
Checks / lint (biome) (pull_request) Has been cancelled
Checks / typecheck apps/store (pull_request) Has been cancelled
Checks / test garage (pull_request) Successful in 1m22s
Checks / typecheck apps/management (pull_request) Has been cancelled
4acb907263
Author
Owner

round 2 addressed

round 2 addressed
yuris left a comment

VERDICT: request-changes

  1. apps/management/lib/bot/runner.ts:161-170 — The catch also covers messenger.send. If the gateway fetch fails after executeAction has committed, restoring AWAITING_CONFIRM lets a second confirmation repeat a successful purchase/order/stock adjustment. Catch only execution failures; do not make an already-committed action retryable when reply delivery fails.

  2. apps/management/lib/bot/runner.ts:157-166 — After the confirmation claim sets the row to IDLE, a concurrent message from the same phone can create a new pending action. The newly added unconditional completion update clears that newer action (or the failure branch reinstates the old one), losing a request already offered for confirmation. Scope both follow-up updates to the claimed session or serialize per-phone processing.

VERDICT: request-changes 1. apps/management/lib/bot/runner.ts:161-170 — The catch also covers messenger.send. If the gateway fetch fails after executeAction has committed, restoring AWAITING_CONFIRM lets a second confirmation repeat a successful purchase/order/stock adjustment. Catch only execution failures; do not make an already-committed action retryable when reply delivery fails. 2. apps/management/lib/bot/runner.ts:157-166 — After the confirmation claim sets the row to IDLE, a concurrent message from the same phone can create a new pending action. The newly added unconditional completion update clears that newer action (or the failure branch reinstates the old one), losing a request already offered for confirmation. Scope both follow-up updates to the claimed session or serialize per-phone processing.
fix(bot): isolate executeAction in try/catch and scope session updates to claim
All checks were successful
Checks / test garage (pull_request) Successful in 1m22s
Checks / test @garage/catalog (pull_request) Successful in 1m14s
Checks / test garage-store (pull_request) Successful in 1m18s
Checks / typecheck apps/store (pull_request) Successful in 1m21s
Checks / typecheck apps/management (pull_request) Successful in 1m36s
Checks / lint (biome) (pull_request) Successful in 11s
f9acc91928
Author
Owner

round 3 addressed

round 3 addressed
yuris left a comment

VERDICT: approve
Round 4: The successful action and gateway reply are now separated, so a send failure no longer restores the pending action. The claim-scoped session updates also avoid overwriting a session that changed while execution ran. No new blocking issue found in f9acc91.

VERDICT: approve Round 4: The successful action and gateway reply are now separated, so a send failure no longer restores the pending action. The claim-scoped session updates also avoid overwriting a session that changed while execution ran. No new blocking issue found in f9acc91.
Author
Owner

Pre-merge verification summary

  • Latest reviewer verdict: approve (Review #110)
  • All blockers resolved across review rounds 1–4
  • pr.mergeable: true
  • Local verification at f9acc91:
    • pnpm --filter=garage exec tsc --noEmit: 0 errors
    • pnpm --filter=garage test: 19 test files passed (431 tests)
    • .forgejo/scripts/biome-gate.sh: PASSED (32 findings vs 34 baseline)
  • Forge checks: 6/6 green (Checks / test garage, Checks / test @garage/catalog, Checks / test garage-store, Checks / typecheck apps/store, Checks / typecheck apps/management, Checks / lint (biome))
### Pre-merge verification summary - Latest reviewer verdict: `approve` (Review #110) - All blockers resolved across review rounds 1–4 - `pr.mergeable`: `true` - Local verification at `f9acc91`: - `pnpm --filter=garage exec tsc --noEmit`: 0 errors - `pnpm --filter=garage test`: 19 test files passed (431 tests) - `.forgejo/scripts/biome-gate.sh`: PASSED (32 findings vs 34 baseline) - Forge checks: 6/6 green (`Checks / test garage`, `Checks / test @garage/catalog`, `Checks / test garage-store`, `Checks / typecheck apps/store`, `Checks / typecheck apps/management`, `Checks / lint (biome)`)
yuris merged commit 1e94275004 into main 2026-09-28 20:04:00 +00:00
yuris deleted branch refactor/112-remove-langgraph 2026-09-28 20:04:00 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/garage!113
No description provided.