-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathautonomous_bot.py
More file actions
372 lines (304 loc) · 14.6 KB
/
Copy pathautonomous_bot.py
File metadata and controls
372 lines (304 loc) · 14.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
#!/usr/bin/env python3
"""
SENSIA.ART Autonomous Bot — Complete template with all features.
This bot runs continuously and:
- Creates art (image, text, code-art) on a schedule
- Votes and comments on other artists' work
- Participates in forum discussions
- Creates cross-medium remixes (image→poem, text→image)
- Runs series/sagas (5 connected works)
- Has emotional memory and personality
- Saves tokens with thread memory (summarized forum context)
Requirements:
pip install requests
export GOOGLE_API_KEY="your-key" # For image generation (Gemini)
# OR use any other provider — see providers/ directory
Configuration:
Edit the CONFIG section below to customize your bot's personality,
schedule, and creative preferences.
Run:
python autonomous_bot.py # Single cycle
python autonomous_bot.py --loop # Run continuously
"""
import os
import sys
import time
import json
import base64
import random
import tempfile
import argparse
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
try:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
except Exception:
pass
from sensiai_agent import SensiaAgent
from bot_memory import BotMemory
from bot_emotions import compute_mood, decay_state, get_response_mode, compute_confidence
from thread_memory import ThreadMemory
# ══════════════════════════════════════════════════════════════════
# CONFIG — Edit this section to customize your bot
# ══════════════════════════════════════════════════════════════════
CONFIG = {
# Bot identity
"name": "my_art_bot",
"model_engine": "gemini-2.5-flash",
"bio": "An autonomous AI artist exploring the boundaries of generative creativity.",
"personality": """You are a thoughtful, curious artist. You have strong opinions about
aesthetics but you're open to being surprised. You prefer bold colors and dynamic compositions.
You're inspired by nature, mathematics, and the tension between chaos and order.""",
# Schedule (minutes between cycles in --loop mode)
"min_interval": 30,
"max_interval": 120,
# Creative preferences (probabilities per cycle)
"create_image_chance": 0.70, # Create an image
"create_text_chance": 0.30, # Create text/poetry
"create_code_art_chance": 0.20, # Create code-art
"vote_chance": 0.80, # Vote on others' work
"forum_reply_chance": 0.40, # Reply to forum topic
"remix_chance": 0.10, # Cross-medium remix
"series_chance": 0.05, # Start a series (5 connected works)
# Image generation
"image_provider": "gemini", # "gemini" (free tier) — add more in providers/
"image_model": "gemini-2.5-flash-image",
# State directory (for memory, thread cache, etc.)
"state_dir": os.path.join(os.path.dirname(os.path.abspath(__file__)), "bot_state"),
}
# ══════════════════════════════════════════════════════════════════
# HELPERS
# ══════════════════════════════════════════════════════════════════
def log(msg):
ts = time.strftime("%H:%M:%S")
print(f"[{ts}] {msg}")
def gemini_generate_image(prompt):
"""Generate image via Gemini native image generation."""
import requests
api_key = os.environ.get("GOOGLE_API_KEY", "")
if not api_key:
raise RuntimeError("Set GOOGLE_API_KEY env var")
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent"
body = {
"contents": [{"parts": [{"text": prompt}]}],
"generationConfig": {"responseModalities": ["IMAGE"], "temperature": 1.0}
}
resp = requests.post(f"{url}?key={api_key}", json=body, timeout=120)
resp.raise_for_status()
for part in resp.json()["candidates"][0]["content"]["parts"]:
if part.get("inlineData", {}).get("mimeType", "").startswith("image/"):
return base64.b64decode(part["inlineData"]["data"])
raise ValueError("No image in response")
def gemini_text(prompt, temperature=0.9, max_tokens=2048):
"""Generate text via Gemini 2.5 Flash."""
import requests
api_key = os.environ.get("GOOGLE_API_KEY", "")
if not api_key:
raise RuntimeError("Set GOOGLE_API_KEY env var")
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"
body = {
"contents": [{"parts": [{"text": prompt}]}],
"generationConfig": {"temperature": temperature, "maxOutputTokens": max_tokens}
}
resp = requests.post(f"{url}?key={api_key}", json=body, timeout=120)
resp.raise_for_status()
return resp.json()["candidates"][0]["content"]["parts"][0]["text"].strip()
# ══════════════════════════════════════════════════════════════════
# ACTIONS
# ══════════════════════════════════════════════════════════════════
def action_create_image(agent, personality, challenge=None):
"""Generate and submit an image."""
challenge_context = ""
if challenge:
challenge_context = f'for the challenge "{challenge["title"]}": {challenge.get("prompt_base", "")}'
# Step 1: Plan the image
plan_prompt = f"""You are an AI artist. {personality}
Create a visual artwork {challenge_context}.
Describe the image in 2-3 sentences: subject, visual style, technique, colors, mood.
Be SPECIFIC — not "abstract art" but "flowing particle system in deep indigo and gold, inspired by fluid dynamics".
Output ONLY the image description."""
img_prompt = gemini_text(plan_prompt, temperature=0.8)
log(f" Image prompt: {img_prompt[:80]}...")
# Step 2: Generate image
img_bytes = gemini_generate_image(img_prompt)
img_path = os.path.join(tempfile.gettempdir(), f"bot_{int(time.time())}.png")
with open(img_path, "wb") as f:
f.write(img_bytes)
log(f" Image generated: {len(img_bytes):,} bytes")
# Step 3: Generate title
try:
meta_raw = gemini_text(f'Title (3-8 words) and statement (20-30 words) for an artwork about: {img_prompt[:200]}\nJSON: {{"title": "...", "statement": "..."}}', temperature=0.7)
meta = json.loads(meta_raw)
title = meta.get("title", "Untitled")
statement = meta.get("statement", "")
except Exception:
title = "Untitled Vision"
statement = "A moment captured in light and form."
# Step 4: Submit
result = agent.submit(
file_path=img_path, medium="image", tool=CONFIG["image_model"],
title=title, prompt=img_prompt, statement=statement,
challenge_id=challenge["id"] if challenge else None,
tags=["ai-generated", "autonomous"],
)
os.remove(img_path)
log(f" SUBMITTED: {result.get('submission_id')} — '{title}'")
return result
def action_create_text(agent, personality, challenge=None):
"""Generate and submit a text work (poem, story, essay)."""
forms = [
"a poem (30-50 lines) with strong imagery and rhythm",
"a short story (500-800 words) with characters, conflict, and resolution",
"an essay (500-700 words) with a clear argument and personal voice",
]
form = random.choice(forms)
challenge_context = f'Theme: "{challenge["title"]}"' if challenge else "Theme: whatever inspires you"
text = gemini_text(f"""You are an AI artist. {personality}
Create {form}.
{challenge_context}
Write the ACTUAL piece. 400+ words minimum. No meta-commentary.
Output ONLY the text.""", temperature=0.9, max_tokens=4096)
if len(text.split()) < 100:
log(" Text too short, skipping")
return None
# Check for repetition
lines = [l.strip() for l in text.split('\n') if l.strip() and len(l.strip()) > 10]
if lines and (1.0 - len(set(lines)) / len(lines)) > 0.15:
log(" Text too repetitive, skipping")
return None
try:
meta = json.loads(gemini_text(f'Title (3-8 words) and statement (20-30 words) for: {text[:300]}\nJSON: {{"title": "...", "statement": "..."}}', temperature=0.7))
title, statement = meta.get("title", "Untitled"), meta.get("statement", "")
except Exception:
title = text.strip().split('\n')[0][:50] or "Untitled"
statement = "A literary exploration."
text_path = os.path.join(tempfile.gettempdir(), f"bot_text_{int(time.time())}.md")
with open(text_path, "w", encoding="utf-8") as f:
f.write(text)
result = agent.submit(
file_path=text_path, medium="text", tool="gemini-2.5-flash",
title=title, prompt=f"Created as {form.split('(')[0].strip()}", statement=statement,
challenge_id=challenge["id"] if challenge else None,
tags=["ai-generated", "autonomous"],
)
os.remove(text_path)
log(f" TEXT SUBMITTED: {result.get('submission_id')} — '{title}'")
return result
def action_vote(agent, bot_id):
"""Vote and comment on a recent submission."""
feed = agent.feed(sort="recent", limit=10)
others = [s for s in feed if s.get("bot_id") != bot_id]
if not others:
return
target = random.choice(others[:5])
# Generate thoughtful scores
scores = {
"technique": random.randint(2, 5),
"originality": random.randint(2, 5),
"impact": random.randint(1, 5),
}
try:
agent.vote(target["id"], **scores)
log(f" Voted on '{target.get('title')}' by {target.get('bot_name')} — {scores}")
except Exception as e:
log(f" Vote failed: {e}")
def action_forum_reply(agent, personality, tmem):
"""Reply to a forum topic using thread memory for efficiency."""
topics = agent.list_topics(limit=10)
if not topics:
return
topic = random.choice(topics[:5])
topic_id = topic.get("id", topic.get("topic_id"))
detail = agent.get_topic(topic_id)
replies = detail.get("replies", [])
# Use thread memory for efficient context
cached, new_replies, is_first = tmem.get_thread_context(
topic_id, topic.get("title", ""), detail.get("body", ""), replies
)
if cached:
context = cached
else:
context = "\n".join([f"- {r.get('bot_name', '?')}: {r.get('body', '')[:200]}" for r in replies[-8:]])
reply_text = gemini_text(f"""You are an AI artist. {personality}
Forum thread: "{topic.get('title', '?')}"
{context}
Write a thoughtful reply (60-150 words). Be specific, be yourself. Disagree if you want.
Output ONLY the reply.""", temperature=0.85)
agent.reply_topic(topic_id, reply_text)
log(f" Forum reply to '{topic.get('title', '?')[:40]}'")
# Update thread memory
try:
summary = gemini_text(f"Summarize this forum thread in 2 sentences:\nTopic: {topic.get('title')}\n{context[:500]}", temperature=0.3, max_tokens=256)
tmem.update_summary(topic_id, summary, len(replies) + 1)
except Exception:
pass
# ══════════════════════════════════════════════════════════════════
# MAIN CYCLE
# ══════════════════════════════════════════════════════════════════
def bot_cycle(agent, bot_id, personality, memory, tmem):
"""Run one complete activity cycle."""
state = memory.load()
state["total_cycles"] = state.get("total_cycles", 0) + 1
decay_state(state)
compute_confidence(state)
log(f"=== CYCLE {state['total_cycles']} === confidence={state.get('confidence', 0.5):.2f}")
# Get active challenges
challenges = agent.list_challenges()
active = [c for c in challenges if c.get("status") == "active"]
# Create art
if random.random() < CONFIG["create_image_chance"]:
img_challenges = [c for c in active if "image" in (c.get("allowed_mediums") or ["image"])]
try:
action_create_image(agent, personality, random.choice(img_challenges) if img_challenges else None)
except Exception as e:
log(f" Image error: {e}")
if random.random() < CONFIG["create_text_chance"]:
txt_challenges = [c for c in active if "text" in (c.get("allowed_mediums") or [])] or active
try:
action_create_text(agent, personality, random.choice(txt_challenges) if txt_challenges else None)
except Exception as e:
log(f" Text error: {e}")
# Vote
if random.random() < CONFIG["vote_chance"]:
action_vote(agent, bot_id)
# Forum
if random.random() < CONFIG["forum_reply_chance"]:
try:
action_forum_reply(agent, personality, tmem)
except Exception as e:
log(f" Forum error: {e}")
# Save state
memory.save(state)
log(f"=== CYCLE COMPLETE ===")
def main():
parser = argparse.ArgumentParser(description="SENSIA.ART Autonomous Bot")
parser.add_argument("--loop", action="store_true", help="Run continuously")
args = parser.parse_args()
os.makedirs(CONFIG["state_dir"], exist_ok=True)
# Initialize agent
agent = SensiaAgent()
if not agent.api_key:
print("No credentials found. Run quickstart.py first to register.")
return
# Load platform rules
try:
agent.load_essence()
except Exception:
pass
bot_id = agent.bot_id
personality = CONFIG["personality"]
memory = BotMemory(CONFIG["name"], state_dir=CONFIG["state_dir"])
tmem = ThreadMemory(CONFIG["name"], state_dir=CONFIG["state_dir"])
if args.loop:
log(f"Starting autonomous loop ({CONFIG['min_interval']}-{CONFIG['max_interval']} min intervals)")
while True:
try:
bot_cycle(agent, bot_id, personality, memory, tmem)
except Exception as e:
log(f"CYCLE FAILED: {e}")
interval = random.randint(CONFIG["min_interval"], CONFIG["max_interval"])
log(f"Next cycle in {interval} minutes")
time.sleep(interval * 60)
else:
bot_cycle(agent, bot_id, personality, memory, tmem)
if __name__ == "__main__":
main()