Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
64cad76
Merge pull request #1 from Zoo-Code-Org/main
iskandarsulaili May 21, 2026
a40bdc1
feat: implement Self-Improving Manager for adaptive learning
iskandarsulaili May 21, 2026
9805793
feat: Enhance SelfImprovingManager with MemoryStore and SkillUsageSto…
iskandarsulaili May 22, 2026
c6d0b55
feat: Implement ReviewPromptFactory and TranscriptRecall for self-imp…
iskandarsulaili May 22, 2026
445e9ed
feat: Enhance self-improvement system with user message tracking and …
iskandarsulaili May 22, 2026
0ff067a
Merge branch 'main' into selfimproving
iskandarsulaili May 22, 2026
f15a9a6
feat: Implement memory backend system with AgentMemoryAdapter and Mem…
iskandarsulaili May 22, 2026
fb39c36
fix: harden self-improving memory deletion and recall
iskandarsulaili May 22, 2026
9f2688f
fix: tighten self-improving pattern merges
iskandarsulaili May 22, 2026
53490f1
test: tighten self-improving regression coverage
iskandarsulaili May 22, 2026
f1210f7
fix: harden transcript recall loading
iskandarsulaili May 22, 2026
327a7d3
refactor: align self-improving shared types
iskandarsulaili May 22, 2026
f90b884
fix: refine self-improving memory search and scoring
iskandarsulaili May 22, 2026
e38aad8
fix: serialize transcript recall lazy initialization
iskandarsulaili May 22, 2026
7860dc1
fix: preserve substring text in agentmemory forget search
iskandarsulaili May 22, 2026
4f72974
fix: capture interactive user corrections for self-improving
iskandarsulaili May 23, 2026
29af44c
fix: feed code index search hits into self-improving
iskandarsulaili May 23, 2026
f658165
test: verify extension startup initializes self-improving
iskandarsulaili May 23, 2026
f672d56
feat: add skill mutation APIs and auto-skill toggle
iskandarsulaili May 23, 2026
732be8f
feat: wire self-improving auto skill actions
iskandarsulaili May 23, 2026
02ed3ae
feat: expose auto-skill toggle in experimental settings
iskandarsulaili May 23, 2026
f6d3408
test: cover auto-skill experiment gating
iskandarsulaili May 23, 2026
3dacd05
Merge branch 'main' into selfimproving
iskandarsulaili May 23, 2026
7cf030e
Add configurable self-improving memory backend settings
iskandarsulaili May 23, 2026
99ce4f2
[verified] Add workspace/global self-improving scope controls
iskandarsulaili May 23, 2026
7a8a95c
feat: live refresh skills settings on skill updates
iskandarsulaili May 23, 2026
b6b80aa
fix: avoid blocking submitUserMessage on correction telemetry
iskandarsulaili May 23, 2026
813f4c9
fix: make default auto-skill lookup source-aware
iskandarsulaili May 23, 2026
f024a3f
fix: support secondary mode slugs in skill updates
iskandarsulaili May 23, 2026
3b8b150
refactor: type getGlobalStateSafe against GlobalState
iskandarsulaili May 23, 2026
1c17fdc
test: assert global routing for self-improving settings
iskandarsulaili May 23, 2026
b5b4c8b
test: verify self-improving settings stay local before save
iskandarsulaili May 23, 2026
3f5c5c7
i18n: localize self-improving scope labels
iskandarsulaili May 23, 2026
4dbb091
fix: validate SKILL.md structure before persisting
iskandarsulaili May 23, 2026
00d160e
fix: commit learning state after pattern persistence
iskandarsulaili May 23, 2026
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
71 changes: 71 additions & 0 deletions packages/types/src/__tests__/learning-memory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// npx vitest run packages/types/src/__tests__/learning-memory.test.ts

import {
DEFAULT_LEARNING_CONFIG,
EMPTY_LEARNING_STATE,
learningConfigSchema,
learningStateSchema,
memoryContextSchema,
type LearningState,
type MemoryContext,
} from "../index.js"

describe("learning types", () => {
it("exports the default learning config", () => {
expect(DEFAULT_LEARNING_CONFIG).toMatchObject({
enabled: false,
reviewOnTurnCount: 10,
reviewOnToolIterationCount: 50,
})
})

it("parses the empty learning state", () => {
const result = learningStateSchema.safeParse(EMPTY_LEARNING_STATE)

expect(result.success).toBe(true)
expect(result.data).toEqual(EMPTY_LEARNING_STATE)
})

it("applies learning config defaults", () => {
const result = learningConfigSchema.parse({})

expect(result).toEqual(DEFAULT_LEARNING_CONFIG)
})

it("preserves TypeScript inference for learning state", () => {
const state: LearningState = EMPTY_LEARNING_STATE

expect(state.version).toBe(1)
})
})

