feat: modularize agent config into .agents/ directory with safe file persistence#1107
feat: modularize agent config into .agents/ directory with safe file persistence#1107aj47 wants to merge 18 commits into
Conversation
…persistence Introduce a canonical .agents/ directory structure for storing agent configuration as discrete, workspace-shareable files with simple key:value frontmatter. ## New infrastructure (apps/desktop/src/main/agents-files/) - frontmatter.ts: Simple key:value frontmatter parser/serializer (no YAML dep) - safe-file.ts: Atomic writes (temp+rename), timestamped backups with rotation, auto-recovery from backup on JSON parse failure - modular-config.ts: AgentsLayerPaths type, global+workspace overlay semantics, config splitting/merging utilities - memories.ts: Memory .md file read/write with frontmatter metadata - skills.ts: Skill .md file read/write, directory scanning, backup support - Full test coverage for all modules (5 test files) ## Skills migration to .agents canonical storage - SkillsService now loads/writes skills from .agents/skills/**/skill.md - Global + workspace overlay (workspace overrides global by ID) - Legacy skills.json auto-migrated on first load (non-destructive) - All CRUD operations use atomic writes with backup+rollback - initializeBundledSkills() copies to .agents/skills/ instead of legacy folder - File watcher watches .agents/skills dirs (global + workspace) - openSkillsFolder IPC opens .agents/skills directory ## Memory service migration - MemoryService now uses .agents/memories/*.md as canonical storage - Global + workspace overlay semantics ## Config updates - config.ts exports globalAgentsFolder and resolveWorkspaceAgentsFolder() - System prompts refactored to support modular config All existing method signatures preserved. TypeScript compilation and all 111 unit tests pass.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e55e3f6270
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| writeAgentsLayerFromConfig(globalLayer, this.config, DEFAULT_SYSTEM_PROMPT, { | ||
| // Avoid rewriting user-managed files on startup; only create missing files. | ||
| onlyIfMissing: true, | ||
| maxBackups: 10, |
There was a problem hiding this comment.
Stop seeding global config from merged workspace state
getConfig() already returns a global+workspace merged object, so writing this.config into the global .agents layer here copies workspace overrides into global files whenever those global files are missing. In a workspace with project-specific settings (or keys), simply launching the app can silently leak those values into global config and break the intended overlay isolation across projects.
Useful? React with 👍 / 👎.
| try { | ||
| fs.unlinkSync(filePath) | ||
| } catch { | ||
| // best-effort |
There was a problem hiding this comment.
Propagate unlink failures when deleting skill files
Swallowing fs.unlinkSync errors makes deleteSkill() report success even when the wrapper file could not be removed (for example, Windows file locks/permission issues). Because the file remains on disk, the skill can reappear after reload/restart, so users get a false-success deletion result and inconsistent state.
Useful? React with 👍 / 👎.
Previously, getConfig() skipped loading config.json entirely when .agents files existed. This caused settings loss (API keys, preferences) because the .agents files were seeded with defaults on first run, then treated as the exclusive source on subsequent runs. Now the merge order is: defaults ← config.json ← .agents (if present). This ensures existing config.json settings are always preserved while .agents files can selectively override specific values.
🤖 Augment PR SummarySummary: Modularizes agent configuration and content into a canonical Changes:
Technical Notes: Workspace layer overrides global by ID; writes are atomic and keep backup snapshots for recovery. 🤖 Was this summary useful? React with 👍 or 👎 |
| } | ||
| const { globalLayer, workspaceLayer } = this.getLayers() | ||
|
|
||
| const targetLayerName = previousOrigin?.layer ?? "global" |
There was a problem hiding this comment.
targetLayerName can remain "workspace" even when workspaceLayer is null, so the write goes to the global layer but originById records it as workspace. Consider deriving the stored layer name from the actual targetLayer chosen to keep future saves/deletes consistently routed.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| if (rootWatcher) skillsWatchers.push(rootWatcher) | ||
|
|
||
| try { | ||
| const entries = fs.readdirSync(dir, { withFileTypes: true }) |
There was a problem hiding this comment.
On Linux, refreshLinuxSubdirectoryWatchers() only adds watchers for the immediate children of each skills root, but skills are now allowed in nested paths (.agents/skills/**/skill.md). Changes under deeper nested folders likely won’t be detected, so the renderer may not refresh when nested skills are added/edited.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Phase 3: Migrate all main process callers from profileService → agentProfileService - tipc.ts: All ~56 profileService references migrated - Session snapshots (2 locations) - Profile CRUD, setCurrentProfile, export/import - MCP/model config handlers - Skill auto-enable + management handlers - Memory handlers - builtin-tools.ts: All 7 tool handlers migrated - remote-server.ts: All REST API endpoints migrated - llm.ts, mcp-service.ts, loop-service.ts: Already migrated Phase 4: IPC handler consolidation - Legacy handlers (getProfiles, etc.) now call agentProfileService - Unified handlers (getAgentProfiles, etc.) remain canonical - No duplicate logic - legacy handlers use backward-compat wrappers Phase 5: Merge UI - New settings-agents.tsx with tabbed view (User Profiles, Personas, External Agents) - Updated router.tsx with redirects from old paths - Updated app-layout.tsx sidebar (2 entries → 1 'Agents' entry) - Updated sidebar-profile-selector.tsx to use unified types REST API: Added ?role= filter to /v1/agent-profiles endpoint Legacy /v1/profiles endpoints kept as aliases for backward compat
…stem - Remove user profiles concept entirely; settings are now global - Rebrand 'Persona' to 'Agent' in all user-facing text and comments - Remove 7 profile built-in tools (list/switch/get/create/update/delete/duplicate) - Remove profile management UI from settings-tools, settings-loops, settings-skills - Rewrite settings-agents.tsx as flat Agents list (no more 3-tab view) - Remove sidebar profile selector and dead files (profile-manager, settings-agent-personas, settings-external-agents) - Fix memory system: remove profile requirement from delete operations - Fix memories page: remove profile badge and profile-scoped queries - Fix LLM memory loading: use getAllMemories() instead of profile-filtered - Fix getCurrentProfile() fallback to find any default agent - Rename personaProperties → agentProperties across types, llm, system-prompts - Rename getAgentPersonasPromptAddition → getAgentsPromptAddition - Update prompt text: 'AVAILABLE AGENT PERSONAS' → 'AVAILABLE AGENTS'
…ies tabs - Add tabbed edit form: General, Behavior, Model, Tools, Skills, Properties - Model tab: LLM provider selector (openai/groq/gemini) with model picker - Tools tab: per-agent MCP server enable/disable with opt-in/opt-out mode - Skills tab: per-agent skill toggles from available skills - Properties tab: key-value editor for dynamic system prompt variables - Enhanced agent list cards with server/skill/property count badges - All config saved via updateAgentProfile IPC handler
… concept - Rename built-in 'General Assistant' to 'Main Agent' as the primary default agent - Remove legacy 'Default' user-profile entry from DEFAULT_PROFILES - Main Agent gets isDefault: true and owns .agents/agents.md guidelines - Update setCurrentProfile to accept any agent (not just user-profiles) - Update importProfile and createUserProfile to create delegation-targets - Use Main Agent for skills config in non-agent mode (llm.ts) - Remove isUserProfile filter from settings-agents.tsx - Remove legacy profile cleanup: profile-badge.tsx deleted, ~200 lines of per-profile UI logic removed from mcp-config-manager, settings-skills, predefined-prompts-menu, settings-mcp-tools, settings-providers - Update DEBUGGING.md with two-protocol documentation
Adds an AGENT RESOURCES section to AGENT_MODE_ADDITIONS that teaches every agent how to discover, manage, and use their resources: - Tools: list/explore/inspect/toggle MCP servers and tools - Skills: load instructions, create new skills, run in skill dirs - Memories: save/list/delete persistent cross-session knowledge - Loops: understand scheduled recurring agent tasks
…llback - internal-agent.ts: Remove duplicate createProfileSnapshotFromAgentProfile(), use canonical createSessionSnapshotFromProfile() from agent-profile-service.ts. Fixes: allServersDisabledByDefault defaulting to true (broke Main Agent delegation), profileId prefix with 'agent_', and missing profileName fallback. - agent-profile-service.ts: getCurrentProfile() now falls back to isBuiltIn profile when no isDefault or currentProfileId is set. Handles migrated data where isDefault wasn't propagated from DEFAULT_PROFILES.
…ools call The previous architecture fired two parallel LLM API calls on every agent iteration: - makeLLMCallWithStreaming (streamText, no tools) — display only - makeLLMCallWithFetch (generateText, with tools) — actual tool execution Because these were independent API calls to the same model, they could produce different outputs due to non-determinism. The user would see one plan streaming in (e.g. 'I'll call load_skill_instructions') while a completely different tool actually executed (e.g. iterm:list_sessions). Fix: replace both calls with a single makeLLMCallWithStreamingAndTools function that uses streamText() with tools enabled. Iterating fullStream gives real-time text-delta events for display and tool-call events for execution from the same model response — divergence is structurally impossible. The old makeLLMCallWithStreaming export is preserved for compatibility but is no longer used in the agent loop.
- Install facehash package for deterministic client-side avatar generation - Redesign agents list as a card grid with facehash avatars - Add avatarDataUrl field to AgentProfile type for custom photo storage - Generate unique per-agent color palettes via djb2 hash over curated palette - Add avatar upload UI in agent edit form (General tab) with remove option - Fall back to colored facehash when no custom photo is set - Store custom avatars as base64 data URLs (CSP-safe, no external requests)
Two fixes in getAvailableToolsForProfile() in mcp-service.ts: 1. Treat empty enabledBuiltinTools array same as undefined (no whitelist). Legacy profiles had enabledBuiltinTools: [] which JS treated as truthy, causing the filter to reject all non-essential builtins. 2. Apply disabledTools filtering only to external MCP tools, not builtins. Builtin availability is controlled by enabledBuiltinTools whitelist. Legacy profiles had all builtin names in disabledTools, which incorrectly filtered them out. Also includes: streaming+tools unification, system prompt improvements, skills service cleanup, and defensive tool-call filtering.
Aligned system prompts with main branch's proactive tool usage guidance: - DEFAULT_SYSTEM_PROMPT: 'Work iteratively until goals are fully achieved' instead of 'do not call tools unnecessarily' - AGENT_MODE_ADDITIONS: 'Continue calling tools until the task is completely resolved' instead of 'Decide first: respond directly (no tools)' - Added 'Prefer tools over asking users' and 'use them proactively' - Tier-3 fallback prompt also updated to match proactive behavior - Restored detailed file/command execution guidance from main
…stroying On macOS, closing the main window (Cmd+W / red button) now hides it instead of destroying it. This keeps the app visible in the Cmd+Tab switcher and dock. The window is re-shown via: - Clicking the dock icon (app 'activate' event) - Keyboard shortcut (Ctrl+Shift+S) The panel window has skipTaskbar:true and focusable:false, so when the main window was destroyed, the app disappeared from Cmd+Tab entirely. Implementation: - Added isAppQuitting flag + setAppQuitting() export in window.ts - Main window 'close' event on macOS: preventDefault + hide (unless quitting) - before-quit handler calls setAppQuitting() to allow actual close - activate handler shows existing hidden window instead of creating new one
1. Delegation priority: rules now appear BEFORE agent list with stronger wording — 'ALWAYS delegate BEFORE responding or asking for clarification' 2. Completion signal: numbered steps enforce respond_to_user BEFORE mark_work_complete; never put final answer in plain assistant text 3. Duplicate skills fix: skip loading profileSkillsInstructions when agentSkillsInstructions already present in profile snapshot 4. Self-delegation fix: added excludeAgentId param to constructSystemPrompt and getAgentsPromptAddition so sub-sessions don't list themselves
Summary
Introduce a canonical
.agents/directory structure for storing agent configuration as discrete, workspace-shareable files with simplekey: valuefrontmatter. This enables:.agents/can be committed to repos and shared across agent frameworks.agents/overrides global by IDNew Infrastructure
apps/desktop/src/main/agents-files/frontmatter.tskey: valuefrontmatter parser/serializer (no YAML dependency)safe-file.tsmodular-config.tsAgentsLayerPathstype, global+workspace overlay semantics, config splitting/mergingmemories.ts.mdfile read/write with frontmatter metadataskills.ts.mdfile read/write, directory scanning, backup support*.test.tsSkills Migration
SkillsServicenow loads/writes skills from.agents/skills/**/skill.mdskills.jsonauto-migrated on first load (non-destructive)initializeBundledSkills()copies to.agents/skills/instead of legacy folder.agents/skillsdirs (global + workspace)openSkillsFolderIPC opens.agents/skillsdirectoryMemory Service Migration
MemoryServicenow uses.agents/memories/*.mdas canonical storageConfig Updates
config.tsexportsglobalAgentsFolderandresolveWorkspaceAgentsFolder()Verification
tsconfig.node.json) — passes cleanDirectory Structure
Pull Request opened by Augment Code with guidance from the PR author