• AI Assistant Use-Case: Performance Feedback

    At my company we give ourselves peer, upward, and self performance feedback.

    I don’t mind this practice because I understand the value that it can bring in terms of career development and team cohesion. But writing reviews can take quite a bit of time, especially for more senior members of the team. We do it twice a year in the spring and fall.

    This cycle, I looked into using generative AI assistants to help me write feedback. So far this process is much preferable to writing it without LLMs.

    Some thoughts and tips…

    • Provide data sources to the AI about what a colleague worked on
      • AI assistants can process large volumes of even unstructured data very quickly
      • Data exists on teammates’ contributions, though some might be easier to access
        • Tickets created and closed by individuals
        • Daily standup notes (we use Geekbot for this)
        • Slack discussions
        • Docs, sheets, wikis, other artifacts
        • Code and code reviews
        • Prior review feedback
      • Data helps ground the feedback and make it more specific
      • Also helps you to recall what somebody worked on
    • Prompt with relevant information
      • Like the colleague’s role/level, your role and relationship to them, how long they’ve worked on the team, their main contributions, etc
      • Your company’s expectations around performance in different capabilities at different levels (career matrix)
      • Your team’s goals
      • Your company’s examples of well-formed performance feedback
    • Provide subjective thoughts on the colleague
      • Recall how it felt working with them
      • What you thought some of their core strengths and weaknesses have been
      • I use voice mode with a stream of consciousness style discussion here to generate ideas and let the AI clean it up later
      • Ask the AI to challenge your beliefs against the data you have (eg does Sally really get bogged down in details or does her work history show she has a good balance with strategic planning?)
      • Work with the AI to uncover nuances in teammates’ performance that might be valuable to recognize
    • Create reusable tools for yourself and others
      • A Custom GPT in ChatGPT, links to collect data, or useful prompts can be shared

    I do think this approach risks turning into that meme where like I ask my chatbot to translate my bullets into a paragraph and my manager asks their chatbot to translate the paragraph back into bullets. But I’m trying to avoid that outcome by tuning the output to be concise.

    So far I feel like this process has helped me write better performance feedback more effectively. Maybe I can return to the same chats in the next cycle or mid cycle to track performance over time.


  • Poor Man's Computer Use with Execute Arbitrary AppleScript MCP Server

    Disclaimer: you should definitely not do this!

    I have been playing with Model Context Protocol and just realized you can get proof-of-concept-level computer use with very minimal code if you give the MCP client the ability to execute AppleScript and take screenshots. With just these rough tools you can coax some agentic behavior with a feedback loop.

    #!/usr/bin/env python3
    from mcp.server.fastmcp import FastMCP, Image
    import os
    import subprocess
    
    # Initialize the MCP server with a chosen name
    mcp = FastMCP("applescript_server", dependencies=["pyautogui", "Pillow"])
    
    @mcp.tool()
    def applescript_run(script: str) -> dict:
        """
        Executes arbitrary AppleScript via osascript.
    
        Args:
            script (str): The AppleScript code to execute.
    
        Returns:
            dict: A dictionary containing stdout, stderr, and the return code.
        """
        try:
            # Run the AppleScript command using osascript
            proc = subprocess.run(
                ['osascript', '-e', script],
                capture_output=True,
                text=True,
                check=False  # Allow non-zero exit codes to be returned in the response
            )
            return {
                "stdout": proc.stdout.strip(),
                "stderr": proc.stderr.strip(),
                "returncode": proc.returncode
            }
        except Exception as e:
            return {"error": str(e)}
    
    @mcp.tool()
    def take_screenshot() -> Image:
        """
        Take a screenshot using AppleScript to execute macOS' screencapture,
        forcing JPEG output. If the JPEG data exceeds 1MB, downscale the image
        to reduce its size.
        """
        import io, tempfile, os
        from PIL import Image as PILImage
    
        # Create a temporary file with a .jpg suffix.
        with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_file:
            tmp_filename = tmp_file.name
    
        # Use AppleScript to run the screencapture with JPEG output.
        script = f'do shell script "screencapture -t jpg -x \\"{tmp_filename}\\""'
        result = applescript_run(script=script)
    
        if result.get("returncode", 0) != 0:
            error_msg = result.get("stderr") or "Unknown error during screenshot capture"
            return {"error": f"Screenshot failed: {error_msg}"}
    
        try:
            # Open the captured image
            img = PILImage.open(tmp_filename)
    
            # Function to save image to JPEG buffer with compression.
            def save_to_buffer(image):
                buf = io.BytesIO()
                image.save(buf, format="JPEG", quality=60, optimize=True)
                return buf.getvalue()
    
            # Save image and check size.
            image_data = save_to_buffer(img)
            max_allowed = 1048576  # 1MB
    
            if len(image_data) > max_allowed:
                # Downscale the image to reduce size.
                new_size = (img.width // 2, img.height // 2)
                img = img.resize(new_size, PILImage.LANCZOS)
                image_data = save_to_buffer(img)
        except Exception as e:
            return {"error": f"Error processing screenshot file: {e}"}
        finally:
            try:
                os.remove(tmp_filename)
            except Exception:
                pass
    
        return Image(data=image_data, format="jpeg")
    
    if __name__ == '__main__':
        # Run the server using stdio transport so that it can be invoked by local MCP clients
        mcp.run(transport="stdio")
    

  • Please don’t disable paste

    It’s tax season. I unfortunately had to log in to the Philadelphia Revenue Department’s website. There are a lot of things that could be said about the city’s web UX, but I’ll save those thoughts for now. What I want to share is a plea.

    Please don’t disable paste. A pattern I saw and have seen on many bureaucratic sites is two fields like this:

    • Enter <some-info>
    • Re-enter <some info> to confirm

    With paste disabled on the second field. I guess that the developers don’t want users to paste a copied typo? But (A) I’m less likely to make a mistake if I’m copying and pasting data directly from another source, and (B) on the off chance the data is wrong, I’d rather pay the low probability price of a round trip for a backend error than the guaranteed price of tediously typing out some string. Or you could do client side mismatch detection.

    With mobile browsers this becomes even more important since I can even copy text directly from photos of physical docs thanks to built-in OCR, and typing on the phone keyboard is especially annoying. Allowing paste is a small thing that can improve user experience cheaply.


  • Blogging via Email

    I’d like to blog more, and specifically to write more short-form posts, but there’s sometimes an impedance mismatch between the maturity of a potential idea (low) and the effort required to create the corresponding post (high). It can feel like I have to get the F-35 out of the hangar for a trip to the corner.

    I was originally drawn to Jekyll for blogging (posts were in plain text, were portable, it was customizable, had plugins, easy to host, etc). But that setup meant publishing a post involved — beyond actually writing the article — committing it to git, SSHing to my VPS, syncing the changes, running a deployment command. Of course that could all be automated, but even needing my laptop handy was a small hurdle that required additional motivation. I still do like having my writing stored this way, and I have written workflow tooling previously.

    I recalled The Past, before certain social media sites had their attention extraction dials turned all the way up, and I was a regular user. The ability to make posts directly from my phone or any browser offered such a low barrier to entry that I could share ideas a lot more freely.

    Maybe it’s a good thing that there’s an effective filter saving the internet from more literal low-effort posts, hot takes, etc. But I want this blog to be a place where I can share ideas, even if some of those ideas might be trite or underdeveloped. Basically I am embracing quantity over quality, because if it’s easier to write more, then I will write more, which will improve my writing (and writing is thinking).

    So recently I wanted to make blogging easier while retaining Jekyll as the underlying blog generator. I like the idea of using email as an interface here, as it’s a cheap way to get rich text post editing and drafting. I wrote mail2blog, a small utility that reads an IMAP mailbox and creates posts from the emails. With a bit of extra scripting I can automatically publish these posts on a cron.

    Hopefully this encourages me to write more, but even if not, it was at least a fun weekend project.


  • Using an E-Ink Monitor: Part 2

    This is a follow up to my 2024 post about using the Dasung Paperlike HD-F e-ink monitor.


    DASUNG monitor with 3D printed stand hinges

    It’s spring again in Philadelphia, which means I’m dusting off my Dasung 13.3” Paperlike HD-F. This portable e-ink monitor allows me to work on my laptop outside, in full sunlight.

    Since last year I’ve made some changes to improve my experience with the monitor.

    Clearing the monitor screen programmatically

    The monitor suffers from ghosting, where an after-image of the screen contents persists faintly. This can be annoying and reduce legibility, especially as it builds up over time. There’s a physical button on the front of the monitor that resets/clears the screen. I was looking for a software solution to clear it so that I could keep my hands on the keyboard and not have to press the button, which nudges the monitor from its position resting on top of the laptop screen.

    In my previous post I reported that I couldn’t get Dasung’s PaperLikeClient software to work on my Macbook. That is still the case, but I discovered a way to clear the monitor using Lunar. With the Lunar CLI (which you can install via right clicking the GUI Lunar app menubar icon > Advanced features > Install CLI integration), you can clear the monitor using this command:

    lunar ddc PaperlikeHD 0x08 0x0603
    

    I put that into a Raycast script, so now clearing the screen is just a few keystrokes away.

    Addressing flickering

    A Reddit poster pointed out that Apple’s temporal dithering (FRC) causes some flickering on the Dasung monitor. I did notice this after they raised it, and I tried their suggested solution of using Stillcolor. Stillcolor does indeed turn off temporal dithering which resolved the flickering.

    Securing external monitor to laptop screen

    Last year, I had been using the Dasung monitor by basically resting it in front of the laptop’s built-in screen. This approach was less than ideal. First, the monitor would often slip and slide down over the keyboard, since there isn’t much lip to hold it up. Second, the monitor is rather heavy, and at certain angles it would make the laptop sreen fall open to its full extent. My temporary solution was to use a bag clip to hold the monitor in place. That only sort of solved the first problem, but it didn’t work that well.


    DASUNG monitor rested on top of laptop with bag clip
    My e-ink monitor setup circa 2024


    In the fall, I roped in my mechanical engineer friend to draft and 3D print some pieces to help secure the monitor in this arrangement. We worked together on developing some hinges to (a) hold the monitor in place and (b) support the laptop screen at a specific angle.


    hinge detail
    Detail of the 3D-printed hinge/holder


    After a couple iterations, he produced these small, adjustable hinges.

    • These feature a thumbnut to allow securing the laptop hinge at a specific angle, preventing the screen from falling fully open
    • They have a pronounced vertical support that the base of the e-ink monitor rests upon, holding it up
    • There’s also a slot to allow access to the ports


    detail of hinge in use
    Close-up of one of the hinges in use


    I’ve only had the opportunity to use the monitor with these hinges a couple times, but so far they’re solving the problem splendidly. I’m looking forward to many days of working from the roof 🕶️


    laptop with monitor rested on it and hinges
    The hinges in use, supporting the e-ink monitor