<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Pieces 🌟</title>
    <description>The latest articles on Forem by Pieces 🌟 (@get_pieces).</description>
    <link>https://forem.com/get_pieces</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F923151%2Fac173cfa-ce16-4b45-9597-92403b976e77.jpg</url>
      <title>Forem: Pieces 🌟</title>
      <link>https://forem.com/get_pieces</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/get_pieces"/>
    <language>en</language>
    <item>
      <title>How to Build an Agentic Blog Generator</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Tue, 03 Feb 2026 15:22:20 +0000</pubDate>
      <link>https://forem.com/getpieces/build-an-agentic-blog-generator-with-pieces-in-flutter-h3b</link>
      <guid>https://forem.com/getpieces/build-an-agentic-blog-generator-with-pieces-in-flutter-h3b</guid>
      <description>&lt;h2&gt;
  
  
  Building an Agentic Blog Generator With Pieces OS (Flutter)
&lt;/h2&gt;

&lt;p&gt;This project is a Flutter app that generates a technical blog (in Markdown) from real, recent context. The core idea is simple: &lt;strong&gt;use Pieces OS as the source of truth for what you’ve been working on&lt;/strong&gt; (workstream summaries + your own annotations/persona signals), then use an LLM to turn that into a structured, high-quality blog—optionally in &lt;strong&gt;agentic mode&lt;/strong&gt; with MCP tools.&lt;/p&gt;




&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto generate title&lt;/strong&gt;: suggests blog titles from recent Pieces workstream summaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw695cfjawevjzjd8zyj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw695cfjawevjzjd8zyj3.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Detect persona&lt;/strong&gt;: pulls recent Pieces user annotations and converts them into “persona signals” for voice/tone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgx9ivvteojg2595d9uz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgx9ivvteojg2595d9uz1.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Detect style&lt;/strong&gt;: analyzes a sample blog to infer a reusable style object for consistent output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvn7pkl2dcgg1e8us85wp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvn7pkl2dcgg1e8us85wp.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generate multi part blog&lt;/strong&gt;: plans part titles + per-part outlines, then generates Markdown one part at a time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjs35fq7gcx0nledgqkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjs35fq7gcx0nledgqkd.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agentic flow&lt;/strong&gt;: connects to the MCP endpoint and uses tools for retrieval/verification (RAG) instead of guessing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fneknizj6jhtte6scnljv.png" alt=" " width="800" height="500"&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Pieces OS gives us (and why it matters)
&lt;/h3&gt;

&lt;p&gt;When you ask an LLM to “write a blog about my project”, you usually get one of two outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A generic post that &lt;em&gt;sounds&lt;/em&gt; plausible but doesn’t match what you actually did.
&lt;/li&gt;
&lt;li&gt;A post that misses the details that made the work interesting (trade-offs, decisions, workflow).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pieces OS helps fix this by providing &lt;strong&gt;grounded context&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workstream Summaries (LTM)&lt;/strong&gt;: a stream of recent summaries of your work so we can generate content from what &lt;em&gt;actually happened&lt;/em&gt; recently.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annotations (persona + preferences + voice cues)&lt;/strong&gt;: your own notes/annotations can be pulled and turned into “persona signals” so the blog tone and framing match you.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP (Model Context Protocol) tools&lt;/strong&gt;: in agentic mode, the generator can call MCP tools (memory/search/context) instead of guessing—so it can fetch or verify information as it writes. Practically, &lt;strong&gt;connecting via the MCP endpoint gives us RAG (retrieval‑augmented generation)&lt;/strong&gt;: the model retrieves relevant context from Pieces and then writes with that grounded input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this app, Pieces is not a “nice-to-have integration”—it’s the backbone for &lt;strong&gt;relevance&lt;/strong&gt; and &lt;strong&gt;personalization&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  How this app uses Pieces OS
&lt;/h3&gt;

&lt;p&gt;At a high level, the wizard does three Pieces-backed things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connect to Pieces OS&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Establishes app connection with Pieces OS (so API calls work).
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull recent context&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pulls recent workstream summary IDs over a short-lived WebSocket.
&lt;/li&gt;
&lt;li&gt;Fetches each summary snapshot.
&lt;/li&gt;
&lt;li&gt;Extracts the human-readable “DESCRIPTION” annotation text from each summary.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull persona signals&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Fetches recent user annotations via &lt;code&gt;UserApi.userGetAnnotations()&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Normalizes/truncates them into a prompt-friendly “persona signals” block.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then, when we run in &lt;strong&gt;agentic mode&lt;/strong&gt;, we also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to the Pieces MCP endpoint
&lt;/li&gt;
&lt;li&gt;List the available tools
&lt;/li&gt;
&lt;li&gt;Allow the LLM to call those tools while planning/writing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combination (summaries + persona + MCP tools) gives the generator a much better shot at producing content that matches reality.&lt;/p&gt;




&lt;h3&gt;
  
  
  Agentic mode: how we got better output (one-shot → plan-first → structured parts)
&lt;/h3&gt;

&lt;p&gt;We saw a clear quality jump as we changed the prompting approach.&lt;/p&gt;

&lt;h4&gt;
  
  
  Attempt 1: one prompt to generate the whole blog
&lt;/h4&gt;

&lt;p&gt;The naive approach is: “Here’s some context—write the entire blog.”&lt;/p&gt;

&lt;p&gt;In practice, that tends to produce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;weak structure (rambling, repetitive sections)
&lt;/li&gt;
&lt;li&gt;missing coverage (important modules not mentioned)
&lt;/li&gt;
&lt;li&gt;brittle accuracy (model fills gaps by guessing)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Attempt 2: plan first, then generate
&lt;/h4&gt;

&lt;p&gt;Next improvement: &lt;strong&gt;separate planning from writing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of writing immediately, we made the app generate a plan first (what parts, what each part is about). That makes the writing phase far more constrained and coherent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model knows the “shape” of the blog before it writes
&lt;/li&gt;
&lt;li&gt;you can review/edit titles before any heavy generation happens&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Attempt 3: even more planning, broken into titled parts
&lt;/h4&gt;

&lt;p&gt;The biggest jump came from adding &lt;em&gt;more&lt;/em&gt; structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate &lt;strong&gt;part titles&lt;/strong&gt; first (so each part has a clear purpose)
&lt;/li&gt;
&lt;li&gt;generate &lt;strong&gt;an outline for each part&lt;/strong&gt; (so headings and subheadings are defined)
&lt;/li&gt;
&lt;li&gt;then write &lt;strong&gt;one part at a time&lt;/strong&gt;, using the outline as a contract&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus&lt;/strong&gt;: each part stays on-topic (because it has a title + outline).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better coverage&lt;/strong&gt;: parts are explicitly scoped, so important areas are less likely to be skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly why titled parts matter: the model is no longer inventing structure as it writes—it’s executing a plan you’ve already approved.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tutorial: integrating Pieces OS in a Flutter app
&lt;/h3&gt;

&lt;p&gt;This section breaks down how we integrated Pieces OS in a way that works well for a UI-driven Flutter app. The goal is to make the model’s output &lt;strong&gt;grounded&lt;/strong&gt; (Pieces workstream summaries + persona signals) and optionally &lt;strong&gt;agentic&lt;/strong&gt; (MCP endpoint → RAG).&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: configure the Pieces endpoints
&lt;/h4&gt;

