Operational checklist and debugging playbook for Pinocchio terminal TUI integrations using Bobatea timeline entities and Watermill event routing.
This playbook is for when you already understand the architecture and you need to operate/debug a terminal TUI integration quickly (or teach a new intern how to do so safely). It focuses on checklists, failure modes, and concrete commands.
If you want the “intern-first” explanation of the whole system, start with:
pinocchio help tui-integration-guideThis section is designed so you can scan it during review or during a live debugging session.
*tea.Program.boba_chat.InitialModel(backend, ...).boba_chat.Backend (Start/Interrupt/Kill/IsFinished).
bobatea/pkg/chat/backend.gomiddleware.NewWatermillSink(router.Publisher, topic).
geppetto/pkg/inference/middleware/sink_watermill.goAck()s the message,events.NewEventFromJson,program.Send(timeline.UIEntity*).pinocchio/pkg/ui/forwarders/agent/forwarder.gollm_text) and then update/complete it.tool_call, tool_call_result) and you should still get assistant text entities.boba_chat.BackendFinishedMsg{} to re-enable input.This section describes the safe debugging steps that don’t require deep code surgery.
Most “nothing shows up” failures are topic mismatches.
What to check:
// Pseudocode imports:
//
// "github.com/go-go-golems/geppetto/pkg/inference/middleware"
//
sink := middleware.NewWatermillSink(router.Publisher, "chat")
// Pseudocode imports:
//
// agentforwarder "github.com/go-go-golems/pinocchio/pkg/ui/forwarders/agent"
// "github.com/go-go-golems/geppetto/pkg/events"
// tea "github.com/charmbracelet/bubbletea"
//
router.AddHandler("ui-forward", "chat", agentforwarder.MakeUIForwarder(p))
If these differ ("ui" vs "chat"), your UI will never update.
If Watermill messages are not ack’d, event flow can stall (especially with transports that block until ack).
What to look for:
// Pseudocode imports:
//
// "github.com/ThreeDotsLabs/watermill/message"
//
msg.Ack()
Anchor:
pinocchio/pkg/ui/forwarders/agent/forwarder.goIf your forwarder emits Kind: "tool_call" but you never registered a model factory for "tool_call", the UI may render it poorly (or not at all, depending on fallback).
Reference patterns:
simple-chat-agent: pinocchio/cmd/agents/simple-chat-agent/main.gobobatea/pkg/chat/model.go (WithTimelineRegister)The command chat TUI now uses the chatapp/sessionstream backend and ChatAppUIFanout, so completion arrives from the backend after chatapp.Service.WaitIdle.
The “agent/tool-loop” forwarder must not emit BackendFinishedMsg on provider-final events because the tool loop may continue past a provider final.
Rule of thumb:
pinocchio/pkg/ui/forwarders/agent, your backend must emit BackendFinishedMsg only when the whole loop completes.Use tmux for any check that starts a long-running UI, because it makes “start → observe → kill” reproducible.
This checks that the command still starts and the Cobra/Glazed wiring works, without creating runtime artifacts.
tmux new-session -d -s tui-smoke -c pinocchio \
"sh -lc 'go run ./cmd/agents/simple-chat-agent --help; echo DONE; sleep 10'"
tmux capture-pane -t tui-smoke:0.0 -p | tail -n 80
tmux kill-session -t tui-smoke
If you have credentials/profiles configured and you accept local artifact creation, run:
tmux new-session -d -s tui-smoke -c pinocchio \
"sh -lc 'go run ./cmd/agents/simple-chat-agent simple-chat-agent'"
tmux attach -t tui-smoke
Then:
q or Ctrl+C depending on model).// Pseudocode imports:
//
// "context"
// tea "github.com/charmbracelet/bubbletea"
// boba_chat "github.com/go-go-golems/bobatea/pkg/chat"
// "github.com/go-go-golems/geppetto/pkg/events"
// "github.com/go-go-golems/geppetto/pkg/inference/middleware"
// toolloopbackend "github.com/go-go-golems/pinocchio/pkg/ui/backends/toolloop"
// agentforwarder "github.com/go-go-golems/pinocchio/pkg/ui/forwarders/agent"
// "golang.org/x/sync/errgroup"
//
router := events.NewEventRouter(...)
sink := middleware.NewWatermillSink(router.Publisher, topic)
backend := toolloopbackend.NewToolLoopBackend(engine, middlewares, registry, sink, hook)
model := boba_chat.InitialModel(backend, WithTimelineRegister(...))
program := tea.NewProgram(model, tea.WithAltScreen())
router.AddHandler("ui-forward", topic, agentforwarder.MakeUIForwarder(program))
run router + program concurrently (errgroup)
cancel router when UI exits
_ = context.Canceled // (placeholder so context import stays visible in pseudocode)
| Symptom | Cause | Fix |
|---|---|---|
| UI is responsive but never shows assistant text | Forwarder never sees events | Topic mismatch; router not started; sink not attached to engine. |
| Assistant entity appears but never completes | Final event not handled or never emitted | Check provider events; inspect forwarder EventFinal handling. |
| Tool calls don’t show up | No tools or no renderer | Register tools; register tool_call / tool_call_result renderers. |
| UI “freezes” during inference | Watermill handler blocks, or messages not ack’d | Ensure msg.Ack() is called; avoid long blocking work in handler. |
| Streaming stalls/hangs under load | In-memory pub/sub backpressure (publish blocks until handler ACK) | Configure gochannel buffering and disable publish→ACK blocking, or switch to Redis Streams transport. |
| Timeline/turn persistence flakes | Context cancellation races | Avoid canceling inference during UI completion cleanup; for DB writes in handlers, use detached bounded contexts (not msg.Context()). |
| You see duplicate entities for logs/modes | ID collisions | Ensure unique local IDs for non-message events (agent forwarder uses a timestamp suffix). |
pinocchio help chatbuilder-guide (simple chat wiring via ChatBuilder)pinocchio help tui-integration-guide (full intern-first integration tutorial)pinocchio help webchat-debugging-and-ops (observability and debugging patterns)