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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [v1.2.8] — 2026-03-27

### Added

- **Multi-Channel CRM Simulation (`src/crm_system.py`, `src/external_email_ingest.py`)**: introduced native integrations for **Salesforce** and **Zendesk**. The simulation now tracks CRM account ownership, flags "at-risk" opportunities during incidents, and automatically escalates customer support tickets based on system health.
- **High-Fidelity Meeting Transcripts (`src/normal_day.py`, `src/day_planner.py`)**: Added **Zoom** as a meeting medium for design discussions. Unlike Slack threads, Zoom produces verbatim Markdown transcripts that capture undocumented verbal decisions, creating intentional "knowledge gaps" for RAG evaluation.
- **Synthetic Business Telemetry (`src/post_sim_artifacts.py`)**: Introduced a post-processing suite that generates deterministic **Datadog** metrics (latency/error spikes), **NPS surveys**, and **customer invoices** featuring SLA credits calculated directly from simulation incident duration.

### Changed

- **State Machine Grounding (`src/flow.py`, `src/memory.py`)**: Expanded the `SimEvent` bus to include CRM and meeting events. LLMs now receive `crm_summary` context during daily planning to ensure outbound sales emails and support responses align with the global organizational state.
- **Advanced Social Graph Dynamics (`src/org_lifecycle.py`)**: Enhanced the employee departure logic. When an engineer leaves, the system now deterministically handles **CRM ownership lapses** alongside ticket reassignment and Dijkstra-based incident handoffs.
- **Simulation Parameters (`config/config.yaml`)**: Updated default simulation length to 30 days and adjusted hire schedules for Janice (Day 7) and Reese (Day 20) to better stress-test mid-sprint transitions.

### Fixed

- **Email Signal Accuracy (`src/external_email_ingest.py`)**: Improved the external contact generator to pull from a larger pool of 14 sources (up from 7) and corrected account name resolution to favor active Salesforce records over static config defaults.
- **PR Review Context (`src/memory.py`)**: Fixed a lack of historical depth in code reviews by implementing `context_for_pr_review`, which deterministically pulls prior comments and linked Jira updates into the LLM prompt.

---

## [v1.2.7] — 2026-03-25

### Added
Expand Down
74 changes: 41 additions & 33 deletions README.md

Large diffs are not rendered by default.

37 changes: 13 additions & 24 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ simulation:
domain: "apexathletics.io"
output_dir: "./export"
db_name: "orgforge"
max_days: 60
max_days: 30
start_date: "2026-03-02"

adhoc_confluence_prob: 0.3
adhoc_confluence_prob: 0.2
aws_alert_prob: 0.4
snyk_alert_prob: 0.2
sprint_tickets_per_planning: 4
Expand Down Expand Up @@ -174,7 +174,7 @@ org_lifecycle:
# events naturally once the roster context shows them as "still warming up".
scheduled_hires:
- name: "Janice"
day: 15
day: 7
dept: "Engineering_Backend"
role: "Backend Engineer"
expertise: ["Python", "FastAPI", "PostgreSQL", "TitanDB"]
Expand All @@ -187,7 +187,7 @@ org_lifecycle:
tenure: "new"

- name: "Reese"
day: 35
day: 20
dept: "Engineering_Backend"
role: "DevOps Engineer"
expertise: ["Kubernetes", "Terraform", "AWS", "Infrastructure"]
Expand Down Expand Up @@ -487,26 +487,15 @@ default_persona:
opinions. Do not let it develop a strong voice or override the tone of named personas
in the same scene.

# ── EXTERNAL PERSONAS ─────────────────────────────────────────

external_contacts:
- name: "AWS_Support"
display_name: "AWS Support"
org: "AWS"
role: "Support Engineer"
trigger_events: ["incident_opened", "fix_in_progress"]
trigger_health_threshold: 70 # only contacts AWS if system_health drops below this
internal_liaison: "Engineering" # which dept's lead gets the summary
summary_tone: "technical and reassuring, but noncommittal about SLA"

