Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 16 additions & 63 deletions src/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,42 +826,6 @@ def _embed_persona(args):
except Exception as e:
logger.error(f"[genesis] Persona embed failed for {name}: {e}")

sim_start = datetime.strptime(CONFIG["simulation"]["start_date"], "%Y-%m-%d")
for gap in CONFIG.get("knowledge_gaps", []):
name = gap["name"]
left_date = gap["left"] # "2024-06"
left_dt = datetime.strptime(left_date, "%Y-%m")
departure_day = -(sim_start - left_dt).days

self._mem.log_event(
SimEvent(
type="employee_departed",
day=departure_day,
date=f"{left_date}-01",
timestamp=f"{left_date}-01T09:00:00",
actors=[name],
artifact_ids={},
facts={
"name": name,
"role": gap.get("role", ""),
"knowledge_domains": gap.get("knew_about", []),
"documented_pct": gap.get("documented_pct", 0.0),
"reason": "voluntary",
"scheduled": True,
},
summary=(
f"{name} ({gap.get('role', 'unknown role')}) departed "
f"Day {departure_day} [voluntary]. "
f"Gaps: {', '.join(gap.get('knew_about', []))}. "
f"~{int(gap.get('documented_pct', 0) * 100)}% documented."
),
tags=["employee_departed", "lifecycle", "genesis"],
)
)
logger.info(
f"[genesis] Logged pre-sim departure: {name} (Day {departure_day})"
)

