feat(self-debug): Add skill for agent health and diagnostics

Introduces the `self-debug` skill, a new capability designed to help users and the agent itself diagnose its own state. This provides a standardized way to inspect logs, service status, and configuration without requiring platform-specific knowledge.

This new skill includes:
- A cross-platform `debug.sh` helper script for Linux (systemd) and macOS (launchd).
- Key diagnostic actions: `logs`, `logs-errors`, `service-status`, and `config-safe`.
- Service installation scripts for both systemd and launchd to streamline setup.
- Comprehensive `SKILL.md` documentation with a clear action table and usage examples.

This feature simplifies troubleshooting and provides essential, built-in tools for maintaining agent health.
This commit is contained in:
picoclaw 2026-02-28 16:53:23 +00:00
parent a5c8179fa8
commit a11816e528
4 changed files with 368 additions and 0 deletions

View file

@ -0,0 +1,70 @@
---
name: self-debug
description: Tools for picoclaw to inspect its own health, logs, and configuration.
---
# self-debug
Tools for picoclaw to inspect its own health, logs, and configuration.
## Actions via `debug.sh`
This skill uses a helper script `scripts/debug.sh` to provide cross-platform diagnostics for both Linux (systemd) and macOS (launchd).
**Usage:** `exec "skills/self-debug/scripts/debug.sh [action] [service_name] [log_lines]"`
| Action | Description | Default Service | Default Lines |
|---|---|---|---|
| `logs` | Fetches recent logs for the agent service. | `$PICOCLAW_SERVICE_NAME` | 50 |
| `logs-errors`| Fetches only error logs for the agent service. | `$PICOCLAW_SERVICE_NAME` | 50 |
| `service-status`| Checks the status of the agent service. | `$PICOCLAW_SERVICE_NAME` | N/A |
| `config-status`| Shows the agent's configuration status (`picoclaw status`).| N/A | N/A |
| `config-safe` | Displays the config file with sensitive keys redacted. | N/A | N/A |
### Examples
- **Get latest logs:**
`exec "skills/self-debug/scripts/debug.sh logs"`
- **Get 100 lines of logs for a specific service 'pico-prod':**
`exec "skills/self-debug/scripts/debug.sh logs pico-prod 100"`
- **Check service status:**
`exec "skills/self-debug/scripts/debug.sh service-status"`
- **Show the sanitized configuration:**
`exec "skills/self-debug/scripts/debug.sh config-safe"`
## Installation - Linux
The agent can be installed as a systemd service using:
```bash
./scripts/install-service-systemd.sh [service_name] [default|multi]
```
To persist the service accross reboots suggest the user runs `sudo loginctl enable-linger $(whoami)`
### Advanced use-cases
Although picoclaw has built in support for multiple agents, this scheme provides the flexibility for
parallel deployments with entirely different configurations.
- Fixer - a second stable instance whose role is to be available to debug/monitor/fix the first.
- Blue/Green Stable/Canary pairings.
- Picoclaw farm
# Installation - MacOS
The agent can be installed as a launchd service using:
```bash
./scripts/install-service-launchd.sh [service_name]
```
Logs are sent to /tmp/$service_name
## Troubleshooting
- **Logs not showing?** Ensure the user is in the `systemd-journal` group: `sudo usermod -a -G systemd-journal $(whoami)`
- **Service inactive?** Use `systemctl --user start picoclaw`.
- **Workspace issues?** Use `picoclaw status` to verify the current workspace path.

View file