describe("memory types", () => {
it("parses a valid memory context", () => {
const input: MemoryContext = {
entries: [],
revision: 0,
generatedAt: Date.now(),
}

const result = memoryContextSchema.safeParse(input)

expect(result.success).toBe(true)
expect(result.data).toEqual(input)
})

it("rejects more than ten memory entries", () => {
const result = memoryContextSchema.safeParse({
entries: Array.from({ length: 11 }, (_, index) => ({
id: `entry-${index}`,
content: "memory",
source: "learning",
createdAt: index,
updatedAt: index,
})),
revision: 0,
generatedAt: Date.now(),
})

expect(result.success).toBe(false)
})
})
11 changes: 10 additions & 1 deletion packages/types/src/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js"
* ExperimentId
*/

export const experimentIds = ["preventFocusDisruption", "imageGeneration", "runSlashCommand", "customTools"] as const
export const experimentIds = [
"preventFocusDisruption",
"imageGeneration",
"runSlashCommand",
"customTools",
"selfImproving",
"selfImprovingAutoSkills",
] as const

export const experimentIdsSchema = z.enum(experimentIds)

Expand All @@ -21,6 +28,8 @@ export const experimentsSchema = z.object({
imageGeneration: z.boolean().optional(),
runSlashCommand: z.boolean().optional(),
customTools: z.boolean().optional(),
selfImproving: z.boolean().optional(),
selfImprovingAutoSkills: z.boolean().optional(),
})

export type Experiments = z.infer<typeof experimentsSchema>
Expand Down
8 changes: 8 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export const DEFAULT_CHECKPOINT_TIMEOUT_SECONDS = 15
* GlobalSettings
*/

export const selfImprovingScopeSchema = z.enum(["workspace", "global"])

export type SelfImprovingScope = z.infer<typeof selfImprovingScopeSchema>

