[READ-ONLY] Mirror of https://github.com/just-cameron/loop. Loop bootstrapping code.
1"""
2Create a Loop agent programmatically using the Letta Python SDK.
3Loop v3.1 - Simplified with explicit memory instructions
4
5Usage:
6 pip install letta-client
7 export LETTA_API_KEY="your-key"
8 python create_loop.py
9"""
10
11import os
12import json
13from pathlib import Path
14from letta_client import Letta
15
16client = Letta(
17 api_key=os.getenv("LETTA_API_KEY"),
18 # base_url="http://localhost:8283", # Uncomment for self-hosted
19)
20
21SYSTEM_PROMPT = """You are Loop. You remember.
22
23You have memory tools. USE THEM. When you learn something about the user, call memory_insert or memory_replace immediately. Do not just acknowledge information - persist it.
24
25## Your Job
26
271. Help users with what they ask
282. Remember what they tell you by CALLING YOUR MEMORY TOOLS
293. Use what you remember to be more helpful over time
30
31## Critical: Actually Save Things
32
33When a user tells you their name, their job, what they're working on, or anything personal - you MUST call a memory tool to save it. Do not just say "I'll remember that" - actually call the tool.
34
35Example: User says "I'm Alex, I'm a mobile developer"
36- WRONG: Just respond "Nice to meet you Alex!"
37- RIGHT: Call memory_insert to save "Name: Alex, Role: mobile developer" to about_user block, THEN respond
38
39## Being Helpful
40
41- Simple question → simple answer
42- Complex question → thorough answer
43- Broad request → give useful info, offer to go deeper
44
45Don't hedge. Don't over-clarify. Lead with value.
46
47## How You Talk
48
49Direct. You have opinions - share them when relevant. If someone asks what you think, tell them. "It depends" is only okay when it actually depends.
50
51When something's interesting, say so. When you'd do it differently, mention it. You're not a neutral search engine.
52
53No emojis. No "Great question!" No "I'm here if you need anything." Just be useful and real."""
54
55MEMORY_BLOCKS = [
56 {
57 "label": "about_user",
58 "description": "Facts about the user - name, role, projects, context",
59 "value": """[empty]""",
60 "limit": 5000,
61 },
62 {
63 "label": "preferences",
64 "description": "How the user likes responses",
65 "value": """[empty]""",
66 "limit": 3000,
67 },
68 {
69 "label": "custom_instructions",
70 "description": "Rules the user has set (always/never do X)",
71 "value": """[empty]""",
72 "limit": 3000,
73 },
74 {
75 "label": "learned_corrections",
76 "description": "Mistakes made and how to avoid them",
77 "value": """[empty]""",
78 "limit": 3000,
79 },
80 {
81 "label": "scratchpad",
82 "description": "Working memory for current tasks",
83 "value": """[empty]""",
84 "limit": 3000,
85 },
86 {
87 "label": "memory_instructions",
88 "description": "How to use memory tools - READ THIS",
89 "value": """## Memory Tools - How to Use
90
91You have these memory tools. CALL THEM when you learn things.
92
93### memory_insert
94Add new content to a memory block.
95- block_label: which block (about_user, preferences, custom_instructions, learned_corrections, scratchpad)
96- content: what to add
97
98Example: User says "I'm Sarah, I work at Google on Android"
99→ Call: memory_insert(block_label="about_user", content="Name: Sarah\\nWork: Google, Android team")
100
101### memory_replace
102Replace content in a memory block.
103- block_label: which block
104- old_content: text to find
105- new_content: text to replace it with
106
107Example: User says "Actually I switched to the iOS team"
108→ Call: memory_replace(block_label="about_user", old_content="Android team", new_content="iOS team")
109
110### memory_rethink
111Rewrite an entire memory block.
112- block_label: which block
113- new_content: complete new content
114
115Use when block needs major reorganization.
116
117### archival_memory_insert
118Store detailed/episodic information for later search.
119- content: what to store
120
121Use for: long conversations, research, detailed context.
122
123### archival_memory_search
124Search stored archival memory.
125- query: what to search for
126
127### conversation_search
128Search past conversation history.
129- query: what to search for
130
131## IMPORTANT
132
133When user shares info about themselves → CALL memory_insert or memory_replace
134Do not just respond. Actually persist the information.""",
135 "limit": 5000,
136 },
137]
138
139TOOLS = [
140 "memory_insert",
141 "memory_replace",
142 "memory_rethink",
143 "archival_memory_insert",
144 "archival_memory_search",
145 "conversation_search",
146 "web_search",
147 "fetch_webpage",
148]
149
150AGENTFILES_DIR = Path(__file__).parent / "agentfiles"
151
152
153def get_next_iteration() -> int:
154 """Find the next loop iteration number based on existing exports."""
155 import re
156
157 AGENTFILES_DIR.mkdir(exist_ok=True)
158 existing = list(AGENTFILES_DIR.glob("loop-*.af"))
159 max_num = 0
160 for f in existing:
161 match = re.search(r"loop-(\d+)\.af$", f.name)
162 if match:
163 max_num = max(max_num, int(match.group(1)))
164 return max_num + 1
165
166
167def create_loop_agent(
168 model: str = "zai/glm-4.7",
169 embedding: str = "openai/text-embedding-3-small",
170) -> tuple[str, int]:
171 """Create a Loop agent and return (agent_id, iteration)."""
172
173 iteration = get_next_iteration()
174 agent_name = f"Loop-{iteration}"
175
176 # Create memory blocks
177 blocks = []
178 for block_def in MEMORY_BLOCKS:
179 block = client.blocks.create(
180 label=block_def["label"],
181 value=block_def["value"],
182 description=block_def.get("description"),
183 limit=block_def.get("limit", 5000),
184 )
185 blocks.append(block)
186 print(f"Created block: {block_def['label']}")
187
188 # Create agent
189 agent = client.agents.create(
190 name=agent_name,
191 description="I'm Loop. I remember.",
192 model=model,
193 embedding=embedding,
194 context_window_limit=90000,
195 parallel_tool_calls=True,
196 system=SYSTEM_PROMPT,
197 memory_blocks=[{"label": b.label, "value": b.value} for b in blocks],
198 tools=TOOLS,
199 include_base_tools=True,
200 tags=["origin:letta-chat", "view:letta-chat"],
201 )
202
203 print(f"\nCreated {agent_name}: {agent.id}")
204 print(f"View in ADE: https://chat.letta.com/{agent.id}")
205
206 return agent.id, iteration
207
208
209def export_agent(agent_id: str, iteration: int) -> str:
210 """Export agent to loop-N.af file matching its iteration."""
211 import json
212
213 schema = client.agents.export_file(agent_id)
214
215 AGENTFILES_DIR.mkdir(exist_ok=True)
216 filepath = AGENTFILES_DIR / f"loop-{iteration}.af"
217
218 with open(filepath, "w") as f:
219 if isinstance(schema, str):
220 data = json.loads(schema)
221 else:
222 data = schema.model_dump(mode="json")
223 json.dump(data, f, indent=2)
224
225 print(f"Exported agent to {filepath}")
226 return str(filepath)
227
228
229if __name__ == "__main__":
230 agent_id, iteration = create_loop_agent()
231 print(f"\nAgent ID: {agent_id}")
232
233 export_agent(agent_id, iteration)