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
48 changes: 48 additions & 0 deletions internal/agentcrd/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ func HostSoulPath(cfg *config.Config, name string) string {
return filepath.Join(HostHomePath(cfg, name), "SOUL.md")
}

// HostNoBundledSkillsMarkerPath returns the location of the `.no-bundled-skills`
// marker file inside the agent's Hermes profile. When this file exists, Hermes'
// installer, `hermes update`, and skill syncs skip seeding bundled skills.
//
// Sub-agents only ever need the narrow, operator-chosen skill subset we layer
// in via OBOL_SKILLS_DIR; Hermes' ~80 bundled skills (apple-notes, spotify,
// github-pr-workflow, gif-search, Pokemon-player, …) just bloat the system
// prompt for an EVM-focused paid service. The marker is the official
// upstream-supported opt-out; see Hermes docs/user-guide/features/skills.
func HostNoBundledSkillsMarkerPath(cfg *config.Config, name string) string {
return filepath.Join(HostHomePath(cfg, name), ".no-bundled-skills")
}

// HostLegacySoulPath is the pre-profile seed path used before Hermes profile
// casing was aligned. It is read during migration only.
func HostLegacySoulPath(cfg *config.Config, name string) string {
Expand All @@ -72,6 +85,18 @@ type SeedOptions struct {
// data path. SOUL.md is only written when missing (or when OverwriteSoul is
// true).
//
// CONTRACT — sub-agents-for-sale ONLY. This helper also writes the
// `.no-bundled-skills` marker (see writeNoBundledSkillsMarker), which makes
// Hermes skip its ~80 bundled skills and run with just the operator-chosen
// subset. That is correct for a narrow, EVM-focused paid service but WRONG for
// the stack-managed master agent, which keeps the full bundled set. The master
// (internal/hermes) seeds its own home via a separate path (it does not call
// this) and must NEVER be routed through SeedHostFiles, or it would silently
// lose its bundled skills. The reusable seed primitives this is built from
// (WriteSoul, embed.WriteSkillSubset) deliberately do NOT write the marker, so
// the objective-only update path — and any future master-side reuse — stays
// safe; locked by TestMarkerOnlyWrittenBySeedHostFiles.
//
// Returns whether SOUL.md was written this call so callers can report the
// difference between "fresh agent" and "existing agent, skills resynced".
func SeedHostFiles(cfg *config.Config, name string, skills []string, objective string, opts SeedOptions) (soulWritten bool, err error) {
Expand All @@ -84,9 +109,32 @@ func SeedHostFiles(cfg *config.Config, name string, skills []string, objective s
return false, fmt.Errorf("write skills: %w", err)
}
}
if err := writeNoBundledSkillsMarker(cfg, name); err != nil {
return false, fmt.Errorf("write no-bundled-skills marker: %w", err)
}
return WriteSoul(cfg, name, objective, opts.OverwriteSoul)
}

// writeNoBundledSkillsMarker drops a `.no-bundled-skills` file into the agent's
// Hermes profile dir so the runtime skips seeding its ~80 bundled skills.
// Idempotent: an existing marker is left as-is. The file is intentionally empty;
// Hermes treats presence-as-flag.
func writeNoBundledSkillsMarker(cfg *config.Config, name string) error {
path := HostNoBundledSkillsMarkerPath(cfg, name)
if _, err := os.Lstat(path); err == nil {
return nil
} else if !os.IsNotExist(err) {
return fmt.Errorf("stat marker: %w", err)
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return fmt.Errorf("create home dir: %w", err)
}
if err := os.WriteFile(path, nil, 0o644); err != nil {
return fmt.Errorf("write marker: %w", err)
}
return nil
}

// WriteSoul renders and writes SOUL.md for the named agent. When overwrite is
// false, an existing SOUL.md is preserved and the return value is false.
func WriteSoul(cfg *config.Config, name, objective string, overwrite bool) (bool, error) {
Expand Down
Loading
Loading