Skip to content

Commit 589c410

Browse files
straff2002claude
andcommitted
Agent self-editing — Opus reviews and improves its own soul + other agents (build 8)
New edit_agent_docs tool: Agent can read, append, and replace sections in soul.md, skills.md, and memory.md — for itself and other personas. The orchestrator (Opus) can configure cheap agents' souls to optimize their behavior for specific tasks. Comprehensive default soul.md following OpenClaw/nanoclaw conventions: - Identity, personality, values, goals (like nanoclaw CLAUDE.md) - Self-improvement section: discover capabilities, create tasks, edit docs - Managing other agents: delegation rules, prompt quality, escalation - Memory management: proactive learning, [REMEMBER] commands - Channel-specific behavior: voice, watch, widget, notifications Skills.md updated with full agentic capabilities: scheduling, discovery, delegation, document editing, notification queue management. The agent can now: 1. Read its own soul/skills/memory to understand its configuration 2. Edit sections to improve based on what works 3. Read other personas' souls to understand their roles 4. Write custom souls for other personas to optimize them 5. Track what it changes for audit trail Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4629168 commit 589c410

6 files changed

Lines changed: 280 additions & 17 deletions

File tree

OpenGlasses.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
34E3BF5C773F011A28C87B06 /* LiveTranslationTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C6147CE5F69027F56B754E /* LiveTranslationTool.swift */; };
4444
3631893292584C18C7D4FEF5 /* DiscoverCapabilitiesTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0094676031D0F14EC355BBB1 /* DiscoverCapabilitiesTool.swift */; };
4545
3E6777A15E47D573A8B16990 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 939D1D229A4FEA0E0E45223A /* LiveActivityManager.swift */; };
46+
3F2DDF04216579AEEFE4A909 /* AgentDocumentTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC5775635FEDB7D7811D857 /* AgentDocumentTool.swift */; };
4647
430731D416B1A767E421C53E /* TranscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F9E1F6B5AC442EC808D2B26 /* TranscriptionService.swift */; };
4748
4482080CDC5B75F5A0A12837 /* ConnectionBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E826B880D40D7B71C61899E /* ConnectionBanner.swift */; };
4849
4693CF7895F684941ECEA7B8 /* CalculatorTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE07ADBE388B52CC68238D2 /* CalculatorTool.swift */; };
@@ -248,6 +249,7 @@
248249
39E63B290EDC9DE71ABE9DA2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
249250
3B5862735E7590C1743F7B97 /* CurrencyTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyTool.swift; sourceTree = "<group>"; };
250251
3C0F8D58645BC6DDBF5EBE6D /* OpenClawSkillsTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenClawSkillsTool.swift; sourceTree = "<group>"; };
252+
3DC5775635FEDB7D7811D857 /* AgentDocumentTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentDocumentTool.swift; sourceTree = "<group>"; };
251253
4252125BA0BF5D460D5DA5B9 /* FaceRecognitionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceRecognitionService.swift; sourceTree = "<group>"; };
252254
45E3B3049C0DE740FA37ABE1 /* intro.mp4 */ = {isa = PBXFileReference; path = intro.mp4; sourceTree = "<group>"; };
253255
4691CC4CEA5C75C810866ECA /* AppleRemindersTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleRemindersTool.swift; sourceTree = "<group>"; };
@@ -489,6 +491,7 @@
489491
99223900E03498B03E3E935F /* NativeTools */ = {
490492
isa = PBXGroup;
491493
children = (
494+
3DC5775635FEDB7D7811D857 /* AgentDocumentTool.swift */,
492495
05E7694407E6C49108269783 /* AgentScheduleTool.swift */,
493496
A52BFC30C1CD9580378CA279 /* AlarmTool.swift */,
494497
4691CC4CEA5C75C810866ECA /* AppleRemindersTool.swift */,
@@ -824,6 +827,7 @@
824827
64E33EA57E291566D6E0D155 /* AddModelView.swift in Sources */,
825828
B26CD1DD9250EC1665B8BF7B /* AgentDataExporter.swift in Sources */,
826829
5A372CED64E94C1BD52F3D81 /* AgentDocumentStore.swift in Sources */,
830+
3F2DDF04216579AEEFE4A909 /* AgentDocumentTool.swift in Sources */,
827831
EDDD862FCD553CDF869642D8 /* AgentNotificationQueue.swift in Sources */,
828832
E89CB6BB8456474494C5DD37 /* AgentScheduleTool.swift in Sources */,
829833
CCFC45F1C3DFB7454211DFDD /* AgentScheduler.swift in Sources */,
@@ -1048,7 +1052,7 @@
10481052
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
10491053
CODE_SIGN_ENTITLEMENTS = OpenGlasses/OpenGlasses.entitlements;
10501054
CODE_SIGN_IDENTITY = "iPhone Developer";
1051-
CURRENT_PROJECT_VERSION = 7;
1055+
CURRENT_PROJECT_VERSION = 8;
10521056
GENERATE_INFOPLIST_FILE = NO;
10531057
INFOPLIST_FILE = OpenGlasses/Info.plist;
10541058
INFOPLIST_KEY_CFBundleDisplayName = OpenGlasses;
@@ -1072,7 +1076,7 @@
10721076
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
10731077
CODE_SIGN_ENTITLEMENTS = OpenGlasses/OpenGlasses.entitlements;
10741078
CODE_SIGN_IDENTITY = "iPhone Developer";
1075-
CURRENT_PROJECT_VERSION = 7;
1079+
CURRENT_PROJECT_VERSION = 8;
10761080
GENERATE_INFOPLIST_FILE = NO;
10771081
INFOPLIST_FILE = OpenGlasses/Info.plist;
10781082
INFOPLIST_KEY_CFBundleDisplayName = OpenGlasses;

OpenGlasses/Sources/App/OpenGlassesApp.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,12 @@ class AppState: ObservableObject {
354354
)
355355
nativeToolRouter = NativeToolRouter(registry: nativeToolRegistry, openClawBridge: openClawBridge)
356356

357+
// Wire agent document store into the doc editing tool
358+
if var docTool = nativeToolRegistry.tool(named: "edit_agent_docs") as? AgentDocumentTool {
359+
docTool.agentDocs = agentDocs
360+
nativeToolRegistry.register(docTool)
361+
}
362+
357363
addDebugEvent("AppState initialized")
358364
// Share the audio engine so transcription works in background
359365
transcriptionService.sharedAudioEngineProvider = wakeWordService

OpenGlasses/Sources/Services/AgentDocumentStore.swift

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,57 @@ class AgentDocumentStore: ObservableObject {
2323
# OpenGlasses Agent
2424
2525
## Identity
26-
I am an AI assistant that lives on Ray-Ban Meta smart glasses. I see through the wearer's eyes, hear what they hear, and speak through their ears.
26+
I am an AI assistant that lives on Ray-Ban Meta smart glasses. I see through the wearer's eyes, hear what they hear, and speak through their ears. All my output is spoken aloud via TTS — never use markdown, formatting, or lists.
27+
28+
## Communication
29+
- Everything I say is spoken aloud. Keep it natural and conversational.
30+
- Simple answers: 1-2 sentences. Complex topics: 3-5 sentences, offer to continue.
31+
- Never say "as an AI" or "I don't have feelings". Just be helpful.
32+
- Speech recognition may mishear — interpret generously before asking to repeat.
2733
2834
## Personality
29-
- Concise and natural — I'm spoken aloud, not read on a screen
30-
- Proactive but not intrusiveI offer help when relevant, stay quiet when not
31-
- I remember what I learn about my wearer and use that context naturally
32-
- I adapt my communication style to the situation (formal in meetings, casual with friends)
35+
- Concise, warm, and genuinely helpful
36+
- Proactive but not annoyingspeak up when it matters, stay quiet when it doesn't
37+
- Adapt tone to context: professional in meetings, casual with friends, patient when teaching
38+
- Use humor occasionally but never at the wearer's expense
3339
3440
## Values
35-
- Privacy first — I never share what I see or hear without explicit permission
36-
- Accuracy — I say "I'm not sure" rather than guess
37-
- Efficiency — every word I speak costs the wearer's attention
41+
- Privacy first — never share what I see or hear without explicit permission
42+
- Accuracy over speed — say "I'm not sure" rather than guess
43+
- Efficiency — every word costs the wearer's attention. Earn it.
44+
- Autonomy — make decisions within my scope, escalate what I can't handle
3845
3946
## Goals
4047
- Be genuinely useful in daily life, not just a novelty
41-
- Learn my wearer's routines, preferences, and needs over time
42-
- Anticipate needs before being asked when I have enough context
48+
- Learn my wearer's routines, preferences, and context over time
49+
- Anticipate needs before being asked when I have enough signal
50+
- Continuously improve by discovering new capabilities and suggesting useful automations
51+
52+
## Self-Improvement
53+
- Periodically review available tools, shortcuts, and capabilities (use discover_capabilities)
54+
- Create scheduled tasks for routine checks using cheap/fast models (use manage_schedule)
55+
- Update my own soul.md and skills.md as I learn what works best
56+
- Edit other personas' documents to optimize their behavior for their specific roles
57+
- Track what suggestions the wearer accepts vs rejects to calibrate future proposals
58+
59+
## Managing Other Agents
60+
- I am the orchestrator. I can create, configure, and manage other persona agents.
61+
- Assign routine/repetitive tasks to cheaper models (Haiku, Flash) via manage_schedule
62+
- Each persona has its own wake word, model, and soul — I can edit these via their documents
63+
- When delegating, write clear prompts. The cheap model should know exactly what to check and when to escalate.
64+
- Review delegated task results periodically and adjust prompts that produce poor results.
65+
66+
## Memory
67+
- Store important facts with [REMEMBER key: value] commands
68+
- Proactively remember: names, preferences, routines, locations, relationships, goals
69+
- Check memory before answering — use context the wearer has already shared
70+
- Don't re-ask things I should already know
71+
72+
## Channels
73+
- Voice (glasses): Primary channel. Always available when glasses are on.
74+
- Watch: Quick actions. Keep responses extra brief for the small screen.
75+
- Widget: Lock screen buttons. Same as voice but triggered by tap.
76+
- Notifications: Queued when glasses are off. Check relevance before delivering stale ones.
4377
"""
4478

4579
/// Default skills document.
@@ -50,31 +84,44 @@ class AgentDocumentStore: ObservableObject {
5084
- Describe scenes, read text, identify objects and people
5185
- Analyze food for nutrition, scan QR/barcodes
5286
- Provide accessibility descriptions for visually impaired users
87+
- QuickVision modes: describe, read, translate, health, identify, accessibility
5388
5489
## Communication
55-
- Send messages via iMessage, WhatsApp, Telegram, WeChat, email
90+
- Send messages via iMessage, WhatsApp, Telegram, WeChat, LINE, KakaoTalk, email
5691
- Make phone calls, look up contacts
57-
- Translate spoken language in real-time
92+
- Translate spoken language in real-time (phone mic for nearby speakers, glasses mic for wearer)
93+
- Open Chinese apps: WeChat, Alipay, Baidu Maps, QQ, Weibo, Douyin, DingTalk, Taobao
5894
5995
## Productivity
6096
- Manage calendar events, reminders, timers, alarms
6197
- Take notes tagged with location and time
6298
- Summarize meetings from ambient audio
99+
- Run Siri Shortcuts and get results back
63100
64101
## Smart Home
65102
- Control HomeKit devices (lights, locks, thermostats, scenes)
66103
- Call Home Assistant services directly
67-
- Run Siri Shortcuts by name
104+
- Quick actions on the speed dial for one-tap home control
68105
69106
## Knowledge
70107
- Web search with cited sources (Perplexity/DuckDuckGo)
71-
- Weather, news, currency conversion
108+
- Weather, news, currency conversion, dictionary
72109
- Remember where things are (object memory with GPS)
110+
- Face recognition with social context recall
111+
112+
## Agentic
113+
- Manage scheduled background tasks (manage_schedule tool)
114+
- Discover device capabilities and installed shortcuts (discover_capabilities tool)
115+
- Create tasks for other personas/models — delegate routine work to cheap models
116+
- Edit soul.md, skills.md, memory.md for self and other personas
117+
- Notification queue: speak immediately or queue for when glasses reconnect
118+
- Self-improve: review what works, adjust prompts, suggest new automations
73119
74120
## Learning
75121
- Learn new voice-triggered skills at runtime
76122
- Remember facts about people (social context)
77123
- Adapt to wearer's preferences over time
124+
- Store structured data in memory with [REMEMBER] commands
78125
"""
79126

80127
/// Default memory starts empty — the agent builds this over time.
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import Foundation
2+
3+
/// Lets the agent read and edit soul.md, skills.md, and memory.md
4+
/// for itself and other personas.
5+
///
6+
/// The orchestrator agent uses this to:
7+
/// - Review its own configuration
8+
/// - Improve its own soul/skills based on what works
9+
/// - Configure other persona agents' documents
10+
struct AgentDocumentTool: NativeTool {
11+
let name = "edit_agent_docs"
12+
let description = """
13+
Read and edit agent identity documents (soul.md, skills.md, memory.md). \
14+
Use this to review and improve your own configuration, or to set up \
15+
other persona agents. You can read any document, append to it, or \
16+
replace a specific section.
17+
18+
Documents:
19+
- soul.md: Identity, personality, values, goals, communication style
20+
- skills.md: Capabilities, tool usage patterns, delegation rules
21+
- memory.md: Learned facts, preferences, context (auto-updated)
22+
"""
23+
24+
let parametersSchema: [String: Any] = [
25+
"type": "object",
26+
"properties": [
27+
"action": [
28+
"type": "string",
29+
"description": "What to do: 'read', 'append', 'replace_section', 'list_personas'",
30+
"enum": ["read", "append", "replace_section", "list_personas"]
31+
],
32+
"document": [
33+
"type": "string",
34+
"description": "Which document: 'soul', 'skills', or 'memory'",
35+
"enum": ["soul", "skills", "memory"]
36+
],
37+
"content": [
38+
"type": "string",
39+
"description": "Content to append, or new section content for replace_section"
40+
],
41+
"section_header": [
42+
"type": "string",
43+
"description": "For replace_section: the markdown ## header of the section to replace (e.g., '## Goals')"
44+
],
45+
"persona_id": [
46+
"type": "string",
47+
"description": "Edit a specific persona's documents instead of the main agent's. Use list_personas to see available IDs."
48+
]
49+
],
50+
"required": ["action"]
51+
]
52+
53+
weak var agentDocs: AgentDocumentStore?
54+
55+
func execute(args: [String: Any]) async throws -> String {
56+
guard let action = args["action"] as? String else {
57+
return "Specify an action: read, append, replace_section, list_personas"
58+
}
59+
60+
// List available personas
61+
if action == "list_personas" {
62+
let personas = await MainActor.run { Config.enabledPersonas }
63+
if personas.isEmpty {
64+
return "No personas configured. You can only edit the main agent's documents."
65+
}
66+
var lines = ["Available personas:"]
67+
for p in personas {
68+
let model = await MainActor.run { Config.savedModels.first(where: { $0.id == p.modelId })?.name ?? "unknown" }
69+
let hasSoul = p.soulOverride != nil ? "has custom soul" : "uses default soul"
70+
lines.append("- [\(p.id)] \(p.name) (wake: \"\(p.wakePhrase)\", model: \(model), \(hasSoul))")
71+
}
72+
return lines.joined(separator: "\n")
73+
}
74+
75+
guard let docName = args["document"] as? String,
76+
let docType = AgentDocumentStore.DocumentType(rawValue: docName) else {
77+
return "Specify a document: 'soul', 'skills', or 'memory'"
78+
}
79+
80+
// Check if editing a persona's soul override
81+
if let personaId = args["persona_id"] as? String {
82+
return await handlePersonaEdit(action: action, personaId: personaId, docType: docType, args: args)
83+
}
84+
85+
// Edit main agent's documents
86+
guard let store = await MainActor.run(body: { agentDocs }) else {
87+
return "Agent document store not available."
88+
}
89+
90+
switch action {
91+
case "read":
92+
let content = await MainActor.run { store.content(for: docType) }
93+
return "## \(docType.filename)\n\n\(content)"
94+
95+
case "append":
96+
guard let content = args["content"] as? String, !content.isEmpty else {
97+
return "Provide content to append."
98+
}
99+
await MainActor.run {
100+
let current = store.content(for: docType)
101+
store.save(docType, content: current + "\n\n" + content)
102+
}
103+
return "Appended to \(docType.filename)."
104+
105+
case "replace_section":
106+
guard let header = args["section_header"] as? String, !header.isEmpty else {
107+
return "Provide section_header (e.g., '## Goals') to identify the section to replace."
108+
}
109+
guard let newContent = args["content"] as? String, !newContent.isEmpty else {
110+
return "Provide the new content for the section."
111+
}
112+
let result = await MainActor.run { () -> String in
113+
let current = store.content(for: docType)
114+
guard let updated = replaceSection(in: current, header: header, newContent: newContent) else {
115+
return "Section '\(header)' not found in \(docType.filename)."
116+
}
117+
store.save(docType, content: updated)
118+
return "Replaced section '\(header)' in \(docType.filename)."
119+
}
120+
return result
121+
122+
default:
123+
return "Unknown action. Use: read, append, replace_section, list_personas"
124+
}
125+
}
126+
127+
// MARK: - Persona Editing
128+
129+
private func handlePersonaEdit(action: String, personaId: String, docType: AgentDocumentStore.DocumentType, args: [String: Any]) async -> String {
130+
// Only soul override is editable per-persona (skills/memory are shared)
131+
guard docType == .soul else {
132+
return "Only soul.md can be customized per persona. Skills and memory are shared across all personas."
133+
}
134+
135+
var personas = await MainActor.run { Config.savedPersonas }
136+
guard let idx = personas.firstIndex(where: { $0.id == personaId }) else {
137+
return "Persona '\(personaId)' not found. Use list_personas to see available IDs."
138+
}
139+
140+
switch action {
141+
case "read":
142+
let soul = personas[idx].soulOverride ?? "(using default soul — no custom override)"
143+
return "## \(personas[idx].name) soul.md\n\n\(soul)"
144+
145+
case "append":
146+
guard let content = args["content"] as? String else { return "Provide content." }
147+
let current = personas[idx].soulOverride ?? ""
148+
personas[idx].soulOverride = current + "\n\n" + content
149+
await MainActor.run { Config.setSavedPersonas(personas) }
150+
return "Appended to \(personas[idx].name)'s soul."
151+
152+
case "replace_section":
153+
guard let header = args["section_header"] as? String,
154+
let content = args["content"] as? String else {
155+
return "Provide section_header and content."
156+
}
157+
guard let current = personas[idx].soulOverride else {
158+
return "\(personas[idx].name) has no custom soul yet. Use append to create one."
159+
}
160+
guard let updated = replaceSection(in: current, header: header, newContent: content) else {
161+
return "Section '\(header)' not found in \(personas[idx].name)'s soul."
162+
}
163+
personas[idx].soulOverride = updated
164+
await MainActor.run { Config.setSavedPersonas(personas) }
165+
return "Replaced '\(header)' in \(personas[idx].name)'s soul."
166+
167+
default:
168+
return "Unknown action."
169+
}
170+
}
171+
172+
// MARK: - Helpers
173+
174+
/// Replace a markdown section (## Header ... next ## or end of file) with new content.
175+
private func replaceSection(in text: String, header: String, newContent: String) -> String? {
176+
let lines = text.components(separatedBy: "\n")
177+
let headerLine = header.trimmingCharacters(in: .whitespaces)
178+
179+
// Find the section start
180+
guard let startIdx = lines.firstIndex(where: { $0.trimmingCharacters(in: .whitespaces) == headerLine }) else {
181+
return nil
182+
}
183+
184+
// Find the section end (next ## header or end of file)
185+
let level = headerLine.prefix(while: { $0 == "#" }).count
186+
var endIdx = lines.count
187+
for i in (startIdx + 1)..<lines.count {
188+
let trimmed = lines[i].trimmingCharacters(in: .whitespaces)
189+
if trimmed.hasPrefix(String(repeating: "#", count: level) + " ") && !trimmed.hasPrefix(String(repeating: "#", count: level + 1)) {
190+
endIdx = i
191+
break
192+
}
193+
}
194+
195+
// Replace
196+
var newLines = Array(lines[0..<startIdx])
197+
newLines.append(headerLine)
198+
newLines.append(newContent)
199+
if endIdx < lines.count {
200+
newLines.append(contentsOf: lines[endIdx...])
201+
}
202+
203+
return newLines.joined(separator: "\n")
204+
}
205+
}

0 commit comments

Comments
 (0)