Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ All notable changes to this project are documented here. The format follows
user, hostname, timezone, git identity), then shows the updated plan. Answers are applied
like `--set` and saved to the config file. Runs with `--yes`, `--dry-run` or without a
terminal never ask.
- Hermes Agent gets the shared `AGENTS.md` too: `~/.hermes/SOUL.md` (the one file Hermes
loads in every session) gets a managed block pointing at it, and `agbox doctor` checks it.

### Changed
- An existing `~/.claude/CLAUDE.md` gets a one-line import of the shared `AGENTS.md` instead
of a full copy, so edits to the shared file reach Claude too.
- The `devtools` line in the plan only lists the GitHub CLI and Playwright when they are
enabled.

Expand Down
19 changes: 16 additions & 3 deletions modules/agents.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,33 +131,46 @@ toml_set_top() {
}

# One instruction file, linked into each agent's global location.
# Claude imports it when the user already has a CLAUDE.md, so there is still one copy.
_agents_instructions() {
local home canonical
home=$(target_home)
[[ -n $home ]] || { log_info "would write ~/$AGENTS_MD_REL"; return 0; }
canonical="$home/$AGENTS_MD_REL"

render_agents_md | ensure_block user "$canonical" 0644 '<!--' '-->'
if _has_agent claude; then _link_instructions "$home/.claude/CLAUDE.md" "$canonical"; fi
if _has_agent claude; then _link_instructions "$home/.claude/CLAUDE.md" "$canonical" "@$canonical"; fi
if _has_agent codex; then _link_instructions "$home/.codex/AGENTS.md" "$canonical"; fi
if _has_agent opencode; then _link_instructions "$home/.config/opencode/AGENTS.md" "$canonical"; fi
}

# _link_instructions LINK CANONICAL [BODY] : symlink LINK to CANONICAL. If the user has their
# own file there, add a managed block instead: BODY (e.g. an import line), or a full copy.
_link_instructions() {
local link=$1 canonical=$2 current
local link=$1 canonical=$2 body=${3:-} current
if as_root test -L "$link"; then
current=$(as_root readlink "$link")
[[ $current == "$canonical" ]] && return 0
fi
if as_root test -e "$link" && ! as_root test -L "$link"; then
# The user has their own file: add our block to it instead of replacing it.
render_agents_md | ensure_block user "$link" 0644 '<!--' '-->'
if [[ -n $body ]]; then
printf '%s\n' "$body" | ensure_block user "$link" 0644 '<!--' '-->'
else
render_agents_md | ensure_block user "$link" 0644 '<!--' '-->'
fi
return 0
fi
run_user mkdir -p "$(dirname "$link")"
run_user ln -sfn "$canonical" "$link"
}

# instructions_pointer PATH -> a paragraph for agents that only load a global identity
# file (Hermes' SOUL.md), sending them to the shared AGENTS.md. Pure, unit-tested.
instructions_pointer() {
printf '%s\n' "This machine's conventions (toolchains, services, ports, where projects live) are in $1. Read it before installing software, starting services, or changing anything outside the current project."
}

render_agents_md() {
local projects sudo_line tools docker_line mode_line
projects=$(expand_home "$AGBOX_PROJECTS_DIR")
Expand Down
19 changes: 19 additions & 0 deletions modules/services.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,27 @@ _services_hermes() {
log_info "installing Hermes Agent (official install script)"
run_script user https://hermes-agent.nousresearch.com/install.sh bash
fi
if [[ $AGBOX_AGENT_INSTRUCTIONS == true ]]; then _services_hermes_instructions; fi
if ! _user_unit_exists 'hermes-gateway.service'; then
add_next_step "Configure Hermes: run 'hermes' (provider + model), then 'hermes gateway setup' and 'hermes gateway install'"
fi
}

# Hermes reads AGENTS.md only from its working directory; the one file it loads in every
# session is ~/.hermes/SOUL.md, so that gets a pointer to the shared AGENTS.md. Hermes seeds
# SOUL.md itself (a SOUL.md replaces its default identity), so never create it.
_services_hermes_instructions() {
local home soul
home=$(target_home)
[[ -n $home ]] || { log_info "would point ~/.hermes/SOUL.md at ~/$AGENTS_MD_REL"; return 0; }
soul="$home/.hermes/SOUL.md"
if ! as_root test -f "$soul"; then
log_skip "no ~/.hermes/SOUL.md yet (re-run after the first 'hermes' run to link AGENTS.md)"
return 0
fi
instructions_pointer "$home/$AGENTS_MD_REL" | ensure_block user "$soul" 0600 '<!--' '-->'
}

services::check() {
if _has_service t3code; then
if _user_unit_exists 't3*.service'; then
Expand All @@ -66,5 +82,8 @@ services::check() {
else
check_warn "hermes installed, gateway not set up" "hermes gateway setup && hermes gateway install"
fi
if user_has hermes && [[ $AGBOX_AGENT_INSTRUCTIONS == true ]]; then
check as_root grep -qF "$AGENTS_MD_REL" "$(target_home)/.hermes/SOUL.md" -- "Hermes SOUL.md points at the shared AGENTS.md" "agbox setup --only services"
fi
fi
}
5 changes: 5 additions & 0 deletions tests/core.bats
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ setup() {
[[ "$output" == *"Projects live in /home/agbox-test-nobody/dev"* ]]
}

@test "instructions_pointer names the shared AGENTS.md" {
run instructions_pointer /home/me/.config/agents/AGENTS.md
[[ "$output" == *"are in /home/me/.config/agents/AGENTS.md. Read it before"* ]]
}

@test "dry-run helpers print instead of executing" {
AGBOX_DRY_RUN=true
local marker="$BATS_TEST_TMPDIR/should-not-exist"
Expand Down
Loading