Skip to content
Inspector, Reasoning, and OMP

Inspector, Reasoning, and OMP

gPTY is a graphical PTY foundation with an observability layer. It does not recreate agent TUIs, scrape PTY output for agent semantics, or host the user’s interactive OMP/Claude/Gemini session.

Surface Role
Terminal User launches omp (or any CLI) normally. The CLI owns its TUI, auth, tools, and permissions.
Inspector Private, tool-free, iterative Q&A. Owns one in-memory GptyAi session; backends: omp (private omp --mode rpc --no-session --no-tools --no-extensions --no-skills --no-rules), mock, or cli (subprocess NDJSON bridge - a configured adapter command, argv never shell-evaluated, documented hooks only). Does not attach to the terminal OMP process.
Reasoning Passive view of documented reasoning/lifecycle events from one terminal, selected by source_attachment_id. Never starts jobs or accepts concept captures. Turn history is an in-memory accordion for the current OMP session only.
@gpty/omp-events Explicitly installed OMP extension. Dormant unless all four GPTY_EVENT_* vars are present. Forwards bounded session/turn/tool metadata plus thinking_delta text only, coalesced (one event per 16 ms / ≤8 KiB UTF-8, split on code points).
  • Inspector and Reasoning are different sessions. The shipped “Agent Workspace” profile opens Terminal + Inspector + Reasoning; only Reasoning is attached to the terminal.
  • Reasoning history is session-scoped RAM (collapsed previous turns, live turn streaming). Closing the pane, changing source_attachment_id, or binding a new omp_session_id drops it. Do not write thinking text or OMP session IDs into layout/profile JSON.
  • The event listener runs on every platform. Workspace control IPC works on Windows (named pipes); the event listener serves \\.\pipe\gpty-events there and gpty-events.sock on Unix, and per-PTY GPTY_EVENT_* injection happens on both. The shipped @gpty/omp-events extension needs no platform branch to reach either one: net.connect(path) opens a Unix socket for a socket path and a named pipe for a pipe path, and the only branch is validation (validateSocket says a Windows path must be a \\.\pipe\ name, answered from the path alone because a pipe cannot be stated). scripts/smoke_pane_api.py runs the extension inside a real pane and asserts the pane reports Tier 1 working, so the chain - env injection, transport, capability check, translation, drain - is observed live on the platform running the smoke (Linux locally, windows-smoke in CI).
  • GptyAi is instance-owned: session_open / session_prompt / session_poll / session_cancel / session_close. There is no process-global subscriber bus. Polled envelopes carry session_id, turn_id, run_id, sequence, channel.
  • Reasoning caps are user settings (Settings → Reasoning tab): cfg_reasoning_max_turns (clamped 1–64, default 16) and cfg_reasoning_max_turn_bytes (clamped 4 KiB–1 MiB, default 64 KiB). reasoning_pane.gd reads them at _ready into max_turns/max_turn_bytes (not consts). When truncating accumulated raw Markdown at the cap, re-close an odd number of ``` fences (_close_open_fences) - an unclosed fence re-renders the tail as code. The cap is visible: the first time a turn reaches it the view gets a source notice (set_source_notice) saying later reasoning is not shown, and truncated/text_bytes ride in the turn record. Appends are O(delta), not O(turn): the byte count is incremental and a delta that arrives after the cap returns before any concatenation or UTF-8 re-encoding (the old path paid two full to_utf8_buffer() copies per dropped delta - 486 ms for one 64 KiB capped turn, now 31 ms; godot/tests/bench/reasoning_bench.gd).
  • Reasoning streaming is four rules that must not regress: (a) only the changed turn renders - _refresh_turn_layout syncs width/fold heights and never calls set_markdown; re-rendering every turn on every delta re-parsed the whole accordion and cleared any selection; (b) one deferred layout refresh per frame, not per delta (a burst of 200 deltas used to enqueue 200 walks of every turn); (c) a held selection pauses the coalesced render (MarkdownView._apply_pending restarts its timer while get_selected_text() != ""; render_now for explicit acts is not deferred); (d) the live turn follows the bottom by intent - _follow_live is cleared when the user scrolls away from the bottom and re-armed when they return (or a new turn starts), and the scrollbar’s changed signal re-scrolls after the layout that follows a render. A distance test taken at scroll time is not enough: content grows by whole lines between renders, so the write clamps at the previous bottom and the follow stops. reasoning_bench.gd measures the append/render split (64 KiB: 14 ms append, 11 ms render; 256 KiB: 120/41; 256 KiB into a 64 KiB cap: 31/10, against 486 ms for the old capped path). GPTY_REASONING_DEBUG=1 prints one line a second - deltas/bytes in, renders/render-ms out, turn/cap state, and GptyTerminal.omp_event_stats()’s accepted/rejected/queued counters - which is how a lossy input stream is told apart from a slow renderer. The status line is a selectable RichTextLabel with the context menu, and every MarkdownView enables selection + the Copy menu.
  • Do not add Claude/Gemini/Antigravity adapters that read OAuth tokens, call private endpoints, or parse undocumented TUI output. Future adapters may use only that CLI’s documented hooks/extensions, with a fresh terms review.