- name: "CustomerSuccess_Acme"
display_name: "Acme Corp (Customer)"
org: "Acme Corp"
role: "IT Director"
trigger_events: ["incident_opened"]
trigger_health_threshold: 60 # only surfaces for severe incidents
internal_liaison: "Sales"
summary_tone: "concerned about uptime, asking for ETA and root cause"
# ── CRM ───────────────────────────────────────────────────────

crm:
salesforce:
enabled: true
seed_accounts: true
zendesk:
enabled: true
link_to_incidents: true

# ── INSIDER THREAT ────────────────────────────────────────────

Expand Down
45 changes: 16 additions & 29 deletions src/confluence_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from crewai import Task, Crew
from memory import Memory, SimEvent
from artifact_registry import ArtifactRegistry, ConfluencePage
from utils.persona_utils import get_voice_card

if TYPE_CHECKING:
from graph_dynamics import GraphDynamics
Expand Down Expand Up @@ -295,7 +296,9 @@ def write_postmortem(
artifact_time, _ = self._clock.advance_actor(on_call, hours=pm_hours)
timestamp = artifact_time.isoformat()

backstory = self._persona(on_call, mem=self._mem, graph_dynamics=self._gd)
backstory = get_voice_card(
on_call, "design", mem=self._mem, graph_dynamics=self._gd
)
related = self._registry.related_context(topic=root_cause, n=3)
_qa_lead = next(
(name for name, p in PERSONAS.items() if "QA" in p.get("expertise", [])),
Expand Down Expand Up @@ -393,16 +396,14 @@ def write_design_doc(
chat_log = "\n".join(f"{m['user']}: {m['text']}" for m in slack_transcript)
ctx = self._mem.recall_with_rewrite(raw_query=topic, n=3, as_of_time=timestamp)
related = self._registry.related_context(topic=topic, n=3)
backstory = get_voice_card(
author, "design", mem=self._mem, graph_dynamics=self._gd
)

agent = make_agent(
role="Technical Lead",
goal="Document technical decisions and extract an actionable ticket.",
backstory=self._persona(
author,
mem=self._mem,
graph_dynamics=self._gd,
extra="You just finished a Slack discussion and need to document decisions and assign follow-up work.",
),
backstory=backstory,
llm=self._planner,
)
task = Task(
Expand Down Expand Up @@ -596,28 +597,19 @@ def write_adhoc_page(
expertise_str = ", ".join(expertise_list)

seed_query = f"{resolved_author} {expertise_str} {daily_theme}"
# Tier 3: LLM-generated seed query benefits from HyDE rewrite.
# recall_with_rewrite degrades to context_for_prompt if no llm_callable
# is passed, so this is safe to land before the callable is wired in.
topic_ctx = self._mem.recall_with_rewrite(
raw_query=seed_query,
n=3,
as_of_time=self._clock.now(resolved_author).isoformat(),
)
backstory = get_voice_card(
resolved_author, "design", mem=self._mem, graph_dynamics=self._gd
)

topic_agent = make_agent(
role="Content Planner",
goal="Identify a unique documentation gap based on your expertise and org history.",
backstory=self._persona(
resolved_author,
mem=self._mem,
graph_dynamics=self._gd,
extra=(
f"You are {resolved_author} from the {dept} department. "
f"Your expertise includes: {expertise_str}. "
f"You are deciding what to document today to provide the most value to the team."
),
),
backstory=backstory,
llm=self._worker,
)

Expand Down Expand Up @@ -662,19 +654,14 @@ def write_adhoc_page(

ctx = self._mem.context_for_prompt(title, n=3, as_of_time=timestamp)
related = self._registry.related_context(topic=title, n=4)
backstory = get_voice_card(
resolved_author, "design", mem=self._mem, graph_dynamics=self._gd
)

writer_agent = make_agent(
role="Corporate Writer",
goal=f"Draft a {title} Confluence page.",
backstory=self._persona(
resolved_author,
mem=self._mem,
graph_dynamics=self._gd,
extra=(
f"You are {resolved_author} from the {dept} department. "
f"Your expertise includes: {expertise_str}. "
),
),
backstory=backstory,
llm=self._planner,
)
task = Task(
Expand Down
Loading
Loading