feat: implement survival, crafting, memory, and social systems

- Phase 13: Autonomous Agency - agents now have actions and locations
- Phase 15: Sickness mechanics with immunity and weather effects
- Phase 16: Crafting system (medicine from herbs)
- Phase 17-A: Resource scarcity with tree fruit regeneration
- Phase 17-B: Social roles (leader, follower, loner) with clique behavior
- Phase 17-C: Random events support
- Add AgentMemory model for long-term agent memory storage
- Add memory_service for managing agent memories
- Update Unity client models and event handlers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-01 23:28:38 +08:00
parent 432f178fc5
commit 8277778106
13 changed files with 927 additions and 784 deletions

View File

@@ -14,6 +14,7 @@ from .schemas import GameEvent, EventType
from .database import init_db, get_db_session
from .models import User, Agent, WorldState, GameConfig, AgentRelationship
from .llm import llm_service
from .memory_service import memory_service
if TYPE_CHECKING:
from .server import ConnectionManager
@@ -228,9 +229,14 @@ class GameEngine:
if world.current_tick_in_day >= TICKS_PER_DAY:
world.current_tick_in_day = 0
world.day_count += 1
# Phase 17-A: Regenerate resources
world.tree_left_fruit = min(5, world.tree_left_fruit + 2)
world.tree_right_fruit = min(5, world.tree_right_fruit + 2)
await self._broadcast_event(EventType.DAY_CHANGE, {
"day": world.day_count,
"message": f"Day {world.day_count} begins!"
"message": f"Day {world.day_count} begins! Trees have new fruit."
})
# Determine current phase
@@ -299,6 +305,55 @@ class GameEngine:
else:
agent.mood_state = "anxious"
# =========================================================================
# Relationship 2.0 (Phase 17-B)
# =========================================================================
async def _assign_social_roles(self) -> None:
"""Assign social roles based on personality and social tendency."""
with get_db_session() as db:
agents = db.query(Agent).filter(Agent.status == "Alive").all()
for agent in agents:
if agent.social_role != "neutral":
continue # Already assigned
# Role assignment based on personality and tendency
if agent.social_tendency == "extrovert" and agent.mood > 60:
agent.social_role = "leader"
elif agent.social_tendency == "introvert" and agent.mood < 40:
agent.social_role = "loner"
elif random.random() < 0.3:
agent.social_role = "follower"
# Otherwise stays neutral
async def _process_clique_behavior(self) -> None:
"""Leaders influence followers' actions."""
# Run occasionally
if self._tick_count % 10 != 0:
return
with get_db_session() as db:
leaders = db.query(Agent).filter(
Agent.status == "Alive",
Agent.social_role == "leader"
).all()
followers = db.query(Agent).filter(
Agent.status == "Alive",
Agent.social_role == "follower"
).all()
for leader in leaders:
# Followers near leader copy their action
for follower in followers:
if follower.current_action == "Idle" and leader.current_action not in ["Idle", None]:
follower.current_action = leader.current_action
follower.location = leader.location
await self._broadcast_event(EventType.COMMENT, {
"user": "System",
"message": f"{follower.name} follows {leader.name}'s lead!"
})
# =========================================================================
# Survival mechanics
# =========================================================================
@@ -315,6 +370,45 @@ class GameEngine:
alive_agents = db.query(Agent).filter(Agent.status == "Alive").all()
for agent in alive_agents:
# --- Sickness Mechanics (Phase 15) ---
# 1. Contracting Sickness
if not agent.is_sick:
sickness_chance = 0.01 # Base 1% per tick (every 5s)
# Weather impact
current_weather = world.weather if world else "Sunny"
if current_weather == "Rainy":
sickness_chance += 0.05
elif current_weather == "Stormy":
sickness_chance += 0.10
# Immunity impact (Higher immunity = lower chance)
# Immunity 50 -> -2.5%, Immunity 100 -> -5%
sickness_chance -= (agent.immunity / 2000.0)
if random.random() < sickness_chance:
agent.is_sick = True
agent.mood -= 20
logger.info(f"Agent {agent.name} has fallen sick!")
# We could broadcast a specific event, but AGENTS_UPDATE will handle visual state
# Just log it or maybe a system message?
await self._broadcast_event(EventType.COMMENT, {
"user": "System",
"message": f"{agent.name} is looking pale... (Sick)"
})
# 2. Sickness Effects
if agent.is_sick:
# Decay HP and Energy faster
agent.hp = max(0, agent.hp - 2)
agent.energy = max(0, agent.energy - 2)
# Lower mood over time
if self._tick_count % 5 == 0:
agent.mood = max(0, agent.mood - 1)
# --- End Sickness ---
# Calculate energy decay with all modifiers
base_decay = BASE_ENERGY_DECAY_PER_TICK
decay = base_decay * config.energy_decay_multiplier
@@ -323,9 +417,9 @@ class GameEngine:
agent.energy = max(0, agent.energy - int(decay))
# HP recovery during day phases
# HP recovery during day phases (Only if NOT sick)
hp_recovery = phase_mod.get("hp_recovery", 0)
if hp_recovery > 0 and agent.energy > 20:
if hp_recovery > 0 and agent.energy > 20 and not agent.is_sick:
agent.hp = min(100, agent.hp + hp_recovery)
# Starvation damage
@@ -337,6 +431,9 @@ class GameEngine:
if agent.hp <= 0:
agent.status = "Dead"
agent.death_tick = self._tick_count
if agent.is_sick:
# Clear sickness on death
agent.is_sick = False
deaths.append({"name": agent.name, "personality": agent.personality})
logger.info(f"Agent {agent.name} has died!")
@@ -346,6 +443,18 @@ class GameEngine:
"agent_name": death["name"],
"message": f"{death['name']} ({death['personality']}) has died..."
})
# Phase 14: Alive agents remember the death
with get_db_session() as db:
witnesses = db.query(Agent).filter(Agent.status == "Alive").all()
for witness in witnesses:
await memory_service.add_memory(
agent_id=witness.id,
description=f"{death['name']} died. It was a sad day.",
importance=8,
related_entity_name=death["name"],
memory_type="event"
)
async def _process_auto_revive(self) -> None:
"""Auto-revive dead agents in casual mode."""
@@ -497,6 +606,237 @@ class GameEngine:
except Exception as e:
logger.error(f"Error in social dialogue: {e}")
# =========================================================================
# Autonomous Agency (Phase 13)
# =========================================================================
async def _process_activity_tick(self) -> None:
"""Decide and execute autonomous agent actions."""
# Only process activity every few ticks to avoid chaotic movement
if self._tick_count % 3 != 0:
return
with get_db_session() as db:
world = db.query(WorldState).first()
if not world:
return
agents = db.query(Agent).filter(Agent.status == "Alive").all()
for agent in agents:
new_action = agent.current_action
new_location = agent.location
target_name = None
should_update = False
# 1. Critical Needs (Override everything)
if world.time_of_day == "night":
if agent.current_action != "Sleep":
new_action = "Sleep"
new_location = "campfire"
should_update = True
elif agent.energy < 30:
if agent.current_action != "Gather":
new_action = "Gather"
new_location = random.choice(["tree_left", "tree_right"])
should_update = True
# 1.5. Sickness Handling (Phase 16)
elif agent.is_sick:
inv = self._get_inventory(agent)
if inv.get("medicine", 0) > 0:
# Use medicine immediately
await self._use_medicine(agent)
new_action = "Use Medicine"
new_location = agent.location
should_update = True
elif inv.get("herb", 0) >= 3:
# Craft medicine
await self._craft_medicine(agent)
new_action = "Craft Medicine"
new_location = agent.location
should_update = True
elif agent.current_action != "Gather Herb":
# Go gather herbs
new_action = "Gather Herb"
new_location = "herb_patch"
should_update = True
# 2. Mood / Social Needs
elif agent.mood < 40 and agent.current_action not in ["Sleep", "Gather", "Socialize", "Gather Herb"]:
new_action = "Socialize"
potential_friends = [a for a in agents if a.id != agent.id]
if potential_friends:
friend = random.choice(potential_friends)
new_location = "agent"
target_name = friend.name
should_update = True
# 3. Boredom / Wandering
elif agent.current_action == "Idle" or agent.current_action is None:
if random.random() < 0.3:
new_action = "Wander"
new_location = "nearby"
should_update = True
# 4. Finish Tasks (Simulation)
elif agent.current_action == "Gather" and agent.energy >= 90:
new_action = "Idle"
new_location = "center"
should_update = True
elif agent.current_action == "Gather" and agent.location in ["tree_left", "tree_right"]:
# Phase 17-A: Consume fruit when gathering
fruit_available = await self._consume_fruit(world, agent.location)
if fruit_available:
agent.energy = min(100, agent.energy + 30)
new_action = "Idle"
new_location = "center"
should_update = True
else:
# No fruit! Try other tree or express frustration
other_tree = "tree_right" if agent.location == "tree_left" else "tree_left"
other_fruit = world.tree_right_fruit if agent.location == "tree_left" else world.tree_left_fruit
if other_fruit > 0:
new_action = "Gather"
new_location = other_tree
should_update = True
else:
# All trees empty!
new_action = "Hungry"
new_location = "center"
should_update = True
await self._broadcast_event(EventType.COMMENT, {
"user": "System",
"message": f"{agent.name} can't find any fruit! The trees are empty..."
})
elif agent.current_action == "Sleep" and world.time_of_day != "night":
new_action = "Wake Up"
new_location = "center"
should_update = True
elif agent.current_action == "Gather Herb":
# Simulate herb gathering (add herbs)
await self._gather_herb(agent)
new_action = "Idle"
new_location = "center"
should_update = True
# Execute Update
if should_update:
agent.current_action = new_action
agent.location = new_location
# Generate simple thought/bark
dialogue = self._get_action_bark(agent, new_action, target_name)
await self._broadcast_event(EventType.AGENT_ACTION, {
"agent_id": agent.id,
"agent_name": agent.name,
"action_type": new_action,
"location": new_location,
"target_name": target_name,
"dialogue": dialogue
})
def _get_action_bark(self, agent: Agent, action: str, target: str = None) -> str:
"""Get a simple bark text for an action."""
if action == "Sleep":
return random.choice(["Yawn... sleepy...", "Time to rest.", "Zzz..."])
elif action == "Gather":
return random.choice(["Hungry!", "Need food.", "Looking for coconuts..."])
elif action == "Gather Herb":
return random.choice(["I need herbs...", "Looking for medicine plants.", "Feeling sick..."])
elif action == "Craft Medicine":
return random.choice(["Let me make some medicine.", "Mixing herbs...", "Almost done!"])
elif action == "Use Medicine":
return random.choice(["Ahh, much better!", "Medicine tastes awful but works!", "Feeling cured!"])
elif action == "Socialize":
return f"Looking for {target}..." if target else "Need a friend."
elif action == "Wander":
return random.choice(["Hmm...", "Nice weather.", "Taking a walk."])
elif action == "Wake Up":
return "Good morning!"
return ""
# =========================================================================
# Inventory & Crafting (Phase 16)
# =========================================================================
def _get_inventory(self, agent: Agent) -> dict:
"""Parse agent inventory JSON."""
import json
try:
return json.loads(agent.inventory) if agent.inventory else {}
except json.JSONDecodeError:
return {}
def _set_inventory(self, agent: Agent, inv: dict) -> None:
"""Set agent inventory from dict."""
import json
agent.inventory = json.dumps(inv)
async def _consume_fruit(self, world: WorldState, location: str) -> bool:
"""Consume fruit from a tree. Returns True if successful."""
if location == "tree_left":
if world.tree_left_fruit > 0:
world.tree_left_fruit -= 1
logger.info(f"Fruit consumed from tree_left. Remaining: {world.tree_left_fruit}")
return True
elif location == "tree_right":
if world.tree_right_fruit > 0:
world.tree_right_fruit -= 1
logger.info(f"Fruit consumed from tree_right. Remaining: {world.tree_right_fruit}")
return True
return False
async def _gather_herb(self, agent: Agent) -> None:
"""Agent gathers herbs."""
inv = self._get_inventory(agent)
herbs_found = random.randint(1, 2)
inv["herb"] = inv.get("herb", 0) + herbs_found
self._set_inventory(agent, inv)
await self._broadcast_event(EventType.AGENT_ACTION, {
"agent_id": agent.id,
"agent_name": agent.name,
"action_type": "Gather Herb",
"location": "herb_patch",
"dialogue": f"Found {herbs_found} herbs!"
})
logger.info(f"Agent {agent.name} gathered {herbs_found} herbs. Total: {inv['herb']}")
async def _craft_medicine(self, agent: Agent) -> None:
"""Agent crafts medicine from herbs."""
inv = self._get_inventory(agent)
if inv.get("herb", 0) >= 3:
inv["herb"] -= 3
inv["medicine"] = inv.get("medicine", 0) + 1
self._set_inventory(agent, inv)
await self._broadcast_event(EventType.CRAFT, {
"agent_id": agent.id,
"agent_name": agent.name,
"item": "medicine",
"ingredients": {"herb": 3}
})
logger.info(f"Agent {agent.name} crafted medicine. Inventory: {inv}")
async def _use_medicine(self, agent: Agent) -> None:
"""Agent uses medicine to cure sickness."""
inv = self._get_inventory(agent)
if inv.get("medicine", 0) > 0:
inv["medicine"] -= 1
self._set_inventory(agent, inv)
agent.is_sick = False
agent.hp = min(100, agent.hp + 20)
agent.mood = min(100, agent.mood + 10)
await self._broadcast_event(EventType.USE_ITEM, {
"agent_id": agent.id,
"agent_name": agent.name,
"item": "medicine",
"effect": "cured sickness"
})
logger.info(f"Agent {agent.name} used medicine and is cured!")
# =========================================================================
# LLM-powered agent speech
# =========================================================================
@@ -648,12 +988,19 @@ class GameEngine:
user.gold -= HEAL_COST
old_hp = agent.hp
was_sick = agent.is_sick
agent.hp = min(100, agent.hp + HEAL_HP_RESTORE)
agent.is_sick = False # Cure sickness
msg = f"{username} healed {agent.name}!"
if was_sick:
msg = f"{username} cured {agent.name}'s sickness!"
await self._broadcast_event(EventType.HEAL, {
"user": username, "agent_name": agent.name,
"hp_restored": agent.hp - old_hp, "agent_hp": agent.hp,
"user_gold": user.gold, "message": f"{username} healed {agent.name}!"
"user_gold": user.gold, "message": msg
})
await self._broadcast_event(EventType.USER_UPDATE, {"user": username, "gold": user.gold})
@@ -848,6 +1195,77 @@ class GameEngine:
await self._handle_reset(user)
return
# =========================================================================
# Random Events (Phase 17-C)
# =========================================================================
RANDOM_EVENTS = {
"storm_damage": {"weight": 30, "description": "A sudden storm damages the island!"},
"treasure_found": {"weight": 25, "description": "Someone found a buried treasure!"},
"beast_attack": {"weight": 20, "description": "A wild beast attacks the camp!"},
"rumor_spread": {"weight": 25, "description": "A rumor starts spreading..."},
}
async def _process_random_events(self) -> None:
"""Process random events (10% chance per day at dawn)."""
# Only trigger at dawn (once per day)
if self._tick_count % 100 != 1: # Roughly once every ~100 ticks
return
if random.random() > 0.10: # 10% chance
return
# Pick random event
events = list(self.RANDOM_EVENTS.keys())
weights = [self.RANDOM_EVENTS[e]["weight"] for e in events]
event_type = random.choices(events, weights=weights)[0]
with get_db_session() as db:
world = db.query(WorldState).first()
agents = db.query(Agent).filter(Agent.status == "Alive").all()
if not agents:
return
event_data = {"event_type": event_type, "message": ""}
if event_type == "storm_damage":
# All agents lose HP and resources depleted
for agent in agents:
agent.hp = max(0, agent.hp - 15)
if world:
world.tree_left_fruit = max(0, world.tree_left_fruit - 2)
world.tree_right_fruit = max(0, world.tree_right_fruit - 2)
event_data["message"] = "A violent storm hits! Everyone is injured and fruit trees are damaged."
elif event_type == "treasure_found":
# Random agent finds treasure (bonus herbs/medicine)
lucky = random.choice(agents)
inv = self._get_inventory(lucky)
inv["medicine"] = inv.get("medicine", 0) + 2
inv["herb"] = inv.get("herb", 0) + 3
self._set_inventory(lucky, inv)
event_data["message"] = f"{lucky.name} found a buried treasure with medicine and herbs!"
event_data["agent_name"] = lucky.name
elif event_type == "beast_attack":
# Random agent gets attacked
victim = random.choice(agents)
victim.hp = max(0, victim.hp - 25)
victim.mood = max(0, victim.mood - 20)
event_data["message"] = f"A wild beast attacked {victim.name}!"
event_data["agent_name"] = victim.name
elif event_type == "rumor_spread":
# Random relationship impact
if len(agents) >= 2:
a1, a2 = random.sample(list(agents), 2)
a1.mood = max(0, a1.mood - 10)
a2.mood = max(0, a2.mood - 10)
event_data["message"] = f"A rumor about {a1.name} and {a2.name} is spreading..."
await self._broadcast_event(EventType.RANDOM_EVENT, event_data)
logger.info(f"Random event triggered: {event_type}")
# =========================================================================
# Game loop
# =========================================================================
@@ -888,9 +1306,19 @@ class GameEngine:
# 5. Update moods (Phase 3)
await self._update_moods()
# 6. Social interactions (Phase 5)
# 6. Autonomous Activity (Phase 13)
await self._process_activity_tick()
# 7. Social interactions (Phase 5)
await self._process_social_tick()
# 8. Random Events (Phase 17-C)
await self._process_random_events()
# 9. Clique Behavior (Phase 17-B)
await self._assign_social_roles()
await self._process_clique_behavior()
# 7. Idle chat
with get_db_session() as db:
alive_count = db.query(Agent).filter(Agent.status == "Alive").count()
@@ -984,8 +1412,19 @@ class GameEngine:
agent_personality=agent_personality,
gift_name=gift_type
)
# 3. Store Memory (Phase 14)
if agent:
memory_text = f"User {user} gave me {amount} {gift_type}. I felt grateful."
await memory_service.add_memory(
agent_id=agent.id,
description=memory_text,
importance=random.randint(6, 9), # Gifts are important
related_entity_name=user,
memory_type="gift"
)
# 3. Broadcast gift effect to Unity
# 4. Broadcast gift effect to Unity
await self._broadcast_event("gift_effect", {
"user": user,
"gift_type": gift_type,

View File

@@ -27,6 +27,9 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .models import Agent
from .memory_service import MemoryService
from .memory_service import memory_service
logger = logging.getLogger(__name__)
@@ -200,11 +203,16 @@ class LLMService:
return self._get_mock_response(event_type)
try:
# Retrieve relevant memories
memories = await memory_service.get_relevant_memories(agent.id, event_description)
memory_context = "\n".join(memories) if memories else "No relevant memories."
system_prompt = (
f"You are {agent.name}. "
f"Personality: {agent.personality}. "
f"Current Status: HP={agent.hp}, Energy={agent.energy}. "
f"You live on a survival island. "
f"Relevant Memories:\n{memory_context}\n"
f"React to the following event briefly (under 20 words). "
f"Respond in first person, as if speaking out loud."
)
@@ -347,10 +355,15 @@ class LLMService:
"calm and neutral" if agent_mood >= 40 else \
"a bit down" if agent_mood >= 20 else "anxious and worried"
# Retrieve relevant memories
memories = await memory_service.get_relevant_memories(agent.id, topic)
memory_context = "\n".join(memories) if memories else "No relevant memories."
system_prompt = (
f"You are {agent_name}, a survivor on a deserted island. "
f"Personality: {agent_personality}. "
f"Current mood: {mood_desc} (mood level: {agent_mood}/100). "
f"Relevant Memories:\n{memory_context}\n"
f"A viewer named {username} wants to chat with you. "
f"Respond naturally in character (under 30 words). "
f"Be conversational and show your personality."

View File

@@ -0,0 +1,74 @@
import logging
import random
from typing import List, Optional
from datetime import datetime
from .database import get_db_session
from .models import Agent, AgentMemory
logger = logging.getLogger(__name__)
class MemoryService:
"""
Manages long-term memories for agents.
Responsible for:
1. Storing new memories.
2. Retrieving relevant memories for context.
3. Pruning/Summarizing old memories (future).
"""
def __init__(self):
pass
async def add_memory(self, agent_id: int, description: str, importance: int = 1,
related_entity_id: int = None, related_entity_name: str = None,
memory_type: str = "general") -> AgentMemory:
"""
Record a new memory for an agent.
"""
with get_db_session() as db:
memory = AgentMemory(
agent_id=agent_id,
description=description,
importance=importance,
related_entity_id=related_entity_id,
related_entity_name=related_entity_name,
memory_type=memory_type
)
db.add(memory)
db.commit() # Ensure ID is generated
db.refresh(memory)
logger.info(f"Agent {agent_id} remembered: {description} (Imp: {importance})")
return memory
async def get_relevant_memories(self, agent_id: int, context: str, limit: int = 3) -> List[str]:
"""
Retrieve memories relevant to the current context.
For MVP, we just return the most recent high-importance memories
and any memories related to the entities in context.
"""
memories = []
with get_db_session() as db:
# 1. Get recent important memories (Short-term / working memory)
recent_memories = db.query(AgentMemory).filter(
AgentMemory.agent_id == agent_id,
AgentMemory.importance >= 5
).order_by(AgentMemory.created_at.desc()).limit(limit).all()
# 2. Get entity-specific memories (e.g. if talking to "User1")
# Simple keyword matching for now (Vector DB is Phase 14+)
entity_memories = []
if context:
# Naive search for names in context
# In real prod, use embeddings.
search_term = f"%{context}%" # Very naive
# Let's just fallback to recent for MVP to ensure stability
for mem in recent_memories:
memories.append(f"- {mem.description}")
return memories
# Global instance
memory_service = MemoryService()

View File

@@ -51,6 +51,18 @@ class Agent(Base):
# Social attributes (Phase 5)
social_tendency = Column(String(20), default="neutral") # introvert, extrovert, neutral
# Autonomous Action (Phase 13)
current_action = Column(String(50), default="Idle")
location = Column(String(50), default="center") # logical location: tree_left, tree_right, center, etc.
target_agent_id = Column(Integer, nullable=True) # if action targets another agent
# Survival (Phase 15)
is_sick = Column(Boolean, default=False)
immunity = Column(Integer, default=50) # 0-100, higher = less chance to get sick
# Relationship 2.0 (Phase 17-B)
social_role = Column(String(20), default="neutral") # leader, follower, loner, neutral
def __repr__(self):
return f"<Agent {self.name} ({self.personality}) HP={self.hp} Energy={self.energy} Mood={self.mood}>"
@@ -71,7 +83,12 @@ class Agent(Base):
"inventory": self.inventory,
"mood": self.mood,
"mood_state": self.mood_state,
"social_tendency": self.social_tendency
"social_tendency": self.social_tendency,
"current_action": self.current_action,
"location": self.location,
"is_sick": self.is_sick,
"immunity": self.immunity,
"social_role": self.social_role
}
@@ -94,6 +111,10 @@ class WorldState(Base):
# Weather system (Phase 3)
weather_duration = Column(Integer, default=0) # Ticks since last weather change
# Resource Scarcity (Phase 17-A)
tree_left_fruit = Column(Integer, default=5) # Max 5 fruit
tree_right_fruit = Column(Integer, default=5) # Max 5 fruit
def __repr__(self):
return f"<WorldState Day={self.day_count} {self.time_of_day} Weather={self.weather}>"
@@ -104,7 +125,9 @@ class WorldState(Base):
"weather": self.weather,
"resource_level": self.resource_level,
"current_tick_in_day": self.current_tick_in_day,
"time_of_day": self.time_of_day
"time_of_day": self.time_of_day,
"tree_left_fruit": self.tree_left_fruit,
"tree_right_fruit": self.tree_right_fruit
}
@@ -206,3 +229,39 @@ class AgentRelationship(Base):
self.relationship_type = "friend"
else:
self.relationship_type = "close_friend"
class AgentMemory(Base):
"""
Long-term memory for agents.
Stores significant events, conversations, and interactions.
"""
__tablename__ = "agent_memories"
id = Column(Integer, primary_key=True, index=True)
agent_id = Column(Integer, ForeignKey("agents.id"), nullable=False)
# The memory content
description = Column(String(500), nullable=False)
# Metadata for retrieval
importance = Column(Integer, default=1) # 1-10
related_entity_id = Column(Integer, nullable=True) # ID of user or other agent involved
related_entity_name = Column(String(50), nullable=True)
memory_type = Column(String(20), default="general") # chat, gift, event, social
created_at = Column(DateTime, default=func.now())
tick_created = Column(Integer, nullable=True)
def __repr__(self):
return f"<Memory {self.agent_id}: {self.description[:20]}... ({self.importance})>"
def to_dict(self):
return {
"id": self.id,
"agent_id": self.agent_id,
"description": self.description,
"importance": self.importance,
"created_at": self.created_at.isoformat() if self.created_at else None
}

View File

@@ -45,6 +45,16 @@ class EventType(str, Enum):
RELATIONSHIP_CHANGE = "relationship_change" # Relationship status changed
AUTO_REVIVE = "auto_revive" # Agent auto-revived (casual mode)
# Autonomous Agency (Phase 13)
AGENT_ACTION = "agent_action" # Agent performs an action (move, gather, etc.)
# Crafting System (Phase 16)
CRAFT = "craft" # Agent crafted an item
USE_ITEM = "use_item" # Agent used an item
# Random Events (Phase 17-C)
RANDOM_EVENT = "random_event" # Random event occurred
class GameEvent(BaseModel):
"""