selfware 0.6.7

Your personal AI workshop — software you own, software that lasts
Documentation
{
  "component": "output",
  "tier": "tooling",
  "loop_stage": "perceive",
  "summary": "The output component is the loop's single managed stdout stream. It holds one global OUTPUT_LOCK so concurrent tasks never interleave, and a set of atomic suppression flags (compact, verbose, quiet, json, streaming, plain, tui-active) that decide whether a given emission is rendered, reshaped, or dropped. It records TokenUsage counters, produces semantic tool-call summaries, prints diffs, progress phases, thinking, and final answers. On the loop it is a downstream perceive surface: it projects loop state to a human or a machine, and its suppression rules are the foundation that keeps other components (TUI, JSON consumers) uncorrupted.",
  "loop_objects": ["OutputStream", "TokenUsage", "SuppressionFlags", "ToolCall", "PhaseStatus", "ProgressPhase", "DiffView", "Message", "AgentState"],
  "context_basis": "Recommendations formed with output read in the context of the full engine (~600k budget framing), where every print funnels through OUTPUT_LOCK and should_suppress_output() gates the TUI/JSON/quiet modes.",
  "examples": [
    {
      "id": "output-01",
      "title": "Serialize all emission through OUTPUT_LOCK",
      "loop_stage": "foundation",
      "pattern": "single-writer-lock",
      "intent": "Guarantee that concurrent loop tasks never tear each other's lines on stdout.",
      "how_it_shapes_the_loop": "Every print acquires the global mutex before writing, so parallel act steps and streaming tokens serialize into one clean channel; it never blocks the loop's control, only orders its output.",
      "loop_objects_touched": ["OutputStream", "Message"],
      "wiring": {"inputs_from": ["agent", "orchestration sub-agents"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the OutputStream node shows the lock's current holder and a queue of pending writers.", "visual": "Node shows a single lit gate; contending writers stack as dimmed chips waiting their turn."},
      "mini_scenario": "Two sub-agents finish at once; both grab OUTPUT_LOCK in turn, so their summaries print whole rather than interleaved.",
      "pitfall": "A panic while holding the lock must recover via into_inner() — the code does this; don't replace it with unwrap()."
    },
    {
      "id": "output-02",
      "title": "Suppress human output in JSON mode",
      "loop_stage": "control",
      "pattern": "mode-gated-emission",
      "intent": "Keep stdout valid JSON when a machine consumes the loop.",
      "how_it_shapes_the_loop": "set_json_mode(true) makes banners, 'Final answer:' prefixes, and ANSI escapes no-ops, so only structured payloads leave the loop; it reshapes the perceive channel by mode.",
      "loop_objects_touched": ["SuppressionFlags", "OutputStream", "Message"],
      "wiring": {"inputs_from": ["CLI flags"], "outputs_to": ["JSON consumer"]},
      "touch_interaction": {"gesture": "double-tap", "canvas_action": "Double-tapping the mode switch cycles the OutputStream between human, compact, quiet, and JSON facets.", "visual": "Node badge flips to a { } glyph; decorative sub-nodes (banners, emoji) grey out."},
      "mini_scenario": "Run with --json; the final answer prints as raw content only, safe for a downstream parser.",
      "pitfall": "final_answer must strip its prefix in json/plain mode — a stray 'Final answer:' breaks the parser."
    },
    {
      "id": "output-03",
      "title": "Detect a non-TTY and drop to plain mode",
      "loop_stage": "foundation",
      "pattern": "capability-probe",
      "intent": "Avoid emitting ANSI/emoji into a pipe or file.",
      "how_it_shapes_the_loop": "init() probes stdout_is_tty(); if false it sets PLAIN_MODE and disables colored overrides, so the loop's output degrades gracefully for captured runs.",
      "loop_objects_touched": ["SuppressionFlags", "OutputStream"],
      "wiring": {"inputs_from": ["stdout tty probe"], "outputs_to": ["pipe/file consumer"]},
      "touch_interaction": {"gesture": "long-press", "canvas_action": "Long-pressing the OutputStream node reveals the detected terminal capabilities and forces plain if desired.", "visual": "When plain, the node loses its color swatches and renders in monochrome."},
      "mini_scenario": "Selfware is piped into a log file; PLAIN_MODE strips escape codes so the log stays readable.",
      "pitfall": "Probe once at init — re-checking mid-run can flip colors after the terminal has already been decorated."
    },
    {
      "id": "output-04",
      "title": "Emit a semantic summary per ToolCall",
      "loop_stage": "act",
      "pattern": "act-summarize",
      "intent": "Turn a raw tool result into a one-line human read of the act.",
      "how_it_shapes_the_loop": "semantic_summary maps the ToolCall name+args+result to a compact phrase (e.g. 'Read foo.rs (120 lines)'), giving the loop's act trace meaning without dumping raw JSON.",
      "loop_objects_touched": ["ToolCall", "OutputStream"],
      "wiring": {"inputs_from": ["agent tool execution"], "outputs_to": ["scrollback"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping a summarized tool node expands to show the raw args and result behind the phrase.", "visual": "Summary chip carries a tool-family icon and a green/red status dot from the success flag."},
      "mini_scenario": "A grep returns 12 matches; the summary reads \"Searched 'todo' (12 matches)\" instead of a match array.",
      "pitfall": "The fallback arm must still render unknown tools — never return an empty string for an unmapped name."
    },
    {
      "id": "output-05",
      "title": "Skip re-printing when streaming is live",
      "loop_stage": "act",
      "pattern": "dedupe-terminal",
      "intent": "Avoid printing the answer twice when tokens already streamed.",
      "how_it_shapes_the_loop": "final_answer checks is_streaming_mode() and returns early, so the loop's terminal emission isn't duplicated after live token display.",
      "loop_objects_touched": ["SuppressionFlags", "Message"],
      "wiring": {"inputs_from": ["streaming token channel"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "flick", "canvas_action": "Flicking the answer node while streaming shows the live token feed instead of a static block.", "visual": "During stream the node pulses per-token; on completion it seals without a second render."},
      "mini_scenario": "Tokens stream live to the screen; at Completed, final_answer no-ops so the text isn't repeated.",
      "pitfall": "If you disable streaming you must re-enable the final print, or the answer never appears."
    },
    {
      "id": "output-06",
      "title": "Record TokenUsage into session counters",
      "loop_stage": "perceive",
      "pattern": "budget-accounting",
      "intent": "Accumulate prompt/completion tokens so the loop knows its spend.",
      "how_it_shapes_the_loop": "record_tokens atomically adds to the session totals that Budget-aware UI and /stats read; it feeds the loop's budget picture without gating it.",
      "loop_objects_touched": ["TokenUsage", "OutputStream"],
      "wiring": {"inputs_from": ["agent LLM response usage"], "outputs_to": ["get_total_tokens", "sticky bar"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the token node shows cumulative prompt/completion and a per-turn delta.", "visual": "A rolling counter animates upward on each turn; color shifts as spend accelerates."},
      "mini_scenario": "After a turn the loop calls record_tokens(1200, 380); the sticky bar's total ticks to 1.58K.",
      "pitfall": "Reset counters only on a new session — mid-run reset_tokens() corrupts the budget read."
    },
    {
      "id": "output-07",
      "title": "Print a color-coded DiffView on edit",
      "loop_stage": "verify",
      "pattern": "verify-by-diff",
      "intent": "Let the operator see exactly what an edit changes.",
      "how_it_shapes_the_loop": "display_file_diff runs an LCS-style diff of old vs new content and prints ± lines, giving the verify stage a visible artifact — but it returns early under TUI/compact/quiet/json.",
      "loop_objects_touched": ["DiffView", "OutputStream"],
      "wiring": {"inputs_from": ["agent file_edit"], "outputs_to": ["stdout diff block"]},
      "touch_interaction": {"gesture": "spread", "canvas_action": "Spreading over the diff node expands the ± hunks into a full panel capped at the max display lines.", "visual": "Added lines green, deleted red; a '… more changes' chip appears when the cap is hit."},
      "mini_scenario": "An edit changes 5 lines; the loop prints a green/red diff block before the write is announced.",
      "pitfall": "The diff caps at max_diff_lines to avoid flooding — don't remove the cap or huge edits swamp the frame."
    },
    {
      "id": "output-08",
      "title": "Drive the multi-phase progress bar",
      "loop_stage": "control",
      "pattern": "phase-progress",
      "intent": "Show staged progress with an ETA as the plan advances.",
      "how_it_shapes_the_loop": "TaskProgress transitions PhaseStatus and prints a bar; complete_phase advances the cursor, mirroring the loop's step machine and estimating remaining time from elapsed/progress.",
      "loop_objects_touched": ["ProgressPhase", "PhaseStatus", "OutputStream"],
      "wiring": {"inputs_from": ["agent plan steps"], "outputs_to": ["progress frame"]},
      "touch_interaction": {"gesture": "drag", "canvas_action": "Dragging along the phase bar previews each phase's status and the computed ETA.", "visual": "Filled cyan blocks for done work, dimmed blocks ahead; ETA chip updates as progress accrues."},
      "mini_scenario": "The loop calls start_phase then complete_phase across three steps; the bar climbs 0→33→66→100% with a shrinking ETA.",
      "pitfall": "estimated_remaining only computes above 5% progress — don't show an ETA on an unstarted plan."
    },
    {
      "id": "output-09",
      "title": "Gate thinking output to keep frames clean",
      "loop_stage": "reason",
      "pattern": "reason-trace-gated",
      "intent": "Stream reasoning without corrupting the TUI or JSON output.",
      "how_it_shapes_the_loop": "thinking() short-circuits under compact/quiet/json/tui and rewrites \\n to \\r\\n so inline reasoning resets to column 0; it exposes the reason stage safely.",
      "loop_objects_touched": ["SuppressionFlags", "Message"],
      "wiring": {"inputs_from": ["agent reasoning stream"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the reasoning node toggles inline thinking visibility for the current turn.", "visual": "Dimmed italic text streams in; in verbose it brightens with a 💭 marker."},
      "mini_scenario": "The model thinks aloud; thinking() prints dimmed text inline, but in TUI mode it silently drops.",
      "pitfall": "Raw \\n in thinking text bleeds across TUI borders — the \\r\\n replacement is load-bearing."
    },
    {
      "id": "output-10",
      "title": "Announce a step at each plan boundary",
      "loop_stage": "control",
      "pattern": "step-announce",
      "intent": "Mark each numbered step as the driver crosses it.",
      "how_it_shapes_the_loop": "step_start prints a step banner, suppressed under should_suppress_output(); it reflects the loop's Planning→Executing progression.",
      "loop_objects_touched": ["AgentState", "OutputStream"],
      "wiring": {"inputs_from": ["agent step counter"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "flick", "canvas_action": "Flicking across step banners scrubs the step history for the current run.", "visual": "Each step prints with a 📝 badge and the step name in bright white."},
      "mini_scenario": "The loop enters step 2; step_start prints '📝 Step 2: edit files...'.",
      "pitfall": "Step banners are suppressed in machine modes — never parse them as protocol."
    },
    {
      "id": "output-11",
      "title": "Log a phase transition in verbose mode",
      "loop_stage": "control",
      "pattern": "state-transition-log",
      "intent": "Surface state-machine moves for debugging.",
      "how_it_shapes_the_loop": "phase_transition prints 'from → to' only when verbose and not quiet, exposing the loop's control transitions without noise in normal runs.",
      "loop_objects_touched": ["AgentState", "OutputStream"],
      "wiring": {"inputs_from": ["agent state machine"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "long-press", "canvas_action": "Long-pressing the control node reveals the transition log for the run.", "visual": "A 🔄 arrow animates from the old state chip to the new one."},
      "mini_scenario": "In verbose mode the loop logs 'Executing → ErrorRecovery' when a tool fails.",
      "pitfall": "This is verbose-only — don't rely on it as the source of truth for state; the agent owns that."
    },
    {
      "id": "output-12",
      "title": "Print token usage summary on demand",
      "loop_stage": "perceive",
      "pattern": "budget-readout",
      "intent": "Show prompt+completion totals when the operator opts in.",
      "how_it_shapes_the_loop": "print_token_usage renders the split only when should_show_tokens() and not quiet/json; a perceive-only budget readout.",
      "loop_objects_touched": ["TokenUsage", "OutputStream"],
      "wiring": {"inputs_from": ["record_tokens totals"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the budget node prints the prompt/completion/total breakdown for the turn.", "visual": "In compact it shows '[N tokens]' dimmed; in normal a 📊 line with cyan numbers."},
      "mini_scenario": "With --show-tokens the loop prints '📊 Tokens: 1200 prompt + 380 completion = 1580 total'.",
      "pitfall": "Respect json/quiet suppression here — a token line in JSON mode corrupts the payload."
    },
    {
      "id": "output-13",
      "title": "Show the smart-fallback auto action",
      "loop_stage": "act",
      "pattern": "recover-then-act",
      "intent": "Tell the operator what the loop auto-decided to do after a no-action turn.",
      "how_it_shapes_the_loop": "smart_fallback_action prints the tool the recovery picked; it narrates an act the driver injected to keep the loop moving.",
      "loop_objects_touched": ["ToolCall", "OutputStream"],
      "wiring": {"inputs_from": ["agent smart fallback"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the auto-action node shows the chosen tool and a preview of its args.", "visual": "A ↪ Auto chip in cyan names the tool with dimmed args."},
      "mini_scenario": "The model described intent but didn't act; the fallback prints '↪ Auto: file_read {path:...}' and proceeds.",
      "pitfall": "Suppressed in TUI/compact/quiet/json — don't make recovery visibility depend solely on this line."
    },
    {
      "id": "output-14",
      "title": "Render a verification report by outcome",
      "loop_stage": "verify",
      "pattern": "verify-report",
      "intent": "Show a concise pass or a full failure report.",
      "how_it_shapes_the_loop": "verification_report prints a one-line pass in normal mode but always shows failures in full, tightening the verify stage's signal.",
      "loop_objects_touched": ["AgentState", "OutputStream"],
      "wiring": {"inputs_from": ["testing verification gate"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "spread", "canvas_action": "Spreading the verify node expands a passed check into the full report text.", "visual": "Pass renders a green '✓ Verification passed'; failure prints the full report regardless of mode."},
      "mini_scenario": "Tests pass; the loop prints '✓ Verification passed'. When they fail, the whole report is shown.",
      "pitfall": "Failures must never be swallowed by compact mode — the code always prints them; preserve that."
    },
    {
      "id": "output-15",
      "title": "Emit gated debug turn dumps",
      "loop_stage": "foundation",
      "pattern": "debug-channel",
      "intent": "Dump per-turn LLM responses only when the debug channel is on.",
      "how_it_shapes_the_loop": "debug_output honors the DebugConfig 'turns' channel (or verbose) so raw turn content is available for diagnosis without polluting normal runs.",
      "loop_objects_touched": ["Message", "OutputStream"],
      "wiring": {"inputs_from": ["config DebugConfig", "agent turn"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "long-press", "canvas_action": "Long-pressing the debug node toggles the turns channel and streams raw responses.", "visual": "A magenta '=== DEBUG ===' frame wraps the dumped content."},
      "mini_scenario": "Run with --debug=turns; each turn's raw LLM text prints between magenta debug fences.",
      "pitfall": "Quiet mode still suppresses debug — don't assume --debug overrides --quiet."
    },
    {
      "id": "output-16",
      "title": "Print a safety-blocked notice",
      "loop_stage": "verify",
      "pattern": "gate-notice",
      "intent": "Tell the operator a tool was blocked by safety.",
      "how_it_shapes_the_loop": "safety_blocked prints a 🚫 line, but returns early under suppression because the block is also delivered via the agent event path in TUI mode.",
      "loop_objects_touched": ["ToolCall", "OutputStream"],
      "wiring": {"inputs_from": ["safety checker"], "outputs_to": ["stdout / agent event"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the blocked node shows the safety reason and the offending command.", "visual": "A red 🚫 badge marks the tool node; it does not proceed to act."},
      "mini_scenario": "A destructive shell command trips the checker; the loop prints '🚫 blocked: rm -rf /' and skips it.",
      "pitfall": "In TUI mode this stdout path is suppressed on purpose — rely on the event path there, not this print."
    },
    {
      "id": "output-17",
      "title": "Collapse to compact single-line progress",
      "loop_stage": "perceive",
      "pattern": "compact-projection",
      "intent": "Fit the loop's status into one line for terse runs.",
      "how_it_shapes_the_loop": "In compact mode print_progress emits '[pct% phase ETA:..]' on one line, compressing the perceive channel for low-noise environments.",
      "loop_objects_touched": ["ProgressPhase", "SuppressionFlags"],
      "wiring": {"inputs_from": ["TaskProgress"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "pinch", "canvas_action": "Pinch-out collapses the multi-phase bar into a single compact status line.", "visual": "One-line '[66% write ETA:~4s]' replaces the full phase list."},
      "mini_scenario": "In --compact the loop shows '[66% write ETA:~4s]' instead of a five-line phase block.",
      "pitfall": "Compact still divides by phase count — guard the empty-phase case to avoid a 0% 'Done' loop."
    },
    {
      "id": "output-18",
      "title": "Strip emoji and ANSI in plain mode",
      "loop_stage": "foundation",
      "pattern": "degrade-gracefully",
      "intent": "Produce clean text for terminals or logs that can't render decoration.",
      "how_it_shapes_the_loop": "Plain-mode branches across print functions swap glyphs for ASCII and drop ANSI, so the perceive channel stays legible when captured.",
      "loop_objects_touched": ["SuppressionFlags", "Message"],
      "wiring": {"inputs_from": ["is_plain_mode probe"], "outputs_to": ["log/pipe"]},
      "touch_interaction": {"gesture": "double-tap", "canvas_action": "Double-tapping the OutputStream node forces plain rendering for a copy-friendly view.", "visual": "Progress icons become o/*/+/x; colors flatten to monochrome."},
      "mini_scenario": "Piped to a CI log, phase icons render as o/*/+/x with no escape codes.",
      "pitfall": "Every decorated print needs a plain branch — a missed one leaks escape codes into the log."
    },
    {
      "id": "output-19",
      "title": "Announce a task-completed banner",
      "loop_stage": "act",
      "pattern": "terminal-banner",
      "intent": "Signal a clean finish for entry points that want a generic banner.",
      "how_it_shapes_the_loop": "task_completed prints a success banner (suppressed in quiet/compact/json); the main path uses the failure-mode classifier, so this is a fallback terminal marker.",
      "loop_objects_touched": ["AgentState", "OutputStream"],
      "wiring": {"inputs_from": ["entry point / integration tests"], "outputs_to": ["stdout"]},
      "touch_interaction": {"gesture": "tap", "canvas_action": "Tapping the completion node shows the finish banner and total elapsed.", "visual": "A green '✅ Task completed successfully!' seals the run."},
      "mini_scenario": "A generic entry point finishes and prints '✅ Task completed successfully!'.",
      "pitfall": "The main agent path uses failure_mode::cli_banner instead — don't double-print completion."
    },
    {
      "id": "output-20",
      "title": "Yield the stream to the TUI renderer",
      "loop_stage": "control",
      "pattern": "handoff-writer",
      "intent": "Make output a no-op while the TUI owns the frame.",
      "how_it_shapes_the_loop": "set_tui_active(true) causes should_suppress_output() to short-circuit prints, handing the loop's perceive channel to the single TUI renderer.",
      "loop_objects_touched": ["SuppressionFlags", "OutputStream"],
      "wiring": {"inputs_from": ["ui TUI launch"], "outputs_to": ["ratatui frame"]},
      "touch_interaction": {"gesture": "double-tap", "canvas_action": "Double-tapping the handoff node routes all emission into the TUI surface node.", "visual": "The OutputStream node dims and an arrow points to the TUI node now doing the rendering."},
      "mini_scenario": "The TUI launches; every output print becomes a no-op so the frame renders uncorrupted.",
      "pitfall": "Reset the flag on TUI exit, or all subsequent CLI output stays silently suppressed."
    }
  ]
}