&lt;p&gt;In &lt;code&gt;PiecesOSService&lt;/code&gt;, we keep the local Pieces defaults in one place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;baseUrl&lt;/code&gt;: REST API base (&lt;code&gt;http://localhost:39300&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;websocketUrl&lt;/code&gt;: WebSocket base (&lt;code&gt;ws://localhost:39300&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;defaultMcpEndpoint&lt;/code&gt;: MCP streamable HTTP endpoint (used for tool-based RAG)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the exact code that defines those endpoints (and the imports used by the service):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'dart:async';
import 'dart:developer' as dev;
import 'dart:convert';
import 'dart:io';
import 'package:mcp_dart/mcp_dart.dart' as mcp;
import 'package:pieces_os_client/api.dart';

import '../models/persona_signals.dart';

/// Service to interact with Pieces OS for LTM (Long Term Memory)
class PiecesOSService {
  // Pieces OS configuration
  static const String baseUrl = 'http://localhost:39300';
  static const String websocketUrl = 'ws://localhost:39300';
  static const String defaultMcpEndpoint =
      'http://localhost:39300/model_context_protocol/2025-03-26/mcp';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: connect the app to Pieces OS (REST)
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;connectApplication()&lt;/code&gt; method registers/connects this app with Pieces via &lt;code&gt;ConnectorApi.connect(...)&lt;/code&gt; and stores the returned &lt;code&gt;Context&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;initialize()&lt;/code&gt; method is a small guard that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ensures we only connect once per session (&lt;code&gt;_isInitialized&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;makes all later calls safe to run “just-in-time” from the UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the service fields + constructor + REST connection code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // API clients
  late final ApiClient client;
  late final ConnectorApi _connectorApi;
  late final WorkstreamSummaryApi _workstreamSummaryApi;
  late final AnnotationApi _annotationApi;
  late final UserApi _userApi;

  // Application context
  Context? _context;

  // Application info
  final ApplicationNameEnum appName = ApplicationNameEnum.OPEN_SOURCE;
  final String appVersion = "0.0.1";
  final PlatformEnum platform = Platform.operatingSystem == "windows"
      ? PlatformEnum.WINDOWS
      : Platform.operatingSystem == "macos"
      ? PlatformEnum.MACOS
      : PlatformEnum.LINUX;

  // Track if service is initialized
  bool _isInitialized = false;

  /// Placeholder persona string
  /// generation prompts later.
  String persona = '';

  PiecesOSService() {
    client = ApiClient(basePath: baseUrl);
    _connectorApi = ConnectorApi(client);
    _workstreamSummaryApi = WorkstreamSummaryApi(client);
    _annotationApi = AnnotationApi(client);
    _userApi = UserApi(client);
  }

  /// Register/Connect the application to Pieces OS
  Future&amp;lt;Application&amp;gt; connectApplication() async {
    if (_context?.application != null) {
      return _context!.application;
    }

    try {
      final seededApp = SeededTrackedApplication(
        name: appName,
        platform: platform,
        version: appVersion,
      );

      final connection = SeededConnectorConnection(application: seededApp);

      _context = await _connectorApi.connect(
        seededConnectorConnection: connection,
      );

      if (_context?.application == null) {
        throw Exception(
          'Failed to connect to Pieces OS: No application returned',
        );
      }

      dev.log(
        'Successfully connected to Pieces OS: ${_context!.application.name}',
        name: 'PiecesOSService',
      );
      return _context!.application;
    } catch (e, st) {
      dev.log(
        'Error connecting to Pieces OS',
        name: 'PiecesOSService',
        error: e,
        stackTrace: st,
      );
      rethrow;
    }
  }

  /// Initialize the service - connects to Pieces OS.
  ///
  /// NOTE: We intentionally do **not** start any WebSocket listeners. This app
  /// fetches workstream summary identifiers on-demand.
  Future&amp;lt;void&amp;gt; initialize() async {
    if (_isInitialized) {
      dev.log('Service already initialized', name: 'PiecesOSService');
      return;
    }

    try {
      // Connect to Pieces OS
      await connectApplication();

      _isInitialized = true;
      dev.log('Initialized successfully', name: 'PiecesOSService');
    } catch (e, st) {
      dev.log(
        'Error initializing',
        name: 'PiecesOSService',
        error: e,
        stackTrace: st,
      );
      rethrow;
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: retrieve recent work context (workstream summaries)
&lt;/h4&gt;

&lt;p&gt;To ground generation in “what I actually worked on”, the service does this on-demand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fetchLatestWorkstreamSummaryIds()&lt;/code&gt; opens a WebSocket to the identifiers stream, reads &lt;strong&gt;one&lt;/strong&gt; payload, then closes the socket.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getLastSummaryContents()&lt;/code&gt; uses those IDs to fetch summary snapshots and extracts the &lt;strong&gt;DESCRIPTION&lt;/strong&gt; annotation text via &lt;code&gt;getSummaryContent()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those DESCRIPTION strings are what we feed into the LLM as “recent context”.&lt;/p&gt;

&lt;p&gt;Here’s the exact code for streaming IDs, fetching summaries, and extracting DESCRIPTION text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  /// Fetch the most recent workstream summary identifiers on-demand.
  ///
  /// This opens a WebSocket connection **once**, reads the first identifiers
  /// payload, then closes the socket. No continuous listener / no caching.
  Future&amp;lt;List&amp;lt;String&amp;gt;&amp;gt; fetchLatestWorkstreamSummaryIds({
    int limit = 10,
    Duration timeout = const Duration(seconds: 8),
  }) async {
    if (limit &amp;lt;= 0) return const [];
    await initialize();

    final wsUrl = '$websocketUrl/workstream_summaries/stream/identifiers';
    WebSocket? socket;
    try {
      socket = await WebSocket.connect(wsUrl).timeout(timeout);
      final first = await socket.first.timeout(timeout);

      String raw;
      if (first is String) {
        raw = first;
      } else if (first is List&amp;lt;int&amp;gt;) {
        raw = utf8.decode(first);
      } else {
        raw = first.toString();
      }

      final decoded = jsonDecode(raw);
      final streamed = StreamedIdentifiers.fromJson(decoded);
      final ids = (streamed?.iterable ?? const [])
          .map((e) =&amp;gt; e.workstreamSummary?.id)
          .whereType&amp;lt;String&amp;gt;()
          .where((s) =&amp;gt; s.trim().isNotEmpty)
          .take(limit)
          .toList(growable: false);
      return ids;
    } finally {
      try {
        await socket?.close();
      } catch (_) {
        // ignore
      }
    }
  }

  /// Get the summary content from a workstream summary's annotations
  Future&amp;lt;String?&amp;gt; getSummaryContent(WorkstreamSummary summary) async {
    try {
      // Loop through annotations to find the DESCRIPTION type
      for (final annotationRef
          in summary.annotations?.indices.keys.toList() ?? []) {
        // Fetch the full annotation using AnnotationApi (singular)
        final annotation = await _annotationApi
            .annotationSpecificAnnotationSnapshot(annotationRef);
        if (annotation == null) {
          continue;
        }

        // Check if this is a DESCRIPTION type annotation
        if (annotation.type == AnnotationTypeEnum.DESCRIPTION) {
          // Return the text content
          return annotation.text;
        }
      }

      return null;
    } catch (e) {
      dev.log(
        'Error fetching annotation content for ${summary.id}: $e',
        name: 'PiecesOSService',
      );
      return null;
    }
  }

  /// Get the last [limit] workstream summary DESCRIPTION texts (most recent first).
  ///
  /// This performs an on-demand identifiers fetch, then retrieves each summary
  /// snapshot and its DESCRIPTION annotation text.
  Future&amp;lt;List&amp;lt;String&amp;gt;&amp;gt; getLastSummaryContents({int limit = 10}) async {
    if (limit &amp;lt;= 0) return const [];
    await initialize();

    final ids = await fetchLatestWorkstreamSummaryIds(limit: limit);
    if (ids.isEmpty) return const [];

    final summaries = await Future.wait(
      ids.map(
        (id) =&amp;gt; _workstreamSummaryApi
            .workstreamSummariesSpecificWorkstreamSummarySnapshot(id),
      ),
    );

    final contents = await Future.wait(
      summaries.whereType&amp;lt;WorkstreamSummary&amp;gt;().map(
        (s) async =&amp;gt; (await getSummaryContent(s))?.trim(),
      ),
    );

    return contents.whereType&amp;lt;String&amp;gt;().where((t) =&amp;gt; t.isNotEmpty).toList();
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: retrieve persona signals (user annotations)
&lt;/h4&gt;

&lt;p&gt;To personalize voice and framing, &lt;code&gt;getPersonaFromUserAnnotations()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resolves the active user (&lt;code&gt;_resolveUserId()&lt;/code&gt; → &lt;code&gt;UserApi.userSnapshot()&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;fetches recent annotations (&lt;code&gt;UserApi.userGetAnnotations(...)&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;normalizes them into a prompt-friendly &lt;code&gt;PersonaAnnotations&lt;/code&gt; object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Pieces returns no annotations, we keep persona optional (so the model doesn’t invent one).&lt;/p&gt;

&lt;p&gt;Here’s the code that resolves the active user and turns annotations into prompt-friendly “persona signals”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Future&amp;lt;String&amp;gt; _resolveUserId() async {
    await initialize();

    final snap = await _userApi.userSnapshot();
    final userId = snap?.user?.id;
    if (userId != null &amp;amp;&amp;amp; userId.trim().isNotEmpty) return userId.trim();

    throw StateError(
      'No active user found. Ensure Pieces has an active user session.',
    );
  }

  /// Get a "persona" text derived from the user's annotations.
  ///
  /// Uses `UserApi.userGetAnnotations()` which returns the resolved active Person
  /// and filtered Annotations.
  Future&amp;lt;PersonaAnnotations&amp;gt; getPersonaFromUserAnnotations({
    int limit = 1,
  }) async {
    if (limit &amp;lt;= 0) return const PersonaAnnotations();
    final userId = await _resolveUserId();
    final out = await _userApi.userGetAnnotations(
      userId,
      UserAnnotationsInput(limit: limit),
    );

    final texts = out.annotations.iterable
        .map((a) =&amp;gt; a.text)
        .whereType&amp;lt;String&amp;gt;()
        .map((t) =&amp;gt; t.trim())
        .where((t) =&amp;gt; t.isNotEmpty)
        .toList();
    dev.log(
      'Fetched ${texts.length} annotation texts for persona.',
      name: 'PiecesOSService',
    );

    // Pieces might return an empty annotations list; in that case we return empty
    // and let the caller omit persona entirely.
    if (texts.isEmpty) return const PersonaAnnotations();

    final normalized = &amp;lt;String&amp;gt;[];
    for (final t in texts) {
      final oneLine = t.replaceAll(RegExp(r'\s+'), ' ').trim();
      if (oneLine.isEmpty) continue;
      normalized.add(
        oneLine.length &amp;gt; 180 ? '${oneLine.substring(0, 180)}…' : oneLine,
      );
    }

    return PersonaAnnotations(annotations: normalized);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5: enable RAG with MCP (agentic mode)
&lt;/h4&gt;

&lt;p&gt;When we connect via the MCP endpoint, the generator can do &lt;strong&gt;RAG (retrieval‑augmented generation)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;connectMcp()&lt;/code&gt; connects a streamable HTTP transport (POST + SSE GET)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;listTools()&lt;/code&gt; fetches and caches the available MCP tools
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;callTool()&lt;/code&gt; lets the agent retrieve/verify relevant context from Pieces during planning/writing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the “agentic” part: instead of guessing, the model can retrieve what it needs.&lt;/p&gt;

&lt;p&gt;Here’s the MCP client code that enables tool calls (MCP endpoint → RAG):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // MCP (Streamable HTTP/SSE) client
  mcp.McpClient? _mcpClient;
  mcp.StreamableHttpClientTransport? _mcpTransport;
  Uri? _mcpEndpoint;
  List&amp;lt;mcp.Tool&amp;gt;? _cachedMcpTools;

  bool get isMcpConnected =&amp;gt;
      _mcpClient != null &amp;amp;&amp;amp; _mcpTransport != null;

  /// Connect to Pieces MCP endpoint using Streamable HTTP (POST + SSE GET).
  ///
  /// Default endpoint is `http://localhost:39300/mcp`.
  Future&amp;lt;void&amp;gt; connectMcp({String? endpoint}) async {
    final ep = Uri.parse((endpoint ?? defaultMcpEndpoint).trim());

    // If already connected to same endpoint, do nothing.
    if (isMcpConnected &amp;amp;&amp;amp; _mcpEndpoint == ep) return;

    // Close any existing transport/client.
    await disconnectMcp();
    _mcpEndpoint = ep;

    final transport = mcp.StreamableHttpClientTransport(ep);
    final client = mcp.McpClient(
      const mcp.Implementation(name: 'blog_generator', version: '0.0.1'),
      options: const mcp.McpClientOptions(
        // Client-side capabilities are for server-initiated requests
        // (sampling, elicitation, tasks, roots). We don't need any for now.
        capabilities: mcp.ClientCapabilities(),
      ),
    );

    transport.onerror = (err) {
      dev.log(
        'MCP transport error: $err',
        name: 'PiecesOSService',
        error: err,
      );
    };
    transport.onclose = () {
      dev.log('MCP transport closed', name: 'PiecesOSService');
      _cachedMcpTools = null;
      _mcpClient = null;
      _mcpTransport = null;
    };

    try {
      await client.connect(transport);
      _mcpTransport = transport;
      _mcpClient = client;
      final server = client.getServerVersion();
      dev.log(
        'MCP connected: ${server?.name ?? 'unknown'} ${server?.version ?? ''}',
        name: 'PiecesOSService',
      );
    } catch (e) {
      await disconnectMcp();
      rethrow;
    }
  }

  Future&amp;lt;void&amp;gt; disconnectMcp() async {
    _cachedMcpTools = null;
    try {
      await _mcpTransport?.terminateSession();
    } catch (_) {
      // Ignore - server may not support.
    }

    try {
      await _mcpClient?.close();
    } catch (_) {
      // Ignore.
    }
    try {
      await _mcpTransport?.close();
    } catch (_) {
      // Ignore.
    }

    _mcpClient = null;
    _mcpTransport = null;
  }

  Future&amp;lt;List&amp;lt;mcp.Tool&amp;gt;&amp;gt; listTools({bool forceRefresh = false}) async {
    final client = _mcpClient;
    if (client == null) {
      throw StateError('MCP client not connected');
    }

    if (!forceRefresh &amp;amp;&amp;amp; _cachedMcpTools != null) return _cachedMcpTools!;

    final res = await client.listTools();
    _cachedMcpTools = res.tools;
    return res.tools;
  }

  Future&amp;lt;mcp.CallToolResult&amp;gt; callTool({
    required String name,
    Map&amp;lt;String, dynamic&amp;gt; arguments = const {},
  }) async {
    final client = _mcpClient;
    if (client == null) {
      throw StateError('MCP client not connected');
    }

    return client.callTool(
      mcp.CallToolRequest(name: name, arguments: arguments),
    );
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 6: clean up
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;dispose()&lt;/code&gt; closes the MCP session (best-effort) and clears state so the service doesn’t leak resources across UI lifecycles.&lt;/p&gt;

&lt;p&gt;Here’s the cleanup method from the service (it also closes the class):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  /// Disconnect from Pieces OS and cleanup resources
  void dispose() {
    _isInitialized = false;

    // Close MCP client/transport
    // Fire and forget; dispose is sync.
    unawaited(disconnectMcp());

    // Clear context
    _context = null;

    dev.log('Disposed', name: 'PiecesOSService');
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  How the UI uses this service (quick walkthrough)
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;BlogWizardScreen&lt;/code&gt;, the flow looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get grounded context&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;_pieces.getLastSummaryContents(limit: 10)&lt;/code&gt; for recent workstream summaries
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_pieces.getPersonaFromUserAnnotations(limit: 1)&lt;/code&gt; for persona signals (optional)
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Turn on agentic RAG&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;_pieces.connectMcp(...)&lt;/code&gt; then &lt;code&gt;_pieces.listTools()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;pass &lt;code&gt;callMcpTool: (...) =&amp;gt; _pieces.callTool(...)&lt;/code&gt; into the agent loop&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;From there, the generator improves quality by moving from one-shot output to a structured workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plan part titles first
&lt;/li&gt;
&lt;li&gt;generate outlines per part (reviewable)
&lt;/li&gt;
&lt;li&gt;write each part with the outline as a contract&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;p&gt;Pieces OS is what makes this blog generator &lt;em&gt;feel real&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it anchors generation in your actual recent work (summaries)
&lt;/li&gt;
&lt;li&gt;it shapes tone/voice via your own signals (annotations → persona)
&lt;/li&gt;
&lt;li&gt;it enables agentic correctness when available (MCP tools)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the prompt strategy matters just as much: moving from &lt;strong&gt;one-shot generation&lt;/strong&gt; to a &lt;strong&gt;plan-first, titled multi-part workflow&lt;/strong&gt; is what consistently turns “okay output” into “publishable output”.&lt;/p&gt;

&lt;p&gt;The rest of the project code (UI, models, generation logic, widgets, etc.) is available on &lt;a href="https://github.com/bishoy-at-pieces/blog-dart-blog-generator" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; repository.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>llm</category>
    </item>
    <item>
      <title>Building a TUI with Pieces SDK - Part 3: Advanced Features</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Mon, 02 Feb 2026 17:57:24 +0000</pubDate>
      <link>https://forem.com/getpieces/building-a-tui-with-pieces-sdk-part-3-advanced-features-3i0o</link>
      <guid>https://forem.com/getpieces/building-a-tui-with-pieces-sdk-part-3-advanced-features-3i0o</guid>
      <description>&lt;h2&gt;
  
  
  Building a Pieces Copilot TUI - Part 3: Advanced Features &amp;amp; Integration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This tutorial is part of the &lt;a href="https://github.com/pieces-app/cli-agent" rel="noopener noreferrer"&gt;Pieces CLI project&lt;/a&gt;. We welcome contributions! Feel free to open issues, submit PRs, or suggest improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to Part 3! In &lt;a href="https://dev.to/getpieces/building-a-tui-with-pieces-sdk-part-2-ui-components-59l9"&gt;Part 2&lt;/a&gt;, we built the core UI components. Now, we'll add the advanced features to create a fully-functional Pieces Copilot TUI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we built in Part 2:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Message widget (&lt;code&gt;chat_message.py&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;✅ Chat panel (&lt;code&gt;chat_panel.py&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;✅ Input widget (&lt;code&gt;chat_input.py&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;✅ Basic view (&lt;code&gt;chat_view.py&lt;/code&gt; - simple version)
&lt;/li&gt;
&lt;li&gt;✅ Main application (&lt;code&gt;app.py&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Create the Conversations List
&lt;/h2&gt;

&lt;p&gt;Let's build &lt;code&gt;chats_list.py&lt;/code&gt; - the sidebar showing all conversations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# chats_list.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Conversations list panel widget.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.widgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Button&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.containers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VerticalScroll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vertical&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.message&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper.basic_identifier.chat&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BasicChat&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Message emitted when a chat is selected.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewChatRequested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Message emitted when new chat is requested.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Widget representing a single chat in the list.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    ChatItem {
        width: 100%;
        height: auto;
        padding: 1 2;
        margin-bottom: 1;
        background: $surface;
        border-left: solid $primary;
    }

    ChatItem:hover {
        background: $panel;
        border-left: solid $accent;
    }

    ChatItem.active {
        background: $primary 30%;
        border-left: thick $accent;
        text-style: bold;
    }

    ChatItem .chat-title {
        color: $text;
        text-style: bold;
    }

    ChatItem .chat-summary {
        color: $text-muted;
        text-style: italic;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compose the chat item.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Untitled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;💬 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat-title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat-summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle click event.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatsList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vertical&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Panel to display list of conversations.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    ChatsList {
        width: 25%;
        height: 100%;
        border: solid $primary;
        background: $background;
        padding: 1;
    }

    ChatsList:focus-within {
        border: solid $accent;
    }

    ChatsList Button {
        width: 100%;
        margin-bottom: 1;
    }

    ChatsList VerticalScroll {
        height: 1fr;
    }

    ChatsList .empty-state {
        text-align: center;
        color: $text-muted;
        text-style: italic;
        margin: 2;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;border_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Conversations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_chat_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compose the chats list panel.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;➕ New Chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-chat-btn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;VerticalScroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chats-container&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_chats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Load chats from the API.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;chats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#chats-container&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VerticalScroll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Clear existing items
&lt;/span&gt;            &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_children&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_chat_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No chats yet...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty-state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;chat_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_chat_items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chat_item&lt;/span&gt;
                    &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#chats-container&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VerticalScroll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Failed to load chats: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty-state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_active_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Set the active chat and update UI.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="c1"&gt;# Remove active class from all items
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_chat_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;

        &lt;span class="c1"&gt;# Add active class to the selected item
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_chat_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_chat_items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;add_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_new_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add a new chat to the list.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#chats-container&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VerticalScroll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Remove empty state if present
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;empty_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.empty-state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;empty_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;LookupError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# No empty state widget present, which is fine
&lt;/span&gt;            &lt;span class="k"&gt;pass&lt;/span&gt;

        &lt;span class="c1"&gt;# Add new chat at the top
&lt;/span&gt;        &lt;span class="n"&gt;chat_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_chat_items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chat_item&lt;/span&gt;

        &lt;span class="c1"&gt;# Mount at the beginning
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_button_pressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pressed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle button press.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new-chat-btn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NewChatRequested&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ChatItem&lt;/code&gt; - Individual conversation with title and summary
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ChatSelected&lt;/code&gt; - Custom message when chat is clicked
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NewChatRequested&lt;/code&gt; - Message when "New Chat" button is pressed
&lt;/li&gt;
&lt;li&gt;Active chat highlighting
&lt;/li&gt;
&lt;li&gt;Hover effects
&lt;/li&gt;
&lt;li&gt;Error handling for API failures&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 2: Create the Streaming Handler
&lt;/h2&gt;

&lt;p&gt;Now &lt;code&gt;src/pieces_copilot_tui/streaming_handler.py&lt;/code&gt; - connects Pieces OS streaming to our UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/streaming_handler.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handler for streaming responses from Pieces OS.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper.basic_identifier.chat&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BasicChat&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamingHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handles streaming responses from the Pieces Copilot.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_thinking_started&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;on_stream_started&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;on_stream_chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;on_stream_completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;on_stream_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Initialize the streaming handler.

        Args:
            pieces_client: The Pieces client instance
            on_thinking_started: Callback when thinking starts
            on_stream_started: Callback when streaming starts (with initial text)
            on_stream_chunk: Callback for each chunk (with full accumulated text)
            on_stream_completed: Callback when streaming completes (with optional new chat)
            on_stream_error: Callback when an error occurs (with error message)
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_thinking_started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_thinking_started&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_stream_started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_stream_started&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_stream_chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_stream_chunk&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_stream_completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_stream_completed&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_stream_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_stream_error&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="c1"&gt;# Register callback
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask_stream_ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask_stream_ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_message_callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_handle_stream_message&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Ask a question to the copilot.

        Args:
            query: The question to ask
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_thinking_started&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_handle_stream_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle streaming messages from the copilot.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN-PROGRESS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;
                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                                &lt;span class="c1"&gt;# First chunk - start streaming
&lt;/span&gt;                                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
                                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_stream_started&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                                &lt;span class="c1"&gt;# Subsequent chunks
&lt;/span&gt;                                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
                                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_stream_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COMPLETED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Streaming completed
&lt;/span&gt;                &lt;span class="n"&gt;new_chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;new_chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_chat&lt;/span&gt;

                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_stream_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOPPED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CANCELED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="c1"&gt;# Handle error
&lt;/span&gt;                &lt;span class="n"&gt;error_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_stream_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_stream_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Create the Full View Orchestrator
&lt;/h2&gt;

&lt;p&gt;Now the &lt;strong&gt;complete&lt;/strong&gt; &lt;code&gt;chat_view.py&lt;/code&gt; - replaces the simple version from Part 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# chat_view.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Main copilot view combining all widgets.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.screen&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Screen&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.containers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Horizontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vertical&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.binding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Binding&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.widgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper.basic_identifier.chat&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BasicChat&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.chat_panel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatPanel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.chats_list&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatsList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ChatSelected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NewChatRequested&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.chat_input&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessageSubmitted&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.streaming_handler&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StreamingHandler&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CopilotView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Main copilot view screen.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;BINDINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctrl+n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;New Chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctrl+r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rename_chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctrl+d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete_chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Delete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctrl+l&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toggle_ltm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Toggle LTM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctrl+q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    CopilotView {
        layout: vertical;
    }

    CopilotView Horizontal {
        height: 1fr;
    }

    CopilotView Vertical.main-content {
        width: 75%;
        layout: vertical;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ChatPanel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ChatsList&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ChatInput&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ltm_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streaming_handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StreamingHandler&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compose the view.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Horizontal&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# Left sidebar - conversations list
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatsList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt;

            &lt;span class="c1"&gt;# Right side - chat panel and input
&lt;/span&gt;            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Vertical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main-content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatPanel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;

                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatInput&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_input&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Initialize the view.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="c1"&gt;# Load chats
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_chats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Show welcome message
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_welcome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Check LTM status
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ltm_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_chat_ltm_enabled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ltm_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="c1"&gt;# Setup streaming handler
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streaming_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StreamingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;on_thinking_started&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_on_thinking_started&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;on_stream_started&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_on_stream_started&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;on_stream_chunk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_on_stream_chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;on_stream_completed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_on_stream_completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;on_stream_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_on_stream_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_chat_selected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatSelected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle chat selection.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_active_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Load conversation
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear_messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;border_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chat: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;
                &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw_content&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Error loading messages: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_new_chat_requested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NewChatRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle new chat request.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_new_chat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message_submitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MessageSubmitted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle user message submission.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_streaming_active&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# Add user message to chat panel
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Send to copilot via streaming handler
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streaming_handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streaming_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ask_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Streaming handler callbacks
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_thinking_started&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Called when copilot starts thinking.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_thinking_indicator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_stream_started&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Called when streaming starts with initial text.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_streaming_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;initial_text&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_stream_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Called for each streaming chunk with accumulated text.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_streaming_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;full_text&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_stream_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BasicChat&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Called when streaming completes.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finalize_streaming_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Update chat reference if new chat was created
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;new_chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;old_chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;old_chat&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;old_chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;new_chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_chat&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_new_chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_active_chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;border_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chat: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new_chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_stream_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Called when a streaming error occurs.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_new_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Create a new chat.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_active_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear_messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;border_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chat: New Conversation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_welcome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_rename_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Rename the current chat.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No chat selected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# For now, just show a notification - you can implement a dialog later
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rename chat - Not implemented yet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_delete_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Delete the current chat.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No chat selected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;chat_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deleted chat: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chat_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Clear view and reload chats
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_new_chat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chats_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_chats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error deleting chat: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_toggle_ltm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Toggle LTM (Long Term Memory).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;is_chat_ltm_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_chat_ltm_enabled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_chat_ltm_enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deactivate_ltm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🧠 Chat LTM disabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ltm_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;is_system_ltm_running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_ltm_running&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_system_ltm_running&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate_ltm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🧠 Chat LTM enabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ltm_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🧠 LTM system not running. Please enable it first.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Error toggling LTM: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚨 &lt;strong&gt;Critical for Thread Safety&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Always use &lt;code&gt;call_from_thread()&lt;/code&gt; when updating UI from background threads. Direct UI updates from threads will cause crashes! The streaming handler runs in a background thread, so all UI updates must go through this method.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split-pane layout (25% sidebar, 75% chat)
&lt;/li&gt;
&lt;li&gt;Handles all custom messages (&lt;code&gt;ChatSelected&lt;/code&gt;, &lt;code&gt;NewChatRequested&lt;/code&gt;, &lt;code&gt;MessageSubmitted&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;Thread-safe UI updates via &lt;code&gt;call_from_thread()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Keyboard shortcuts (Ctrl+N, Ctrl+D, Ctrl+L, Ctrl+Q)
&lt;/li&gt;
&lt;li&gt;Conversation management
&lt;/li&gt;
&lt;li&gt;LTM toggle support&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Step 4: Update the Main Application
&lt;/h2&gt;

&lt;p&gt;Now update &lt;code&gt;src/pieces_copilot_tui/app.py&lt;/code&gt; to use the full &lt;code&gt;CopilotView&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/app.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pieces Copilot TUI - A Terminal User Interface for Pieces OS.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.chat_view&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CopilotView&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PiecesCopilotTUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pieces Copilot TUI with full features.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# CSS styles for the entire app
&lt;/span&gt;    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Screen {
        background: $background;
        color: $text;
    }

    .error {
        color: $error;
        text-style: bold;
    }

    .success {
        color: $success;
        text-style: bold;
    }

    .warning {
        color: $warning;
        text-style: bold;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nc"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot_view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Initialize the application.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pieces Copilot TUI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Initialize Pieces client
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_websocket&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to connect to Pieces OS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# Create and push the full copilot view
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot_view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CopilotView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push_screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot_view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_tui&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run the Pieces Copilot TUI application.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Initialize Pieces client
&lt;/span&gt;    &lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Check if Pieces OS is running
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_pieces_running&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: Pieces OS is not running. Please start Pieces OS first.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PiecesCopilotTUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;run_tui&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Changes from Part 2:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import &lt;code&gt;CopilotView&lt;/code&gt; instead of &lt;code&gt;SimpleChatView&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;warning&lt;/code&gt; CSS class
&lt;/li&gt;
&lt;li&gt;Now using the full-featured view&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 5: Run Your Complete TUI
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;__main__.py&lt;/code&gt; file is already set up from Part 2. Run it the Pythonic way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Navigate to src directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;src

&lt;span class="c"&gt;# Run the module&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pieces_copilot_tui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Simple, clean, and Pythonic. ✨&lt;/p&gt;

&lt;p&gt;🎉 &lt;strong&gt;You now have a fully functional Pieces Copilot TUI!&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Add Debug Logging
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In any widget method
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Current state: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;some_variable&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Info message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Warning message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# View in textual console
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Issues
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Streaming doesn't start&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Check WebSocket connection
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask_stream_ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ WebSocket not connected!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_websocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: UI not updating from streaming&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Make sure you're using call_from_thread
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# NOT: self.chat_panel.add_message("assistant", text)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Messages not loading&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add error handling
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to load messages: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






</description>
      <category>programming</category>
      <category>ai</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a TUI with Pieces SDK - Part 2: UI Components</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Mon, 02 Feb 2026 17:57:15 +0000</pubDate>
      <link>https://forem.com/getpieces/building-a-tui-with-pieces-sdk-part-2-ui-components-59l9</link>
      <guid>https://forem.com/getpieces/building-a-tui-with-pieces-sdk-part-2-ui-components-59l9</guid>
      <description>&lt;h2&gt;
  
  
  Building a Pieces Copilot TUI - Part 2: Basic UI Components
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This tutorial is part of the &lt;a href="https://github.com/pieces-app/cli-agent" rel="noopener noreferrer"&gt;Pieces CLI project&lt;/a&gt;. We welcome contributions! Feel free to open issues, submit PRs, or suggest improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to Part 2! In &lt;a href="https://dev.to/getpieces/building-a-tui-with-pieces-sdk-5eca"&gt;Part 1&lt;/a&gt;, we learned how to use the Pieces OS SDK. Now, we'll start building the Terminal User Interface (TUI) using Textual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we'll build in Part 2:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understanding Textual fundamentals
&lt;/li&gt;
&lt;li&gt;Message display widget
&lt;/li&gt;
&lt;li&gt;Chat panel for conversations
&lt;/li&gt;
&lt;li&gt;User input widget
&lt;/li&gt;
&lt;li&gt;A working basic chat interface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What we'll add in Part 3:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conversations list sidebar
&lt;/li&gt;
&lt;li&gt;Streaming handler integration
&lt;/li&gt;
&lt;li&gt;Full view orchestration
&lt;/li&gt;
&lt;li&gt;Testing &amp;amp; optimization&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Understanding Textual
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://textual.textualize.io/" rel="noopener noreferrer"&gt;Textual&lt;/a&gt; is a modern Python framework for building TUIs. It uses a reactive, component-based architecture similar to React.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pieces-copilot-tui/
├── venv/                                    # Virtual environment
├── requirements.txt                         # Dependencies
├── run.py                                   # Entry point
└── src/                                     # Source code
    └── pieces_copilot_tui/                  # Main package
        ├── __init__.py                      # Package initialization
        ├── app.py                           # Main TUI application
        ├── chat_message.py                  # Individual message widget (Part 2)
        ├── chat_panel.py                    # Message display panel (Part 2)
        ├── chat_input.py                    # User input widget (Part 2)
        ├── streaming_handler.py             # Pieces SDK streaming logic (Part 3)
        ├── chats_list.py                    # Conversations list (Part 3)
        └── chat_view.py                     # Main view orchestrator (Part 3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we'll build the core UI components. In &lt;strong&gt;Part 3&lt;/strong&gt;, we'll add conversations management and full integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Create the Message Widget
&lt;/h2&gt;

&lt;p&gt;Let's start with the foundation - &lt;code&gt;src/pieces_copilot_tui/chat_message.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/chat_message.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Chat message widget for displaying individual messages.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.widgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Static&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.containers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Vertical&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vertical&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Widget to display a single chat message.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    ChatMessage {
        width: 100%;
        height: auto;
        padding: 1 2;
        margin-bottom: 1;
    }

    ChatMessage.user-message {
        background: $primary 20%;
        border-left: thick $primary;
    }

    ChatMessage.assistant-message {
        background: $accent 20%;
        border-left: thick $accent;
    }

    ChatMessage.system-message {
        background: $warning 20%;
        border-left: thick $warning;
    }

    ChatMessage .message-header {
        color: $text-muted;
        text-style: italic;
        margin-bottom: 1;
    }

    ChatMessage .message-content {
        color: $text;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Today %I:%M %p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Set CSS class based on role
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compose the message widget.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;role_emoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;👤&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🤖&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚙️&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;emoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;role_emoji&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;emoji&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message-header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message-content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key concepts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Vertical&lt;/code&gt; container stacks children vertically
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compose()&lt;/code&gt; defines child widgets
&lt;/li&gt;
&lt;li&gt;CSS classes are added dynamically based on message role
&lt;/li&gt;
&lt;li&gt;Emojis make it visually appealing! 🎨&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CSS Variables:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$primary&lt;/code&gt;, &lt;code&gt;$accent&lt;/code&gt;, &lt;code&gt;$warning&lt;/code&gt; - Textual's built-in color variables
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$text&lt;/code&gt;, &lt;code&gt;$text-muted&lt;/code&gt; - Text color variables
&lt;/li&gt;
&lt;li&gt;You can customize these in your app's theme&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 2: Create the Chat Panel
&lt;/h2&gt;

&lt;p&gt;Now &lt;code&gt;src/pieces_copilot_tui/chat_panel.py&lt;/code&gt; - the scrollable message display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/chat_panel.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Chat panel widget for displaying conversation messages.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.widgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Static&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.containers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VerticalScroll&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.chat_message&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatMessage&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VerticalScroll&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Panel to display chat messages.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    ChatPanel {
        width: 100%;
        height: 1fr;
        border: solid $primary;
        background: $background;
        padding: 1 2;
    }

    ChatPanel:focus {
        border: solid $accent;
    }

    ChatPanel .thinking-indicator {
        color: $warning;
        text-style: italic bold blink;
        text-align: center;
        background: $surface;
        border: dashed $warning;
        padding: 1;
        margin: 1;
    }

    ChatPanel .streaming-message {
        border-left: thick $accent;
        text-style: bold;
    }

    ChatPanel .welcome-message {
        text-align: center;
        margin: 4 2;
        padding: 3;
        border: dashed $primary;
        background: $primary 10%;
        color: $text;
        width: 100%;
        height: auto;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;border_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thinking_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add a new message to the chat panel.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_clear_thinking_indicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Today %I:%M %p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scroll_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_thinking_indicator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add a thinking indicator.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_clear_thinking_indicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thinking_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🤔 Thinking...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thinking-indicator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thinking_widget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scroll_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_streaming_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add a streaming message with cursor.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_clear_thinking_indicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Today %I:%M %p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; ▌&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;streaming-message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scroll_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_streaming_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Update the streaming message content.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Update the content of the last Static widget in the streaming message
&lt;/span&gt;            &lt;span class="n"&gt;content_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.message-content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content_widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;content_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; ▌&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scroll_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;finalize_streaming_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Convert streaming message to permanent message.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;content_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.message-content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content_widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Remove cursor
&lt;/span&gt;                &lt;span class="n"&gt;current_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;renderable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; ▌&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;content_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;streaming-message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clear_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Clear all messages from the chat panel.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_clear_thinking_indicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_mounted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_welcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Show welcome message.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;welcome_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;🎯 Pieces Copilot TUI

Type your message below to start chatting!

Press Ctrl+Q to quit.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

        &lt;span class="n"&gt;welcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;welcome_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;welcome-message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_clear_thinking_indicator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Remove thinking indicator if present.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thinking_widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thinking_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_mounted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thinking_widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thinking_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_streaming_active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check if streaming or thinking is currently active.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_streaming_widget&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thinking_widget&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;VerticalScroll&lt;/code&gt; makes it scrollable
&lt;/li&gt;
&lt;li&gt;Thinking indicator shows "🤔 Thinking..."
&lt;/li&gt;
&lt;li&gt;Streaming shows content + cursor (" ▌")
&lt;/li&gt;
&lt;li&gt;Auto-scrolls to bottom on new messages
&lt;/li&gt;
&lt;li&gt;Welcome message for first-time users&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Create the Input Widget
&lt;/h2&gt;

&lt;p&gt;Simple but crucial - &lt;code&gt;src/pieces_copilot_tui/chat_input.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/chat_input.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Chat input widget for user message entry.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.widgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.message&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessageSubmitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Message emitted when user submits input.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Input widget for chat messages.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    ChatInput {
        width: 100%;
        height: 3;
        background: $surface;
        border: solid $primary;
        padding: 0 1;
        margin: 0;
        dock: bottom;
    }

    ChatInput:focus {
        border: solid $accent;
        background: $panel;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Type your message here...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_input_submitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Submitted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle input submission.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MessageSubmitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Focus the input when mounted.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Textual messages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom &lt;code&gt;MessageSubmitted&lt;/code&gt; message bubbles up to parent
&lt;/li&gt;
&lt;li&gt;Parent widgets can listen for this message
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;event.stop()&lt;/code&gt; prevents further propagation
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;post_message()&lt;/code&gt; sends the message up the widget tree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User types message and presses Enter
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;on_input_submitted()&lt;/code&gt; is called
&lt;/li&gt;
&lt;li&gt;Creates &lt;code&gt;MessageSubmitted&lt;/code&gt; message with the text
&lt;/li&gt;
&lt;li&gt;Parent widget receives it via &lt;code&gt;on_message_submitted()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Input is cleared for next message&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 4: Create a Simple View (For Testing)
&lt;/h2&gt;

&lt;p&gt;Let's create a basic &lt;code&gt;src/pieces_copilot_tui/chat_view.py&lt;/code&gt; to test our components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/chat_view.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Simple chat view for testing (will be expanded in Part 3).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.screen&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Screen&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.containers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Vertical&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.binding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Binding&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.widgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.chat_panel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatPanel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.chat_input&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessageSubmitted&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SimpleChatView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Simple chat view for testing our components.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;BINDINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctrl+q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SimpleChatView {
        layout: vertical;
    }

    SimpleChatView Vertical {
        width: 100%;
        height: 100%;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compose the view.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Vertical&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatPanel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;

                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatInput&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_input&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Initialize the view.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="c1"&gt;# Show welcome message
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_welcome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message_submitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MessageSubmitted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle user message submission.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="c1"&gt;# Add user message
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Show thinking indicator
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_thinking_indicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# In Part 3, we'll connect this to the streaming handler
&lt;/span&gt;        &lt;span class="c1"&gt;# For now, just simulate a response
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;simulate_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;simulate_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Simulate a response (will be replaced with real streaming in Part 3).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_response&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Echo: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;(Real AI responses coming in Part 3!)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add_response&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Note&lt;/strong&gt;: This is a simplified version for testing. In Part 3, we'll replace &lt;code&gt;simulate_response()&lt;/code&gt; with real streaming from Pieces OS and add the conversations list sidebar.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Create the Main Application
&lt;/h2&gt;

&lt;p&gt;Now let's create &lt;code&gt;src/pieces_copilot_tui/app.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/app.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pieces Copilot TUI - A Terminal User Interface for Pieces OS.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textual.app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.chat_view&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SimpleChatView&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PiecesCopilotTUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pieces Copilot TUI (Basic version for Part 2).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# CSS styles for the entire app
&lt;/span&gt;    &lt;span class="n"&gt;DEFAULT_CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Screen {
        background: $background;
        color: $text;
    }

    .error {
        color: $error;
        text-style: bold;
    }

    .success {
        color: $success;
        text-style: bold;
    }
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nc"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Initialize the application.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pieces Copilot TUI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Initialize Pieces client
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_websocket&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to connect to Pieces OS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# Create and push the simple chat view
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleChatView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push_screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_tui&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run the Pieces Copilot TUI application.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Initialize Pieces client
&lt;/span&gt;    &lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Check if Pieces OS is running
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_pieces_running&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: Pieces OS is not running. Please start Pieces OS first.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PiecesCopilotTUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;run_tui&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We inject &lt;code&gt;PiecesClient&lt;/code&gt; for better testability
&lt;/li&gt;
&lt;li&gt;CSS is used for styling (similar to web CSS!)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;on_mount()&lt;/code&gt; is called when the app starts
&lt;/li&gt;
&lt;li&gt;Error handling for Pieces OS connection&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 6: Create the Package Initialization
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;src/pieces_copilot_tui/__init__.py&lt;/code&gt; to make it a proper Python package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/__init__.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pieces Copilot TUI Package.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;__version__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesCopilotTUI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_tui&lt;/span&gt;

&lt;span class="n"&gt;__all__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PiecesCopilotTUI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_tui&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the relative imports (&lt;code&gt;.chat_message&lt;/code&gt;, &lt;code&gt;.chat_panel&lt;/code&gt;, etc.) to work correctly and exports the main functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Create the Module Entry Point
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;src/pieces_copilot_tui/__main__.py&lt;/code&gt; to make the package executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pieces_copilot_tui/__main__.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Command-line entry point for Pieces Copilot TUI.

This allows the package to be run with: python -m pieces_copilot_tui
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;run_tui&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;run_tui&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the Pythonic way to make a package runnable with &lt;code&gt;python -m package_name&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Run Your Basic TUI
&lt;/h2&gt;

&lt;p&gt;The cleanest way to run the TUI is from the &lt;code&gt;src&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Navigate to src directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;src

&lt;span class="c"&gt;# Run the module&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pieces_copilot_tui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! No complex setup, no PYTHONPATH manipulation, just clean Python!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you should see:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A welcome message in the center
&lt;/li&gt;
&lt;li&gt;An input box at the bottom
&lt;/li&gt;
&lt;li&gt;You can type messages and see them displayed
&lt;/li&gt;
&lt;li&gt;Simulated responses after 1 second
&lt;/li&gt;
&lt;li&gt;Press Ctrl+Q to quit&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;In Part 2, we built the &lt;strong&gt;foundation of our TUI&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Files Created
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;✅ &lt;code&gt;src/pieces_copilot_tui/chat_message.py&lt;/code&gt; - Individual message widget
&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;src/pieces_copilot_tui/chat_panel.py&lt;/code&gt; - Scrollable message display
&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;src/pieces_copilot_tui/chat_input.py&lt;/code&gt; - User input widget
&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;src/pieces_copilot_tui/chat_view.py&lt;/code&gt; - Simple view (basic version)
&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;src/pieces_copilot_tui/app.py&lt;/code&gt; - Main application
&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;src/pieces_copilot_tui/__init__.py&lt;/code&gt; - Package definition
&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;src/pieces_copilot_tui/__main__.py&lt;/code&gt; - Module entry point&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What's Next in Part 3?
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 3: Advanced Features &amp;amp; Integration&lt;/strong&gt;, we'll add:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;📋 &lt;strong&gt;Conversations List&lt;/strong&gt; - Sidebar showing all your chats
&lt;/li&gt;
&lt;li&gt;🌊 &lt;strong&gt;Real Streaming&lt;/strong&gt; - Connect to Pieces OS for actual AI responses
&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Full Orchestration&lt;/strong&gt; - Complete chat_view.py with all features
&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;LTM Support&lt;/strong&gt; - Long-Term Memory toggle
&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Advanced Features&lt;/strong&gt; - Delete, rename, create conversations
&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;Testing &amp;amp; Optimization&lt;/strong&gt; - Make it production-ready&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Preview of Part 3 components:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chats_list.py&lt;/code&gt; - Full conversations management
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;streaming_handler.py&lt;/code&gt; - Real-time streaming from Pieces
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chat_view.py&lt;/code&gt; - Complete orchestrator (expanded version)
&lt;/li&gt;
&lt;li&gt;Testing, debugging, and performance tips&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Textual Not Found
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;textual&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;0.47.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  UI Not Updating
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Make sure you're using self.mount() to add widgets
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Not just appending to a list
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Ready for Part 3?
&lt;/h2&gt;

&lt;p&gt;You now have a working TUI foundation! In Part 3, we'll transform this into a fully-featured Pieces Copilot interface with real AI streaming, conversations management, and all the bells and whistles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continue to &lt;a href="https://dev.to/getpieces/building-a-tui-with-pieces-sdk-part-3-advanced-features-3i0o"&gt;Part 3&lt;/a&gt;&lt;/strong&gt; when you're ready! 🚀  &lt;/p&gt;

</description>
      <category>ui</category>
      <category>softwaredevelopment</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Building a TUI with Pieces SDK</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Mon, 02 Feb 2026 17:57:03 +0000</pubDate>
      <link>https://forem.com/getpieces/building-a-tui-with-pieces-sdk-5eca</link>
      <guid>https://forem.com/getpieces/building-a-tui-with-pieces-sdk-5eca</guid>
      <description>&lt;h2&gt;
  
  
  Building a Pieces Copilot TUI - Part 1: Getting Started with PiecesOS SDK
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This tutorial is part of the &lt;a href="https://github.com/pieces-app/cli-agent" rel="noopener noreferrer"&gt;Pieces CLI&lt;/a&gt;. We welcome contributions! Feel free to open issues, submit PRs, or suggest improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this two-part tutorial series, we'll build a fully functional Terminal User Interface (TUI) for Pieces Copilot from scratch. In Part 1, we'll explore the PiecesOS SDK and learn how to interact with PiecesOS through coding. In Part 2, we'll create a beautiful TUI using &lt;a href="https://textual.textualize.io/" rel="noopener noreferrer"&gt;Textual&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we'll build:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A chat interface with streaming responses
&lt;/li&gt;
&lt;li&gt;Chat management (create, view, delete)
&lt;/li&gt;
&lt;li&gt;Long-Term Memory (LTM) support
&lt;/li&gt;
&lt;li&gt;Real-time UI updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.8+ (check with &lt;code&gt;python --version&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;PiecesOS installed and running (&lt;a href="https://docs.pieces.app/products/meet-pieces" rel="noopener noreferrer"&gt;Download here&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up Your Environment
&lt;/h2&gt;

&lt;p&gt;Open or create a new folder for this project. You can name it whatever you’d like. Inside of the folder, follow the steps below:&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Virtual Environment
&lt;/h3&gt;

&lt;p&gt;First, let's create an isolated Python environment for our project. Open up a command terminal, and enter the appropriate commands one by one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="c"&gt;# Create a virtual environment&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv

&lt;span class="c"&gt;# Activate the virtual environment&lt;/span&gt;
&lt;span class="c"&gt;## On macOS/Linux:&lt;/span&gt;
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate

&lt;span class="c"&gt;## On Windows:&lt;/span&gt;
venv&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\a&lt;/span&gt;ctivate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Still inside of your project folder, create a file called &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# PiecesOS Python SDK for interacting with PiecesOS
pieces-os-client&amp;gt;=3.0.0

# Textual TUI framework for building terminal user interfaces
textual[syntax]&amp;gt;=5.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside of a command terminal, install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Connecting to PiecesOS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Initialize the Pieces Client
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;PiecesClient&lt;/code&gt; is your gateway to PiecesOS. Let's create a simple script to connect. Create a new file called &lt;code&gt;test_connection.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# test_connection.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the client
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Check if PiecesOS is running
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_pieces_running&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Connected to PiecesOS!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Version: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ PiecesOS is not running. Please start it first.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go ahead and test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python test_connection.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening here?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PiecesClient()&lt;/code&gt; automatically discovers your PiecesOS instance port
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;is_pieces_running()&lt;/code&gt; checks if PiecesOS is accessible
&lt;/li&gt;
&lt;li&gt;The client handles port scanning and WebSocket connections&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Working with Chats
&lt;/h2&gt;

&lt;p&gt;Now, let’s start working on creating and getting chats! First we’ll load all of your chats, then create a new chat, and then get that new chat:&lt;/p&gt;

&lt;h3&gt;
  
  
  List All Chats
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add this section to the inside of 'if client.is_pieces_running()', as this all should only run if Pieces can connect.
&lt;/span&gt;
&lt;span class="c1"&gt;# Get all chats
&lt;/span&gt;&lt;span class="n"&gt;chats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; chats:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a New Chat
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Place this right below the code you just added above
&lt;/span&gt;
&lt;span class="c1"&gt;# chats are created automatically when you ask a question
# without setting an active chat
&lt;/span&gt;
&lt;span class="c1"&gt;# Clear current chat to create a new one when we call the stream_question method
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# You can also do this if you want to create one on the spot
# client.copilot.create_chat("My awesome chat")
&lt;/span&gt;

&lt;span class="c1"&gt;# Ask a question - this will create a new chat
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is Python?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Load chat Messages
&lt;/h3&gt;

&lt;p&gt;To make our new chat actually print, we have to print each &lt;code&gt;raw_content&lt;/code&gt; that comes back from PiecesOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add this after 'client.copilot.stream_question("What is Python?")'
&lt;/span&gt;
&lt;span class="c1"&gt;# Get all messages in the chat
&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw_content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Asking Questions with Streaming
&lt;/h2&gt;

&lt;p&gt;One of the most powerful features is streaming responses. Instead of waiting for the entire response, you get chunks as they're generated, like you’d see in ChatGPT.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Streaming Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Callback function for streaming responses.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INITIALIZED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🤔 Thinking...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN-PROGRESS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Get the text chunks
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COMPLETED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;✅ Done!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;❌ Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Register the callback
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask_stream_ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask_stream_ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_message_callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle_stream&lt;/span&gt;

&lt;span class="c1"&gt;# Ask a question
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain Python decorators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;Error Handling Tip&lt;/strong&gt;: In production, we will always handle WebSocket disconnections gracefully. The SDK will attempt to reconnect, but you should inform users about connection status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the Streaming Flow
&lt;/h3&gt;

&lt;p&gt;The streaming response goes through several states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;INITIALIZED&lt;/strong&gt;: Copilot is preparing to respond
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IN-PROGRESS&lt;/strong&gt;: Streaming text chunks
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPLETED&lt;/strong&gt;: Response is complete
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FAILED/STOPPED/CANCELED&lt;/strong&gt;: Something went wrong&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 5: Handling Streaming Properly
&lt;/h2&gt;

&lt;p&gt;Let's create a more robust streaming handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# streaming_handler.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pieces_os_client.wrapper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamingHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handles streaming responses from Pieces Copilot.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_thinking_started&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;on_text_chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;on_completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;on_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pieces_client&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_thinking_started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_thinking_started&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_text_chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_text_chunk&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_completed&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_error&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

        &lt;span class="c1"&gt;# Register callback
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask_stream_ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask_stream_ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_message_callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_handle_stream&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Ask a question and handle streaming.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_thinking_started&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_handle_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Internal stream handler.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IN-PROGRESS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="c1"&gt;# First chunk
&lt;/span&gt;                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
                        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="c1"&gt;# Subsequent chunks
&lt;/span&gt;                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

                        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_text_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COMPLETED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_completed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOPPED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CANCELED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;error_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Handle specific streaming errors
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_current_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;Best Practice&lt;/strong&gt;: Use specific exception types rather than broad &lt;code&gt;except Exception&lt;/code&gt; handlers. This makes debugging easier and prevents masking unexpected errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the StreamingHandler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example usage
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PiecesClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_thinking&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🤔 Thinking...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;✅ Done!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;❌ Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StreamingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pieces_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_thinking_started&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_thinking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_text_chunk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_completed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Ask a question
&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ask_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What are Python generators?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Working with Long-Term Memory (LTM)
&lt;/h2&gt;

&lt;p&gt;LTM allows Pieces to remember context across chats, making responses more personalized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check LTM Status
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Check if LTM system is running - place after ensure_initialization()
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ltm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ LTM system is running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ LTM system is not available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# client.copilot.ltm.enable()
&lt;/span&gt;
    &lt;span class="c1"&gt;# Check if current chat has LTM enabled - place after LTM system check
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ltm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_chat_ltm_enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Chat LTM is enabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Chat LTM is disabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Toggle LTM for Chat
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt; &lt;span class="c1"&gt;# Enable LTM for current chat - place after LTM checks
&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ltm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_enabled&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
     &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ltm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Chat LTM enabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ LTM system must be running first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c1"&gt;# Disable LTM for current chat - place after enable LTM
&lt;/span&gt;    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copilot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat_disable_ltm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Chat LTM disabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 7: Managing chats
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Delete a chat
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Get a chat to delete - place after getting all chats
&lt;/span&gt;&lt;span class="n"&gt;chat_to_delete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Delete a chat - place after getting chat to delete
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deleting: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chat_to_delete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chat_to_delete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rename a chat
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Get a chat to rename - place after getting all chats
&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Rename a chat - place after getting chat
&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My New Chat Name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Renamed to: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common Issues &amp;amp; Troubleshooting
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;### "Connection refused" Error&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;✅ Ensure PiecesOS is running
&lt;/li&gt;
&lt;li&gt;✅ Check if port 39300 is available&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;### "Module not found" Error&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;✅ Activate your virtual environment
&lt;/li&gt;
&lt;li&gt;✅ Reinstall dependencies: &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ Check Python version: &lt;code&gt;python --version&lt;/code&gt; (needs 3.8+)&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;### Streaming Not Working&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;✅ Ensure callback is registered before &lt;code&gt;stream_question()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ Check that PiecesOS is connected and responsive&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;In Part 1, we learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;✅ Setting up a Python virtual environment
&lt;/li&gt;
&lt;li&gt;✅ Installing the PiecesOS SDK
&lt;/li&gt;
&lt;li&gt;✅ Connecting to PiecesOS
&lt;/li&gt;
&lt;li&gt;✅ Working with chats
&lt;/li&gt;
&lt;li&gt;✅ Handling streaming responses
&lt;/li&gt;
&lt;li&gt;✅ Managing Long-Term Memory
&lt;/li&gt;
&lt;li&gt;✅ Creating a robust streaming handler&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we'll use everything we learned to build a beautiful Terminal User Interface (TUI) with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split-pane layout for chats and messages
&lt;/li&gt;
&lt;li&gt;Real-time streaming chat interface
&lt;/li&gt;
&lt;li&gt;Interactive widgets and keyboard shortcuts
&lt;/li&gt;
&lt;li&gt;Proper state management and UI updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Useful Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.pieces.app" rel="noopener noreferrer"&gt;PiecesOS Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pieces-app/pieces-os-client-sdk-for-python" rel="noopener noreferrer"&gt;Python SDK Reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pieces-app/cli-agent" rel="noopener noreferrer"&gt;Pieces CLI on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.gg/getpieces" rel="noopener noreferrer"&gt;Join our Discord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Ready for &lt;a href="https://dev.to/getpieces/building-a-tui-with-pieces-sdk-part-2-ui-components-59l9"&gt;Part 2&lt;/a&gt;?&lt;/strong&gt; Let's build the TUI! 🚀  &lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>howto</category>
      <category>ai</category>
      <category>pieces</category>
    </item>
    <item>
      <title>The Scale Trap: How AI's Biggest Win Became Its Biggest Problem</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Fri, 19 Dec 2025 14:58:03 +0000</pubDate>
      <link>https://forem.com/get_pieces/the-scale-trap-how-ais-biggest-win-became-its-biggest-problem-4jp5</link>
      <guid>https://forem.com/get_pieces/the-scale-trap-how-ais-biggest-win-became-its-biggest-problem-4jp5</guid>
      <description>&lt;p&gt;&lt;strong&gt;What happens when an entire field forgets everything it learned in the rush to chase one breakthrough?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AI community is experiencing collective amnesia. We're so focused on making language models bigger that we've forgotten the diverse research that got us here in the first place. This isn't just about nostalgia - it's about understanding why our current approach is hitting hard limits, and what we need to remember to move forward.&lt;/p&gt;

&lt;p&gt;Let's trace how we got here, what we lost along the way, and where the most interesting work is happening now.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Golden Age Nobody Remembers
&lt;/h2&gt;

&lt;p&gt;After AlexNet won ImageNet in 2012, AI research exploded in every direction. This wasn't just about making networks deeper - it was a multi-front advance across fundamentally different approaches to intelligence.&lt;/p&gt;

&lt;p&gt;The diversity was staggering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NLP foundations&lt;/strong&gt;: Word2Vec gave us semantic embeddings, LSTMs handled sequential data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generative models&lt;/strong&gt;: GANs and VAEs competed with totally different philosophies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategic AI&lt;/strong&gt;: Deep RL conquered Atari, Go (AlphaGo), and StarCraft II&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning efficiency&lt;/strong&gt;: Meta-learning (MAML) and self-supervised learning tackled data scarcity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scientific inquiry&lt;/strong&gt;: XAI, Bayesian methods, adversarial attacks revealed model limitations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjr0yosr9jtju5ldlfgu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjr0yosr9jtju5ldlfgu.png" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was AI's Cambrian Explosion - tons of different species competing, each solving problems in their own way. Then everything changed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bet That Changed Everything
&lt;/h2&gt;

&lt;p&gt;In 2017, "Attention Is All You Need" introduced the Transformer. The architecture itself was clever, but OpenAI saw something bigger: an engine built for industrial-scale computation.&lt;/p&gt;

&lt;p&gt;Their hypothesis was radical: &lt;strong&gt;scale alone could trigger a phase transition from pattern matching to genuine reasoning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The progression was methodical:&lt;/p&gt;

&lt;h3&gt;
  
  
  The GPT Evolution
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GPT-1&lt;/strong&gt; established the recipe: pre-training + fine-tuning&lt;br&gt;
&lt;strong&gt;GPT-2&lt;/strong&gt; showed multitask learning emerging from scale&lt;br&gt;
&lt;strong&gt;GPT-3&lt;/strong&gt; (175B parameters) demonstrated in-context learning that felt like a &lt;a href="https://pieces.app/blog/ai-memory-landscape" rel="noopener noreferrer"&gt;paradigm shift&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the real earthquake was ChatGPT, followed by GPT-4 in 2023. This wasn't a research demo anymore - it was a genuinely useful assistant. The bet had paid off spectacularly.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Success Killed Diversity
&lt;/h2&gt;

&lt;p&gt;GPT-4's success created a gravitational collapse. The entire field got pulled into a single race down the scaling highway. This is where the amnesia began.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Within 2-3 years&lt;/strong&gt;, researchers could build entire careers in LLM research without deep knowledge of alternative architectures or learning frameworks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The "&lt;a href="https://arxiv.org/abs/2001.08361" rel="noopener noreferrer"&gt;Scaling Laws&lt;/a&gt;" paper codified this into engineering: &lt;strong&gt;invest X compute, get predictable Y improvement&lt;/strong&gt;. Innovation shifted from algorithmic creativity to capital accumulation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Incentive Trap
&lt;/h3&gt;

&lt;p&gt;The monoculture became self-reinforcing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PhD students&lt;/strong&gt;: Fastest path to publication is LLM research&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labs&lt;/strong&gt;: Funding follows the hype&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Companies&lt;/strong&gt;: Existential race for market dominance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Exploring alternative approaches became career suicide&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What gets celebrated now? Clever workarounds for LLM limitations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://pieces.app/blog/llm-prompt-engineering" rel="noopener noreferrer"&gt;Prompt Engineering&lt;/a&gt;&lt;/strong&gt;: Crafting inputs for opaque models&lt;br&gt;
&lt;strong&gt;&lt;a href="https://pieces.app/blog/spencer-gallardo-ai-summit" rel="noopener noreferrer"&gt;RAG&lt;/a&gt;&lt;/strong&gt;: Patching hallucination and knowledge gaps&lt;br&gt;
&lt;strong&gt;PEFT (LoRA)&lt;/strong&gt;: Making massive models slightly more adaptable&lt;/p&gt;

&lt;p&gt;These are valuable techniques, but they're all downstream fixes. We're accepting the scaled Transformer as gospel instead of questioning the foundation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qw57ve97ig6ynpnjrcz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qw57ve97ig6ynpnjrcz.png" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Technical Debt Comes Due
&lt;/h2&gt;

&lt;p&gt;Just as the monoculture peaked, its fundamental limitations became impossible to ignore. More scale can't solve these problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: The Quadratic Wall
&lt;/h3&gt;

&lt;p&gt;The Transformer's self-attention scales quadratically with sequence length. This creates a hard limit on context windows - analyzing a full codebase, book, or video becomes prohibitively expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The revival&lt;/strong&gt;: Architectures like &lt;strong&gt;Mamba&lt;/strong&gt; and &lt;strong&gt;RWKV&lt;/strong&gt; achieve linear-time scaling by bringing back recurrent principles. They prove attention isn't all you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2: Running Out of Internet
&lt;/h3&gt;

&lt;p&gt;The scaling hypothesis assumed infinite high-quality data. We're hitting the limits:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Data exhaustion&lt;/strong&gt;: The supply of quality text is finite&lt;br&gt;
&lt;strong&gt;Model collapse&lt;/strong&gt;: Training on AI-generated content degrades performance&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The counter-move&lt;/strong&gt;: &lt;a href="https://pieces.app/blog/phi-3-mini-integrations" rel="noopener noreferrer"&gt;Microsoft's Phi series&lt;/a&gt; flips the script. By training &lt;a href="https://pieces.app/blog/small-language-models-outshine-large-language-models-enterprise-users" rel="noopener noreferrer"&gt;smaller models&lt;/a&gt; on curated, "textbook-quality" data, they match models 25x their size. Quality beats quantity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: Centralization
&lt;/h3&gt;

&lt;p&gt;A few labs control the frontier. This sparked a grassroots response: the &lt;a href="https://pieces.app/blog/the-importance-of-on-device-ai-for-developer-productivity" rel="noopener noreferrer"&gt;Local AI movement&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enabled by open models (Meta's Llama) and efficient inference (VLLM), developers are running powerful models on consumer hardware. This creates evolutionary pressure for efficiency - models need to be &lt;a href="https://pieces.app/blog/nano-models" rel="noopener noreferrer"&gt;small and fast&lt;/a&gt;, not just powerful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Path Forward
&lt;/h2&gt;

&lt;p&gt;The scale era unlocked real capabilities. LLMs are genuinely useful tools. But the amnesia it created - the narrowing of our field's intellectual horizons - is holding us back.&lt;/p&gt;

&lt;p&gt;The most interesting work now is happening at the intersection of old and new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architectural diversity&lt;/strong&gt;: Linear-time alternatives to attention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data science&lt;/strong&gt;: Quality curation over quantity scraping
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficiency research&lt;/strong&gt;: Models that run locally, not just in datacenters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid approaches&lt;/strong&gt;: Combining LLMs with symbolic reasoning, retrieval, and other paradigms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're not abandoning the lessons of scale. We're rediscovering that the forgotten paths - architectural diversity, data-centric training, algorithmic efficiency - are essential for the next phase.&lt;/p&gt;

&lt;p&gt;The future won't be a simple extrapolation of scaling laws. It'll be a new synthesis: the raw power discovered through scale, combined with the diversity and ingenuity that defined AI's golden age.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq9qduojr2lqdr3e1oxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq9qduojr2lqdr3e1oxw.png" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;What's your take? Are you working on alternatives to the scaling paradigm? Have you hit these limitations in production? Drop your experiences in the comments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt;: #ai #machinelearning #llm #architecture&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Daily Productivity App with Pieces - Part 2: Adding AI Intelligence with Gemini</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Wed, 17 Dec 2025 15:39:12 +0000</pubDate>
      <link>https://forem.com/get_pieces/building-a-daily-productivity-app-with-pieces-part-2-adding-ai-intelligence-with-gemini-2nc4</link>
      <guid>https://forem.com/get_pieces/building-a-daily-productivity-app-with-pieces-part-2-adding-ai-intelligence-with-gemini-2nc4</guid>
      <description>&lt;p&gt;Welcome back! In &lt;a href="https://pieces.app/blog/building-daily-standup-generator-with-pieces-api-sdk" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;, we built a complete PiecesOS service that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connects to PiecesOS and maintains a WebSocket connection&lt;/li&gt;
&lt;li&gt;Fetches and caches workstream summaries grouped by day&lt;/li&gt;
&lt;li&gt;Extracts the summary content from annotations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we have all of the infrastructure in place. But here's the thing: having raw summaries with their content is cool, but it's not exactly... useful. I mean, I have all this text about what I did, but what did I actually accomplish today?&lt;/p&gt;

&lt;p&gt;That's where Part 2 comes in. We're going to use Google's Gemini AI to transform those raw summaries into actual insights. Think:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What did I work on?&lt;/li&gt;
&lt;li&gt;Which projects did I touch?&lt;/li&gt;
&lt;li&gt;Who did I collaborate with?&lt;/li&gt;
&lt;li&gt;What should I remember for tomorrow?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Thanks to Part 1, we now have access to rich summary data using &lt;code&gt;SummaryWithContent:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/models/daily_recap_models.dart

// Don't forget the imports!
import 'dart:convert';
import 'package:google_generative_ai/google_generative_ai.dart';
import '../models/daily_recap_models.dart';

SummaryWithContent {
  id: "4f302bfd-f3c2-4f85-aa79-e7cb314e111d",
  title: "Implemented WebSocket sync",
  content: "# Project XYZ\nFixed critical authentication bug in OAuth token refresh.
            Implemented real-time WebSocket synchronization with automatic 
            reconnection. Pair programmed with Bob on the WebSocket integration...",
  timestamp: 2025-11-04 14:32:15
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is great! But it's still just raw text. What we want is structured and actionable insights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUMMARY:
   Successfully fixed critical authentication bug and implemented 
   real-time WebSocket synchronization for better data flow.

PROJECTS:
   ✅ Authentication Service [completed]
      Fixed OAuth token refresh logic

   🔄 WebSocket Integration [in_progress]
      Implemented real-time sync with automatic reconnection

PEOPLE WORKED WITH:
   • Alice (code review)
   • Bob (pair programming)

REMINDERS:
   ⚠️  Test WebSocket with production load
   ⚠️  Update documentation for new auth flow
NOTES:
  - Send a message in Google Chat about the progress!

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the difference? One is data, the other is &lt;strong&gt;information.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Gemini
&lt;/h2&gt;

&lt;p&gt;Google's Gemini API is perfect for this. It can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand natural language&lt;/li&gt;
&lt;li&gt;Extract structured information&lt;/li&gt;
&lt;li&gt;Return JSON (which is exactly what we need!)&lt;/li&gt;
&lt;li&gt;Process multiple summaries at once&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up
&lt;/h2&gt;

&lt;p&gt;First, add the Gemini SDK to &lt;code&gt;pubspec.yaml&lt;/code&gt; below the &lt;code&gt;‘git:’&lt;/code&gt;dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies:
  google_generative_ai: ^0.4.6

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also need an API key. Get one from &lt;a href="https://makersuite.google.com/app/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt; – it's free for reasonable usage!&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Daily Recap Service
&lt;/h2&gt;

&lt;p&gt;Let's create a new service: &lt;code&gt;lib/services/daily_recap_service.dart.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class DailyRecapService {
  final GenerativeModel _model;

  DailyRecapService({required String apiKey})
      : _model = GenerativeModel(
          model: 'gemini-2.5-flash-lite',  // Fast, efficient, and cost-effective!
          apiKey: apiKey,
          generationConfig: GenerationConfig(
            temperature: 0.7,  // Balanced creativity
            topK: 40,
            topP: 0.95,
            maxOutputTokens: 2048,
            responseMimeType: 'application/json',
          ),
        );

  Future&amp;lt;DailyRecapData&amp;gt; generateDailyRecap({
    required DateTime date,
    required List&amp;lt;SummaryWithContent&amp;gt; summaries,
  }) async {
    // We'll build this step by step!
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-** gemini-2.5-flash-lite:** faster and more cost-effective&lt;br&gt;
-** temperature: 0.7: **Not too creative, not too rigid&lt;/p&gt;

&lt;p&gt;Now, let's build the &lt;code&gt;generateDailyRecap&lt;/code&gt; function piece by piece.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Prompt Engineering
&lt;/h2&gt;

&lt;p&gt;Crafting the right prompt is &lt;strong&gt;an art.&lt;/strong&gt; Here are some important tips:&lt;/p&gt;
&lt;h2&gt;
  
  
  Avoid Vague prompts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Analyze these summaries and tell me what I did today.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Result: Always try to be specific. AI does not read your mind… yet! (Pieces does read your mind, but whatever 😉)&lt;/p&gt;
&lt;h2&gt;
  
  
  Better Structure
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Return JSON with: summary, projects, people.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Result: Always say what do you expect the AI to return to be able to correctly parse it&lt;/p&gt;
&lt;h2&gt;
  
  
  Show Some Examples
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract and organize into this EXACT JSON format:
{
  "summary": "Brief 1-2 sentence overview",
  "people": ["Person1", "Person2"],
  "projects": [
    {
      "name": "Project Name",
      "description": "What was done",
      "status": "in_progress"  // or "completed" or "not_started"
    }
  ],
  "reminders": ["Reminder 1"],
  "notes": ["Important insight"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; AI is similar to human best way to understand by examples&lt;/p&gt;
&lt;h2&gt;
  
  
  Our Beautiful Prompt
&lt;/h2&gt;

&lt;p&gt;Here's the final prompt we’ll use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/services/daily_recap_service.dart

  // Add below DailyRecapService() and above Future&amp;lt;&amp;gt;


  /// Build the prompt for Gemini
  String _buildPrompt(DateTime date, String context) {
    final dateStr =
        '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';

    return '''You are an AI assistant analyzing a developer's workstream summaries for the day: $dateStr.

Based on the following workstream summaries, extract and organize the information into a structured daily recap.

WORKSTREAM SUMMARIES:
$context

Your task is to analyze these summaries and create a comprehensive daily recap with the following information:

1. **summary** (string, 1-2 sentences): A brief overview of what was accomplished today. Focus on the main achievements and work done.

2. **people** (array of strings): List of people mentioned or collaborated with. Look for names, @mentions, or collaboration indicators. Can be empty if no one is mentioned.

3. **projects** (array of objects): Projects worked on today. Each project should have:
   - **name** (string): Project or feature name
   - **description** (string): Brief description of what was done
   - **status** (string): One of: "completed", "in_progress", or "not_started"

4. **reminders** (array of strings): Action items, TODOs, or things to remember for later. Look for phrases like "need to", "should", "TODO", "remember to", etc. Can be empty.

5. **notes** (array of strings): Important observations, learnings, or technical notes from the day. Look for insights, discoveries, or important information. Can be empty.

IMPORTANT GUIDELINES:
- Be concise but informative
- Extract actual information from the summaries, don't make things up
- If a category has no relevant information, use an empty array [] or empty string ""
- For project status: use "completed" if the work is done, "in_progress" if actively working on it, "not_started" if mentioned but not begun
- People names should be just the name (e.g., "Alice", "Bob")
- Keep descriptions clear and specific

Return ONLY valid JSON in this exact format:
{
  "summary": "Brief 1-2 sentence overview",
  "people": ["Person1", "Person2"],
  "projects": [
    {
      "name": "Project Name",
      "description": "What was done",
      "status": "in_progress"
    }
  ],
  "reminders": ["Reminder 1", "Reminder 2"],
  "notes": ["Note 1", "Note 2"]
}
''';
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this works:&lt;/p&gt;

&lt;p&gt;1.&lt;strong&gt;Clear role:&lt;/strong&gt; "You are an AI assistant..."&lt;br&gt;
2.&lt;strong&gt;Specific format:&lt;/strong&gt; Exact JSON structure&lt;br&gt;
3*&lt;em&gt;. Examples:&lt;/em&gt;* Shows what we want&lt;br&gt;
4.** Constraints:** "Don't make things up", "Empty arrays if no data"&lt;br&gt;
5.** Enum values:** Explicit status options&lt;/p&gt;

&lt;p&gt;These two helper methods,&lt;code&gt;_buildPrompt and _buildSummariesContext&lt;/code&gt;(we'll see next), are what power our analysis. But where do they fit in the actual application?&lt;/p&gt;
&lt;h2&gt;
  
  
  Sending Rich Context to Gemini
&lt;/h2&gt;

&lt;p&gt;Now that we have the actual summary content, we can build a rich prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; // lib/services/daily_recap_service.dart

 // Add below _buildPrompt() and above generateDailyRecap()

 String _buildSummariesContext(List&amp;lt;SummaryWithContent&amp;gt; summaries) {
    final buffer = StringBuffer();

    for (int i = 0; i &amp;lt; summaries.length; i++) {
      final summary = summaries[i];
      buffer.writeln('Summary ${i + 1}:');
      buffer.writeln('  ID: ${summary.id}');
      buffer.writeln('  Title: ${summary.title}');
      buffer.writeln(
        '  Time: ${summary.timestamp.hour.toString().padLeft(2, '0')}:${summary.timestamp.minute.toString().padLeft(2, '0')}',
      );
      buffer.writeln('  Content: ${summary.content}');
      buffer.writeln();
    }

    return buffer.toString();
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This formats each summary with structured labels and metadata, giving Gemini way more context to work with&lt;/p&gt;

&lt;p&gt;Let’s add an empty factory method to create an empty &lt;code&gt;DailyRecapData&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/services/daily_recap_service.dart

// Add below _buildPrompt() and above generateDailyRecap() 
 factory DailyRecapData.empty(DateTime date) {
    return DailyRecapData(
      date: date,
      summary: '',
      people: [],
      projects: [],
      reminders: [],
      notes: [],
    );
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling the Response
&lt;/h2&gt;

&lt;p&gt;Gemini returns JSON (because we set &lt;code&gt;responseMimeType)&lt;/code&gt;, so parsing is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/services/daily_recap_service.dart

// Replace the comment in generateDailyRecap() with this:

try {
  final response = await _model.generateContent([Content.text(prompt)]);
  print("Gemini response received. ${response.text}");  // Debug output
  final rawText = response.text ?? '{}';

  // Extract JSON from markdown code blocks
  final jsonText = _extractJsonFromMarkdown(rawText);
  final data = jsonDecode(jsonText) as Map&amp;lt;String, dynamic&amp;gt;;

  return DailyRecapData.fromJson(date, data); // ... generate recap
} catch (e) {
  print('Error generating recap: $e');
  return DailyRecapData.empty(date);  // Safe fallback
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting It All Together: The Complete generateDailyRecap Function
&lt;/h2&gt;

&lt;p&gt;Now that we've seen all the pieces, here's how they fit together in the actual &lt;code&gt;generateDailyRecap function&lt;/code&gt; (how yours should look 😉):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;DailyRecapData&amp;gt; generateDailyRecap({
  required DateTime date,
  required List&amp;lt;SummaryWithContent&amp;gt; summaries,
}) async {
  // Step 1: Handle edge case - no summaries
  if (summaries.isEmpty) {
    return DailyRecapData.empty(date);
  }

  // Step 2: Build context from summaries using our helper method
  final context = _buildSummariesContext(summaries);

  // Step 3: Craft the prompt using our prompt builder
  final prompt = _buildPrompt(date, context);

  try {
    // Step 4: Send to Gemini and get response
    final response = await _model.generateContent([Content.text(prompt)]);
    print("Gemini response received. ${response.text}");

    // Step 5: Parse the JSON response
    final jsonText = response.text ?? '{}';
    final data = jsonDecode(jsonText) as Map&amp;lt;String, dynamic&amp;gt;;

    // Step 6: Convert to our data model and return
    return DailyRecapData.fromJson(date, data);
  } catch (e) {
    print('Error generating daily recap: $e');
    rethrow; // Let the UI handle the error
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See how it flows?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check for empty summaries&lt;/li&gt;
&lt;li&gt;Build the context string from all summaries&lt;/li&gt;
&lt;li&gt;Create the prompt with instructions&lt;/li&gt;
&lt;li&gt;Send to Gemini&lt;/li&gt;
&lt;li&gt;Parse the JSON response&lt;/li&gt;
&lt;li&gt;Return structured data (or throw error)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Data Models
&lt;/h2&gt;

&lt;p&gt;I created clean data classes to work with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/models/daily_recap_models.dart

// Add before SummaryWithContent class and after ProjectStatus {}

class ProjectData {
  final String name;
  final String description;
  final ProjectStatus status;  // enum: completed, inProgress, notStarted

  ProjectData({
    required this.name,
    required this.description,
    required this.status,
  });

  factory ProjectData.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    return ProjectData(
      name: json['name'] as String,
      description: json['description'] as String,
      status: _statusFromString(json['status'] as String),
    );
  }

  static ProjectStatus _statusFromString(String status) {
    switch (status) {
      case 'completed':
        return ProjectStatus.completed;
      case 'in_progress':
        return ProjectStatus.inProgress;
      case 'not_started':
        return ProjectStatus.notStarted;
      default:
        return ProjectStatus.notStarted;
    }
  }
}

class DailyRecapData {
  final DateTime date;
  final String summary;
  final List&amp;lt;String&amp;gt; people;
  final List&amp;lt;ProjectData&amp;gt; projects;
  final List&amp;lt;String&amp;gt; reminders;
  final List&amp;lt;String&amp;gt; notes;

  DailyRecapData({
    required this.date,
    required this.summary,
    required this.people,
    required this.projects,
    required this.reminders,
    required this.notes,
  });

  factory DailyRecapData.fromJson(DateTime date, Map&amp;lt;String, dynamic&amp;gt; json) {
    return DailyRecapData(
      date: date,
      summary: json['summary'] as String? ?? '',
      people: (json['people'] as List&amp;lt;dynamic&amp;gt;?)
              ?.map((e) =&amp;gt; e as String)
              .toList() ??
          [],
      projects: (json['projects'] as List&amp;lt;dynamic&amp;gt;?)
              ?.map((e) =&amp;gt; ProjectData.fromJson(e as Map&amp;lt;String, dynamic&amp;gt;))
              .toList() ??
          [],
      reminders: (json['reminders'] as List&amp;lt;dynamic&amp;gt;?)
              ?.map((e) =&amp;gt; e as String)
              .toList() ??
          [],
      notes: (json['notes'] as List&amp;lt;dynamic&amp;gt;?)
              ?.map((e) =&amp;gt; e as String)
              .toList() ??
          [],
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us type safety and makes it easy to work with the data later.&lt;/p&gt;

&lt;p&gt;Look at what Gemini did:&lt;/p&gt;

&lt;p&gt;✅ Understood that I was working on "Pieces OS Integration"&lt;br&gt;
✅ Correctly identified it as "completed"&lt;br&gt;
✅ Extracted actual reminders from my work&lt;br&gt;
✅ Pulled out technical notes I discovered&lt;br&gt;
✅ Wrote a coherent summary of the day&lt;/p&gt;

&lt;p&gt;And it did all this from just timestamps and titles!&lt;/p&gt;
&lt;h2&gt;
  
  
  Real-World Example: What Gemini Generated
&lt;/h2&gt;

&lt;p&gt;Here's an actual JSON response that Gemini generated from my workstream summaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "summary": "Today's work focused on enhancing user experience for video content, developing a tag generator, testing Flutter capabilities, and reviewing code and infrastructure. Significant progress was made on persona generation for AI training data.",
  "people": [],
  "projects": [
    {
      "name": "Video Analytics &amp;amp; User Experience",
      "description": "Analyzed YouTube video analytics for 'Pieces' content and discussed strategies for improving new user experience by leveraging context and memory,
 considering phased UI exposure and temporary access keys.",
      "status": "in_progress"
    },
    {
      "name": "Tag Generator",
      "description": "Generated a Python script for thematic tagging.",
      "status": "completed"
    },
    {
      "name": "Flutter macOS Dynamic Library Loading",
      "description": "Demonstrated Flutter's macOS dynamic library loading and confirmed clipboard monitoring functionality and its integration with long-term memory.",
      "status": "in_progress"
    },
    {
      "name": "AI Persona Generation",
      "description": "Discussed UI for AI persona generation and initiated content compilation for a partner. Presented the 'persona-query-tag-dataset-gen' project, det
ailing the creation of realistic user personas for the Pieces AI assistant, showcasing comprehensive attributes and providing an example of 'Anja Vestergaard'.",
      "status": "in_progress"
    },
    {
      "name": "ML Training &amp;amp; Django API",
      "description": "Addressed an `UnboundLocalError` in ML training and validated nested task management for a Django API.",
      "status": "completed"
    },
    {
      "name": "Infrastructure Upgrades &amp;amp; Containerization",
      "description": "Reviewed infrastructure upgrades, containerization strategies, cost optimizations, and bug fixes across multiple services, including timezone and 
user invitation flows.",
      "status": "in_progress"
    }
  ],
  "reminders": [],
  "notes": [
    "Leveraging context and memory for new user experience improvements.",
    "Clipboard monitoring functionality confirmed and integrated with long-term memory.",
    "Objective for persona generation project is to generate authentic training data for the AI assistant."
  ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Complete Reference Implementation
&lt;/h2&gt;

&lt;p&gt;For your reference, here's the complete &lt;code&gt;lib/services/daily_recap_service.dart file&lt;/code&gt; with everything we've built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The service initialization with Gemini configuration&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;generateDailyRecap function&lt;/code&gt; that orchestrates everything&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;_buildSummariesContext&lt;/code&gt; helper for formatting summaries&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;_buildPrompt&lt;/code&gt; helper for crafting the AI prompt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Continue reading &lt;a href="https://pieces.app/blog/building-pieces-productivity-app-with-gemini-ai" rel="noopener noreferrer"&gt;here. &lt;/a&gt;&lt;/p&gt;

</description>
      <category>gemini</category>
      <category>tutorial</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building Daily Stand-Up Generator using Pieces API - Part 1: The SDK overview</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Wed, 17 Dec 2025 15:22:08 +0000</pubDate>
      <link>https://forem.com/get_pieces/building-daily-stand-up-generator-using-pieces-api-part-1-the-sdk-overview-83k</link>
      <guid>https://forem.com/get_pieces/building-daily-stand-up-generator-using-pieces-api-part-1-the-sdk-overview-83k</guid>
      <description>&lt;p&gt;&lt;em&gt;- written by Bishoy Hany&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Monday Morning Problem
&lt;/h2&gt;

&lt;p&gt;Imagine the following scenario: Monday, 9 AM. You grab your coffee, join the stand-up call, and suddenly realise: you have absolutely no idea what you did last week. &lt;br&gt;
The weekend wiped your mental cache clean. Was it the React component refactor? Or was that two weeks ago? You vaguely remember fighting with TypeScript errors on Thursday, but what was the actual solution? Your Git commits say 'fix: update logic'—thanks, past you, very helpful.&lt;/p&gt;

&lt;p&gt;Here's the thing: your brain isn't built to be a perfect activity log. But your computer? It remembers everything. That's where &lt;a href="https://pieces.app/" rel="noopener noreferrer"&gt;PiecesOS&lt;/a&gt; comes in.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;Create an app that tracks what work is done every day. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom8jvsf6yf5d8hvulncd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom8jvsf6yf5d8hvulncd.png" alt=" " width="512" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Part 1, we will work on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.pieces.app/products/meet-pieces" rel="noopener noreferrer"&gt;Connecting to PiecesOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Getting our workstream summaries&lt;/li&gt;
&lt;li&gt;Grouping the summaries by day&lt;/li&gt;
&lt;li&gt;Keeping everything updated in real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll hit a UI later on. Let’s take this one step at a time!&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Dart SDK version 3.8.1 or higher (flutter.dev)&lt;br&gt;
Gemini API key (will mention that in &lt;a href="https://pieces.app/blog/building-pieces-productivity-app-with-gemini-ai" rel="noopener noreferrer"&gt;part 2&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting Up
&lt;/h2&gt;

&lt;p&gt;Let’s begin by creating a new flutter app&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter create daily_recap_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before you can start using the Pieces SDK, you’ll need to add it to your Dart or Flutter project. Open your project’s pubspec.yaml file and add the following under &lt;code&gt;‘dependencies:’:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies:
  # The Pieces SDK
  pieces_os_client:
    git:
      url: https://github.com/pieces-app/pieces-os-client-sdk-for-dart.git

  web_socket_channel: ^3.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK is automatically generated from our API setup, so it’s mostly made up of code that helps your app talk to Pieces’ api.&lt;br&gt;
We also added the web_socket_channel dependency to connect to the Pieces web-socket&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Connecting to PiecesOS
&lt;/h2&gt;

&lt;p&gt;Alright, first challenge – how do we even talk to PiecesOS?&lt;/p&gt;

&lt;p&gt;Let's first create a new file &lt;code&gt;lib/services/pieces_os_service.dart&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:pieces_os_client/api.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

class PiecesOSService {
  // Super important Pieces sometimes could run on other ports so you should implement your own port-finding algo
  // here is example using the python sdk
  // https://github.com/pieces-app/pieces-os-client-sdk-for-python/blob/db1fa83f21407323b98d21507a59d1e4f176d064/src/pieces_os_client/wrapper/client.py#L139-L168
  // We gonna use 39300 for simplicity and PiecesOS will be running on port 39300 for most of the time as well
  static const String baseUrl = 'http://localhost:39300';
  static const String websocketUrl = 'ws://localhost:39300';

  // Used to register application
  late final ConnectorApi _connectorApi;
  // Used to retrieve the workstream summaries
  late final WorkstreamSummaryApi _workstreamSummaryApi;

  // Used to retrieve the summary content
  late final AnnotationApi _annotationApi;

  // Cache for workstream summaries grouped by day
  final Map&amp;lt;DateTime, List&amp;lt;WorkstreamSummary&amp;gt;&amp;gt; _summariesByDay = {};

  PiecesOSService() {
    final client = ApiClient(basePath: baseUrl);
    _connectorApi = ConnectorApi(client);
    _workstreamSummaryApi = WorkstreamSummaryApi(client);
    _annotationApi = AnnotationApi(client);
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy – just setting up our API clients.&lt;/p&gt;

&lt;p&gt;Now the fun part – actually connecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; // existing code from above
  Future&amp;lt;Application&amp;gt; connectApplication() async {
    final seededApp = SeededTrackedApplication(
      name: ApplicationNameEnum.OPEN_SOURCE,  // Hey Pieces, I'm open source!
      platform: Platform.operatingSystem == "macos"
          ? PlatformEnum.MACOS
          : PlatformEnum.WINDOWS,
      version: "0.0.1",
    );

    final connection = SeededConnectorConnection(
      application: seededApp,
    );

    _context = await _connectorApi.connect(
      seededConnectorConnection: connection,
    );

    print('Successfully connected to Pieces OS!');
    return _context!.application;
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, we're saying, "Hey Pieces, I'm a new app, let me in!" and it gives us back a context with our application info.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: The WebSocket
&lt;/h2&gt;

&lt;p&gt;This websocket specifically sends us all of the IDs for the users' workstream summaries in Pieces upon first connection. After, it sends only the IDs of either newly created summaries, summaries that have updated data, or summaries that were deleted.&lt;/p&gt;

&lt;p&gt;Here's the setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // existing code from above
  void _startWebSocketListener() {
    final wsUrl = '$websocketUrl/workstream_summaries/stream/identifiers';
    _wsChannel = WebSocketChannel.connect(Uri.parse(wsUrl));

    print('WebSocket connected to $wsUrl');

    _wsSubscription = _wsChannel!.stream.listen(
      (message) async {
        try {
          // Important: WebSocket sends JSON strings, decode them first!
          final streamedIdentifiers = StreamedIdentifiers.fromJson(
            jsonDecode(message),
          );

          // Loop through each identifier we received
          for (final id in streamedIdentifiers!.iterable) {
            final summaryId = id.workstreamSummary?.id;
            if (summaryId != null) {
              await _fetchAndCacheSummary(summaryId);
            }
          }
        } catch (e) {
          print('Error processing message: $e');
        }
      },
      onDone: () {
        print('WebSocket closed, reconnecting...');
        _reconnectWebSocket();
      },
      onError: (error) {
        print('WebSocket error: $error');
        _reconnectWebSocket();
      },
    );
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the beauty of this approach: The WebSocket just keeps until their app shuts down or they manually close it, sending us identifiers. When we first connect, it dumps ALL existing identifiers on us (could be from a year ago!) and stays open to send us new ones as they're created in real-time. One connection handles everything!&lt;br&gt;
Step 3: Fetching and Caching Summaries&lt;br&gt;
Okay, so we have identifiers. Now what? We need to actually fetch the summary data and organize it.&lt;/p&gt;

&lt;p&gt;Here's what a WorkstreamSummary looks like (the important parts anyway):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This is a model defined in the sdks don't add any thing to the code yet.

class WorkstreamSummary {
  String id;              // Unique identifier
  String? name;           // Optional name/title
  GroupedTimestamp created;  // When it was created
  GroupedTimestamp? updated; // When it was last updated
  // ... and a bunch of other fields
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;GroupedTimestamp&lt;/code&gt; is basically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This is a model defined in the sdks don't add any thing to the code yet.

class GroupedTimestamp {
  DateTime value;  // The actual timestamp
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when we fetch a summary, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the created date&lt;/li&gt;
&lt;li&gt;Strip the time from the date&lt;/li&gt;
&lt;li&gt;Add the summary to that day's list&lt;/li&gt;
&lt;li&gt;Sort everything by time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; // existing code from above
  Future&amp;lt;void&amp;gt; _fetchAndCacheSummary(String identifier) async {
    final summary = await _workstreamSummaryApi
        .workstreamSummariesSpecificWorkstreamSummarySnapshot(identifier);

    if (summary == null) {
      return;
    }
    // Normalize to day (remove time)
    final createdDate = summary.created.value;
    final dayKey = DateTime(
      createdDate.year,
      createdDate.month,
      createdDate.day,
    );

    // Create day entry if it doesn't exist
    _summariesByDay.putIfAbsent(dayKey, () =&amp;gt; []);

    // Check for duplicates (avoid adding the same summary twice)
    final existingIndex = _summariesByDay[dayKey]!
        .indexWhere((s) =&amp;gt; s.id == summary.id);

    if (existingIndex != -1) {
      // Update existing
      _summariesByDay[dayKey]![existingIndex] = summary;
    } else {
      // Add new
      _summariesByDay[dayKey]!.add(summary);
    }

    // Sort by time (most recent first)
    _summariesByDay[dayKey]!.sort((a, b) {
      return b.created.value.compareTo(a.created.value);
    });

    // Notify anyone listening
    _summariesStreamController.add(Map.unmodifiable(_summariesByDay));

    print('Cached summary ${summary.id} for day $dayKey');
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_summariesByDay&lt;/code&gt; is just a&lt;code&gt;Map&amp;lt;DateTime, List&amp;lt;WorkstreamSummary&amp;gt;&amp;gt;&lt;/code&gt; where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key: A DateTime set to midnight (e.g., Nov 4, 2025 at 00:00:00)&lt;/li&gt;
&lt;li&gt;Value: List of all summaries for that day, sorted by time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's also add some useful methods to retrieve a summary&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; // existing code
  /// Get summaries for a specific day from cache
  List&amp;lt;WorkstreamSummary&amp;gt; getSummariesForDay(DateTime date) {
    final dayKey = DateTime(date.year, date.month, date.day);
    return _summariesByDay[dayKey] ?? [];
  }

  /// Get all days that have summaries (sorted most recent first)
  List&amp;lt;DateTime&amp;gt; getDaysWithSummaries() {
    final days = _summariesByDay.keys.toList();
    days.sort((a, b) =&amp;gt; b.compareTo(a)); // Most recent first
    return days;
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4: Keeping Everything in Sync&lt;br&gt;
Speaking of streams – let's add a broadcast stream that others can listen to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// existing code
final StreamController&amp;lt;Map&amp;lt;DateTime, List&amp;lt;WorkstreamSummary&amp;gt;&amp;gt;&amp;gt;
    _summariesStreamController = StreamController.broadcast();

Stream&amp;lt;Map&amp;lt;DateTime, List&amp;lt;WorkstreamSummary&amp;gt;&amp;gt;&amp;gt; get summariesStream =&amp;gt;
    _summariesStreamController.stream;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So in the UI (when we build it), we can just do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service.summariesStream.listen((summaries) {
  print('Got new data!');
  // Update UI here
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4.5: Waiting for Initial WebSocket Sync
&lt;/h2&gt;

&lt;p&gt;Here's a problem: when the app first starts, the WebSocket dumps all existing summaries on us. This could be hundreds of identifiers! If we try to show the UI or generate a recap before they're all loaded, we'll have incomplete data.&lt;/p&gt;

&lt;p&gt;But we only need to wait once – on the first message. After that, the WebSocket just sends new updates in real-time and we don't want to block.&lt;/p&gt;

&lt;p&gt;Here's the solution using a &lt;code&gt;Completer:&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Add Fields and Wait Method
&lt;/h2&gt;

&lt;p&gt;Place this code block right after the PiecesOSService() constructor and before the connectApplication() method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Add these fields to the class
Completer&amp;lt;void&amp;gt;? _initialSyncCompleter;
bool get _isInitialSyncComplete =&amp;gt;
      _initialSyncCompleter?.isCompleted ?? false;
/// Wait for the initial WebSocket sync to complete
/// Only blocks on the first call, returns immediately after
Future&amp;lt;void&amp;gt; waitForInitialSync() async {
  // If already synced, return immediately
  if (_isInitialSyncComplete) {
    return;
  }

  // If sync is in progress, wait for it
  if (_initialSyncCompleter != null) {
    return _initialSyncCompleter!.future;
  }

  // Start waiting for first sync
  _initialSyncCompleter = Completer&amp;lt;void&amp;gt;();

  return _initialSyncCompleter!.future;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Update WebSocket Listener
&lt;/h2&gt;

&lt;p&gt;Replace &lt;code&gt;_wsSubscription&lt;/code&gt; with this new section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_wsSubscription = _wsChannel!.stream.listen(
  (message) async {
    try {
      final streamedIdentifiers = StreamedIdentifiers.fromJson(
        jsonDecode(message),
      );

      // Process all identifiers
      for (final id in streamedIdentifiers!.iterable) {
        final summaryId = id.workstreamSummary?.id;
        if (summaryId != null) {
          await _fetchAndCacheSummary(summaryId);
        }
      }

      // Mark initial sync as complete after first message
      if (!_isInitialSyncComplete) {
        _isInitialSyncComplete = true;
        _initialSyncCompleter?.complete();
        print('Initial WebSocket sync complete!');
      }
    } catch (e) {
      print('Error processing message: $e');
    }
  },
  // ... onDone, onError handlers
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Use in App Initialization
&lt;/h2&gt;

&lt;p&gt;Place this code in your app's entry point (typically &lt;code&gt;lib/main.dart,&lt;/code&gt; create the file if you don't have it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'services/pieces_os_service.dart';

Future&amp;lt;void&amp;gt; main() async {
  final service = PiecesOSService();

  await service.connectApplication();
  service.startWebSocketListener();

  // Wait for all existing summaries to load
  await service.waitForInitialSync();

  // Now we can safely generate recaps or show UI!
  print('Ready to go! All summaries loaded.');
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Accessing the Actual Summary Content
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;WorkstreamSummary objects&lt;/code&gt; from the SDK don't directly expose the summary text. The actual content is stored in &lt;strong&gt;annotations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each summary has annotations, and we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loop through the summary's annotations&lt;/li&gt;
&lt;li&gt;Find the one with type &lt;code&gt;SUMMARY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fetch that annotation using &lt;code&gt;AnnotationApi&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extract the text content&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's add a method to fetch annotation content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// At the bottom of pieces_os_service.dart

/// Get the summary content from a workstream summary's annotations
Future&amp;lt;String?&amp;gt; getSummaryContent(WorkstreamSummary summary) async {
  try {
    // Loop through annotations to find the DESCRIPTION type
    for (final annotationRef in summary.annotations?.indices.keys.toList() ?? []) {
      // Fetch the full annotation using AnnotationApi
      final annotation = await _annotationApi
          .annotationSpecificAnnotationSnapshot(annotationRef);

      if (annotation == null) {
        continue;
      }
      // Check if this is a DESCRIPTION type annotation
      if (annotation.type == AnnotationTypeEnum.SUMMARY) {
        // Return the text content - this is the actual summary!
        return annotation.text;
      }
    }

    return null;
  } catch (e) {
    print('Error fetching annotation content for ${summary.id}: $e');
    return null;
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK separates metadata (ID, timestamp) from content (stored in annotations).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Creating a Proper Data Structure
&lt;/h2&gt;

&lt;p&gt;Let's create a proper class for summaries with their content. Add this to &lt;code&gt;lib/models/daily_recap_models.dart:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// Data class to hold a summary with its content
class SummaryWithContent {
  final String id;
  final String title;
  final String content;        // The actual summary text!
  final DateTime timestamp;

  SummaryWithContent({
    required this.id,
    required this.title,
    required this.content,
    required this.timestamp,
  });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's add a convenient method to get summaries with their content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Don't forget to import the model that we just created
import 'package:daily_recap_app/models/daily_recap_models.dart';

// Below existing code in pieces_os_straervice.dart

/// Get summaries with their content for a specific day
Future&amp;lt;List&amp;lt;SummaryWithContent&amp;gt;&amp;gt; getSummariesWithContentForDay(
  DateTime date,
) async {
  final summaries = getSummariesForDay(date);
  final List&amp;lt;SummaryWithContent&amp;gt; summariesWithContent = [];

  for (final summary in summaries) {
    final content = await getSummaryContent(summary);
    if (content != null &amp;amp;&amp;amp; content.isNotEmpty) {
      summariesWithContent.add(
        SummaryWithContent(
          id: summary.id,
          title: summary.name,
          content: content,              // Actual text from annotation!
          timestamp: summary.created.value,
        ),
      );
    }
  }

  return summariesWithContent;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect! Now we have rich, typed data ready for AI processing and UI display.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1 Summary:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Connect to PiecesOS&lt;/li&gt;
&lt;li&gt;Open WebSocket for real-time updates&lt;/li&gt;
&lt;li&gt;Fetch and cache summaries&lt;/li&gt;
&lt;li&gt;Group by day&lt;/li&gt;
&lt;li&gt;Stream updates to UI&lt;/li&gt;
&lt;li&gt;Extract summary content from annotations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What is created so far &lt;code&gt;lib/services/pieces_os_service.dart&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:pieces_os_client/api.dart';
import 'package:pieces_os_client/api_client.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:daily_recap_app/models/daily_recap_models.dart';

/// Service to interact with Pieces OS for LTM (Long Term Memory) tracking
class PiecesOSService {
  static const String baseUrl = 'http://localhost:39300';
  static const String websocketUrl = 'ws://localhost:39300';

  // Used to register application
  late final ConnectorApi _connectorApi;
  // Used to retrieve the workstream summaries
  late final WorkstreamSummaryApi _workstreamSummaryApi;

  // Used to retrieve the summary content
  late final AnnotationApi _annotationApi;

  final StreamController&amp;lt;Map&amp;lt;DateTime, List&amp;lt;WorkstreamSummary&amp;gt;&amp;gt;&amp;gt;
      _summariesStreamController = StreamController.broadcast();

  Stream&amp;lt;Map&amp;lt;DateTime, List&amp;lt;WorkstreamSummary&amp;gt;&amp;gt;&amp;gt; get summariesStream =&amp;gt;
      _summariesStreamController.stream;

  // Track initial WebSocket sync
  bool _isInitialSyncComplete = false;
  Completer&amp;lt;void&amp;gt;? _initialSyncCompleter;

  PiecesOSService() {
    final client = ApiClient(basePath: baseUrl);
    _connectorApi = ConnectorApi(client);
    _workstreamSummaryApi = WorkstreamSummaryApi(client);
    _annotationApi = AnnotationApi(client);
  }

  Future&amp;lt;Application&amp;gt; connectApplication() async {
    final seededApp = SeededTrackedApplication(
      name: ApplicationNameEnum.OPEN_SOURCE,  // Hey Pieces, I'm open source!
      platform: Platform.operatingSystem == "macos"
          ? PlatformEnum.MACOS
          : PlatformEnum.WINDOWS,
      version: "0.0.1",
    );

    final connection = SeededConnectorConnection(
      application: seededApp,
    );

    _context = await _connectorApi.connect(
      seededConnectorConnection: connection,
    );

    print('Successfully connected to Pieces OS!');
    return _context!.application;
  }

  /// Wait for the initial WebSocket sync to complete
  /// Only blocks on the first call, returns immediately after
  Future&amp;lt;void&amp;gt; waitForInitialSync() async {
    // If already synced, return immediately
    if (_isInitialSyncComplete) {
      return;
    }

    // If sync is in progress, wait for it
    if (_initialSyncCompleter != null) {
      return _initialSyncCompleter!.future;
    }

    // Start waiting for first sync
    _initialSyncCompleter = Completer&amp;lt;void&amp;gt;();
    return _initialSyncCompleter!.future;
  }

  void _startWebSocketListener() {
    final wsUrl = '$websocketUrl/workstream_summaries/stream/identifiers';
    _wsChannel = WebSocketChannel.connect(Uri.parse(wsUrl));

    print('WebSocket connected to $wsUrl');

    _wsSubscription = _wsChannel!.stream.listen(
      (message) async {
        try {
          // Important: WebSocket sends JSON strings, decode them first!
          final streamedIdentifiers = StreamedIdentifiers.fromJson(
            jsonDecode(message),
          );

          // Loop through each identifier we received
          for (final id in streamedIdentifiers!.iterable) {
            final summaryId = id.workstreamSummary?.id;
            if (summaryId != null) {
              await _fetchAndCacheSummary(summaryId);
            }
          }

          // Mark initial sync as complete after first message
          if (!_isInitialSyncComplete) {
            _isInitialSyncComplete = true;
            _initialSyncCompleter?.complete();
            print('Initial WebSocket sync complete!');
          }
        } catch (e) {
          print('Error processing message: $e');
        }
      },
      onDone: () {
        print('WebSocket closed, reconnecting...');
        _reconnectWebSocket();
      },
      onError: (error) {
        print('WebSocket error: $error');
        _reconnectWebSocket();
      },
    );
  }

  Future&amp;lt;void&amp;gt; _fetchAndCacheSummary(String identifier) async {
    final summary = await _workstreamSummaryApi
        .workstreamSummariesSpecificWorkstreamSummarySnapshot(identifier);

    if (summary == null) {
      return;
    }
    // Normalize to day (remove time)
    final createdDate = summary.created.value;
    final dayKey = DateTime(
      createdDate.year,
      createdDate.month,
      createdDate.day,
    );

    // Create day entry if it doesn't exist
    if (!_summariesByDay.containsKey(dayKey)) {
      _summariesByDay[dayKey] = [];
    }

    // Check for duplicates (avoid adding the same summary twice)
    final existingIndex = _summariesByDay[dayKey]!
        .indexWhere((s) =&amp;gt; s.id == summary.id);

    if (existingIndex != -1) {
      // Update existing
      _summariesByDay[dayKey]![existingIndex] = summary;
    } else {
      // Add new
      _summariesByDay[dayKey]!.add(summary);
    }

    // Sort by time (most recent first)
    _summariesByDay[dayKey]!.sort((a, b) {
      return b.created.value.compareTo(a.created.value);
    });

    // Notify anyone listening
    _summariesStreamController.add(Map.unmodifiable(_summariesByDay));

    print('Cached summary ${summary.id} for day $dayKey');
  }

  /// Get summaries for a specific day from cache
  List&amp;lt;WorkstreamSummary&amp;gt; getSummariesForDay(DateTime date) {
    final dayKey = DateTime(date.year, date.month, date.day);
    return _summariesByDay[dayKey] ?? [];
  }

  /// Get all days that have summaries (sorted most recent first)
  List&amp;lt;DateTime&amp;gt; getDaysWithSummaries() {
    final days = _summariesByDay.keys.toList();
    days.sort((a, b) =&amp;gt; b.compareTo(a)); // Most recent first
    return days;
  }

  /// Get ries with their content for a specific day
  Future&amp;lt;List&amp;lt;SummaryWithContent&amp;gt;&amp;gt; getSummariesWithContentForDay(
    DateTime date,
  ) async {
    final summaries = getSummariesForDay(date);
    final List&amp;lt;SummaryWithContent&amp;gt; summariesWithContent = [];

    for (final summary in summaries) {
      final content = await getSummaryContent(summary);
      if (content != null &amp;amp;&amp;amp; content.isNotEmpty) {
        summariesWithContent.add(
          SummaryWithContent(
            id: summary.id,
            title: summary.name,
            content: content,
            timestamp: summary.created.value,
          ),
        );
      }
    }

    return summariesWithContent;
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/pieces-app/blog-dart-daily-stand-up-generator" rel="noopener noreferrer"&gt;Reference GitHub&lt;/a&gt; for viewing the full project.&lt;/p&gt;

</description>
      <category>api</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>3 Ways to Actually Improve Your Work Performance (That Changed Everything for Me)</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Wed, 26 Nov 2025 16:11:43 +0000</pubDate>
      <link>https://forem.com/get_pieces/3-ways-to-actually-improve-your-work-performance-that-changed-everything-for-me-1nn4</link>
      <guid>https://forem.com/get_pieces/3-ways-to-actually-improve-your-work-performance-that-changed-everything-for-me-1nn4</guid>
      <description>&lt;p&gt;A year ago, if you'd asked me how to boost productivity, I'd have given you the standard advice: work harder, multitask better, attend more meetings. Turns out, that's exactly the wrong approach.&lt;/p&gt;

&lt;p&gt;Real improvement comes from working smarter. Here are three strategies that actually moved the needle for me.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Context Drift Problem
&lt;/h2&gt;

&lt;p&gt;We're all drowning in information. Emails, meetings, Slack threads, endless to-do lists. The real productivity killer isn't the work itself – it's trying to remember what happened yesterday, why you made that decision, or where that critical feedback lives.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;23 minutes&lt;/strong&gt; – that's how long it takes to refocus after an interruption&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This "context drift" destroys more productivity than most people realize. Here's what actually works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capture as you go.&lt;/strong&gt; Don't trust your memory. Write down decisions, key takeaways, and blockers as they happen. Notebook, notes app, shared doc – doesn't matter. Just capture it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Centralize everything.&lt;/strong&gt; Scattered notes across five different tools? That's a recipe for losing context. Keep your work information in as few places as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review regularly.&lt;/strong&gt; Spend 5-10 minutes at the end of each day scanning your notes. What's moving forward? What's stuck? What needs attention next? This habit surfaces patterns you'd otherwise miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make context visible.&lt;/strong&gt; Share your context with your team. When everyone understands the "why" behind decisions, collaboration gets smoother and misunderstandings drop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automating Context Tracking
&lt;/h3&gt;

&lt;p&gt;Here's the thing – you don't have to do this manually anymore. I use &lt;a href="https://pieces.app/standup" rel="noopener noreferrer"&gt;Pieces&lt;/a&gt; to automatically track my work across docs, content plans, campaigns, and conversations.&lt;/p&gt;

&lt;p&gt;Instead of scrambling to remember what I did last week, I can instantly pull up a timeline of my decisions, blockers, and progress:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhadxtpeq8ree4hgu84q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhadxtpeq8ree4hgu84q.png" alt="Pieces workstream activity showing automatic context tracking" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This saves me hours every month and gives me confidence that nothing important slips through the cracks. Context tracking goes from manual chore to effortless advantage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Killing the Reporting Mental Load
&lt;/h2&gt;

&lt;p&gt;Once you've captured your context, the next challenge is reporting on it. Stand-ups, reviews, status updates – they all require sifting through scattered notes and memories. This mental load is one of the most overlooked productivity killers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Research shows that &lt;strong&gt;managing cognitive load&lt;/strong&gt; is a proven way to improve workplace performance&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's what works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch your reporting.&lt;/strong&gt; Don't update constantly. Set specific times for reporting and stick to them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep a running log.&lt;/strong&gt; Maintain one place where you track accomplishments and blockers throughout the week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automate what you can.&lt;/strong&gt; Use tools that generate reports from your actual work, not from your memory of the work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't aim for perfection.&lt;/strong&gt; Good enough is good enough for most updates.&lt;/p&gt;

&lt;p&gt;The less mental energy you spend on reporting, the more you have for creative problem-solving and actual execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I Automated My Reporting
&lt;/h3&gt;

&lt;p&gt;Before Pieces, I dreaded reporting cycles. Hours spent reconstructing my week, second-guessing what mattered, worrying I'd forget something important.&lt;/p&gt;

&lt;p&gt;Now? I just ask Pieces "What did I accomplish this week?" and get a clear, data-driven narrative of my progress:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fragox35jzngjcbfocl09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fragox35jzngjcbfocl09.png" alt="Pieces generating automatic work reports" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This automation freed up tons of mental bandwidth. I can focus on higher-impact work instead of administrative overhead.&lt;/p&gt;

&lt;p&gt;You can try it yourself – &lt;a href="https://pieces.app/standup" rel="noopener noreferrer"&gt;download Pieces&lt;/a&gt;, enable &lt;code&gt;Long-Term Memory&lt;/code&gt;, and let it capture your workflow for a day or two. You have full control over what's recorded (you can block any apps), and all &lt;a href="https://pieces.app/blog/the-importance-of-on-device-ai-for-developer-productivity" rel="noopener noreferrer"&gt;sensitive data stays on-device&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once it's set up, try generating your &lt;a href="https://docs.pieces.app/products/desktop/workstream-activity" rel="noopener noreferrer"&gt;workstream activity&lt;/a&gt;. You'll see how quickly you can turn real activity into a clear, ready-to-share update.&lt;/p&gt;




&lt;h2&gt;
  
  
  Turning Updates Into Growth
&lt;/h2&gt;

&lt;p&gt;With context captured and reporting automated, you can do something powerful: &lt;strong&gt;reflect and grow&lt;/strong&gt;. Use your daily updates not just as a record, but as a mirror to spot patterns, celebrate wins, and identify improvement areas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Look for patterns.&lt;/strong&gt; What days are you most productive? What types of tasks drain you? When do blockers typically appear?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Celebrate small wins.&lt;/strong&gt; Don't wait for big milestones. Acknowledge daily progress to maintain momentum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identify growth areas.&lt;/strong&gt; Where are you consistently stuck? What skills would help you move faster?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Share and seek feedback.&lt;/strong&gt; Use your updates as conversation starters with your team or manager.&lt;/p&gt;

&lt;p&gt;Turning routine updates into actionable insights is the foundation of growth. It's how you move from just "doing the work" to understanding how to improve it.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Updates Became a Feedback Loop
&lt;/h3&gt;

&lt;p&gt;At first, my daily updates felt like another checkbox. But once I started using Pieces, everything changed. The AI analyzed my updates, surfaced productivity trends, highlighted recurring blockers, and suggested improvement areas:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fru3te9dgpykozzzhjzb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fru3te9dgpykozzzhjzb5.png" alt="Pieces AI analyzing work patterns and suggesting improvements" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I discovered I was most productive in the mornings and that meetings were my biggest interruption source. With this data, I adjusted my schedule and advocated for fewer midday meetings.&lt;/p&gt;

&lt;p&gt;Result? Noticeable improvements in both output and job satisfaction.&lt;/p&gt;

&lt;p&gt;Now every update isn't just a status report – it's a mini case study in how I solve problems, collaborate, and grow. Pieces turns my daily routines into a personalized feedback loop, helping me continuously improve and align my work with long-term goals.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Works
&lt;/h2&gt;

&lt;p&gt;Improving work performance isn't about longer hours or more meetings. The real breakthroughs came from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gaining clarity through context tracking&lt;/strong&gt; – Making my work visible and accessible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reducing mental load by automating reporting&lt;/strong&gt; – Freeing up bandwidth for high-impact work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turning daily updates into personal insights&lt;/strong&gt; – Using data to drive continuous improvement&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These aren't abstract ideas. They're changes I made through experience, supported by the right tools and honest reflection on my workflow.&lt;/p&gt;

&lt;p&gt;If you're looking for ways to improve your work performance, start by making your work visible, your reporting effortless, and your growth intentional. &lt;a href="https://pieces.app/standup" rel="noopener noreferrer"&gt;Try Pieces&lt;/a&gt; and see the results for yourself.&lt;/p&gt;




&lt;p&gt;What strategies have worked for you? Have you found ways to reduce context switching or automate your reporting? Drop your experiences in the comments.&lt;/p&gt;

&lt;h1&gt;
  
  
  productivity #workperformance #developertools #automation
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>How I Automated My Standup Meetings (And Why You Should Too)</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Wed, 26 Nov 2025 16:11:08 +0000</pubDate>
      <link>https://forem.com/get_pieces/how-i-automated-my-standup-meetings-and-why-you-should-too-4aa8</link>
      <guid>https://forem.com/get_pieces/how-i-automated-my-standup-meetings-and-why-you-should-too-4aa8</guid>
      <description>&lt;p&gt;&lt;strong&gt;Stop wasting 15 minutes every morning on status updates. Here's how I built a system that writes my standups automatically—and actually makes them useful.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;If you're like most developers, your morning standup feels like this: scramble to remember what you did yesterday, mumble something about "working on tickets," promise to finish that PR today, then immediately forget what everyone else said.&lt;/p&gt;

&lt;p&gt;I used to spend &lt;strong&gt;15 minutes every morning&lt;/strong&gt; just preparing for a 5-minute standup. Multiply that by 5 days a week, and I was burning over an hour weekly on meeting prep alone.&lt;/p&gt;

&lt;p&gt;Here's the thing: standups aren't the problem. The manual status reporting is.&lt;/p&gt;

&lt;p&gt;So I automated mine. Completely. And it's been a game-changer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Standups Are Broken (But We Need Them)
&lt;/h2&gt;

&lt;p&gt;Daily standups exist for good reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the team aligned on priorities&lt;/li&gt;
&lt;li&gt;Surface blockers before they become disasters&lt;/li&gt;
&lt;li&gt;Build accountability and momentum&lt;/li&gt;
&lt;li&gt;Create visibility across distributed teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But somewhere along the way, they turned into performance theater. We spend more time &lt;em&gt;preparing&lt;/em&gt; to talk about our work than actually &lt;em&gt;doing&lt;/em&gt; the work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;23 minutes&lt;/strong&gt; to refocus after interruptions (University of California, Irvine study)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every standup interrupts your flow state. And if you're context-switching between multiple projects, remembering what you did yesterday becomes genuine cognitive work.&lt;/p&gt;

&lt;p&gt;The worst part? Most standup updates are already documented somewhere. In your commits. Your PRs. Your task tracker. Your Slack messages. You're just manually re-typing information that already exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built: A Standup That Writes Itself
&lt;/h2&gt;

&lt;p&gt;I wanted a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tracks my work automatically throughout the day&lt;/li&gt;
&lt;li&gt;Generates accurate standup updates without manual input&lt;/li&gt;
&lt;li&gt;Surfaces actual blockers, not generic status&lt;/li&gt;
&lt;li&gt;Takes less than 30 seconds to review and send&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what I ended up with:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Capture Everything Automatically
&lt;/h3&gt;

&lt;p&gt;The foundation is automatic work capture. I needed a system that tracks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code commits and PR activity&lt;/li&gt;
&lt;li&gt;Task updates and completions&lt;/li&gt;
&lt;li&gt;Meeting notes and decisions&lt;/li&gt;
&lt;li&gt;Research and documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started using &lt;code&gt;Pieces for Developers&lt;/code&gt; because it captures my workflow context automatically. When I'm coding, it tracks my commits and branches. When I'm in meetings, it captures notes and action items. When I'm researching, it saves relevant snippets and links.&lt;/p&gt;

&lt;p&gt;The key is &lt;strong&gt;passive capture&lt;/strong&gt;. I don't have to remember to log anything. It just happens in the background.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Connect Your Work Sources
&lt;/h3&gt;

&lt;p&gt;Most developers work across tons of tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub/GitLab for code&lt;/li&gt;
&lt;li&gt;Jira/Linear for tasks&lt;/li&gt;
&lt;li&gt;Slack for communication&lt;/li&gt;
&lt;li&gt;Notion/Confluence for docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your standup automation needs to pull from all of them. I set up integrations so my system knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which PRs I opened or reviewed&lt;/li&gt;
&lt;li&gt;Which tickets moved to "In Progress" or "Done"&lt;/li&gt;
&lt;li&gt;Which meetings I attended and what was discussed&lt;/li&gt;
&lt;li&gt;Which documentation I created or updated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a complete activity log without manual tracking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Generate Smart Summaries
&lt;/h3&gt;

&lt;p&gt;Raw activity logs are useless for standups. You need intelligent summarization that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Groups related work together&lt;/li&gt;
&lt;li&gt;Highlights completed items&lt;/li&gt;
&lt;li&gt;Surfaces actual blockers&lt;/li&gt;
&lt;li&gt;Formats for quick reading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use &lt;code&gt;Pieces Copilot&lt;/code&gt; to transform my activity log into standup format. It knows to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combine multiple commits into "Implemented feature X"&lt;/li&gt;
&lt;li&gt;Flag PRs waiting on review as potential blockers&lt;/li&gt;
&lt;li&gt;Identify patterns like "spent 3 hours debugging Y"&lt;/li&gt;
&lt;li&gt;Format everything in past/present/future tense&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gs"&gt;**Yesterday:**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Completed authentication refactor (PR #234)
&lt;span class="p"&gt;-&lt;/span&gt; Fixed critical bug in payment processing
&lt;span class="p"&gt;-&lt;/span&gt; Reviewed 3 PRs from team members

&lt;span class="gs"&gt;**Today:**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Finishing API documentation
&lt;span class="p"&gt;-&lt;/span&gt; Starting work on user dashboard redesign
&lt;span class="p"&gt;-&lt;/span&gt; Team sync at 2pm

&lt;span class="gs"&gt;**Blockers:**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Waiting on design approval for dashboard mockups
&lt;span class="p"&gt;-&lt;/span&gt; Need database migration review from DevOps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Review and Send in Seconds
&lt;/h3&gt;

&lt;p&gt;Automation doesn't mean zero human involvement. I spend &lt;strong&gt;30 seconds&lt;/strong&gt; each morning reviewing the generated standup to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify accuracy&lt;/li&gt;
&lt;li&gt;Add context if needed&lt;/li&gt;
&lt;li&gt;Adjust priorities&lt;/li&gt;
&lt;li&gt;Flag urgent items&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I copy-paste into Slack or our standup tool. Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Results: Time Saved and Better Communication
&lt;/h2&gt;

&lt;p&gt;After three months of automated standups, here's what changed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;65 minutes saved per week&lt;/strong&gt; on standup prep and delivery&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's over &lt;strong&gt;50 hours per year&lt;/strong&gt; I'm not spending on status updates.&lt;/p&gt;

&lt;p&gt;But the time savings aren't even the best part. The &lt;em&gt;quality&lt;/em&gt; of my standups improved:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before automation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generic updates like "worked on tickets"&lt;/li&gt;
&lt;li&gt;Forgot to mention blockers until too late&lt;/li&gt;
&lt;li&gt;Inconsistent detail level&lt;/li&gt;
&lt;li&gt;Missed cross-team dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After automation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specific, actionable updates with PR links&lt;/li&gt;
&lt;li&gt;Blockers surfaced immediately&lt;/li&gt;
&lt;li&gt;Consistent format and detail&lt;/li&gt;
&lt;li&gt;Better visibility for stakeholders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My team lead actually commented that my standups became the most useful on the team. Not because I'm doing more work, but because the updates are more accurate and actionable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Frustrations (And How to Fix Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "My work doesn't fit into yesterday/today/blockers format"
&lt;/h3&gt;

&lt;p&gt;Fair point. Customize your template. Some teams use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus areas&lt;/li&gt;
&lt;li&gt;Progress metrics&lt;/li&gt;
&lt;li&gt;Help needed&lt;/li&gt;
&lt;li&gt;Wins and learnings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The automation adapts to whatever format you need. The key is having a consistent structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  "I work on too many projects to track automatically"
&lt;/h3&gt;

&lt;p&gt;This is actually &lt;em&gt;more&lt;/em&gt; reason to automate. Context-switching makes manual standups even harder.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;Long-Term Memory&lt;/code&gt; in Pieces to maintain separate contexts for each project. When you switch projects, your standup automation knows which context to pull from.&lt;/p&gt;

&lt;h3&gt;
  
  
  "My manager wants more detail than automated summaries provide"
&lt;/h3&gt;

&lt;p&gt;Automation generates the baseline. You add the narrative.&lt;/p&gt;

&lt;p&gt;I use the automated standup as my outline, then add 1-2 sentences of context about &lt;em&gt;why&lt;/em&gt; something matters or &lt;em&gt;how&lt;/em&gt; I approached a problem. Still takes less than 2 minutes total.&lt;/p&gt;

&lt;h3&gt;
  
  
  "What about remote/async teams?"
&lt;/h3&gt;

&lt;p&gt;Async standups are perfect for automation. Instead of a synchronous meeting, everyone posts their update to a shared channel.&lt;/p&gt;

&lt;p&gt;With automation, you can post your standup at any time without scrambling to remember what you did. Some teams even automate the &lt;em&gt;posting&lt;/em&gt; itself on a schedule.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Start Automating Your Standups Today
&lt;/h2&gt;

&lt;p&gt;You don't need to build a custom system from scratch. Here's how to start:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Use Pieces for Developers (What I Use)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install Pieces and connect your development tools&lt;/li&gt;
&lt;li&gt;Let it capture your workflow for a few days&lt;/li&gt;
&lt;li&gt;Use Copilot to generate standup summaries&lt;/li&gt;
&lt;li&gt;Review and send&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;Long-Term Memory&lt;/code&gt; feature maintains context across sessions, so your standups reflect your actual work patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Build Your Own Integration
&lt;/h3&gt;

&lt;p&gt;If you want full control, build a custom integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudo-code for standup automation
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_standup&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Pull from Git
&lt;/span&gt;    &lt;span class="n"&gt;commits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_git_commits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yesterday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Pull from task tracker
&lt;/span&gt;    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_completed_tasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yesterday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Pull from calendar
&lt;/span&gt;    &lt;span class="n"&gt;meetings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_meetings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yesterday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Generate summary
&lt;/span&gt;    &lt;span class="n"&gt;standup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;format_standup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yesterday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;summarize_work&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meetings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;today&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;get_planned_work&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blockers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;identify_blockers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;standup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to GitHub API, Jira API, and Google Calendar API. Use an LLM API for summarization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Start Manual, Automate Incrementally
&lt;/h3&gt;

&lt;p&gt;Not ready for full automation? Start with these habits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep a daily work log (even just bullet points)&lt;/li&gt;
&lt;li&gt;Use a template for consistency&lt;/li&gt;
&lt;li&gt;Track blockers in real-time, not retrospectively&lt;/li&gt;
&lt;li&gt;Review your log before standup instead of trying to remember&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then gradually add automation as you identify repetitive patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Picture: Automation Frees You to Focus
&lt;/h2&gt;

&lt;p&gt;Automating standups isn't just about saving 15 minutes. It's about removing cognitive overhead.&lt;/p&gt;

&lt;p&gt;When you're not mentally tracking "what did I do yesterday for standup," you can focus completely on the problem you're solving right now. No background anxiety about forgetting to mention something important.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I used to dread standups because I could never remember everything I worked on. Now my standup writes itself and I actually look forward to sharing progress." — &lt;strong&gt;Me&lt;/strong&gt;, three months into automation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The best part? This approach works for other recurring status updates too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weekly team reports&lt;/li&gt;
&lt;li&gt;Sprint retrospectives&lt;/li&gt;
&lt;li&gt;Monthly progress summaries&lt;/li&gt;
&lt;li&gt;Performance review prep&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have automatic work capture, generating &lt;em&gt;any&lt;/em&gt; status update becomes trivial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Turn: What's Stopping You?
&lt;/h2&gt;

&lt;p&gt;If you're still manually writing standups every morning, you're wasting time and mental energy on something that can be automated.&lt;/p&gt;

&lt;p&gt;The tools exist. The integrations work. The time savings are real.&lt;/p&gt;

&lt;p&gt;Start small:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick one work source to track automatically (start with Git commits)&lt;/li&gt;
&lt;li&gt;Use a template for your standups&lt;/li&gt;
&lt;li&gt;Add more automation incrementally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Or go all-in with a tool like Pieces that handles the whole workflow.&lt;/p&gt;

&lt;p&gt;Either way, stop treating standup prep like it's valuable work. Automate it and focus on what actually matters: building great software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your biggest standup frustration? Drop a comment—I'd love to hear how other devs are handling this.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to see how I automated my entire development workflow? Check out my &lt;a href="https://pieces.app/standups?utm_source=yt-lf&amp;amp;utm_medium=social&amp;amp;utm_campaign=standup" rel="noopener noreferrer"&gt;other posts on developer productivity&lt;/a&gt; or try &lt;a href="https://pieces.app/standups?utm_source=yt-lf&amp;amp;utm_medium=social&amp;amp;utm_campaign=standup" rel="noopener noreferrer"&gt;Pieces for Developers&lt;/a&gt; to start automating your standups today.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;code&gt;productivity&lt;/code&gt; &lt;code&gt;automation&lt;/code&gt; &lt;code&gt;devtools&lt;/code&gt; &lt;code&gt;agile&lt;/code&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Your Go-To Daily Standup Agenda and How to Actually Make It Work</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Wed, 26 Nov 2025 16:00:32 +0000</pubDate>
      <link>https://forem.com/get_pieces/your-go-to-daily-standup-agenda-and-how-to-actually-make-it-work-46ki</link>
      <guid>https://forem.com/get_pieces/your-go-to-daily-standup-agenda-and-how-to-actually-make-it-work-46ki</guid>
      <description>&lt;p&gt;If you've ever zoned out during a daily standup or wondered why you're even there, you're not alone. I've seen how a solid standup can transform a team's energy and output, but I've also watched these meetings turn into soul-crushing rituals where everyone just recites what they did yesterday while secretly checking Slack.&lt;/p&gt;

&lt;p&gt;This guide is for anyone who wants standups that actually help - whether you're new to agile, leading a remote team, or just tired of wasting 15 minutes every morning on status theater.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is a Standup Meeting? (And Why Do We Still Do Them?)
&lt;/h2&gt;

&lt;p&gt;Let's start with the basics: a standup is a short, focused daily meeting where team members quickly share what they're working on, what's next, and what's blocking them. The idea is to keep everyone aligned, surface blockers early, and move work forward without getting bogged down in status updates or side conversations.&lt;/p&gt;

&lt;p&gt;But here's the truth: the value of a standup isn't in the ritual itself. It's in the clarity, accountability, and momentum it creates. When done right, a team standup is the heartbeat of a high-performing group. When done wrong, it's 15 minutes of your life you'll never get back.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Most Daily Standups Miss the Mark
&lt;/h2&gt;

&lt;p&gt;Daily standups have been a staple of team workflows for years, but they're often more performative than productive. Instead of fostering real communication, these meetings devolve into rote status updates. Team members recite yesterday's tasks or scramble to sound busy while deeper blockers and real collaboration go unaddressed.&lt;/p&gt;

&lt;p&gt;It becomes a kind of "productivity theater" where the focus shifts from building something meaningful to justifying your existence each morning. You know the feeling: you're halfway through your update and realize nobody's actually listening because they're mentally rehearsing their own lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Common Frustrations
&lt;/h3&gt;

&lt;p&gt;Here's what I've seen go wrong in tons of standups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Status updates nobody needs&lt;/strong&gt;: "Yesterday I worked on ticket 1234, today I'll work on ticket 1235" tells the team nothing useful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hidden blockers&lt;/strong&gt;: Real problems don't surface because people don't want to "waste everyone's time"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meeting drift&lt;/strong&gt;: What should take 10 minutes stretches to 25 because someone starts debugging in the standup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disengagement&lt;/strong&gt;: Half the team is on mute, clearly multitasking, because they know their turn won't come for another 8 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context switching pain&lt;/strong&gt;: You're deep in flow, then boom - standup time, and it takes forever to get back into your work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At Pieces, we saw this firsthand and decided there had to be a better way. We built an AI memory tool that automatically captures your work context across code, docs, chats, and more, so you don't have to manually prepare or recall every detail for your standup.&lt;/p&gt;

&lt;p&gt;By sharing routine updates asynchronously and letting the AI surface what matters, we improved standups from a daily performance into a focused, action-oriented discussion. We use our meetings to solve real problems, unblock each other, and move work forward.&lt;/p&gt;




&lt;h2&gt;
  
  
  Great Daily Standup Agenda: Classical vs. Modern Approach
&lt;/h2&gt;

&lt;p&gt;When it comes to daily standups, most teams follow a familiar script. Here's how the classical approach compares to a more modern method, and why rethinking the format can be a game-changer for your team.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Classical Standup Agenda
&lt;/h3&gt;

&lt;p&gt;Traditionally, a daily standup agenda looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kickoff &amp;amp; Purpose (1 min)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick greeting, reminder of the meeting's goal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Round-Robin Updates (10 min)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each person answers the classic questions:

&lt;ul&gt;
&lt;li&gt;What did you do yesterday?&lt;/li&gt;
&lt;li&gt;What will you do today?&lt;/li&gt;
&lt;li&gt;Any blockers?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Blocker Triage (2-3 min)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discuss and assign action items for any blockers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Announcements &amp;amp; Wrap-Up (1-2 min)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share reminders, deadlines, or shoutouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This format is simple and widely used, especially in agile and scrum teams. But over time, it often leads to repetitive status updates, disengagement, and meetings that drift off-topic.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Modern Standup Approach
&lt;/h3&gt;

&lt;p&gt;Here's how we've evolved our standup format to focus on what actually matters:&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Async Status Updates Before the Meeting
&lt;/h4&gt;

&lt;p&gt;Everyone shares their routine updates (what they did, what's next, minor blockers) in a shared space before the meeting. This could be a Slack thread, a doc, or any tool that captures context automatically.&lt;/p&gt;

&lt;p&gt;The key questions to answer asynchronously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What progress did I make yesterday?&lt;/li&gt;
&lt;li&gt;Where do I need the most help or support today?&lt;/li&gt;
&lt;li&gt;Which accomplishments had the biggest impact on my teammates or the project?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajc1ng6rq249wa37onvu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajc1ng6rq249wa37onvu.png" alt="Screenshot showing async standup updates captured automatically" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gives the team time to review, reflect, and prepare questions or feedback in advance. It also means no one has to scramble to remember yesterday's work on the spot, especially with tools that automatically capture your workflow.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Focused, Synchronous Standup (5-10 min)
&lt;/h4&gt;

&lt;p&gt;The live meeting is reserved for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Surfacing critical blockers&lt;/strong&gt; that need group input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discussing urgent decisions&lt;/strong&gt; or dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aligning on priorities&lt;/strong&gt; for the day or sprint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No round-robin "what I did yesterday" unless it's relevant to the group. Tangents and deep dives are parked for after the meeting or handled asynchronously.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Action-Oriented Wrap-Up
&lt;/h4&gt;

&lt;p&gt;Confirm next steps, owners for blockers, and any follow-up conversations. You can use AI to help surface the most important blockers and challenges from your work context.&lt;/p&gt;

&lt;p&gt;For example, asking "What was my biggest blocker or challenge, and how did I handle it?" can give you the full context without manual recall.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwi4c0u5n792ffsia77fd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwi4c0u5n792ffsia77fd.png" alt="Screenshot showing AI-surfaced blocker context" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;End on time - always. Respect your team's deep work time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Are the Benefits of This Approach?
&lt;/h2&gt;

&lt;p&gt;Since the context is already available, you don't waste time catching up or rehashing yesterday's news. You can dive straight into the conversations that matter most, making every minute count.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Benefits
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;15 minutes&lt;/strong&gt; saved per standup on average when using async updates&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The biggest benefit is the respect it gives to deep work. Meetings become shorter and sharper, freeing up more of the day for actual progress. No one feels their focus is being hijacked by unnecessary chatter or drawn-out updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specific improvements we've seen:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduced context switching&lt;/strong&gt;: Fewer interruptions mean developers can maintain flow state longer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better blocker visibility&lt;/strong&gt;: Real problems surface earlier because there's time to think before the meeting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increased engagement&lt;/strong&gt;: When you're only meeting for what matters, people actually pay attention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More inclusive for remote teams&lt;/strong&gt;: Async updates level the playing field for different time zones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better documentation&lt;/strong&gt;: Written updates create a searchable history of what happened and when&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the format keeps evolving. We regularly check in as a team, asking what's working and what could be better, and we're not afraid to tweak our format based on feedback or new challenges.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Tips for Better Standups
&lt;/h2&gt;

&lt;p&gt;Whether you're running a classic scrum standup or experimenting with async check-ins, here are some concrete ways to level up your meetings:&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep It Focused
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time-box ruthlessly&lt;/strong&gt;: Set a timer and stick to it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Park tangents&lt;/strong&gt;: Create a "parking lot" for topics that need deeper discussion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay standing&lt;/strong&gt;: If you're in person, actually stand - it naturally keeps things moving&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One speaker at a time&lt;/strong&gt;: No side conversations or debugging sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Make Status Updates Useful
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus on outcomes, not activities&lt;/strong&gt;: "Shipped the login feature" beats "Worked on authentication code"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highlight dependencies&lt;/strong&gt;: Call out when you need something from another team member&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be specific about blockers&lt;/strong&gt;: "Waiting on API docs" is more actionable than "Blocked on backend"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share context, not just task IDs&lt;/strong&gt;: Help your team understand why your work matters&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use the Right Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Async-first communication&lt;/strong&gt;: Use Slack, Discord, or dedicated standup tools for routine updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context capture&lt;/strong&gt;: Tools that automatically track your work can save tons of prep time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared docs&lt;/strong&gt;: Keep a running log of blockers and decisions for reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video for remote teams&lt;/strong&gt;: Seeing faces builds connection, even in short meetings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Experiment and Iterate
&lt;/h3&gt;

&lt;p&gt;Your standup format isn't set in stone. Try different approaches and see what works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rotate facilitators&lt;/strong&gt;: Different perspectives can surface new improvements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Survey your team&lt;/strong&gt;: Ask what's working and what's not every few weeks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try different schedules&lt;/strong&gt;: Maybe your team works better with standups at 2pm instead of 9am&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go async for a week&lt;/strong&gt;: See if eliminating the meeting entirely improves or hurts communication&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Make Your Standups Work for You
&lt;/h2&gt;

&lt;p&gt;The key to effective standups is keeping them simple, focused, and action-oriented. Use the format that fits your team, stick to the essentials, and don't be afraid to tweak as you go.&lt;/p&gt;

&lt;p&gt;Consider tools that help you capture context, automate summaries, and surface insights, so you can spend less time scrambling and more time building. The goal isn't to have perfect standups - it's to have standups that actually help your team ship better software faster.&lt;/p&gt;

&lt;p&gt;What's your experience with daily standups? Have you found ways to make them more effective, or are you still stuck in status theater? Drop a comment below and let's discuss what's worked (or hasn't) for your team.&lt;/p&gt;




&lt;p&gt;Ready to level up your standups? Try &lt;a href="https://pieces.app/standup" rel="noopener noreferrer"&gt;Pieces for free&lt;/a&gt; at your next meeting and see how automatic context capture can transform your team's clarity and momentum.&lt;/p&gt;

&lt;h1&gt;
  
  
  agile #productivity #teamwork #devtools
&lt;/h1&gt;

</description>
      <category>management</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>What's Ahead for Programmers: Tools Shaping the Future</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Mon, 21 Oct 2024 18:14:37 +0000</pubDate>
      <link>https://forem.com/get_pieces/whats-ahead-for-programmers-tools-shaping-the-future-bak</link>
      <guid>https://forem.com/get_pieces/whats-ahead-for-programmers-tools-shaping-the-future-bak</guid>
      <description>&lt;p&gt;In a recent talk, &lt;a href="https://sales.transformlabs.com/" rel="noopener noreferrer"&gt;Chris Slee, a founder of Transform Labs&lt;/a&gt; in Columbus, Ohio, identified the tools developers will need to understand within the next 18 months. He posits that programming will move beyond the use of traditional copilots into using tools that reshape how developers interact with code. Slee described five key categories of tools with examples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Frameworks&lt;/li&gt;
&lt;li&gt;AI-friendly databases&lt;/li&gt;
&lt;li&gt;Code writing tools&lt;/li&gt;
&lt;li&gt;Pieces as a unique tool&lt;/li&gt;
&lt;li&gt;Future frameworks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these is discussed in one of the following sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Frameworks for Faster Code
&lt;/h2&gt;

&lt;p&gt;As software development continues to evolve, programmers are increasingly turning to frameworks to accelerate their development processes. These frameworks, built upon existing code and libraries, provide a solid foundation for building, which saves time and effort. There are numerous commonly used frameworks such as TensorFlow and PyTorch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DSPy&lt;/strong&gt; is a Python library designed for digital signal processing (DSP). It offers a wide range of functions and tools for tasks such as filtering, Fourier transforms, and spectral analysis. By leveraging DSPy, developers can quickly implement complex DSP algorithms without having to write everything from scratch. It provides a user-friendly interface and efficient implementations, making it a popular choice for developers working on signal processing projects, even if the developers do not have extensive DSP experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Langchain&lt;/strong&gt; is a framework focused on natural language processing (NLP). It provides a modular architecture and pre-built components for tasks like text generation, summarization, and question-answering. &lt;a href="https://docs.pieces.app/build/glossary/terms/langchain" rel="noopener noreferrer"&gt;Langchain&lt;/a&gt; simplifies the process of building NLP applications, allowing developers to focus on the core functionality rather than low-level details.&lt;/p&gt;

&lt;p&gt;There are several key benefits of using frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster development:&lt;/strong&gt; Frameworks provide pre-built components and libraries, reducing the amount of code developers need to write. This enables developers to focus on core functionality and build applications more quickly. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increased efficiency&lt;/strong&gt;: By leveraging existing code, developers can avoid common pitfalls and improve the overall efficiency of their projects. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved code quality:&lt;/strong&gt; Frameworks often adhere to best practices and coding standards, leading to higher-quality code. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced collaboration:&lt;/strong&gt; Frameworks can facilitate collaboration among developers by providing a common foundation, shared vocabulary, and an active community that provides support and contributes to the framework's development. This can be invaluable for troubleshooting and learning new techniques. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frameworks have become an essential tool for modern software development, enabling developers to build applications more efficiently and effectively. The best framework for a particular project depends on several factors, such as project requirements, language compatibility, dependencies on external libraries, ease of use, and community support.&lt;/p&gt;

&lt;p&gt;By understanding the benefits and considerations of the specific requirements and tradeoffs of a project, developers can make informed decisions and choose the right tools to accelerate their projects. As the field of software development continues to evolve, even more innovative frameworks will emerge, further shaping the future of software development.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Efficient AI-Friendly Databases
&lt;/h2&gt;

&lt;p&gt;The rapid advancements in artificial intelligence (AI) have created a surge in demand for databases that can effectively handle the unique storage requirements of AI applications. Traditional relational databases, while powerful, may struggle to efficiently store and process the massive datasets and complex computations often associated with AI. &lt;/p&gt;

&lt;p&gt;To address these challenges, new database tools, particularly vector databases, have emerged as specialized solutions for AI workloads. Graphs and time series are also AI-friendly databases. Several leading AI-friendly databases have gained significant users in recent years:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt;: A low-code, open-source Firebase alternative that includes a powerful PostgreSQL database with built-in features for authentication, authorization, and storage. Supabase is well-suited for building real-time applications and APIs. It is designed to simplify the development of AI applications and can be easily integrated with popular AI frameworks. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vertex AI&lt;/strong&gt;: A fully managed platform from Google Cloud that provides a suite of tools for building, training, and deploying AI models. Vertex AI includes a high-performance vector database that is optimized for large-scale machine-learning workloads. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pinecone&lt;/strong&gt;: A cloud-based &lt;a href="https://pieces.app/blog/a-beginners-guide-to-vector-embeddings" rel="noopener noreferrer"&gt;vector database&lt;/a&gt; service that is optimized for high-performance similarity search. It is designed to handle massive datasets, which means it is good for applications such as recommendation systems, search engines, and anomaly detection. It provides a flexible API and integrates well with popular AI frameworks. &lt;/p&gt;

&lt;p&gt;These specialized databases offer several advantages for AI applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Efficient storage:&lt;/strong&gt; They are optimized for storing and retrieving large datasets, which are common in AI workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast search capabilities:&lt;/strong&gt; Vector databases efficiently perform &lt;a href="https://docs.pieces.app/build/glossary/terms/vector-search" rel="noopener noreferrer"&gt;similarity searches&lt;/a&gt;, which are essential for many AI tasks. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability:&lt;/strong&gt; They can easily scale to handle increasing workloads as AI applications grow. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with AI tools:&lt;/strong&gt; Many AI-friendly databases integrate seamlessly with popular AI frameworks and tools, simplifying development and deployment. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best choice of AI-friendly database depends on the specific requirements of the application, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data type:&lt;/strong&gt; Consider the type of data you will be storing (e.g., text, images, numerical data)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search requirements:&lt;/strong&gt; Determine the type of search queries you will perform (e.g., similarity search, exact match).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance requirements:&lt;/strong&gt; Evaluate the performance requirements of your application, such as latency and throughput&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability needs:&lt;/strong&gt; Consider how your data and workload may grow over time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with existing systems:&lt;/strong&gt; Consider how the database will integrate with your existing infrastructure and applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI-friendly databases are essential for building and scaling AI applications. By understanding the key features and benefits of these databases, you can make better decisions and be prepared for innovations in future databases designed specifically for AI systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Code-Writing Tools: Beyond Copilots
&lt;/h2&gt;

&lt;p&gt;The landscape of code-writing tools is rapidly evolving, with new and innovative tools emerging to enhance developer productivity. While traditional code editors like VS Code remain popular, a new generation of code-writing tools is surpassing copilots by offering more advanced features and capabilities. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aider&lt;/strong&gt;: Aider is a comprehensive code-writing tool that can generate code from natural language prompts, explain code snippets, and suggest improvements. It supports a wide range of programming languages and integrates seamlessly with popular development environments. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cline&lt;/strong&gt;: Cline, previously known as Claude Dev, is a powerful AI-powered code completion tool that can suggest code snippets and entire functions based on the context of your code, and it can do &lt;a href="https://docs.pieces.app/build/glossary/terms/ai-code-refactoring" rel="noopener noreferrer"&gt;AI code refactoring&lt;/a&gt;. It can help developers write cleaner, more maintainable code and improve their overall productivity. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;01-Engineer&lt;/strong&gt;: 01-Engineer is a versatile code-writing tool that can generate code from natural language prompts, explain code snippets, and provide &lt;a href="https://docs.pieces.app/build/glossary/terms/debugging-ai" rel="noopener noreferrer"&gt;debugging AI&lt;/a&gt; assistance. It is well-suited for data science and machine learning tasks. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pythagora&lt;/strong&gt;: Pythagora is a code generation tool that specializes in Python development. It can automatically generate Python code based on natural language descriptions, making it a valuable tool for data scientists or developers who are new to Python or need to quickly prototype new features. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supermaven&lt;/strong&gt;: Supermaven is a cloud-based development platform that offers a variety of tools and services for building, reviewing, testing, and deploying applications in several languages. It is designed to be highly customizable and can be tailored to the specific needs of individual developers. &lt;/p&gt;

&lt;p&gt;Entire posts have been written about the benefits of using Pieces and other, less advanced copilot tools. This is a brief summary of the benefits of code-writing tools in general.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Increased productivity:&lt;/strong&gt; Code writing tools can significantly accelerate the development process by automating repetitive tasks and suggesting code snippets. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved code quality:&lt;/strong&gt; These tools can help developers write cleaner, more efficient, and more maintainable code. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced errors:&lt;/strong&gt; These tools can help identify and prevent common coding errors, reducing the risks of bugs and defects. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced learning curve:&lt;/strong&gt; For new developers, code writing tools can help reduce the learning curve by providing suggestions and explanations. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced collaboration:&lt;/strong&gt; Some of these tools offer features that facilitate collaboration among development teams, such as code sharing and review. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the field of artificial intelligence continues to advance, we can expect to see even more sophisticated code-writing tools emerge. These tools have the potential to revolutionize the way developers work, making them more productive, efficient, and creative. &lt;/p&gt;

&lt;h2&gt;
  
  
  4. Pieces for Developers
&lt;/h2&gt;

&lt;p&gt;Slee called out Pieces as a "special thing" because its retrieval of similar code provides comparisons of other people's programming and "allows me to talk about code that somebody else wrote." &lt;/p&gt;

&lt;p&gt;Pieces is also unique because of its innovative approach to context retention, knowledge repository, and collaboration functionality.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pieces.app/blog/20-novel-ai-prompts-made-possible-only-by-pieces-copilot" rel="noopener noreferrer"&gt;Context retention&lt;/a&gt;:&lt;/strong&gt; Pieces will answer questions that no other software can, such as "What was the function I was working on last Wednesday?" The repository maintains a parallel workstream that maintains context information about the work done while it is active.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pieces.app/blog/workflow-integration-with-ai-a-unified-approach-to-development" rel="noopener noreferrer"&gt;Knowledge repository&lt;/a&gt;:&lt;/strong&gt; Pieces' plugins are available in various IDEs, browsers, and communication tools like Microsoft Teams. These all connect to the &lt;a href="https://docs.pieces.app/installation-getting-started/pieces-os" rel="noopener noreferrer"&gt;Pieces OS&lt;/a&gt; central repository, which means the developer can ask a question in the browser about work done earlier in the IDE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pieces.app/blog/making-code-reuse-and-reference-seamless" rel="noopener noreferrer"&gt;Reuse and Collaboration:&lt;/a&gt;&lt;/strong&gt; Pieces stores information that enriches stored code for reuse and sharing, such as who sent it to you or what website it came from. &lt;a href="https://pieces.app/blog/modern-code-organization-techniques" rel="noopener noreferrer"&gt;The code is organized&lt;/a&gt; and enhanced with information that makes it easy to understand the code, even when the code was written long ago or by someone else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The context workflow and the enrichment information in Pieces is a game-changer. Overall, Pieces offers a unique and powerful solution for code reuse and collaboration. It provides developers with a comprehensive context workflow that can significantly improve their productivity and efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Emerging Frameworks and Tools:
&lt;/h2&gt;

&lt;p&gt;The software development landscape is constantly evolving, with new frameworks and tools emerging to address the changing needs of developers. These are the futuristic tools that Slee included in his list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vercel V0:&lt;/strong&gt; This generative UI framework, developed by Vercel, leverages machine learning to automatically generate code from design files (e.g., Figma, Sketch). Developers can describe their desired interface elements, and V0 automatically generates code snippets or prototypes. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor&lt;/strong&gt;: Cursor is a fork of VS Code that provides a natural language interface for a developer to write code by talking with an AI. It includes numerous tools for understanding, finding, refactoring, debugging, and writing code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zed AI&lt;/strong&gt;: This open-source framework allows developers to create user interfaces with voice control capabilities. It provides pre-built components for tasks like computer vision, natural language processing, and machine learning.  It offers tools for training machine learning models, integrating AI functionality into existing applications, and deploying AI systems. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FastBuilder AI&lt;/strong&gt;: Another open-source framework, FastBuilder AI empowers developers to build web applications and APIs faster. It offers tools for automatic code generation and deployment, streamlining the development workflow. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pear&lt;/strong&gt;: This open-source AI platform focuses on simplifying web development for non-programmers. Pear enables users to create web pages using natural language commands with the intent to simplify and automate repetitive front-end development tasks. It offers tools for building reusable UI components, managing design systems, and streamlining the front-end development process. &lt;/p&gt;

&lt;p&gt;The development landscape is constantly evolving, and these emerging tools showcase the innovative approaches being taken to improve developer productivity and functionality. The emergence of these innovative frameworks and tools suggests a future where development processes are faster, more collaborative, and more accessible. As these tools mature and gain wider adoption, we can expect to see a significant shift in the way software is designed and built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Everything listed so far is predominantly software to be installed on a local machine. If two developers want to work on a function, they each would have to install it on their local machine and then sync their work through some type of repository. &lt;/p&gt;

&lt;p&gt;In contrast, there is a new set of products coming online that are in the cloud. There is no need to install Visual Studio or other local software. Programming is moving away from using local machines to write code, and these innovations will strongly impact developers. &lt;/p&gt;

&lt;p&gt;Consequently, it is crucially important to “stay ahead of the curve” and be aware of what is going to happen to programming in the near future. AI-based flexible tools such as Pieces will be able to go into the future, but static traditional tools will be left behind as time passes and the environment changes.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AI Transformation: How AI is Reshaping Enterprise Development</title>
      <dc:creator>Pieces 🌟</dc:creator>
      <pubDate>Mon, 21 Oct 2024 17:48:46 +0000</pubDate>
      <link>https://forem.com/get_pieces/ai-transformation-how-ai-is-reshaping-enterprise-development-1c34</link>
      <guid>https://forem.com/get_pieces/ai-transformation-how-ai-is-reshaping-enterprise-development-1c34</guid>
      <description>&lt;p&gt;AI has become a &lt;a href="https://pieces.app/blog/ai-upskilling-and-how-to-develop-essential-skills-for-the-ai-workforce" rel="noopener noreferrer"&gt;major focus for business owners&lt;/a&gt; as we clearly see that the technology is gaining traction. Foundry’s 2024 CIO Tech Priorities &lt;a href="https://foundryco.com/research/cio-tech-priorities/" rel="noopener noreferrer"&gt;study found that 89%&lt;/a&gt; of IT decision-makers are exploring, testing, or actively using AI technologies, a big jump from 72% in 2023.&lt;/p&gt;

&lt;p&gt;64% of these decision-makers expect AI and machine learning to reshape business operations in the next three to five years, a significant rise from 39% who thought that just a year ago.&lt;/p&gt;

&lt;p&gt;IT leaders see AI revolutionizing their organizations primarily through process automation, increased efficiency, and improved customer experiences. AI delivers benefits at a scale capable of driving transformation, not small improvements. Its impact has reached nearly every industry and is set to play a pivotal role in shaping the future of technology.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Exactly is AI Transformation?
&lt;/h2&gt;

&lt;p&gt;AI transformation (or sometimes referred to as AI digital transformation) is a strategic process where businesses use artificial intelligence in their operations, products, and services, driving innovation, efficiency, and growth. By leveraging a variety of &lt;a href="https://pieces.app/blog/ai-machine-interpretability-and-explainable-ai" rel="noopener noreferrer"&gt;AI models and technologies&lt;/a&gt;, organizations optimize workflows, make businesses more adaptive, and keep evolving.&lt;/p&gt;

&lt;p&gt;Through machine learning, deep learning, and technologies like computer vision, natural language processing, and generative AI, companies can develop systems that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate repetitive administrative tasks&lt;/li&gt;
&lt;li&gt;Modernize applications and IT processes through code generation&lt;/li&gt;
&lt;li&gt;Provide data-driven insights for better decision-making using advanced analytics&lt;/li&gt;
&lt;li&gt;Continuously improve accuracy and performance by "learning" from data&lt;/li&gt;
&lt;li&gt;Enhance customer experiences with personalization and AI chatbots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With rapid advancements in AI, embracing AI transformation is increasingly crucial for long-term business success. A report from the IBM Institute for Business Value, Augmented Work for an Automated, AI-Driven World, highlights that companies integrating AI into their transformation processes consistently outperform their competitors.&lt;/p&gt;

&lt;p&gt;Unlike simply applying new technologies to replicate current processes, AI transformation is a more comprehensive initiative. A robust AI strategy for business transformation can introduce entirely new business methods, boost productivity, and promote sustainable growth. However, scaling AI often requires shifts in business strategies and company culture to fully embrace the technology's potential.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is the AI Transformation Changing Businesses?
&lt;/h2&gt;

&lt;p&gt;AI is important to the future of business and changing conventional practices to introduce new levels of &lt;a href="https://pieces.app/blog/5-best-ai-tools-for-productive-development-in-2024" rel="noopener noreferrer"&gt;efficiency and innovation&lt;/a&gt;. With AI and machine learning, businesses can analyze large data volumes, spot patterns, and deliver recommendations to improve decision-making.&lt;/p&gt;

&lt;p&gt;This ability to process data faster than humans not only saves time, but also opens up new opportunities by enabling faster and more informed decisions.&lt;/p&gt;

&lt;p&gt;One good example is how AI is going to change customer interaction. For example, ChatGPT, powered by generative AI, will allow companies to respond promptly and precisely to customer queries, thus reaching higher satisfaction levels.&lt;/p&gt;

&lt;p&gt;AI-driven chatbots form one of the many ways in which this technology is used in reshaping operations, with applications even more general across a host of industries.&lt;/p&gt;

&lt;p&gt;Let’s dive into how AI is helping businesses maintain their competitive edge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data-Driven Insights&lt;/strong&gt;: Allow businesses to look through datasets faster and more accurately, using raw information to deliver actionable insights. Companies will be able to understand customer behavior better, optimize operations, and see market trends, which leads to more strategic decision-making.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: Efficiency is bolstered, with the automation of tasks by the use of AI technologies like RPA and Machine Learning, to such an extent that manual input is considerably reduced. On this count, it will free staff for high-value, creative tasks, innovating more inside the organization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalization&lt;/strong&gt;: AI-driven algorithms analyze customer data and offer personal experiences, from product recommendations to marketing messages. This approach enhances satisfaction and loyalty and increases revenue by tailoring interactions to individual preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictive Analytics&lt;/strong&gt;: Predicting upcoming trends by examining past data, allowing for a proactive approach to market changes, dangers, and possibilities. This enables companies to make proactive decisions that minimize risks and optimize growth opportunities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Customer Support&lt;/strong&gt;: Artificial intelligence virtual assistants and chatbots can manage customer service round the clock, offer fast responses, solve problems, and even finalize purchases. This improves customer experiences and lowers operational costs, reducing the need for human involvement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product Innovation&lt;/strong&gt;: Helps in making innovative concepts by checking out market demands, identifying deficiencies, and producing fresh concepts for new products. This results in shorter product development cycles, faster time-to-market, and a more robust competitive edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply Chain Optimization&lt;/strong&gt;: It makes supply chains easier to manage by forecasting demand, managing inventory efficiently, and improving logistics. Enhancements reduce costs and prevent disruptions, therefore improving general customer service and making supply chains more agile and responsive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Path to Digital Transformation and AI in Enterprises
&lt;/h2&gt;

&lt;p&gt;Turning AI from a novelty into a powerful tool that drives enterprise success requires a structured approach. The process starts by identifying clear business objectives and assembling a dedicated team of data experts and business leaders.&lt;/p&gt;

&lt;p&gt;These individuals must possess strong technical skills and be collaborative, detail-oriented, and capable of creating a comprehensive data ecosystem. &lt;/p&gt;

&lt;p&gt;Architects are essential for designing, connecting, and securing this system, while a dedicated team ensures smooth operations and monitoring. To turn AI from a buzzword into a valuable business asset, the following steps are critical:&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Business Objectives
&lt;/h3&gt;

&lt;p&gt;The journey begins with a thorough evaluation of the company’s goals, capabilities, scalability needs, and compliance requirements. AI must be aligned with these factors to deliver meaningful value from the outset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cultural and Ethical Integration
&lt;/h3&gt;

&lt;p&gt;Smooth adoption of AI is not just a technical challenge, there are also cultural and ethical issues to consider. Senior executives should be involved in brainstorming on where AI can assist. Also, ethical issues need to be considered, including how human judgment will oversee AI and how it would supplement a team’s work without displacing jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identifying Data Sources
&lt;/h3&gt;

&lt;p&gt;A crucial step is locating all potential data sources within the organization, whether they are linked or operate independently. This includes data from systems like CRMs, ERPs, customer service logs, emails, images, and other key records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a Comprehensive Data Repository
&lt;/h3&gt;

&lt;p&gt;Creating a structured data set that aligns with business goals is vital for training AI models. This requires careful planning around building a data lake, data warehouse, or data lakehouse, along with data migration, modernization, storage considerations, and ensuring that the data meets security and compliance standards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Training AI with Proprietary Data
&lt;/h3&gt;

&lt;p&gt;While using pre-trained models such as ChatGPT can be beneficial, the true value of AI emerges when it is trained on an organization’s proprietary data. This allows for the automation and augmentation of repetitive tasks, freeing employees to focus on more complex and value-driven activities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ethical and Environmental Challenges of AI Transformation
&lt;/h2&gt;

&lt;p&gt;While AI offers tremendous benefits, its adoption brings significant ethical challenges. One pressing concern is algorithmic bias. For example, facial recognition technologies, like those used by law enforcement, have faced criticism for racial biases that have led to false arrests. The&lt;a href="https://www.nist.gov/news-events/news/2019/12/nist-study-evaluates-effects-race-age-sex-face-recognition-software" rel="noopener noreferrer"&gt; National Institute of Standards and Technology (NIST)&lt;/a&gt; found that these algorithms were more likely to misidentify people of color, raising concerns about fairness and discrimination.&lt;/p&gt;

&lt;p&gt;Moreover, &lt;a href="https://pieces.app/blog/how-ai-energy-issues-can-affect-your-life" rel="noopener noreferrer"&gt;AI’s environmental impact&lt;/a&gt; cannot be ignored. Training large AI models requires enormous computational power, which consumes vast amounts of energy. According to a study by the University of Massachusetts, training a single deep-learning model can emit as much carbon dioxide as five cars over their entire lifetimes.&lt;/p&gt;

&lt;p&gt;Considering the environmental footprints, companies today are finding ways to offset the effects of AI by powering such data centers with renewable energy. For example, Google plans to run all its data centers on &lt;a href="https://www.datacenterknowledge.com/sustainability/google-targets-100-percent-renewable-energy-for-its-data-centers-by-2030" rel="noopener noreferrer"&gt;100% renewable energy by 2030&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Businesses must meet these challenges by showing responsibility when developing and deploying AI. Best practices for ethical AI include routine audits to prevent biases, focusing on privacy protection, and embracing sustainable computing. Microsoft is a good example and has started an AI for Earth program to use AI to solve environmental problems like climate change, biodiversity, and water sustainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare Your Business for AI Transformation With Pieces
&lt;/h2&gt;

&lt;p&gt;AI transformation continues to reshape industries and drive growth. Projections estimate AI could contribute &lt;a href="https://www.forbes.com/sites/greatspeculations/2019/02/25/ai-will-add-15-trillion-to-the-world-economy-by-2030/" rel="noopener noreferrer"&gt;$15.7 trillion to the global economy by 2030&lt;/a&gt;. As more AI technologies integrate into manufacturing, healthcare, and other industries, the impact on the economy will be big. Embracing AI early positions businesses to lead, driving new standards for excellence.&lt;/p&gt;

&lt;p&gt;We are prepared to assist you in integrating AI technology with customized solutions to boost productivity, enhance customer engagement, and promote ethical and sustainable AI utilization.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