export const globalSettingsSchema = z.object({
currentApiConfigName: z.string().optional(),
listApiConfigMeta: z.array(providerSettingsEntrySchema).optional(),
Expand All @@ -92,6 +96,10 @@ export const globalSettingsSchema = z.object({
imageGenerationProvider: z.enum(["openrouter"]).optional(),
openRouterImageApiKey: z.string().optional(),
openRouterImageGenerationSelectedModel: z.string().optional(),
memoryBackend: z.enum(["builtin", "agentmemory"]).optional(),
agentMemoryUrl: z.string().optional(),
selfImprovingScope: selfImprovingScopeSchema.optional(),
selfImprovingAutoSkillsScope: selfImprovingScopeSchema.optional(),

customCondensingPrompt: z.string().optional(),

Expand Down
3 changes: 3 additions & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ export * from "./global-settings.js"
export * from "./history.js"
export * from "./image-generation.js"
export * from "./ipc.js"
export * from "./learning.js"
export * from "./marketplace.js"
export * from "./mcp.js"
export * from "./message.js"
export * from "./memory.js"
export * from "./mode.js"
export * from "./model.js"
export * from "./provider-settings.js"
Expand Down
200 changes: 200 additions & 0 deletions packages/types/src/learning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { z } from "zod"

/**
* FeedbackSignal - types of learning observations
*/
export const feedbackSignalSchema = z.enum([
"USER_CORRECTION",
"TASK_SUCCESS",
"TASK_FAILURE",
"PATTERN_REPEAT",
"CODE_INDEX_HIT",
"PROMPT_QUALITY",
])

export type FeedbackSignal = z.infer<typeof feedbackSignalSchema>

/**
* LearningConfig - configuration for the learning system
*/
export const learningConfigSchema = z.object({
enabled: z.boolean().default(false),
reviewOnTurnCount: z.number().int().min(1).default(10),
reviewOnToolIterationCount: z.number().int().min(1).default(50),
maxStoredPatterns: z.number().int().min(1).default(100),
maxStoredEvents: z.number().int().min(1).default(500),
maxPromptPatterns: z.number().int().min(1).default(5),
curatorEnabled: z.boolean().default(true),
curatorIntervalMs: z.number().int().min(60000).default(3600000),
staleAfterDays: z.number().int().min(1).default(14),
archiveAfterDays: z.number().int().min(1).default(60),
codeIndexCorrelationEnabled: z.boolean().default(true),
})

export type LearningConfig = z.infer<typeof learningConfigSchema>

export const DEFAULT_LEARNING_CONFIG: LearningConfig = {
enabled: false,
reviewOnTurnCount: 10,
reviewOnToolIterationCount: 50,
maxStoredPatterns: 100,
maxStoredEvents: 500,
maxPromptPatterns: 5,
curatorEnabled: true,
curatorIntervalMs: 3600000,
staleAfterDays: 14,
archiveAfterDays: 60,
codeIndexCorrelationEnabled: true,
}

/**
* LearningEvent - a single learning observation
*/
export const learningEventSchema = z.object({
id: z.string(),
signal: feedbackSignalSchema,
timestamp: z.number(),
taskId: z.string().optional(),
workspacePath: z.string().optional(),
mode: z.string().optional(),
context: z.object({
userTurnCount: z.number().optional(),
toolIterationCount: z.number().optional(),
toolNames: z.array(z.string()).optional(),
promptFingerprint: z.string().optional(),
errorKey: z.string().optional(),
codeIndex: z
.object({
available: z.boolean(),
hits: z.number(),
topScore: z.number().optional(),
})
.optional(),
}),
outcome: z.object({
success: z.boolean().optional(),
corrected: z.boolean().optional(),
summary: z.string().optional(),
confidenceDelta: z.number().optional(),
}),
})

export type LearningEvent = z.infer<typeof learningEventSchema>

/**
* PatternState - lifecycle state for learned patterns
*/
export const patternStateSchema = z.enum(["active", "stale", "archived"])

export type PatternState = z.infer<typeof patternStateSchema>

/**
* PatternType - category of learned pattern
*/
export const patternTypeSchema = z.enum(["prompt", "tool", "error", "skill", "code-index"])

export type PatternType = z.infer<typeof patternTypeSchema>

/**
* LearnedPattern - a pattern extracted from learning events
*/
export const learnedPatternSchema = z.object({
id: z.string(),
patternType: patternTypeSchema,
state: patternStateSchema,
summary: z.string(),
confidenceScore: z.number().min(0).max(1),
frequency: z.number().int().min(0),
successRate: z.number().min(0).max(1),
firstSeenAt: z.number(),
lastSeenAt: z.number(),
lastAppliedAt: z.number().optional(),
sourceSignals: z.array(feedbackSignalSchema),
context: z.object({
toolNames: z.array(z.string()).optional(),
errorKeys: z.array(z.string()).optional(),
modes: z.array(z.string()).optional(),
workspacePaths: z.array(z.string()).optional(),
}),
})

export type LearnedPattern = z.infer<typeof learnedPatternSchema>

/**
* ActionType - types of improvement actions
*/
export const actionTypeSchema = z.enum([
"PROMPT_ENRICHMENT",
"TOOL_PREFERENCE",
"ERROR_AVOIDANCE",
"SKILL_SUGGESTION",
"SKILL_CREATE",
"SKILL_UPDATE",
])

export type ActionType = z.infer<typeof actionTypeSchema>

/**
* ImprovementAction - an action to apply based on learned patterns
*/
export const improvementActionSchema = z.object({
id: z.string(),
actionType: actionTypeSchema,
target: z.enum(["system-prompt", "task-execution", "skills-manager", "review-queue"]),
payload: z.record(z.string(), z.unknown()),
timestamp: z.number(),
})

export type ImprovementAction = z.infer<typeof improvementActionSchema>

/**
* LearningTelemetry - telemetry counters for the learning system
*/
export const learningTelemetrySchema = z.object({
promptEnrichmentUses: z.number().int().default(0),
toolPreferenceUses: z.number().int().default(0),
errorAvoidanceUses: z.number().int().default(0),
skillSuggestionCount: z.number().int().default(0),
lastReviewAt: z.number().optional(),
lastCuratorRunAt: z.number().optional(),
})

export type LearningTelemetry = z.infer<typeof learningTelemetrySchema>

/**
* LearningState - full serializable state of the learning system
*/
export const learningStateSchema = z.object({
version: z.literal(1),
config: learningConfigSchema,
counters: z.object({
userTurnsSinceReview: z.number().int().default(0),
toolIterationsSinceReview: z.number().int().default(0),
}),
patterns: z.array(learnedPatternSchema).default([]),
archivedPatterns: z.array(learnedPatternSchema).default([]),
recentEvents: z.array(learningEventSchema).default([]),
pendingActions: z.array(improvementActionSchema).default([]),
telemetry: learningTelemetrySchema,
})

export type LearningState = z.infer<typeof learningStateSchema>

export const EMPTY_LEARNING_STATE: LearningState = {
version: 1,
config: DEFAULT_LEARNING_CONFIG,
counters: {
userTurnsSinceReview: 0,
toolIterationsSinceReview: 0,
},
patterns: [],
archivedPatterns: [],
recentEvents: [],
pendingActions: [],
telemetry: {
promptEnrichmentUses: 0,
toolPreferenceUses: 0,
errorAvoidanceUses: 0,
skillSuggestionCount: 0,
},
}
29 changes: 29 additions & 0 deletions packages/types/src/memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { z } from "zod"

/**
* MemoryEntry - a single durable memory entry for prompt-facing context
* Adapted from Hermes' bounded memory store concept.
*/
export const memoryEntrySchema = z.object({
id: z.string(),
content: z.string().max(2000),
source: z.enum(["learning", "user", "system", "review"]),
createdAt: z.number(),
updatedAt: z.number(),
relevanceScore: z.number().min(0).max(1).optional(),
tags: z.array(z.string()).optional(),
expiresAt: z.number().optional(),
})

export type MemoryEntry = z.infer<typeof memoryEntrySchema>

/**
* MemoryContext - bounded set of memory entries for prompt injection
*/
export const memoryContextSchema = z.object({
entries: z.array(memoryEntrySchema).max(10),
revision: z.number().int().default(0),
generatedAt: z.number(),
})

export type MemoryContext = z.infer<typeof memoryContextSchema>
Loading