Skip to content

Commit 6867278

Browse files
feat: implement user profile sync functionality
- Added functions to push and pull user profiles to/from the dashboard. - Implemented additive profile syncing to prevent local data loss. - Created userProfile.ts for managing user profiles and auto-learning features. - Added tests for user profile functionalities and agent management. - Introduced agents.ts to define and manage sub-agent behaviors. - Enhanced vitest configuration to support setup files for isolated test environments.
1 parent 8a2f304 commit 6867278

18 files changed

Lines changed: 1497 additions & 12 deletions

CHANGELOG.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,60 @@ For releases before v1.3.35, see [GitHub Releases](https://github.com/VladoIvank
1111
> as the social-share summary (IFTTT → X/Bluesky), capped at 220 chars.
1212
> If omitted, the feed falls back to the first paragraph.
1313
14+
## [2.3.0] — 2026-05-25
15+
16+
> Codeep gets personal and gains a team: a **user profile** (`/me`) makes it adapt to you across every surface, and **multi-agent delegation** lets it hand self-contained sub-tasks to specialist sub-agents that run in their own context.
17+
18+
### Added — Personalization
19+
20+
- **User profile (`/me`).** A durable, human-readable description of you, injected
21+
into the agent's system prompt on every run so it adapts to how you work
22+
without you repeating yourself. Two scopes: global `~/.codeep/profile.md`
23+
(reply language, response style, default stack, universal "always / never")
24+
and project `.codeep/profile.md` (your role, goals, constraints for this repo).
25+
Manage with `/me`, `/me init [project]`, and `/me on` / `/me off`. Flows to
26+
every surface because they share the same files.
27+
- **Opt-in profile auto-learn.** `/me learn on` lets Codeep quietly extract your
28+
durable preferences from sessions — one cheap, throttled LLM pass at session
29+
save — and merge them into a separate `profile.learned.md` (global + project),
30+
kept apart from your hand-written file so it's never clobbered. `/me learn`
31+
runs it once on demand, `/me learn project` scopes to this repo, `/me forget`
32+
clears it. Off by default; gated by `autoLearnProfile`.
33+
- **Profile sync.** `codeep account sync` pushes your global `profile.md` to the
34+
codeep.dev dashboard (where it's editable) and pulls it to new machines. Pull
35+
is additive — a web edit never overwrites an existing local profile.
36+
- **`/me` in ACP.** Zed, VS Code, and any ACP client can view and manage the
37+
profile, not just the terminal.
38+
39+
### Added — Multi-agent delegation
40+
41+
- **Sub-agents + the `delegate` tool.** The agent can delegate a self-contained
42+
sub-task to a specialist that runs in its OWN fresh context window and returns
43+
only a summary — so the main context stays small and each sub-task runs with a
44+
tuned persona and a scoped toolset. Four built-ins: `planner` (read-only
45+
planning), `researcher` (read-only explorer), `reviewer` (read-only senior
46+
review), `tester` (writes + runs tests). Run `/agents` to list them.
47+
- **Custom sub-agents.** Define your own with a frontmatter `.md` in
48+
`.codeep/agents/<name>.md` (project) or `~/.codeep/agents/` (global): name,
49+
description, a `tools` allowlist, optional `model` override, `personality`
50+
preset, and `maxIterations` budget. Mirrors the personalities/skills pattern.
51+
- **Auto-review pipeline.** Enable **Agent Auto-Review** (`agentAutoReview`, off
52+
by default) and after any run that changes files, Codeep automatically
53+
delegates to the `reviewer` and appends its findings — a review stage that
54+
always happens, without relying on the model to self-delegate one.
55+
- **`/agents`** surfaced in the TUI and ACP (Zed / VS Code).
56+
57+
### Notes
58+
59+
- Profile is local-first and opt-in: injection is gated by `userProfile` (default
60+
on), auto-learn by `autoLearnProfile` (default off). Nothing reaches the
61+
dashboard unless you run `codeep account sync`.
62+
- Sub-agent tool scoping is enforced at dispatch — a `researcher` can't write
63+
files even if it tries. Sub-agents inherit your profile, project rules, and
64+
permission prompts, and their file changes are covered by `/undo` (they record
65+
into the parent's session). Delegation depth is capped at 1; model overrides
66+
are sequential-safe.
67+
1468
## [2.1.4] — 2026-05-22
1569

1670
> Long agent runs no longer silently forget how they started — when prior chat history overflows the context budget, the dropped older messages are summarized instead of just truncated. Plus a command-whitelist hardening.

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,50 @@ Agent learns your coding preferences:
242242
- Preferred libraries
243243
- Custom rules you define
244244

245+
### User Profile (`/me`)
246+
A durable, human-readable description of **you** that Codeep injects into the agent's context on every run — so it adapts to how you work (reply language, response style, default stack, "always / never" rules). It flows to every surface (terminal, VS Code, Zed) since they share the same files.
247+
248+
Two layers:
249+
- **`~/.codeep/profile.md`** — global: who you are across all projects
250+
- **`.codeep/profile.md`** — project: your role, goals, and constraints for this repo
251+
252+
Commands:
253+
- `/me` — view your profile + status
254+
- `/me init [project]` — scaffold a template to fill in
255+
- `/me on` / `/me off` — toggle injection
256+
- `/me learn [on|off]` — opt-in auto-learn: Codeep extracts durable preferences from sessions and merges them into `profile.learned.md` (kept separate from your hand-written file). Off by default.
257+
- `/me learn project` — one-off learn scoped to this repo
258+
- `/me forget` — clear the auto-learned profile(s)
259+
260+
Sync your global profile across machines (and edit it on the web) from the [dashboard](https://codeep.dev/dashboard) with `codeep account sync`. In VS Code: **Codeep: Edit Profile** and **Codeep: Toggle Profile Auto-Learn**.
261+
262+
### Sub-agents (delegation)
263+
The agent can delegate a self-contained sub-task to a specialist **sub-agent** that runs in its own fresh context window and returns only a summary — keeping the main context small and letting each sub-task run with a tuned persona and scoped tools.
264+
265+
Built-ins:
266+
- **`planner`** — read-only; investigates, then returns a step-by-step implementation plan
267+
- **`researcher`** — read-only; explores the codebase/web and returns a tight, cited summary
268+
- **`reviewer`** — read-only; senior review for correctness, security, and design
269+
- **`tester`** — writes and runs tests, iterates to green
270+
271+
Run `/agents` to list them. The agent invokes them itself via the `delegate` tool (you'll see `⤷ <agent>: …` lines). Add your own with a frontmatter `.md` in `.codeep/agents/<name>.md` (project) or `~/.codeep/agents/<name>.md` (global):
272+
273+
```markdown
274+
---
275+
name: migrator
276+
description: Writes and runs database migrations
277+
tools: [read_file, write_file, edit_file, execute_command] # allowlist; omit = all
278+
model: glm-5.1 # optional override
279+
personality: senior-reviewer # optional preset
280+
maxIterations: 12 # optional budget
281+
---
282+
You write safe, reversible DB migrations…
283+
```
284+
285+
`tools` is an allowlist enforced at dispatch (a `researcher` literally can't write files). Sub-agents inherit your profile + project rules, and their changes are covered by `/undo`.
286+
287+
**Guaranteed review:** enable **Agent Auto-Review** in `/settings` (`agentAutoReview`) and after any run that changes files, Codeep automatically delegates to the `reviewer` and appends its findings — a review stage that always happens. Off by default.
288+
245289
### Project Rules
246290
Define project-specific instructions that the AI always follows. Create a rules file in your project root:
247291

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codeep",
3-
"version": "2.1.4",
3+
"version": "2.3.0",
44
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
55
"type": "module",
66
"main": "dist/index.js",

src/acp/commands.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,64 @@ Anything else the agent should know — edge cases, gotchas, things to double-ch
728728
};
729729
}
730730

731+
case 'agents': {
732+
const { formatAgentList } = await import('../utils/agents.js');
733+
return { handled: true, response: formatAgentList(session.workspaceRoot) };
734+
}
735+
736+
case 'me': {
737+
const {
738+
formatProfileView, scaffoldProfile, updateLearnedProfile, clearLearnedProfile,
739+
} = await import('../utils/userProfile.js');
740+
const sub = args[0]?.toLowerCase();
741+
742+
if (sub === 'on' || sub === 'off') {
743+
config.set('userProfile', sub === 'on');
744+
return { handled: true, response: sub === 'on'
745+
? "Profile injection on — your profile is added to the agent's context."
746+
: 'Profile injection off — profile is saved but not used.', configOptionsChanged: true };
747+
}
748+
if (sub === 'init') {
749+
const scope = args[1]?.toLowerCase() === 'project' ? 'project' : 'global';
750+
if (scope === 'project' && !session.workspaceRoot) {
751+
return { handled: true, response: 'No project here — use `/me init` for a global profile.' };
752+
}
753+
const res = scaffoldProfile(scope, session.workspaceRoot);
754+
if (!res) return { handled: true, response: 'Could not create the profile file.' };
755+
return { handled: true, response: res.created
756+
? `Created ${scope} profile: \`${res.path}\` — edit it and Codeep uses it automatically.`
757+
: `${scope === 'global' ? 'Global' : 'Project'} profile already exists: \`${res.path}\`.` };
758+
}
759+
if (sub === 'learn') {
760+
const arg = args[1]?.toLowerCase();
761+
if (arg === 'on' || arg === 'off') {
762+
config.set('autoLearnProfile', arg === 'on');
763+
return { handled: true, response: arg === 'on'
764+
? 'Auto-learn on — Codeep updates your learned profile (global + project) from sessions.'
765+
: 'Auto-learn off — Codeep stops updating the learned profile.', configOptionsChanged: true };
766+
}
767+
const scope = arg === 'project' ? 'project' : 'global';
768+
if (scope === 'project' && !session.workspaceRoot) {
769+
return { handled: true, response: 'No project here — run `/me learn` for your global profile.' };
770+
}
771+
if (session.history.filter((m) => m.role !== 'system').length < 2) {
772+
return { handled: true, response: 'Not enough conversation yet to learn from.' };
773+
}
774+
const res = await updateLearnedProfile(session.history, scope, session.workspaceRoot);
775+
if (!res) return { handled: true, response: 'Nothing durable to learn right now (or the model call failed).' };
776+
const file = scope === 'global' ? '~/.codeep/profile.learned.md' : '.codeep/profile.learned.md';
777+
return { handled: true, response: res.updated
778+
? `Updated your ${scope} learned profile (\`${file}\`):\n\n${res.facts}`
779+
: `No changes — your ${scope} learned profile already covers this:\n\n${res.facts}` };
780+
}
781+
if (sub === 'forget') {
782+
return { handled: true, response: clearLearnedProfile(session.workspaceRoot)
783+
? 'Cleared the auto-learned profile(s).'
784+
: 'No learned profile to clear.' };
785+
}
786+
return { handled: true, response: formatProfileView(session.workspaceRoot) };
787+
}
788+
731789
case 'insights': {
732790
const { formatInsights } = await import('../utils/insights.js');
733791
let days = 7;
@@ -1505,6 +1563,8 @@ function buildHelp(): string {
15051563
'| Command | Description |',
15061564
'|---------|-------------|',
15071565
'| `/memory <note>` | Add a project note (or `list` / `remove <n>` / `clear`) |',
1566+
'| `/me` | Your user profile — reply language, style, stack (`init [project]`, `learn [on\\|off\\|project]`, `forget`, `on`/`off`) |',
1567+
'| `/agents` | List sub-agents the agent can delegate self-contained tasks to |',
15081568
'| `/profile save <name>` | Save current provider/model/settings (or `load` / `delete` / `list`) |',
15091569
'| `/hooks` | List installed lifecycle hooks (`.codeep/hooks/<event>.sh`) |',
15101570
'',

src/acp/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ const AVAILABLE_COMMANDS = [
8080
{ name: 'go', description: 'Execute the pending plan from /plan' },
8181
// Personalities + insights (2.0.3)
8282
{ name: 'personality', description: 'List or switch agent tone preset', input: { hint: '[name | off]' } },
83+
{ name: 'me', description: 'Your user profile — adapts the agent to you (reply language, style, stack)', input: { hint: '[init [project] | on | off | learn [on|off|project] | forget]' } },
84+
{ name: 'agents', description: 'List sub-agents the agent can delegate self-contained tasks to' },
8385
{ name: 'insights', description: 'Activity summary over the last N days (default 7)', input: { hint: '[--days N]' } },
8486
// Project intelligence
8587
{ name: 'scan', description: 'Scan project structure and generate summary' },
@@ -811,7 +813,9 @@ export function startAcpServer(): Promise<void> {
811813
} else if (
812814
configId === 'agentConfirmDeleteFile' ||
813815
configId === 'agentConfirmExecuteCommand' ||
814-
configId === 'agentConfirmWriteFile'
816+
configId === 'agentConfirmWriteFile' ||
817+
configId === 'userProfile' ||
818+
configId === 'autoLearnProfile'
815819
) {
816820
// Accept boolean or "true"/"false" string from Zed/VSCode
817821
const bool = value === true || value === 'true';

src/config/index.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ interface ConfigSchema {
6161
* discarding them — so long sessions keep early decisions/constraints.
6262
* Default true; set false to fall back to plain truncation (no extra call). */
6363
autoSummarizeHistory: boolean;
64+
/** Inject the user profile (`~/.codeep/profile.md` + project
65+
* `.codeep/profile.md`) into the agent's system prompt so it adapts to the
66+
* user (reply language, style, stack, preferences). Default true; set false
67+
* to keep the profile files but stop injecting them. Managed via `/me`. */
68+
userProfile: boolean;
69+
/** Auto-learn: at session save, run one LLM pass to extract durable facts /
70+
* preferences about the user and merge them into `~/.codeep/profile.learned.md`
71+
* (injected alongside the hand-written profile). OFF by default — opt in via
72+
* `/me learn on`. Throttled + single-flight so it doesn't spam API calls. */
73+
autoLearnProfile: boolean;
6474
/** Absolute workspace roots whose project-local `.codeep/hooks/*` the user
6575
* has approved to run. Untrusted projects' hooks are skipped (a cloned repo
6676
* can't execute shell on first tool call). Granted via `/hooks trust`. */
@@ -81,6 +91,10 @@ interface ConfigSchema {
8191
agentAutoCommit: boolean; // Auto-commit after agent completes
8292
agentAutoCommitBranch: boolean; // Create new branch for commits
8393
agentAutoVerify: 'off' | 'build' | 'typecheck' | 'test' | 'all'; // Auto-run verification after changes
94+
/** After a top-level agent run that changed files, delegate to the `reviewer`
95+
* sub-agent and append its findings — a guaranteed review stage. Default
96+
* false (opt-in); one extra nested LLM pass when on. */
97+
agentAutoReview: boolean;
8498
agentMaxFixAttempts: number; // Max attempts to fix errors (default: 1)
8599
agentMaxIterations: number; // Max agent iterations (default: 50)
86100
agentMaxDuration: number; // Max agent duration in minutes (default: 20)
@@ -271,6 +285,7 @@ function createConfig(): Conf<ConfigSchema> {
271285
agentAutoCommit: false,
272286
agentAutoCommitBranch: false,
273287
agentAutoVerify: 'off',
288+
agentAutoReview: false,
274289
// One fix attempt is enough for modern models — if verification fails twice
275290
// with the same approach, the agent usually needs human input, not more loops.
276291
agentMaxFixAttempts: 1,
@@ -287,6 +302,8 @@ function createConfig(): Conf<ConfigSchema> {
287302
autoSave: true,
288303
autoSessionTitle: true,
289304
autoSummarizeHistory: true,
305+
userProfile: true,
306+
autoLearnProfile: false,
290307
trustedHookProjects: [],
291308
currentSessionId: '',
292309
temperature: 0.7,
@@ -302,6 +319,19 @@ function createConfig(): Conf<ConfigSchema> {
302319
deviceId: '',
303320
};
304321

322+
// Test/CI isolation: when CODEEP_CONFIG_DIR is set, keep config in that
323+
// directory (one per test worker) so parallel workers don't race on a shared
324+
// on-disk config file (e.g. clobbering trustedHookProjects). Never set in
325+
// normal use, so the production resolution below is unchanged.
326+
const overrideDir = process.env.CODEEP_CONFIG_DIR;
327+
if (overrideDir) {
328+
try {
329+
return new Conf<ConfigSchema>({ projectName: 'codeep', cwd: overrideDir, defaults });
330+
} catch {
331+
// fall through to standard resolution
332+
}
333+
}
334+
305335
// First try standard location
306336
try {
307337
const standardConfig = new Conf<ConfigSchema>({
@@ -862,6 +892,17 @@ export function saveSession(name: string, history: Message[], projectPath?: stri
862892
&& history.filter(m => m.role !== 'system').length >= 3) {
863893
void maybeGenerateSessionTitle(name, projectPath).catch(() => {});
864894
}
895+
896+
// Fire-and-forget: when the user opted into profile learning, observe the
897+
// session and merge durable facts into ~/.codeep/profile.learned.md.
898+
// Throttled + single-flight inside maybeLearnUserProfile, so the 5s
899+
// autosave cadence doesn't spawn an LLM call every tick. Default off.
900+
if (config.get('autoLearnProfile') === true
901+
&& history.filter(m => m.role !== 'system').length >= 4) {
902+
void import('../utils/userProfile.js')
903+
.then(({ maybeLearnUserProfile }) => maybeLearnUserProfile(name, history, projectPath))
904+
.catch(() => {});
905+
}
865906
return true;
866907
} catch (error) {
867908
logSession('save', name, false);

src/renderer/App.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
108108
'plan': 'Generate a numbered plan for a task — review before /go executes it',
109109
'go': 'Execute the pending plan from /plan',
110110
'personality': 'Switch agent tone: concise / verbose / security / senior-reviewer / etc',
111+
'me': 'Your user profile (reply language, style, stack) — adapts the agent to you. /me init, /me learn',
112+
'agents': 'List sub-agents the agent can delegate self-contained tasks to (researcher / reviewer / tester / custom)',
111113
'insights': 'Activity summary over the last N days (default 7): runs, files, tools, projects',
112114
'recall': 'Search across ALL saved sessions (cross-session; /search is current-session only)',
113115
};
@@ -308,6 +310,10 @@ export class App {
308310
'personality', 'insights',
309311
// 2.1.0 — cross-session recall.
310312
'recall',
313+
// 2.2.0 — user profile.
314+
'me',
315+
// 2.3.0 — sub-agents / delegation.
316+
'agents',
311317
'c', 't', 'd', 'r', 'f', 'e', 'o', 'b', 'p',
312318
];
313319

0 commit comments

Comments
 (0)