self._confluence.write_genesis_batches_parallel(
[
{
Expand Down Expand Up @@ -1790,33 +1754,7 @@ def _handle_incident(self):
on_call,
)

involves_gap = any(
k.lower() in root_cause.lower()
for emp in DEPARTED_EMPLOYEES.values()
for k in emp["knew_about"]
)

gap_areas: List[str] = []
gap_context_str: str = ""
if involves_gap:
departed_details: List[str] = []
for emp_name, emp in DEPARTED_EMPLOYEES.items():
hits = [k for k in emp["knew_about"] if k.lower() in root_cause.lower()]
if hits:
gap_areas.extend(hits)
departed_details.append(
f"{emp_name} (ex-{emp['role']}, left {emp['left']}, "
f"{int(emp['documented_pct'] * 100)}% documented) "
f"owned: {', '.join(hits)}"
)
if departed_details:
gap_context_str = (
f"KNOWLEDGE GAP FLAG: This incident touches underdocumented systems. "
f"{' | '.join(departed_details)}. "
f"Resolution may be blocked pending knowledge recovery."
)

self._lifecycle.scan_for_knowledge_gaps(
detected_gaps = self._lifecycle.scan_for_knowledge_gaps(
text=root_cause,
triggered_by=ticket_id,
day=self.state.day,
Expand All @@ -1825,6 +1763,21 @@ def _handle_incident(self):
timestamp=incident_start_iso,
)

involves_gap = len(detected_gaps) > 0
gap_areas = [g.domain_hit for g in detected_gaps]
gap_context_str = ""

if involves_gap:
details = [
f"{g.departed_name} (owned {g.domain_hit}, {int(g.documented_pct * 100)}% documented)"
for g in detected_gaps
]
gap_context_str = (
f"KNOWLEDGE GAP FLAG: This incident touches underdocumented systems. "
f"{' | '.join(details)}. "
f"Resolution may be blocked pending knowledge recovery."
)

prior = self._recurrence_detector.find_prior_incident(
root_cause, self.state.day, ticket_id
)
Expand Down
51 changes: 50 additions & 1 deletion src/genesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
LEGACY,
ORG_CHART,
)
from memory import Memory
from memory import Memory, SimEvent
from agent_factory import make_agent
from crewai import Task, Crew

Expand All @@ -43,6 +43,7 @@ def initialize(config, planner_llm, reset=False):
seed_tech_stack(mem, planner_llm)
seed_external_sources(mem, planner_llm)
seed_crm_accounts(mem)
seed_knowledge_gaps(mem)
logger.info("[genesis] ✅ Seeding complete.")

return mem
Expand Down Expand Up @@ -334,6 +335,54 @@ def seed_crm_accounts(mem: Memory):
pass


def seed_knowledge_gaps(mem: Memory):
"""Embeds skills and logs departure events for pre-simulation employees."""
if not CONFIG.get("knowledge_gaps"):
return

logger.info("[genesis] Seeding pre-simulation knowledge gaps...")

sim_start = datetime.strptime(CONFIG["simulation"]["start_date"], "%Y-%m-%d")

for gap in CONFIG.get("knowledge_gaps", []):
name = gap["name"]
left_date = gap["left"]
left_dt = datetime.strptime(left_date, "%Y-%m")
departure_day = -(sim_start - left_dt).days

mem.embed_persona_skills(
name=name,
data={
"expertise": gap.get("knew_about", []),
"social_role": gap.get("role", "Former Employee"),
},
dept=gap.get("dept", "Engineering"),
day=departure_day,
timestamp_iso=f"{left_date}-01T09:00:00",
)

mem.log_event(
SimEvent(
type="employee_departed",
day=departure_day,
date=f"{left_date}-01",
timestamp=f"{left_date}-01T09:00:00",
actors=[name],
artifact_ids={},
facts={
"name": name,
"role": gap.get("role", ""),
"knowledge_domains": gap.get("knew_about", []),
"documented_pct": gap.get("documented_pct", 0.5),
"is_genesis_gap": True,
},
summary=f"Genesis Gap: {name} ({gap.get('role')}) left Day {departure_day}.",
tags=["employee_departed", "lifecycle", "genesis"],
)
)
logger.info(f" [dim]→ Seeded Genesis Gap: {name}[/dim]")


@staticmethod
def _parse_sources(raw: str) -> List[dict]:
cleaned = re.sub(r"^```[a-z]*\n?", "", raw.strip()).rstrip("` \n")
Expand Down
28 changes: 17 additions & 11 deletions src/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,27 +483,33 @@ def _init_vector_indexes(self):
}

for coll_name in ["artifacts", "events"]:
if coll_name not in self._db.list_collection_names():
try:
self._db.create_collection(coll_name)
logger.info(f"[memory] Created collection: {coll_name}")
except Exception:
pass

coll = self._db[coll_name]

existing_indexes = list(coll.list_search_indexes())
if not any(idx.get("name") == "vector_index" for idx in existing_indexes):
existing = list(coll.list_search_indexes())
vector_idx = next(
(i for i in existing if i.get("name") == "vector_index"), None
)

if vector_idx:
status = vector_idx.get("status")
if status in ("FAILED", "DOES_NOT_EXIST"):
logger.warning(
f"[memory] Index on {coll_name} is {status}. Dropping..."
)
coll.drop_search_index("vector_index")
vector_idx = None

if not vector_idx:
try:
search_index_model = SearchIndexModel(
definition=index_definition,
name="vector_index",
type="vectorSearch",
)
coll.create_search_index(model=search_index_model)
logger.info(f"[memory] Created vector_index on {coll.name}")
logger.info(f"[memory] Created vector_index on {coll_name}")
except Exception as e:
logger.error(f"[memory] Could not create index on {coll.name}: {e}")
logger.error(f"[memory] Failed to create index on {coll_name}: {e}")

# ─── WRITE ────────────────────────────────

Expand Down
79 changes: 25 additions & 54 deletions src/normal_day.py
Original file line number Diff line number Diff line change
Expand Up @@ -3023,38 +3023,21 @@ def _save_slack(
"ts", self._clock.now("system").isoformat()
)

if self._embed_worker:
self._embed_worker.enqueue(
id=thread_id,
type="slack_thread",
title=f"{interaction_type.replace('_', ' ').title()} in #{channel}",
content=full_transcript,
day=self._state.day,
date=date_str,
timestamp=start_timestamp,
metadata={
"channel": channel,
"interaction_type": interaction_type,
"participants": list({m["user"] for m in messages}),
"message_count": len(messages),
},
)
else:
self._mem.embed_artifact(
id=thread_id,
type="slack_thread",
title=f"{interaction_type.replace('_', ' ').title()} in #{channel}",
content=full_transcript,
day=self._state.day,
date=date_str,
timestamp=start_timestamp,
metadata={
"channel": channel,
"interaction_type": interaction_type,
"participants": list({m["user"] for m in messages}),
"message_count": len(messages),
},
)
self._mem.embed_artifact(
id=thread_id,
type="slack_thread",
title=f"{interaction_type.replace('_', ' ').title()} in #{channel}",
content=full_transcript,
day=self._state.day,
date=date_str,
timestamp=start_timestamp,
metadata={
"channel": channel,
"interaction_type": interaction_type,
"participants": list({m["user"] for m in messages}),
"message_count": len(messages),
},
)

return slack_path, thread_id

Expand Down Expand Up @@ -3283,28 +3266,16 @@ def _save_zoom_transcript(
"medium": "zoom",
}

if self._embed_worker:
self._embed_worker.enqueue(
id=transcript_id,
type="zoom_transcript",
title=f"Zoom: {topic[:80]}",
content=full_text,
day=self._state.day,
date=date_str,
timestamp=meeting_time_iso,
metadata=embed_metadata,
)
else:
self._mem.embed_artifact(
id=transcript_id,
type="zoom_transcript",
title=f"Zoom: {topic[:80]}",
content=full_text,
day=self._state.day,
date=date_str,
timestamp=meeting_time_iso,
metadata=embed_metadata,
)
self._mem.embed_artifact(
id=transcript_id,
type="zoom_transcript",
title=f"Zoom: {topic[:80]}",
content=full_text,
day=self._state.day,
date=date_str,
timestamp=meeting_time_iso,
metadata=embed_metadata,
)

logger.info(
f" [dim]📹 Zoom transcript saved: {transcript_id} "
Expand Down
Loading
Loading