Appendix C

Pi Agent internal architecture

You do not need to understand Pi Agent's internals for everyday use. This appendix lifts the hood for the occasions when Chapter 22 is not enough: an unfamiliar error appears in the Logs tab, you want to debug a problem instead of waiting for the next release, or you simply wonder why a chat interface needs so many background services. No programming is required. By the end, you will understand what s6-overlay does, why nginx sits in front of pi-web, what belongs under /data/pi-agent/, how the Watchdog probe works, and how Skills are discovered. Think of it as the wiring diagram for your home: you may never need to touch it, but it helps to know where the main switch is.

Sources cited in this appendix (all public repositories): the add-on package at WOOWTECH/Woow_ha_pi_agent_add_on (config.yaml, Dockerfile, rootfs/etc/nginx/nginx.conf, CHANGELOG.md, and DOCS.md); the upstream pi-web project at agegr/pi-web (the HA add-on pins version 0.8.4; see that tag's package.json); and the agent SDK at earendil-works/pi. If anything below looks questionable, check those repositories. Their source files are more authoritative than this guide.

Why read this appendix?

To be clear, you will not need this appendix 99% of the time. Follow Chapter 2 to install Pi Agent and Chapter 3 to open it from the sidebar. If everything works, you no more need to understand the services behind it than you need to understand a compressor to use an air conditioner.

For the other 1%, the architecture becomes useful:

  • The Logs tab shows an unfamiliar error. Messages such as “nginx: [emerg] host not found in upstream,” “s6-rc: fatal: unable to start service pi-web,” or “video-tools-init exited with code 1” may not have Pi Agent-specific search results. This appendix explains what each service does and what its failure means.
  • You want to debug the problem yourself. Before entering the add-on container over SSH, you need to know its directories, processes, and logs.
  • You want to customize or contribute. Anyone forking Pi Agent, contributing upstream, or building a companion add-on needs to understand the complete pipeline.
  • You are simply curious. If you are the kind of person who opens a new device to see how it works, read on.
A note about versions: this appendix will age as the software changes. It describes the Pi Agent v0.13 series. If you are reading it on a much later release, such as v1.x, paths and service names may differ. Treat the directories actually present under /etc/services.d/ as authoritative; this appendix is a map, not a guarantee.

Architecture overview: the apartment-building analogy

A building provides a useful mental model for the layers of the system:

AnalogyComponentRole
The building Home Assistant (HA) operating system Provides the foundation, utilities, and network that its tenants—the add-ons—need.
The building manager HA Supervisor Installs and starts add-ons, checks their health, and restarts them when necessary.
The reception desk HA Ingress Authenticates visitors—your browser—and routes them to the appropriate add-on.
One apartment The Woow HA Pi Agent add-on container The subject of this appendix.
The occupants Processes inside the container nginx, pi-web, and video-tools-init each perform a different part of the work.
The household manager s6-overlay (process supervisor) Starts the processes, coordinates their order, and restarts long-running services when required.
The storeroom /data/pi-agent/ Holds valuable persistent data: conversations, keys, Skills, and video projects.

Keep two design principles in mind:

  1. Ingress is the only entry point

    Your browser cannot connect directly to Pi Agent's internal services. Every request passes through HA Ingress, which confirms that you are signed in as an HA administrator before forwarding the request to Pi Agent. This is why Chapter 3 emphasizes the automatically created sidebar button: that button opens the Ingress route.

  2. The container has several layers

    The Pi Agent add-on container does not run just one process. nginx receives and forwards traffic, pi-web is the main application, and video-tools-init is a one-time initialization worker. s6-overlay coordinates them in the background.

Key concept: an HA add-on is essentially a Docker container. Running several processes in one Pi Agent container departs from the textbook “one process per container” convention, but it is common for HA add-ons. s6-overlay exists to manage those processes cleanly within a single container.

The s6-overlay boot sequence

When HA Supervisor starts Pi Agent, events inside the container follow a defined sequence. s6-overlay (often shortened to s6) is the process supervisor that starts and manages the container's services.

Stage (see the note below)ServiceTypeWhat it doesTypical duration
1 s6-rc-init System initialization Starts s6 itself and prepares the internal signaling channels needed by the other services. < 1 second
2 video-tools-init One-time task (oneshot) Checks for the .video-tools-installed sentinel and verifies that Python 3 actually exists in the virtual environment. If either check fails, it downloads Playwright, edge-tts, pyyaml, mutagen, and Chromium (~720 MB total), installs them under /data/pi-agent/venv/ and /data/pi-agent/playwright-cache/, and then creates the sentinel. Failure is non-fatal: chat remains available even if initialization fails. 3–8 minutes on the first run; then < 1 second
3 nginx Long-running service Starts the reverse proxy on internal port 30142. Every request from HA Ingress reaches nginx first. < 1 second
4 pi-web Long-running service Starts the main Node.js/Next.js application—the interface in your browser—on internal port 30141. nginx forwards requests to it. 3–15 seconds

There is an important source-level detail behind that numbered overview: the video-tools-init oneshot is not listed in nginx or pi-web's dependencies.d. s6-overlay therefore starts all three services in parallel. While video-tools-init downloads 720 MB of dependencies, nginx and pi-web can already accept requests. During the initial 3–8 minutes, you can use chat even though the video pipeline is not yet ready. A comment at the start of rootfs/etc/s6-overlay/scripts/video-tools-init calls this “Parallel with pi-web.” The deliberate trade-off is an immediately available chat UI rather than making the whole interface wait for the video tools.

Supervisor also sends the Watchdog probe described below. If pi-web becomes stuck or too slow to respond, Supervisor restarts the entire container. s6-overlay then performs the same initialization again. Stage 2 normally exits in under a second, but only when both the sentinel and executable ${VENV}/bin/python3 are present. This double check prevents an interrupted first installation from being mistaken for a complete one.

Why separate services? They have different responsibilities and lifetimes: video-tools-init runs only when initialization is needed, nginx sits in front of the application, and pi-web is the application. Combining them would create one oversized, difficult-to-maintain program. s6-overlay manages these distinct roles independently.

nginx: the translator for HA Ingress

Why put nginx in front of a web application? In this container, nginx is both subtle and essential: it translates between HA Ingress and pi-web.

HA gives each add-on a URL like this:

https://homeassistant.local:8123/hassio/ingress/woow_ha_pi_agent/

Internally, pi-web understands only the root path (/); it does not know that HA has placed it under /hassio/ingress/woow_ha_pi_agent/. Without translation, a pi-web link such as /settings would send the browser to https://homeassistant.local:8123/settings, outside HA's route, and return a 404.

nginx performs that translation in several stages:

  1. Receive the request from HA Ingress

    HA sends the request to port 30142 in the Pi Agent container (ingress_port: 30142). Because nginx listens on that port, it receives the request first.

  2. Read the Ingress path from the request header

    HA supplies an HTTP header named X-Ingress-Path, with a value such as /api/hassio_ingress/A1b2C3d4e5F6g7H8. Its random identifier is 16–128 characters long and changes whenever HA restarts.

  3. Forward the request to pi-web

    nginx proxies the request to http://127.0.0.1:30141, pi-web's internal port, and receives pi-web's HTML response.

  4. Validate the header against an allowlist

    To prevent a forged X-Ingress-Path from injecting arbitrary content, the map $http_x_ingress_path $safe_ingress_path block validates it against ^/api/hassio_ingress/[A-Za-z0-9_-]{16,128}$. This expression comes directly from nginx.conf; it is not a guess. A nonmatching value becomes an empty string, making subsequent body rewrites no-ops and preventing XSS through a forged header. This defense was added in v0.7.0 for security-audit finding F-01.

  5. Rewrite absolute paths with sub_filter

    nginx uses sub_filter to rewrite outgoing HTML, CSS, and JavaScript byte by byte. It prefixes the Ingress path to href="/_next/, src="/_next/, href="/manifest, href="/icons/, CSS references such as url(/_next/, and the JSON-escaped \"/_next/ references in RSC flight payloads.

  6. Patch runtime JavaScript that sub_filter cannot reach

    This is the most intricate part of the configuration. The pi-web front-end bundle makes 40+ absolute-path /api/* calls through fetch, EventSource, and XMLHttpRequest. It also uses Next.js App Router RSC prefetches (?_rsc=<token>), history.pushState/replaceState, and links inserted at runtime by ReactDOM.preinit()/next/font as <link> elements. Static sub_filter rewriting cannot intercept these operations. nginx therefore injects JavaScript before </head> that monkey-patches window.fetch, EventSource, XMLHttpRequest.prototype.open, history.pushState/replaceState, Element.prototype.setAttribute, and the HTMLLinkElement/HTMLScriptElement/HTMLImageElement href/src setters, adding window.__INGRESS_PATH__ where needed. It also stubs navigator.serviceWorker.register with an empty registration because a PWA service worker is not useful under HA Ingress and would generate console errors. The history in CHANGELOG.md v0.5.0–v0.10.4 shows how difficult this compatibility layer has been.

Together, these layers make it possible for pi-web to remain completely unaware of HA Ingress. pi-web behaves like an ordinary Next.js application; nginx combines header rewriting, sub_filter path rewriting, and a client-side shim to fit it into HA's routing system. This design is called an Ingress shim: an adapter between incompatible interfaces.

Correction: an earlier version of this appendix said that the HA nginx configuration rewrote paths while the Podman version rewrote headers. That was inaccurate. The HA version does both: it changes Host to localhost and clears Origin (the same technique as the Podman version, added in v0.6.0 to fix a 403 from pi-web's isApiRequestAllowed()), then applies Ingress path rewriting and the client-side shim. The HA version adds two Ingress-specific layers; it does not perform an entirely different job.

/data/pi-agent/ directory map

Chapter 20 provides a short summary. The full map below shows what you may find after entering the add-on container:

PathContentsIncluded in backups?
Files under sessions/ with the .jsonl extension One JSONL file per conversation Session, with one message per line. DOCS.md promises only “one per conversation.” The upstream pi coding agent SDK determines whether and how files are grouped by working directory. For the exact structure, enter the container and run ls /data/pi-agent/sessions/ instead of relying on a guessed directory tree. Yes. These are the conversation records; the per-add-on mount is covered by HA backups.
models.json Providers, model names, and API keys configured in the Models panel. API keys are stored in plaintext, not encrypted. Permissions such as chmod 600 restrict which local user can read the file, but they do not protect a copied backup or a compromised owner account. See Chapter 6. Yes. You need this file when moving to another HA host, but its plaintext secrets also make every backup sensitive.
auth.json Refresh tokens for OAuth providers, such as Google Drive authorization; likewise protected with chmod 600. Yes.
home/pi-cwd-*/ Working directories created by the pi coding agent. Since v0.10.0, the container's HOME is pinned to /data/pi-agent/home, so worktrees created by the pi CLI under pi-cwd-* survive image upgrades. The upstream project controls the naming convention. DOCS.md guarantees only pi-cwd-*/; inspect the container rather than assuming a suffix such as pi-cwd-<date>. Yes, except that node_modules/ and .cache/ are listed in backup_exclude to keep backups smaller.
skills/<skill-name>/ Installed Skills—typically directories created with git clone from GitHub. See Chapter 14. Yes. Skill files are usually small.
rclone/rclone.conf rclone cloud authorization, including the Google Drive access token configured in Chapter 19. Yes.
projects/<project>/ Video-pipeline workspaces containing script/, clips/, segments/, voice/, and output/. These are the intermediate files listed in Chapter 17. Partly. final.mp4, script.md, and subtitles.srt are included; large directories such as clips/ and voice/ are usually excluded to avoid filling a backup.
venv/ The Python virtual environment containing edge-tts and other pip packages. No. HA backups exclude it because it can be downloaded again.
playwright-cache/ Playwright's downloaded Chromium binary, approximately 200 MB. No. It can also be downloaded again.
.video-tools-installed An empty sentinel file indicating that video-tools-init completed. The reset procedure in Chapter 18 removes this file so initialization can run again. Yes. It is tiny, although including it has little practical effect.
settings.json UI preferences, including the theme and default model. Yes.

Remember two principles:

  • Irreplaceable data lives under /data/pi-agent/. Sessions, models.json, Skills, and the rclone configuration form the core of an HA backup. Because key and token files contain plaintext secrets, protect exported backups accordingly.
  • Large, reproducible data such as venv and playwright-cache is deliberately excluded. Including the virtual environment would make each backup at least 500 MB larger. Instead, video-tools-init downloads these resources again after restoration.
Host-path caveat: possible host-side locations include /mnt/data/supervisor/addon_configs/<addon-slug>/ on HA OS and /usr/share/hassio/addon_configs/<addon-slug>/ on Supervised installations. The add-on source does not guarantee either path. Supervisor controls the mount, so it can vary by installation and version. If you must inspect the host, check ls /mnt/data/supervisor/addon_configs/ and ls /usr/share/hassio/addon_configs/ rather than assuming one. The safer approach is to discover the installed container name; do not blindly run docker exec -it addon_<hash>_woow_ha_pi_agent bash. After verifying the discovered name, inspect /data/pi-agent/ inside that container. The <hash> depends on the repository; b9cf5676_woow_ha_pi_agent is only an example, while local_woow_ha_pi_agent applies only to a local override. Never assume a fixed Docker or Podman container name.

How the Watchdog probe checks Pi Agent

As a long-running service, Pi Agent can become unresponsive because of a memory leak, infinite loop, or stalled external API. HA Supervisor's Watchdog detects services that still have a running process but no longer function correctly.

ItemDetails
Probe endpoint http://[HOST]:[PORT:30142]/api/home, copied directly from config.yaml's watchdog: field. Supervisor contacts port 30142, and nginx forwards the request to pi-web's /api/home. This endpoint was selected because, on a fresh installation, it is the only pi-web route that returns 200 without Session context: /api/home, as recorded in CHANGELOG v0.10.0.
How it is enabled config.yaml contains the complete URL: watchdog: "http://[HOST]:[PORT:30142]/api/home". It does not use watchdog: true. An earlier version of this appendix got that HA add-on-specific URL syntax wrong.
Frequency HA Supervisor determines the interval. No fixed value—30 seconds, 60 seconds, or otherwise—is guaranteed, and it may change between HA releases. To measure it on your system, inspect the timing of Watchdog entries in the add-on Logs tab.
Success condition An HTTP 200 response, regardless of its body. A timeout or non-200 status is a failure.
Action on failure Supervisor restarts the entire add-on container—the equivalent of docker restart addon_<hash>_woow_ha_pi_agent when that placeholder is replaced with the discovered name—and s6-overlay begins its sequence again. Do not assume the generated name.
Effect on Sessions Completed messages are retained. Conversation files are append-only JSONL: each message is appended and flushed to disk. A restart can at most interrupt the message still being written; the next opening continues from the last complete line.
Effect on the video pipeline If Watchdog restarts the container while pitch_video is running, clips/ may contain a partially written WebM file. There is roughly an 80% chance that the output is unusable, so rerun the job. This is one possible cause of the “video suddenly broke” symptom in Chapter 22.

Watchdog is a double-edged sword. Automatically recovering a stuck service is useful, but a healthy add-on that merely responds slowly—for example, while processing a very long conversation history—can be mistaken for a hang and restarted. In that situation, increase the timeout in config.yaml or temporarily disable Watchdog with the switch on the HA page.

Debugging clue: “Watchdog missed heartbeat, restarting” in the Logs tab means Supervisor initiated the restart. Seeing “s6-rc-init started” without a preceding crash also strongly suggests a Watchdog restart. An isolated event may not warrant investigation; a repeating pattern usually does.

How Skill discovery works

Chapter 14 describes a Skill as a work manual for the AI. This section explains how the AI may discover those manuals—and why a Skill installed with pi install may not appear until you start a new Session.

This section is an informed inference, not a source-code citation. Skill discovery is implemented in the earendil-works/pi SDK; the HA add-on repository merely installs that SDK, and its DOCS.md does not document the process. The outline below is inferred from the common SKILL.md + progressive disclosure pattern in the Claude/Anthropic ecosystem. The observation that a new Session is needed has been tested, but details of these six steps may differ upstream. To be 100% certain, read the source of @earendil-works/pi-coding-agent.

The likely process occurs whenever you select “New Conversation”:

  1. Create the Session

    pi-web creates a new Session file under sessions/.../*.jsonl.

  2. Scan the Skills directory

    pi-web scans /data/pi-agent/skills/*/SKILL.md and reads the SKILL.md file in each subdirectory.

  3. Parse the YAML frontmatter

    Each SKILL.md begins with YAML frontmatter like this:

    ---
    name: fridge_inventory
    description: Help the user inventory refrigerator items and identify food that is about to expire
    ---
    
    # Fridge inventory skill
    
    ...Details...

    pi-web reads only name and description at this point. It does not place the entire SKILL.md body into the AI's context; it initially supplies only the summary.

  4. Build an <available_skills> block

    The names and descriptions are combined into a list wrapped in XML tags:

    <available_skills>
    <skill name="fridge_inventory">
    Help the user inventory refrigerator items and identify food that is about to expire
    </skill>
    <skill name="pitch_video">
    Script-to-YouTube Uploadable Video Pipeline
    </skill>
    </available_skills>
  5. Add the block to the system prompt

    The <available_skills> block is placed near the start of the AI's system prompt, giving it a menu of capabilities available on this machine.

  6. Let the AI select a Skill when relevant

    For each request, the AI compares the task with that list. When it chooses a Skill, it uses a tool call to read the complete SKILL.md and follow the detailed instructions.

This explains why installing a new Skill requires a new Session: an existing Session still has the old system prompt. It also explains why the description in SKILL.md is so important. A vague description makes the Skill hard for the AI to select; a clear one makes it useful at the right time.

Key concept: providing summaries first and loading details only when needed is called progressive disclosure. It avoids filling the context window with every manual at once, which would consume tokens and distract the model.

The pi-web technology stack

pi-web, a Node.js application, provides the entire browser interface: the message composer, conversation list, model selector, and Skills panel. Its main components are:

ComponentPurposeNotes
Node.js 22 JavaScript runtime The Dockerfile installs node_22.x from NodeSource; pi-web's package.json specifies "node": ">=22.19.0".
Next.js 16.2.12 Web framework handling both the React front end and back-end API routes Version taken from the package.json at the pi-web 0.8.4 tag. It uses the App Router under app/; this appendix has not verified whether any legacy pages/ routes coexist, so consult the pi-web repository.
React ^19.2.4 Front-end UI component library Version taken from the same package.json. Server Components and Suspense are expected, but this appendix has not inspected pi-web's source to identify their use on individual routes.
pino Structured JSON logger It produces the JSON lines shown in the Logs tab. The DOCS.md description of log_level explicitly says it is “exported as LOG_LEVEL for pi-web's Next.js pino logger.” pino is not listed directly in pi-web 0.8.4's package.json; it arrives indirectly through Next.js or the upstream SDK.
@earendil-works/pi-coding-agent The coding-agent SDK that handles AI logic, tool calls, and Skill management pi-web 0.8.4 directly depends on @earendil-works/[email protected] and calls it in-process in the same Node process. It does not start a separate daemon; DOCS.md explicitly states that “there is no separate agent daemon.”
Internal port 30141 pi-web's listening port Exposed only inside the container; external requests arrive through nginx on 30142.
Ingress port 30142 nginx's listening port for HA Ingress Matches the setting in config.yaml: ingress_port: 30142.

Several technical details are worth noting:

  • The pi-coding-agent SDK runs in-process. If pi-web crashes, the agent crashes with it. Agent tool calls, such as file writes, use pi-web's permissions. Omitting a separate daemon avoids another layer of IPC.
  • There is no database. Conversations use JSONL files, settings use JSON files, and all state lives in the filesystem. This intentional simplification also makes backups straightforward.
  • Chat streams over SSE (Server-Sent Events). SSE delivers each part of an AI response as it is generated. This is why the nginx configuration must disable proxy_buffering; otherwise nginx would hold the response until generation finished and then deliver it all at once.
For upstream contributors: pi-web is at github.com/agegr/pi-web, the pi coding agent SDK is at github.com/earendil-works/pi, and Woow_ha_pi_agent_add_on packages them for HA. The add-on supplies the three-layer nginx shim, s6-overlay configuration, and video-tools-init; UI or agent-logic changes belong upstream. The add-on pins pi-web at 0.8.4, not @latest, through the Dockerfile setting ARG PI_WEB_VERSION=0.8.4. The nginx filters and shim depend on that release's _next chunk names and RSC prefetch behavior, so an upstream refactor can silently break Ingress. Review the matching nginx.conf before changing the pi-web version.

Why v0.13.0 moved API keys into the pi-web UI

v0.13.0 removed the API-key fields from the add-on Configuration page. If you upgrade from an older release, those fields disappear; Chapter 21 covers the procedure. This section explains the architectural reason.

Old architecture (through v0.12.x):

  1. Enter keys on the add-on Configuration page

    The form contained fields such as anthropic_api_key, openai_api_key, and glm_api_key.

  2. Supervisor writes /data/options.json

    When you saved, Supervisor serialized the form as JSON inside the container at /data/options.json.

  3. The startup script exports environment variables

    The s6-overlay startup script read /data/options.json, converted each field into an environment variable such as ANTHROPIC_API_KEY=..., and then executed pi-web.

  4. pi-web reads the environment

    At startup, pi-web obtained the key from values such as process.env.ANTHROPIC_API_KEY.

Problems with the old architecture:

  • Adding a provider required a new config.yaml schema and a new release; providers could not be added dynamically.
  • One provider could not have two keys, such as separate work and personal credentials, because each field held one value.
  • There was no Test button. A bad key was not discovered until the first real request returned 401.
  • Changing a key required restarting the add-on before the new environment variable took effect.

New architecture (v0.13.0 and later):

  1. Open pi-web's Models panel

    Provider configuration now lives in the browser interface, not on the add-on Configuration page.

  2. Select Add Provider, complete the form, and select Test

    Follow the procedure in Chapter 6. pi-web sends a real API request to validate the key and saves it only after a successful test.

  3. pi-web writes /data/pi-agent/models.json

    This structured JSON supports many providers, multiple keys per provider, and multiple models per key.

  4. The setting takes effect without an add-on restart

    The next Session uses the new configuration because pi-web rereads models.json whenever it opens a Session.

Benefits of the new architecture:

  • Providers, keys, and models can all be managed dynamically.
  • The Test action validates credentials in advance.
  • No add-on restart is required.
  • The UI can enable only selected models for a provider.

Migration path—hard cut, no auto-migration. v0.13.0 does not seed old keys into models.json. Both CHANGELOG.md and DOCS.md explicitly say “No auto-import”: there is no automatic migration. After upgrading, re-enter every provider key in the pi-web Models panel and select Test. Supervisor discards the old fields in options.json under the new schema. Existing models.json placeholders such as $GLM_API_KEY resolve to empty strings, so requests return 401 until you supply the keys again. This is the expected transitional state, not an automatic migration in progress.

Before upgrading: because there is no automatic migration, copy every key from the old Configuration page before upgrading and place it temporarily in a secure password manager—not an unencrypted note. After the upgrade, re-enter and test each key in Models. A provider whose key is missing will return 401 until you restore it. This is the one mandatory manual step when upgrading from v0.12.x to v0.13.x. See Chapter 21 for the complete procedure.

Advanced debugging inside the container

The add-on Logs tab is enough for routine debugging. For deeper investigation, you may need to enter the container itself. These are the most useful techniques:

  1. Enable debug logging

    On the add-on Configuration page, change log_level from info to debug. Appendix A describes the levels. Saving restarts the add-on, after which the Logs tab becomes much more verbose. Return the setting to info when you finish.

  2. Discover and enter the add-on container

    Install SSH & Web Terminal or use Advanced SSH. First list the running containers and capture the exact Pi Agent name reported by your host. Repository hashes and slugs vary, so never hard-code a Docker or Podman container name:

    CONTAINER_NAME="$(docker ps --format '{{.Names}}' | grep pi_agent | head -n 1)"
    printf '%s\n' "$CONTAINER_NAME"
    docker exec -it "$CONTAINER_NAME" bash

    Confirm that the printed value is the intended Pi Agent container before executing the final command. Once inside, use ls /data/pi-agent, cat /etc/nginx/nginx.conf, and ps aux to inspect its state. Treat the container filesystem as read-only unless you deliberately need to change persistent content under /data. Changes elsewhere disappear on restart.

  3. Inspect the nginx access log

    Inside the container:

    tail -f /var/log/nginx/access.log

    This shows each request's URL, status code, and response time. It is the quickest way to investigate whether HA is sending the expected X-Ingress-Path header.

  4. Test the edge-tts connection to Microsoft

    Inside the container:

    /data/pi-agent/venv/bin/python -m edge_tts --list-voices | head

    If this lists voices, the network connection works and the virtual environment is intact. If it does not, treat the video-tool installation as incomplete and use the reset procedure in Chapter 18.

  5. Test rclone access to Google Drive

    Inside the container:

    rclone --config /data/pi-agent/rclone/rclone.conf ls gdrive: | head

    If this lists Google Drive content, the authorization still works. An error such as token expired means you need to run rclone config reconnect, as described at the end of Chapter 19.

Warning: take care when using a shell on the HA host. A mistaken change there can affect the entire system. If you only need to inspect Pi Agent, discover its running container and enter it with docker exec rather than writing to the HA host filesystem. Run exit when you finish.

Architecture-level troubleshooting

This section does not repeat the user-facing symptoms in Chapter 22. It covers cases that require an understanding of the architecture:

  1. You cannot find /data/pi-agent/ on the HA host

    Symptom: you want to use File Editor or SSH to vi models.json on the HA host, but cannot find the path.
    Cause: an add-on's internal /data is not mounted as host /data. The add-on source specifies only a per-add-on mount and does not confirm a host path. Candidates include /mnt/data/supervisor/addon_configs/<slug>/ on HA OS and /usr/share/hassio/addon_configs/<slug>/ on Supervised installations, but Supervisor controls this location and it can change.
    Solution: if you must work from the host, use ls to check both candidate directories. A safer approach is to discover the container name first. Do not copy docker exec -it addon_<hash>_woow_ha_pi_agent bash literally; use the discovery procedure above, verify the printed name, enter that container, and then vi /data/pi-agent/models.json. Its internal /data is the add-on's mounted storage. An add-on restart is not normally required because pi-web rereads the file, although restarting after a consequential manual edit is safer.

  2. Ingress fails: nginx repeatedly returns 403

    Symptom: selecting Pi Agent in the sidebar produces a blank page or nginx's 403 page.
    Cause: nginx's allowlist rejected the X-Ingress-Path header. An HA update may have changed the format—for example, by adding a character not covered by _—or HA may not be sending the header.
    Solution: inspect tail -f /var/log/nginx/access.log inside the container. If the observed format has clearly changed, open an issue in Woow_ha_pi_agent_add_on and include the relevant details. As a temporary measure, restart HA so Ingress generates a new identifier.

  3. pi-web hangs: Watchdog keeps restarting the add-on

    Symptom: the Logs tab shows s6-rc-init started every few minutes, indicating repeated Supervisor restarts.
    Cause: pi-web's /api/home endpoint did not return 200 within 60 seconds. CPU exhaustion, insufficient memory, or a synchronous operation blocking Node's event loop may be responsible.
    Solution: (a) check whether the HA host's CPU or RAM is saturated—Pi 3 hardware and older mini PCs are especially vulnerable; (b) temporarily disable Watchdog on the HA add-on page to observe whether pi-web recovers; (c) enable log_level: debug and capture the final activity before each restart; and (d) if the cause remains unclear, open a GitHub issue with the logs.

  4. Video tools are incomplete even though the sentinel exists

    Symptom: the video pipeline fails because edge-tts or Playwright cannot be found.
    Cause: an earlier video-tools initialization was interrupted, leaving state that needs to be reset.
    Solution: on the add-on Configuration page, set reset_video_tools: true and save. The reset removes the sentinel so initialization can run again. This is a one-shot trigger: the add-on automatically returns the option to false after handling it, preventing a new 720 MB download at every start. See Chapter 18 for details.

  5. A Skill is installed, but the AI cannot see it

    Symptom: pi install reports success, but the AI never selects the Skill.
    Cause: the Skill discovery process runs when a new Session starts. The existing Session's system prompt does not include the new Skill.
    Solution: select “New Conversation.” If the Skill still does not appear, run ls /data/pi-agent/skills/ to confirm that its directory exists, then cat SKILL.md and verify that the frontmatter contains both name and description. A YAML error, including incorrect indentation, can cause the entire Skill to be skipped.

Frequently asked questions

May I fork and modify the Pi Agent add-on?
Yes. The Pi Agent add-on uses the MIT License, as stated by the Dockerfile label org.opencontainers.image.licenses="MIT"; the repository's LICENSE is authoritative. You may fork, modify, and publish it. Keep two points in mind: (1) upstream pi-web (agegr/pi-web, whose package.json says MIT) and pi-coding-agent (earendil-works/pi) are separate repositories; review each LICENSE before modifying its code. (2) Give your fork a distinct name, such as Woow_ha_pi_agent_myfork, rather than reusing the original woow_ha_pi_agent slug, which could confuse HA during upgrades.
Can Pi Agent run with Home Assistant Container rather than Supervised or HA OS?
No. Pi Agent is an HA add-on, and only HA OS and HA Supervised installations include Supervisor. Without Supervisor, there is no add-on system. Home Assistant Container—a standalone Docker deployment of homeassistant/home-assistant—contains only Core and cannot install add-ons. If you use Home Assistant Container and want Pi Agent, run Woow_podman_pi_agent_package instead. It provides an equivalent Podman/Docker deployment without the HA add-on system.
Are there K3s or Podman versions for Kubernetes or other hosts?
Yes. Three parallel packages are available: All three use the same upstream pi-web and pi-coding-agent projects but package them differently. The HA version adds the Ingress shim, the K3s version adds a Helm chart and Deployment, and the Podman version adds Quadlet systemd units. Their capabilities are broadly similar; choose the package that matches your environment.
Where should I contribute changes to pi-web or the agent?
Choose the repository that owns the relevant layer: Follow each upstream project's contribution process. For packaging changes, open an issue or pull request in the WOOWTECH repository. Discussing a proposed change in an issue first can prevent wasted effort.
Why is panel_admin: true? Can a non-admin HA account use Pi Agent?
Pi Agent's config.yaml sets panel_admin: true, so only HA administrator accounts can see the sidebar entry. This is deliberate: Pi Agent holds API keys and conversation history, and its AI can execute tools. Giving an ordinary household account access creates security and billing risks. If you choose to allow non-admin accounts, change config.yaml by setting panel_admin to false, but understand the consequences: shared credentials, mixed conversation histories, and unintended API spending. A compromise is to create a separate administrator account for each trusted person so actions remain attributable.
Does Pi Agent send my conversations to the cloud?
Conversation records are stored locally in sessions/*.jsonl on the HA disk, and no WOOWTECH telemetry uploads them. Generating an AI response still contacts the provider you selected. Messages sent to OpenAI, Anthropic, GLM, DeepSeek, or another cloud provider are governed by that provider's privacy policy. For a 100% local setup, configure a local LLM, such as Ollama or llama.cpp, as the provider. This choice is one benefit of the BYOK architecture.
Can I run two Pi Agent add-ons, one for work and one for home?
It is technically possible but operationally awkward. HA can install one copy from each of two repositories—for example, the official WOOWTECH package and your own fork—but they need distinct slugs and storage, and their side-panel names can conflict. A more practical arrangement is one Pi Agent with separate Sessions, as described in Chapter 8: use one Session for work and another for personal tasks. For complete isolation, use two HA hosts.
Will an upgrade, such as v0.12 to v0.14, delete my data?
Normally, no. An HA add-on upgrade replaces the container image—the application code—but does not remove content under /data. Sessions, models.json, and Skills remain. The v0.13 key transition described in Chapter 21 changed the configuration structure, but it is a hard cut: API keys do not migrate automatically and must be re-entered in Models. Persistent data is removed only when you explicitly delete it, for example by uninstalling and reinstalling the add-on. The reset_video_tools option resets only reproducible video-tool state and automatically returns to false after the one-shot reset.