AI Advanced (Agents)
Other formats: Markdown version
Using advanced AI techniques for data visualization with command-line agents including llms.txt, subagents, and markdown task files.Contents
Setup
For working with agents on the command line, the best place to get started is to create a new empty directory to do the work:
$ mkdir wolves_moose_viz $ cd wolves_moose_viz
Then, write a requirements.txt file which is a way to denote what libraries are in use for that project (which is useful both to humans and AI working on the project with you):
$ echo "sketchingpy[static]" >> requirements.txt
In addition to Sketchingpy, we will also grab Pillow:
$ echo "pillow" >> requirements.txt
Let's also add some tools that can help us write cleaner Python code. The first is pyflakes which is a tool to automatically check Python files for issues that would prevent that code from running.
$ echo "pyflakes" >> requirements.txt
Let's also add pycodestyle which can check code for issues that might make it more difficult for other engineers (or AI!) to read the code even if the code is valid:
$ echo "pycodestyle" >> requirements.txt
Finally, it is often a good idea to use a virtual environment to keep your system clean:
$ python3 -m venv venv $ source venv/bin/activate $ pip install -r requirements.txt
If that isn't your preference though, you can install directly:
$ pip install -r requirements.txt
Lastly, go ahead and install your AI assistant's command line tool in a different terminal window. For this tutorial, we will use Claude Code. Please follow the install instructions and then start the assistant to ensure you are logged in.
$ claude
It is recommended that you start Claude from within the
wolves_moose_viz directory.
Importing Knowledge
First, let's tell our AI assistant about the latest version of Sketchingpy which we will use to perform the drawing.
We will have two agents: one that completes a task as described
in a text file and another that checks the work of the first
agent and fixes any issues with styling using pycodestyle and
pyflakes. Depending on the assistant you are using, there might
be different instructions for adding a new agent. For Claude,
type /agents into the Claude Code interface and then
add a new agent. When asked for description, try something like
(note that, if you are not using a virtual environment, remove
the comment about using venv):
This implementer agent should be given the path to a markdown file describing a task to complete. It should read that file and complete the task using Sketchingpy. In order to prepare to do its work, it should also then read https://sketchingpy.org/llms-full.txt?v=20250915. After it has gathered all of the necessary information, it should make a detailed todo list to complete the task assigned. Finally, when done, it should update the task markdown file with a description of what it did and if it encountered any issues. It should run the script before concluding work but it does not need to worry about any style issues which will be addressed by a later agent. Please call this agent `implementer`. Please ask it to use the venv.
You can use the recommended agent settings including to put this in the project folder and the option to provide a description but let Claude make the actual agent file. After done, go ahead and add a second agent:
This checker agent should be given the path to a markdown file describing a task that was just worked on by another agent. It should read that file and investigate the specific changes made to complete that task which should have been done using Sketchingpy. In order to prepare to do its work, it should also then read https://sketchingpy.org/llms-full.txt?v=20250915. After it has gathered all of the necessary information, it should double check and, where appropriate, improve that work. In addition to running the script to ensure it executes successfully, it should also run pyflakes followed by pycodestyle to resolve any problems identified. Please have it update the task file with any issues encountered and report overall status to the agent which called it. Please call this agent `checker`. Please ask it to use the venv.
Having added these two agents, we now have kinda a "special purpose" AI assistant for implementing our ideas and another to check that work and improve it. We are splitting across these two so that neither on their own run out of room in their context. However, we are also giving them instructions they should follow each time to ensure success.
Scaffolding
Alright we should be ready to take our first step which is just to sketch out the outlines of the code we want to have draw this visualization. This will let us dictate the code's architecture and then fill it in progressively through subsequent tasks.
That objective in sight, consider that many AI assistants use
Markdown as a way to converse with the user and to internally
represent conversation. Indeed, agent files are just markdown!
Anyway, this is a lightweight way to structure text where
# means a top level header, ## means a
sub-header, ** means bold, the tick character means
code, and - means a list. There are resources to
learn how to use
Markdown with its many features but we will try to stick to
that for now. Specifically, we will make Markdown files each with
an individual task to be completed by our AI assistant.
All that in mind, let's make a new text file called
01_scaffold.md. Then read and place the following
inside:
# Table presenter Please create a class TablePresenter which takes in a sketch and which has stubbed methods and make docstrings for the given description (please use Google-style docstrings). Please have these simply throw NotImplementedError as we will fill them in as we go along. Please wrap at 80 characters. `__init__(self, sketch: sketchingpy.Sketch2D, data: list[dict])` Create a presenter to draw a visualization of the given data into the given sketch where records in the data look like {"year": 1980, "wolves": 50, "moose": 664}. `draw(self)` Draw the table with inline numbers with bars below that text. This will make a "tornado" chart which has one series (wolves) going left and the other (moose) going right. `_draw_top(self)` Draw the top row in black text for Year, Wolves, and Moose. `_draw_bottom(self)` Draw the bottom scale showing 0 to 500 for wolves and 0 to 1800 for moose. `_draw_body(self)` Draw year, wolves, and moose: - Year is on the left-most column centered. - Wolves is on the next column right aligned with bars going left. - Moose is on the far right column with left aligned but bars going right. `_get_y_pos(self, year: int) -> float` Get the y position corresponding to a year which is to be used in all series. `_get_length(self, series: str, count: int) -> float` Get the length of the bar to show for a series (wolves, moose) in pixels. Error thrown if the series is unknown or unsupported. `_get_color(self, series: str) -> str` Get the hex code to use for a given series (wolves, moose, year). Error thrown if the series is unknown or unsupported. `_get_max(self, series: str) -> float` Get the maximum count for a series (wolves, moose, year). Error thrown if the series is unknown or unsupported.
Go ahead and run Claude Code in the directory in a new chat and ask it something like the following:
Hello! Please have the implementer agent followed by the checker agent complete `01_scaffold.md`. However, please first learn more about Sketchingpy by reading https://sketchingpy.org/llms-full.txt?v=20250915.
This should have the AI assistant (the "coordinating agent") invoke the implementer agent first to write out the "first draft" of the code for our task. Then, when the implementer is done, it should invoke a checker agent to confirm the work. Note that implementer and checker both have their own context that is cleared after their invocation is done.
Data
Alright we have our scaffold here, let's bring in our data.
Following the same flow, let's make a 02_data.md:
# Load data In this next step, we should gather the required data and give our sketch access to that file. ## Gather data Please write a `wolvesMoose.csv` which contains the table at https://www.nps.gov/isro/learn/nature/wolf-moose-populations.htm with the columns year, wolves, and moose. ## Add to script Next, I would like to update our script to create a sketch that is 400 pixels wide by 650 pixels tall. Let's have this cleared with a background of color #FFFFFF. Let's then use the data layer to load the file `wolvesMoose.csv` which we will plan to pass into the class we just constructed along with the sketch. Please initialize an instance of our presenter and call its draw method before calling show on the sketch. We do not need interactivity on this sketch, it should just write to a `wolvesMoose.png` file. Please leave our class stubbed except in the constructor where we should save data and sketch as private instance variables. Please leave all of this in one script file. ## Notes If needed, prior task markdown files are present in this directory like `01_scaffold.md`.
As before, make sure to review the output and run the script. Note that, if your agent cannot access the information from the internet, you can copy from that table into spreadsheet software and provide it as a CSV directly.
Draw Outlines
Now that we have gotten through so much of that set up, we are in
a really good position to start doing some drawing. Let's make a
03_draw_outline.md:
# Draw outline Let's draw the axes for our sketch. Let's not use push / pop style or transform. This should be able to run just to draw the top and bottom of the sketch. ## Define constants At the top of the file after imports, let's define some constants: - Let's move our min and max values for wolves and moose to constants `MIN_WOLVES` (0), `MAX_WOLVES` (50), `MIN_MOOSE` (0), and `MAX_MOOSE` (2400). - Let's also define the colors we want to use as `BACKGROUND_COLOR` (#F0F0F0), `AXIS_COLOR` (#333333), `WOLVES_COLOR` (#D95F02), and `MOOSE_COLOR` (#7570B3). - Let's also use the `Public Sans` font (`FONT`) with `AXIS_SIZE` as 15px (15) and `BODY_SIZE` as 13px (13). - Let's also create some common spacing where the center of the first column for year is at x coordinate of 55px (`YEAR_X = 55`), the right align target for wolves is x coordinate of 245px (`WOLVES_X = 245`) and the align target for moose is 255px (`MOOSE_X = 255`). The width of the year column should be 50px (`YEAR_WIDTH`) and columns for wolves and moose should be 140px wide (but no constant needed since we will use the value inline). - Let's also use `WIDTH` and `HEIGHT` for sketch dimensions. - Let's say that the header and footer height (`AXIS_HEIGHT`) is 30px. Please group the constants together by similar function and sort alphabetically within the groups. ## Draw Next, let's go ahead and have `draw` start with calling `_draw_top` and `_draw_bottom`. Let's then take care of drawing both the top and bottom. ## Draw Top Let's then implement `_draw_top` to use axis color and to draw the text Year, Wolves, Moose at the center, right, and left coordinate for those three columns at 50px y coordinate from the top (with 20px reserved for title). Let's set those back vertically by 5px and have a horizontal line in axis color drawn spanning the extent of the column for each. Please use `AXIS_SIZE` for all text in this section. Text should use fill and no stroke while line should use no fill and stroke. _Note_: We should have text aligned to bottom 45px from the top and the line at 50px from the top of the sketch. ## Draw Bottom Let's then implement `_draw_bottom` to use axis color to draw a horizontal line 30px y from the bottom spanning the length of each of the three columns. Below the wolves and moose columns let's draw the min and max for each with: - 0 at the far right for wolves and max at the far left of the column span - 0 at the far left for moose and max at the far right of the column span Please use `BODY_SIZE` for text in the bottom. Text should use fill and no stroke (clear_stroke) while line should use no fill and stroke. Please draw the text color using the series specific color but use `AXIS_COLOR` for year. To do this, please implement `_get_color` and use the series name to get the color to use. ## Notes Please use `AXIS_HEIGHT` in both draw bottom and draw top. Also, if needed, prior task markdown files are present in this directory like `01_scaffold.md`. Please use the font at http://mooc.interactivedatascience.courses/support/web/PublicSans- Regular.otf.
Please execute this using the agent flow from before and check the result. Don't forget that your coordinating agent (the one you talk to directly) can also assist with edits like if something feels too cramped or not quite right! For example:
The 1980 seems to start too far down. Can you have the years start maybe 15 pixels higher?
Draw Years
Next, we can start filling in some of the elements from the data
themselves, starting with the years. Let's make a
04_draw_years.md:
# Draw years Next I would like to draw the years as the first part of _draw_series. ## Method Let's loop through the data we saved and get that year's y coordinate can be found by using _get_y_pos which should do a linear scaling from the top of the sketch to the bottom: `top_line + padding + (year - MIN_YEAR) / (MAX_YEAR - MIN_YEAR) * (bottom_line - top_line - 2 * padding)` where `top_line = 50`, `bottom_line = HEIGHT - AXIS_HEIGHT`, and `padding = 15`. Let's add MIN_YEAR and MAX_YEAR to represent that we are going from 1979 to 2019. ## Notes If needed, prior task markdown files are present in this directory like `01_scaffold.md`.
As before, give this a shot with your agents.
Draw Body
Alright let's get the rest of the data visible. In
05_draw_body.md:
# Draw body Next, I would like to implement `_get_length` and support drawing the labels and bars for the other two columns in `draw`. ## Method Let's start with `_get_length`. This should determine how wide the bar should be in pixels based on if the data point is for wolves or for moose. This should be a linear scale similar to our year scale but without the extra padding. Next, let's have the count for the species drawn at the y position for year and right aligned for wolves / left aligned for moose but in the color corresponding to the series. This will create the "tornado" that makes it easier to see one species trading off for the other. This should happen 5 pixels above the y position with bottom alignment. Then, at the y position itself, we should draw the rectangle using rect mode of corner where the rectangle is 3 pixels tall. ## Notes If needed, prior task markdown files are present in this directory like `01_scaffold.md`.
Continue and we should have a pretty good visualization! However, there are a few things we might want to tighten up since we have so much control over drawing. Once more, you might be seeing some things to improve and you can ask for help like:
I think we are almost there... the text is a bit high off of the corresponding bar. Can you bring them closer together and make the text right above the bars like 2px smaller (ensure the year text still uses the original size)? Let's also shift the bar down just maybe 2 pixels.
Finishing Touches
Finishing off this iterative process, let's highlight the max
values from both series and clean up the title / labeling.
Consider this 06_finish.md:
# Finish To finish off the visualization, we will add highlighting and clean up on spacing / labels. ## Highlighting Let's add a dictionary called `self._observed_maxes` which has entries for wolves and moose. Let's use the `max` function over a `map` to get the max observed value for each: `max(map(lambda x: int(x), map(lambda x: x['Wolves'], data)))`. Let's then, while iterating in `_draw_body` see if the value for the series matches the maximum value for the series and, if it is, let's have the fill for that text for just that data point for just that series alone be `AXIS_COLOR`. ## Spacing / labels Let's use 18px `AXIS_COLOR` text at the top of the sketch that says "Isle Royale National Park" as the title, drawn at y position 21 with center and bottom alignment. Let's also ensure the year labels are bottom aligned and 5 pixels above the y position for the year. ## Notes If needed, prior task markdown files are present in this directory like `01_scaffold.md`.
Take a look at the result and, if anything needs tweaks, be sure to use the coordinating agent to resolve anything final or use this as a chance to modify the code directly!
Reflection
This tutorial offers some techniques to engage AI on difficult or long tasks where you can break up elements into individual steps and, optionally, then ask subagents to work through those steps using repeatable structured actions all without exhausting context. In any case, this co-iteration with AI remains essential so that you can retain an active hand in ensuring a positive overall outcome, guiding assistants in their work as they go. Indeed, thanks to things like llms.txt, you can also invite assistants into tasks where they can use the latest information available.
Granted, what we discussed today may not necessarily be required for each task:
- A markdown document can be helpful when you have a lot to say or want to ensure there is an auditable trail of what you tried within larger projects. However, sometimes we just need a small script or edits.
- Perhaps llms.txt are more important for libraries which change often, are new, or may be less well known. That said, for well established libraries, this might not be helpful.
- Subagents really help keep larger efforts manageable. Even so, for smaller edits, this might not be worth the overhead.
Consider these as valuable ingredients as you craft your engagement with AI. However, as with all things in engineering, you have to decide what is best for the task at hand.
Citations
- S. Hickey, "llms.txt," llms.txt, 2025. [Online]. Available: https://llmstxt.org/
- Anthropic, "Claude," Anthropic PBC, 2025. [Online]. Available: https://claude.ai
- Anthropic, "Claude Code," Anthropic PBC, 2025. [Online]. Available: https://code.claude.com
- M. Cone, "Markdown Guide," Markdown Guide, 2025. [Online]. Available: https://www.markdownguide.org/
- A. Pottinger, "Sketchingpy," Sketchingpy Project, 2025. [Online]. Available: https://sketchingpy.org/
- Python Packaging Authority, "Requirements File Format," Python Software Foundation, 2025. [Online]. Available: https://pip.pypa.io/en/stable/reference/requirements-file-format/
- PyCQA, "Pyflakes," PyCQA, 2025. [Online]. Available: https://github.com/PyCQA/pyflakes
- PyCQA, "pycodestyle," PyCQA, 2025. [Online]. Available: https://pypi.org/project/pycodestyle/
- K. Reitz, "Virtual Environments," The Hitchhiker's Guide to Python, 2025. [Online]. Available: https://docs.python-guide.org/dev/virtualenvs/
- National Park Service, "Wolf and Moose Populations," Isle Royale National Park, 2025. [Online]. Available: https://www.nps.gov/isro/learn/nature/wolf-moose-populations.htm
- USWDS, "Public Sans," General Services Administration, 2026. [Online]. Available: https://public-sans.digital.gov
- Python Software Foundation and Python Contributors, "Python," Python Software Foundation, 2026. [Online]. Available: https://python.org
- A. Clark and Contributors, "Pillow," Pillow Project, 2025. [Online]. Available: https://python-pillow.org/