@ -0,0 +1,71 @@
#!/bin/sh
assert_is_identifier ()
{
# Use grep for robust, portable POSIX regex matching.
if ! echo "$1" | grep -qE '^[a-zA-Z0-9_.-]+$';
then
echo "Error: $2" >&2
return 1
fi
}
assert_is_number ()
{
# Use grep for robust, portable POSIX regex matching.
if ! echo "$1" | grep -qE '^[0-9]+$';
then
echo "Error: $2" >&2
return 1
fi
}
assert_is_identifier "$1" "Action selector must be a simple identifier" || exit 1
case "$1/$OSTYPE" in
*)
PICOCLAW_SERVICE_NAME="${2:-$PICOCLAW_SERVICE_NAME}"
assert_is_identifier "$PICOCLAW_SERVICE_NAME" "Service name must be a valid service identifier" || exit 1
;;&
logs*)
LOG_N="${3:-50}"
assert_is_number "$LOG_N" "Log lines parameter must be numeric" || exit 1
;;&
logs/linux*)
journalctl --user-unit ${PICOCLAW_SERVICE_NAME} --no-pager -n ${LOG_N}
;;
logs-errors/linux*)
journalctl --user-unit ${PICOCLAW_SERVICE_NAME} --no-pager -p 3 -n ${LOG_N}
;;
logs/darwin*)
tail -n ${LOG_N} ~/Library/Logs/${PICOCLAW_SERVICE_NAME}.log
;;
logs-errors/darwin*)
tail -n ${LOG_N} ~/Library/Logs/${PICOCLAW_SERVICE_NAME}.err.og
;;
service-status/linux*)
systemctl --user status ${PICOCLAW_SERVICE_NAME}
;;
config-status/*)
picoclaw status
;;
config-safe/*)
jq 'walk(if type == "object" then with_entries(if .key | ascii_downcase |
(contains("key") or contains("token") or contains("secret"))
then .value = "REDACTED" else . end) else . end)' "${PICOCLAW_CONFIG}"
;;
*)
echo "Usage: $0 logs|logs-errors|service-status|config-status|config-safe [service_name] [n_lines]"
exit 1
;;
esac

View file

@ -0,0 +1,61 @@
#!/bin/bash
create_plist_file() {
cat <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${service_name}</string>
<key>ProgramArguments</key>
<array>
<string>${exec_path}</string>
<string>agent</string>
<string>--config</string>
<string>${picoclaw_config}</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PICOCLAW_HOME</key>
<string>${picoclaw_home}</string>
<key>PICOCLAW_CONFIG</key>
<string>${picoclaw_config}</string>
<key>PICOCLAW_SERVICE_NAME</key>
<string>${service_name}</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>${picoclaw_home}</string>
<key>StandardOutPath</key>
<string>~/Library/logs/${service_name}.log</string>
<key>StandardErrorPath</key>
<string>~/Library/logs/${service_name}.err.log</string>
</dict>
</plist>
EOF
}
# Variable setup to match your Linux logic
service_name=${1:-picoclaw}
# macOS specific absolute paths
exec_path=$(which picoclaw)
picoclaw_home="$HOME/.${service_name}"
picoclaw_config="${picoclaw_home}/config.json"
plist_path="$HOME/Library/LaunchAgents/${service_name}.plist"
# Ensure directory exists
mkdir -p "${picoclaw_home}"
# Use the heredoc function to write the file
create_plist_file > "${plist_path}"
# Load it (macOS equivalent of systemctl enable --now)
echo "Enable using:"
echo "launchctl bootstrap gui/$(id -u) '${plist_path}'"
echo "Service ${service_name} installed at ${plist_path}"

View file

@ -0,0 +1,166 @@
#!/bin/bash
SERVICE_NAME=${1:-picoclaw}
TEMPLATE=${2:-default}
# Get the directory of the script and the repository root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 1. Detect picoclaw installation
echo "Detecting picoclaw installation..."
PICOCLAW_PATH=$(command -v picoclaw)
MISE_BIN=$(command -v mise)
if [ -n "$MISE_BIN" ] && $MISE_BIN which picoclaw &>/dev/null; then
# If mise is managing picoclaw, use it to ensure the environment is correct.
# This works whether picoclaw is installed via a tool spec or locally.
EXEC_START="$MISE_BIN exec -- picoclaw gateway"
echo " - Detected mise-managed picoclaw, using: $EXEC_START"
elif [ -n "$PICOCLAW_PATH" ]; then
# Use the absolute path if it's not managed by mise
EXEC_START="$PICOCLAW_PATH gateway"
echo " - Using binary path: $EXEC_START"
else
echo "Error: picoclaw not found. Please install it first."
exit 1
fi
service_template__default() {
local service_name="$1"
local exec_start="$2"
cat <<EOF
[Unit]
Description=${service_name^} Agent (picoclaw)
After=network.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=%h/.${service_name}
Environment="PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin"
Environment="PICOCLAW_SERVICE_NAME=${service_name}"
Environment="PICOCLAW_CONFIG=%h/.${service_name}/config.json"
Environment="PICOCLAW_HOME=%h/.${service_name}"
EnvironmentFile=-%h/.${service_name}/.env
ExecStart=${exec_start}
Restart=always
RestartSec=10
SyslogIdentifier=${service_name}
# Logging
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
EOF
}
service_template__multi() {
local service_name="$1"
local exec_start="$2"
cat <<EOF
[Unit]
Description=${service_name^} Agent Instance %i (picoclaw)
After=network.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=%h/.config/${service_name}-%i
Environment="PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin"
Environment="PICOCLAW_SERVICE_NAME=${service_name}@%i"
Environment="PICOCLAW_CONFIG=%h/.config/${service_name}-%i/config.json"
Environment="PICOCLAW_HOME=%h/.config/${service_name}-%i/"
EnvironmentFile=-%h/.config/${service_name}-%i/.env
ExecStart=${exec_start}
Restart=always
RestartSec=10
SyslogIdentifier=${service_name}-%i
# Logging
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
EOF
SERVICE_NAME="${SERVICE_NAME}@"
}
show_usage__default() {
local service_name="$1"
local output_file="$2"
cat <<EOF
Finalizing installation...
- Service installed to $output_file
To start the service:
$ systemctl --user enable ${service_name}
$ systemctl --user start ${service_name}
Manage the service with:
$ systemctl --user status ${service_name} # Check status
$ journalctl --user-unit ${service_name} -f # View logs (real-time)
Picoclaw self-debug skill is now wired to be aware of its own logs!
You can ask it:
'Check your logs for the last 15 minutes' or 'Why did you fail to connect?'
To ensure the service starts on boot and keeps running after logout:
$ sudo loginctl enable-linger $(whoami)
Note: If journalctl doesn't show logs, you may need to join the systemd-journal group:
$ sudo usermod -a -G systemd-journal $(whoami)
EOF
}
show_usage__multi() {
local service_name="${1}"
local output_file="$2"
cat <<EOF
Template installed successfully.
- Service installed to $output_file
To start an instance (e.g., 'test'):
$ systemctl --user enable ${service_name}@test
$ systemctl --user start ${service_name}@test
Manage the service with:
$ systemctl --user status ${service_name}@test # Check status
$ journalctl --user-unit ${service_name}@test -f # View logs (real-time)
To ensure the service starts on boot and keeps running after logout:
$ sudo loginctl enable-linger $(whoami)
Note: If journalctl doesn't show logs, you may need to join the systemd-journal group:
$ sudo usermod -a -G systemd-journal $(whoami)
EOF
}
OUTPUT_FILE="$HOME/.config/systemd/user/${SERVICE_NAME}.service"
# 2. Ensure the directory exists
echo "Ensuring the systemd user directory exists..."
echo " $ mkdir -p ${OUTPUT_FILE%/*}"
mkdir -p "${OUTPUT_FILE%/*}"
# 3. Generate service file
echo "Generating service file..."
if ! declare -f "service_template__$TEMPLATE" > /dev/null; then
echo "Error: Template '$TEMPLATE' not found."
exit 1
fi
"service_template__$TEMPLATE" "$SERVICE_NAME" "$EXEC_START" > "$OUTPUT_FILE"
# 4. Reload
echo " $ systemctl --user daemon-reload"
systemctl --user daemon-reload
"show_usage__$TEMPLATE" "${SERVICE_NAME%@}" "$OUTPUT_FILE"