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:
parent
a5c8179fa8
commit
a11816e528
4 changed files with 368 additions and 0 deletions
70
workspace/skills/self-debug/SKILL.md
Normal file
70
workspace/skills/self-debug/SKILL.md
Normal 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.
|
||||||
71
workspace/skills/self-debug/scripts/debug.sh
Normal file
71
workspace/skills/self-debug/scripts/debug.sh
Normal 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
|
||||||
61
workspace/skills/self-debug/scripts/install-service-launchd.sh
Executable file
61
workspace/skills/self-debug/scripts/install-service-launchd.sh
Executable 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}"
|
||||||
166
workspace/skills/self-debug/scripts/install-service-systemd.sh
Executable file
166
workspace/skills/self-debug/scripts/install-service-systemd.sh
Executable 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"
|
||||||
Loading…
Add table
Reference in a new issue