mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
* Enhance debug-agent-prompts script and prompt rendering logic - Updated the `debug-agent-prompts.sh` script to always run `cargo build`, ensuring the latest binary is used and preventing stale binaries from affecting agent behavior. - Modified the output directory handling to wipe and recreate it at the start of each run, ensuring a clean snapshot of the current agent set. - Enhanced the `render_subagent_system_prompt` function to unconditionally inject `PROFILE.md`, ensuring it is included even when identity information is omitted, thus improving personalization for agents like `welcome`. - Added tests to verify the correct injection of `PROFILE.md` under various conditions, ensuring robust functionality and preventing regressions in prompt rendering. * Enhance debug-agent-prompts script to utilize the currently-logged-in user's workspace - Updated the `debug-agent-prompts.sh` script to point to the real user's workspace, ensuring onboarding-generated files like `PROFILE.md` are included in the dump. - Improved workspace resolution logic to prioritize the active user's workspace, falling back to default paths as necessary. - Added error handling for cases where the workspace is not found, providing clear guidance for users to complete onboarding or specify a different workspace. - Enhanced output to include the presence state of `PROFILE.md`, improving visibility into the onboarding process. * Add profile inclusion for user-facing agents to enhance personalization - Updated agent TOML configurations for orchestrator, trigger reactor, trigger triage, and welcome agents to include user profile data by setting `omit_profile = false`. This allows agents to personalize interactions based on user context derived from `PROFILE.md`. - Refactored the `AgentDefinition` struct to include a new `omit_profile` field, ensuring that agents can opt-in to utilize user profile information. - Enhanced prompt rendering logic to conditionally inject `PROFILE.md` based on the `omit_profile` flag, improving the relevance and personalization of agent responses. - Updated tests to verify the correct behavior of profile inclusion across various agents, ensuring robust functionality and preventing regressions. * Implement `omit_profile` flag in `AgentBuilder` and `Agent` for profile management - Added a new `omit_profile` field to the `AgentBuilder` struct, allowing agents to specify whether to include user profile data in their responses. - Updated the `Agent` struct to mirror the `omit_profile` flag, ensuring that the profile inclusion logic is consistent across agent instances. - Enhanced the `omit_profile` method in `AgentBuilder` to facilitate the configuration of this flag during agent construction. - Adjusted the default behavior to omit profiles for legacy agents while allowing opt-in for specific agents that require user context. - Updated documentation to clarify the purpose and usage of the `omit_profile` flag in agent definitions. * Implement profile management enhancements in Agent and prompt rendering - Introduced an `omit_profile` flag in the `AgentBuilder` and `Agent` to control the inclusion of user profile data in responses, defaulting to true for legacy paths. - Updated the `Agent` struct to utilize the `omit_profile` flag, ensuring consistent profile inclusion logic across agent instances. - Enhanced prompt rendering logic to conditionally include or exclude `PROFILE.md` based on the `omit_profile` setting, improving personalization for user-facing agents. - Added tests to verify the correct behavior of profile inclusion and omission across various scenarios, ensuring robust functionality and preventing regressions. * feat(prompt): per-agent MEMORY.md injection with 2000-char cap Add `omit_memory_md` to `AgentDefinition` (mirror of `omit_profile`) and inject `MEMORY.md` alongside `PROFILE.md` in both the main and sub-agent render paths. Both user-specific files are capped at `USER_FILE_MAX_CHARS = 2_000` (~1000 tokens each) via a new `inject_workspace_file_capped` helper so growing on-disk files can't balloon the system prompt. Opt-in on the same four user-facing agents (welcome, orchestrator, trigger_triage, trigger_reactor). Narrow specialists leave it at the `true` default. KV-cache contract is documented on the flag, the injection sites, and the capped helper: rendered bytes are frozen per session, and mid-session writes only surface on the next session. Pinned with a new `rendered_subagent_system_prompt_is_byte_stable_across_repeat_calls` test plus coverage for injection / opt-out / 2000-char cap. * refactor(tests): clean up test formatting and improve readability - Removed unnecessary line breaks and adjusted indentation in test files for better consistency and clarity. - Reformatted the `RewardsCouponSection` test to enhance readability and maintain a uniform style across test cases. - Ensured that all test cases align with the updated formatting standards, improving overall maintainability. * feat(debug-agent-prompts): enhance output directory validation and canonicalization - Improved the `debug-agent-prompts.sh` script to validate and canonicalize the output directory (`OUT_DIR`) before performing any file operations. - Added checks to reject relative paths and ensure the output directory is an absolute path, preventing potential catastrophic deletions. - Implemented a `canonicalize` function that uses `realpath` or `readlink` to resolve paths, with a fallback to Python for compatibility on barebones systems. - Enhanced error handling to provide clear feedback when the output directory cannot be validated or canonicalized. - Ensured that the script operates on the canonicalized path for all subsequent commands, maintaining consistency and safety. * style(turn): single-line rustfmt for redacted log branch
285 lines
11 KiB
Bash
Executable File
285 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# debug-agent-prompts.sh — Dump the exact system prompt the context engine
|
|
# would produce for every built-in agent (plus the main / orchestrator
|
|
# agent), so prompt-engineering changes can be reviewed in one place.
|
|
#
|
|
# Each prompt is written to a numbered file under the output directory
|
|
# along with a side-car `.meta.txt` containing the metadata banner
|
|
# (agent id, model, tool count, cache boundary, …) that the CLI prints
|
|
# to stderr. Useful workflow:
|
|
#
|
|
# bash scripts/debug-agent-prompts.sh
|
|
# diff -u prompts.before/skills_agent.md prompts.after/skills_agent.md
|
|
#
|
|
# When run with `--stub-composio` (the default) the dumper injects the
|
|
# five Composio meta-tools into the registry even on machines that are
|
|
# not signed in, so `skills_agent` always renders the full Composio
|
|
# surface. Pass `--no-stub-composio` to see the raw on-disk state instead
|
|
# (useful for sanity-checking the unauthed onboarding path).
|
|
#
|
|
# The dumper runs against the currently-logged-in user's workspace
|
|
# (`$OPENHUMAN_WORKSPACE`, falling back to `~/.openhuman/workspace`) so
|
|
# onboarding-generated files like `PROFILE.md` appear in the dump. Export
|
|
# `OPENHUMAN_WORKSPACE=<path>` before running if you want to target a
|
|
# different workspace.
|
|
#
|
|
# Usage:
|
|
# bash scripts/debug-agent-prompts.sh [--out <dir>] [--no-stub-composio] [--with-tools] [-v]
|
|
#
|
|
# The output directory is wiped and recreated at the start of each run
|
|
# so the snapshot only reflects the current agent set — stale files from
|
|
# an earlier run cannot hide a regression.
|
|
#
|
|
# Defaults:
|
|
# --out ./prompt-dumps (deleted + recreated each run)
|
|
# --stub-composio ON (override with --no-stub-composio)
|
|
# --with-tools OFF (pass to also list each agent's tool names)
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Locate repo root + binary ─────────────────────────────────────────────
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
BIN="${REPO_ROOT}/target/debug/openhuman-core"
|
|
|
|
# Always run `cargo build` — it no-ops when the binary is already
|
|
# up-to-date, and re-links quickly when it isn't. The old `-x` existence
|
|
# check let a stale debug binary survive across agent-registry changes
|
|
# (e.g. new entries in `agents::BUILTINS`), which made this script
|
|
# silently skip newly added agents like `welcome`.
|
|
echo "[debug-agent-prompts] building openhuman-core (no-op if up-to-date) …" >&2
|
|
( cd "${REPO_ROOT}" && cargo build --manifest-path Cargo.toml --bin openhuman-core >&2 )
|
|
|
|
# ── Parse flags ───────────────────────────────────────────────────────────
|
|
OUT_DIR=""
|
|
STUB_COMPOSIO=1
|
|
WITH_TOOLS=0
|
|
VERBOSE_FLAG=()
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--out)
|
|
if [[ -z "${2-}" ]] || [[ "${2-}" == -* ]]; then
|
|
echo "[debug-agent-prompts] missing value for --out" >&2
|
|
exit 64
|
|
fi
|
|
OUT_DIR="$2"
|
|
shift 2
|
|
;;
|
|
--stub-composio)
|
|
STUB_COMPOSIO=1
|
|
shift
|
|
;;
|
|
--no-stub-composio)
|
|
STUB_COMPOSIO=0
|
|
shift
|
|
;;
|
|
--with-tools)
|
|
WITH_TOOLS=1
|
|
shift
|
|
;;
|
|
-v|--verbose)
|
|
VERBOSE_FLAG=(-v)
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
sed -n '2,30p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "[debug-agent-prompts] unknown flag: $1" >&2
|
|
exit 64
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "${OUT_DIR}" ]]; then
|
|
OUT_DIR="${REPO_ROOT}/prompt-dumps"
|
|
fi
|
|
|
|
# ── Validate & canonicalize OUT_DIR before `rm -rf` ─────────────────────
|
|
# The output directory is wiped at the start of each run. Literal string
|
|
# matching against "/" / $HOME / $REPO_ROOT is not enough on its own:
|
|
# trailing slashes, ".", "..", or symlinked paths can slip past and
|
|
# trigger `rm -rf` on a sensitive target. So:
|
|
#
|
|
# 1. Reject obviously bad inputs up-front ("", ".", "..", relative).
|
|
# 2. Canonicalize OUT_DIR and REPO_ROOT via `realpath` (falling back
|
|
# to python when realpath is unavailable on barebones macOS).
|
|
# 3. Match the canonicalized form against the disallow list.
|
|
# 4. Only then `rm -rf` the canonicalized path.
|
|
case "${OUT_DIR}" in
|
|
"" | "." | "..")
|
|
echo "[debug-agent-prompts] refusing to wipe --out='${OUT_DIR}' (relative/empty)" >&2
|
|
exit 64
|
|
;;
|
|
esac
|
|
if [[ "${OUT_DIR}" != /* ]]; then
|
|
echo "[debug-agent-prompts] --out must be an absolute path (starts with '/'), got '${OUT_DIR}'" >&2
|
|
exit 64
|
|
fi
|
|
|
|
canonicalize() {
|
|
local p="$1"
|
|
# `realpath` is GNU + modern macOS (coreutils), and `readlink -f` on
|
|
# Linux. Try both; if neither resolves the path (target missing) we
|
|
# fall back to python3, which handles symlinks even for non-existent
|
|
# leaves via `os.path.realpath`.
|
|
if command -v realpath >/dev/null 2>&1; then
|
|
realpath -m -- "${p}" 2>/dev/null && return 0
|
|
fi
|
|
if command -v readlink >/dev/null 2>&1 && readlink -f / >/dev/null 2>&1; then
|
|
readlink -f -- "${p}" 2>/dev/null && return 0
|
|
fi
|
|
python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "${p}"
|
|
}
|
|
|
|
resolved_out="$(canonicalize "${OUT_DIR}")"
|
|
resolved_repo="$(canonicalize "${REPO_ROOT}")"
|
|
resolved_home="$(canonicalize "${HOME}")"
|
|
|
|
if [[ -z "${resolved_out}" ]]; then
|
|
echo "[debug-agent-prompts] failed to canonicalize --out='${OUT_DIR}'" >&2
|
|
exit 64
|
|
fi
|
|
case "${resolved_out}" in
|
|
"/" | "${resolved_home}" | "${resolved_repo}")
|
|
echo "[debug-agent-prompts] refusing to wipe --out (resolves to ${resolved_out})" >&2
|
|
exit 64
|
|
;;
|
|
esac
|
|
|
|
# Use the canonicalized path from here on so every subsequent command
|
|
# (rm, mkdir, per-agent dump writes) operates on the same resolved
|
|
# target — no symlink window between validation and deletion.
|
|
OUT_DIR="${resolved_out}"
|
|
rm -rf "${OUT_DIR}"
|
|
mkdir -p "${OUT_DIR}"
|
|
|
|
# Point the binary at the real, currently-logged-in user's workspace so
|
|
# onboarding-generated files like `PROFILE.md` appear in the dump. A
|
|
# throwaway mktemp workspace would silently hide them.
|
|
#
|
|
# Resolution order (matches what the desktop app does):
|
|
# 1. $OPENHUMAN_WORKSPACE if explicitly exported.
|
|
# 2. ~/.openhuman/users/<active_user_id>/workspace, where active_user_id
|
|
# is read from ~/.openhuman/active_user.toml. This is the path the
|
|
# Tauri app writes PROFILE.md into after onboarding.
|
|
# 3. ~/.openhuman/workspace as a last-resort fallback for pre-multi-user
|
|
# installs.
|
|
#
|
|
# The binary auto-seeds SOUL.md/IDENTITY.md/HEARTBEAT.md when they are
|
|
# missing, so pointing at the real workspace is read-mostly — the only
|
|
# writes are the same self-healing writes that happen on every normal
|
|
# agent run.
|
|
if [[ -n "${OPENHUMAN_WORKSPACE:-}" ]]; then
|
|
WORKSPACE="${OPENHUMAN_WORKSPACE}"
|
|
else
|
|
ACTIVE_USER_TOML="${HOME}/.openhuman/active_user.toml"
|
|
ACTIVE_USER_ID=""
|
|
if [[ -f "${ACTIVE_USER_TOML}" ]]; then
|
|
# Extract `user_id = "…"` without pulling in a TOML parser — the file
|
|
# is one or two lines and the shape is stable (config/ops.rs writes
|
|
# it with a plain quoted string).
|
|
ACTIVE_USER_ID="$(sed -n 's/^[[:space:]]*user_id[[:space:]]*=[[:space:]]*"\([^"]*\)"[[:space:]]*$/\1/p' "${ACTIVE_USER_TOML}" | head -n 1)"
|
|
fi
|
|
if [[ -n "${ACTIVE_USER_ID}" && -d "${HOME}/.openhuman/users/${ACTIVE_USER_ID}/workspace" ]]; then
|
|
WORKSPACE="${HOME}/.openhuman/users/${ACTIVE_USER_ID}/workspace"
|
|
else
|
|
WORKSPACE="${HOME}/.openhuman/workspace"
|
|
fi
|
|
fi
|
|
|
|
if [[ ! -d "${WORKSPACE}" ]]; then
|
|
echo "[debug-agent-prompts] workspace not found: ${WORKSPACE}" >&2
|
|
echo "[debug-agent-prompts] complete onboarding in the app first, or export OPENHUMAN_WORKSPACE=<path>." >&2
|
|
exit 66
|
|
fi
|
|
|
|
if [[ -f "${WORKSPACE}/PROFILE.md" ]]; then
|
|
PROFILE_STATE="present"
|
|
else
|
|
PROFILE_STATE="MISSING (onboarding enrichment has not run)"
|
|
fi
|
|
|
|
echo "[debug-agent-prompts] output dir : ${OUT_DIR}" >&2
|
|
echo "[debug-agent-prompts] workspace : ${WORKSPACE}" >&2
|
|
echo "[debug-agent-prompts] PROFILE.md : ${PROFILE_STATE}" >&2
|
|
echo "[debug-agent-prompts] stub composio: $([[ ${STUB_COMPOSIO} -eq 1 ]] && echo on || echo off)" >&2
|
|
echo >&2
|
|
|
|
# ── Discover agent ids from `agent list --json` ───────────────────────────
|
|
# `mapfile` is bash 4+, but macOS ships bash 3 — use a portable
|
|
# read-while-IFS loop instead so the script works out of the box on a
|
|
# vanilla `/bin/bash`.
|
|
AGENT_LIST_JSON="$("${BIN}" agent list --workspace "${WORKSPACE}" --json 2>/dev/null)"
|
|
AGENT_IDS=()
|
|
while IFS= read -r line; do
|
|
[[ -n "${line}" ]] && AGENT_IDS+=("${line}")
|
|
done < <(printf '%s' "${AGENT_LIST_JSON}" | python3 -c '
|
|
import json, sys
|
|
for entry in json.load(sys.stdin):
|
|
aid = entry.get("id", "")
|
|
# The synthetic `fork` definition replays the parent verbatim and
|
|
# has no standalone prompt — skip it.
|
|
if aid and aid != "fork":
|
|
print(aid)
|
|
')
|
|
|
|
# Always include the main / orchestrator prompt as the first dump.
|
|
TARGETS=("main" "${AGENT_IDS[@]}")
|
|
|
|
# ── Build common dump-prompt flag list ────────────────────────────────────
|
|
DUMP_FLAGS=(--workspace "${WORKSPACE}")
|
|
if [[ ${STUB_COMPOSIO} -eq 1 ]]; then
|
|
DUMP_FLAGS+=(--stub-composio)
|
|
fi
|
|
if [[ ${WITH_TOOLS} -eq 1 ]]; then
|
|
DUMP_FLAGS+=(--with-tools)
|
|
fi
|
|
if [[ ${#VERBOSE_FLAG[@]} -gt 0 ]]; then
|
|
DUMP_FLAGS+=("${VERBOSE_FLAG[@]}")
|
|
fi
|
|
|
|
# ── Dump every target ─────────────────────────────────────────────────────
|
|
INDEX=0
|
|
SUMMARY=""
|
|
for AGENT in "${TARGETS[@]}"; do
|
|
INDEX=$((INDEX + 1))
|
|
SAFE_NAME="$(printf '%s' "${AGENT}" | tr -c 'A-Za-z0-9._-' '_')"
|
|
PROMPT_PATH="${OUT_DIR}/${INDEX}_${SAFE_NAME}.md"
|
|
META_PATH="${OUT_DIR}/${INDEX}_${SAFE_NAME}.meta.txt"
|
|
|
|
printf '[debug-agent-prompts] %-20s → %s\n' "${AGENT}" "${PROMPT_PATH}" >&2
|
|
if "${BIN}" agent dump-prompt --agent "${AGENT}" "${DUMP_FLAGS[@]}" \
|
|
> "${PROMPT_PATH}" 2> "${META_PATH}"; then
|
|
LINES="$(wc -l < "${PROMPT_PATH}" | tr -d ' ')"
|
|
TOOL_COUNT="$(grep -E '^tool_count:' "${META_PATH}" | awk '{print $2}')"
|
|
SKILL_COUNT="$(grep -E '^skill_tools:' "${META_PATH}" | awk '{print $2}')"
|
|
SUMMARY+="$(printf '%-20s lines=%-5s tools=%-4s skill=%-4s\n' \
|
|
"${AGENT}" "${LINES}" "${TOOL_COUNT:-?}" "${SKILL_COUNT:-?}")
|
|
"
|
|
else
|
|
echo "[debug-agent-prompts] ✘ failed to dump ${AGENT} (see ${META_PATH})" >&2
|
|
SUMMARY+="$(printf '%-20s FAILED — see %s\n' "${AGENT}" "${META_PATH}")
|
|
"
|
|
fi
|
|
done
|
|
|
|
# ── Write a summary index file alongside the dumps ────────────────────────
|
|
SUMMARY_PATH="${OUT_DIR}/SUMMARY.txt"
|
|
{
|
|
echo "OpenHuman agent prompt dump summary"
|
|
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
echo "Workspace: ${WORKSPACE}"
|
|
echo "Stub composio: $([[ ${STUB_COMPOSIO} -eq 1 ]] && echo on || echo off)"
|
|
echo
|
|
echo "${SUMMARY}"
|
|
} > "${SUMMARY_PATH}"
|
|
|
|
echo >&2
|
|
echo "[debug-agent-prompts] done — ${INDEX} prompts dumped" >&2
|
|
echo "[debug-agent-prompts] summary : ${SUMMARY_PATH}" >&2
|