feat(personas): Add persona_id support to Ticket/WorkerContext and ConductorEngine
This commit is contained in:
@@ -8,9 +8,9 @@
|
|||||||
- [x] Task: Conductor - User Manual Verification 'Phase 1: Core Model and Migration' (Protocol in workflow.md)
|
- [x] Task: Conductor - User Manual Verification 'Phase 1: Core Model and Migration' (Protocol in workflow.md)
|
||||||
|
|
||||||
## Phase 2: Granular MMA Integration
|
## Phase 2: Granular MMA Integration
|
||||||
- [ ] Task: Write Tests: Verify that a `Ticket` or `Track` can hold a `persona_id` override.
|
- [x] Task: Write Tests: Verify that a `Ticket` or `Track` can hold a `persona_id` override.
|
||||||
- [ ] Task: Implement: Update the MMA internal state to support per-epic, per-track, and per-task Persona assignments.
|
- [x] Task: Implement: Update the MMA internal state to support per-epic, per-track, and per-task Persona assignments.
|
||||||
- [ ] Task: Implement: Update the `WorkerContext` and `ConductorEngine` to resolve and apply the correct Persona before spawning an agent.
|
- [x] Task: Implement: Update the `WorkerContext` and `ConductorEngine` to resolve and apply the correct Persona before spawning an agent.
|
||||||
- [ ] Task: Implement: Add "Persona" metadata to the Tier Stream logs to visually confirm which profile is active.
|
- [ ] Task: Implement: Add "Persona" metadata to the Tier Stream logs to visually confirm which profile is active.
|
||||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Granular MMA Integration' (Protocol in workflow.md)
|
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Granular MMA Integration' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
|||||||
12
config.toml
12
config.toml
@@ -22,10 +22,10 @@ active = "C:/projects/gencpp/gencpp_sloppy.toml"
|
|||||||
separate_message_panel = false
|
separate_message_panel = false
|
||||||
separate_response_panel = false
|
separate_response_panel = false
|
||||||
separate_tool_calls_panel = false
|
separate_tool_calls_panel = false
|
||||||
bg_shader_enabled = true
|
bg_shader_enabled = false
|
||||||
crt_filter_enabled = false
|
crt_filter_enabled = false
|
||||||
separate_task_dag = false
|
separate_task_dag = false
|
||||||
separate_usage_analytics = false
|
separate_usage_analytics = true
|
||||||
separate_tier1 = false
|
separate_tier1 = false
|
||||||
separate_tier2 = false
|
separate_tier2 = false
|
||||||
separate_tier3 = false
|
separate_tier3 = false
|
||||||
@@ -48,8 +48,8 @@ separate_tier4 = false
|
|||||||
"Tier 4: QA" = false
|
"Tier 4: QA" = false
|
||||||
"Discussion Hub" = true
|
"Discussion Hub" = true
|
||||||
"Operations Hub" = true
|
"Operations Hub" = true
|
||||||
Message = true
|
Message = false
|
||||||
Response = true
|
Response = false
|
||||||
"Tool Calls" = false
|
"Tool Calls" = false
|
||||||
Theme = true
|
Theme = true
|
||||||
"Log Management" = true
|
"Log Management" = true
|
||||||
@@ -60,8 +60,8 @@ palette = "Nord Dark"
|
|||||||
font_path = "C:/projects/manual_slop/assets/fonts/Inter-Regular.ttf"
|
font_path = "C:/projects/manual_slop/assets/fonts/Inter-Regular.ttf"
|
||||||
font_size = 14.0
|
font_size = 14.0
|
||||||
scale = 1.2000000476837158
|
scale = 1.2000000476837158
|
||||||
transparency = 0.550000011920929
|
transparency = 1.0
|
||||||
child_transparency = 0.6399999856948853
|
child_transparency = 1.0
|
||||||
|
|
||||||
[mma]
|
[mma]
|
||||||
max_workers = 4
|
max_workers = 4
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ Collapsed=0
|
|||||||
DockId=0xAFC85805,2
|
DockId=0xAFC85805,2
|
||||||
|
|
||||||
[Window][Theme]
|
[Window][Theme]
|
||||||
Pos=0,977
|
Pos=0,1602
|
||||||
Size=659,1160
|
Size=387,935
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,2
|
DockId=0x00000002,2
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ Collapsed=0
|
|||||||
DockId=0x0000000C,2
|
DockId=0x0000000C,2
|
||||||
|
|
||||||
[Window][Context Hub]
|
[Window][Context Hub]
|
||||||
Pos=0,977
|
Pos=0,1602
|
||||||
Size=659,1160
|
Size=387,935
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000002,1
|
||||||
|
|
||||||
@@ -102,26 +102,26 @@ Collapsed=0
|
|||||||
DockId=0x0000000D,0
|
DockId=0x0000000D,0
|
||||||
|
|
||||||
[Window][Discussion Hub]
|
[Window][Discussion Hub]
|
||||||
Pos=1660,28
|
Pos=680,28
|
||||||
Size=1243,2109
|
Size=452,2509
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000013,0
|
DockId=0x00000013,0
|
||||||
|
|
||||||
[Window][Operations Hub]
|
[Window][Operations Hub]
|
||||||
Pos=661,28
|
Pos=389,28
|
||||||
Size=997,2109
|
Size=289,2509
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000012,0
|
DockId=0x00000012,0
|
||||||
|
|
||||||
[Window][Files & Media]
|
[Window][Files & Media]
|
||||||
Pos=0,977
|
Pos=0,1602
|
||||||
Size=659,1160
|
Size=387,935
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000002,0
|
||||||
|
|
||||||
[Window][AI Settings]
|
[Window][AI Settings]
|
||||||
Pos=0,28
|
Pos=0,28
|
||||||
Size=659,947
|
Size=387,1572
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000001,0
|
||||||
|
|
||||||
@@ -131,14 +131,14 @@ Size=416,325
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][MMA Dashboard]
|
[Window][MMA Dashboard]
|
||||||
Pos=2905,28
|
Pos=1134,28
|
||||||
Size=935,2109
|
Size=306,2509
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000C,0
|
DockId=0x0000000C,0
|
||||||
|
|
||||||
[Window][Log Management]
|
[Window][Log Management]
|
||||||
Pos=2905,28
|
Pos=1134,28
|
||||||
Size=935,2109
|
Size=306,2509
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000C,1
|
DockId=0x0000000C,1
|
||||||
|
|
||||||
@@ -337,8 +337,8 @@ Size=275,375
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Tool Preset Manager]
|
[Window][Tool Preset Manager]
|
||||||
Pos=827,642
|
Pos=192,440
|
||||||
Size=973,688
|
Size=1066,1324
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Table][0xFB6E3870,4]
|
[Table][0xFB6E3870,4]
|
||||||
@@ -429,17 +429,17 @@ Column 2 Weight=1.0000
|
|||||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=3840,2109 Split=X
|
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=1440,2509 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2903,1183 Split=X
|
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=1132,1183 Split=X
|
||||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=659,858 Split=Y Selected=0x8CA2375C
|
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=387,858 Split=Y Selected=0x8CA2375C
|
||||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,947 CentralNode=1 Selected=0x7BD57D6A
|
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,1172 CentralNode=1 Selected=0x7BD57D6A
|
||||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,1160 Selected=0x1DCB2623
|
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,935 Selected=0x1DCB2623
|
||||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=2242,858 Split=X Selected=0x418C7449
|
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=743,858 Split=X Selected=0x418C7449
|
||||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=997,402 Selected=0x418C7449
|
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=289,402 Selected=0x418C7449
|
||||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=1243,402 Selected=0x6F2B5B04
|
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=452,402 Selected=0x6F2B5B04
|
||||||
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
||||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=935,1183 Split=Y Selected=0x3AEC3498
|
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=306,1183 Split=Y Selected=0x3AEC3498
|
||||||
DockNode ID=0x0000000C Parent=0x00000004 SizeRef=1074,1208 Selected=0x3AEC3498
|
DockNode ID=0x0000000C Parent=0x00000004 SizeRef=1074,1208 Selected=0x3AEC3498
|
||||||
DockNode ID=0x0000000F Parent=0x00000004 SizeRef=1074,899 Selected=0x5CDB7A4B
|
DockNode ID=0x0000000F Parent=0x00000004 SizeRef=1074,899 Selected=0x5CDB7A4B
|
||||||
|
|
||||||
|
|||||||
10
personas.toml
Normal file
10
personas.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[personas.Default]
|
||||||
|
system_prompt = ""
|
||||||
|
provider = "minimax"
|
||||||
|
model = "MiniMax-M2.5"
|
||||||
|
preferred_models = [
|
||||||
|
"MiniMax-M2.5",
|
||||||
|
]
|
||||||
|
temperature = 0.0
|
||||||
|
top_p = 1.0
|
||||||
|
max_output_tokens = 32000
|
||||||
@@ -121,22 +121,25 @@ def parse_history_entries(history_strings: list[str], roles: list[str]) -> list[
|
|||||||
entries.append({"role": role, "content": content, "collapsed": True, "ts": ts})
|
entries.append({"role": role, "content": content, "collapsed": True, "ts": ts})
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
@dataclass
|
||||||
@dataclass
|
@dataclass
|
||||||
class Ticket:
|
class Ticket:
|
||||||
id: str
|
id: str
|
||||||
description: str
|
description: str
|
||||||
|
target_symbols: List[str] = field(default_factory=list)
|
||||||
|
context_requirements: List[str] = field(default_factory=list)
|
||||||
|
depends_on: List[str] = field(default_factory=list)
|
||||||
status: str = "todo"
|
status: str = "todo"
|
||||||
assigned_to: str = "unassigned"
|
assigned_to: str = "unassigned"
|
||||||
priority: str = "medium"
|
priority: str = "medium"
|
||||||
target_file: Optional[str] = None
|
target_file: Optional[str] = None
|
||||||
target_symbols: List[str] = field(default_factory=list)
|
|
||||||
context_requirements: List[str] = field(default_factory=list)
|
|
||||||
depends_on: List[str] = field(default_factory=list)
|
|
||||||
blocked_reason: Optional[str] = None
|
blocked_reason: Optional[str] = None
|
||||||
step_mode: bool = False
|
step_mode: bool = False
|
||||||
retry_count: int = 0
|
retry_count: int = 0
|
||||||
manual_block: bool = False
|
manual_block: bool = False
|
||||||
model_override: Optional[str] = None
|
model_override: Optional[str] = None
|
||||||
|
persona_id: Optional[str] = None
|
||||||
|
|
||||||
def mark_blocked(self, reason: str) -> None:
|
def mark_blocked(self, reason: str) -> None:
|
||||||
self.status = "blocked"
|
self.status = "blocked"
|
||||||
@@ -175,6 +178,7 @@ class Ticket:
|
|||||||
"retry_count": self.retry_count,
|
"retry_count": self.retry_count,
|
||||||
"manual_block": self.manual_block,
|
"manual_block": self.manual_block,
|
||||||
"model_override": self.model_override,
|
"model_override": self.model_override,
|
||||||
|
"persona_id": self.persona_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -194,6 +198,7 @@ class Ticket:
|
|||||||
retry_count=data.get("retry_count", 0),
|
retry_count=data.get("retry_count", 0),
|
||||||
manual_block=data.get("manual_block", False),
|
manual_block=data.get("manual_block", False),
|
||||||
model_override=data.get("model_override"),
|
model_override=data.get("model_override"),
|
||||||
|
persona_id=data.get("persona_id"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -224,12 +229,15 @@ class Track:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
@dataclass
|
||||||
@dataclass
|
@dataclass
|
||||||
class WorkerContext:
|
class WorkerContext:
|
||||||
ticket_id: str
|
ticket_id: str
|
||||||
model_name: str
|
model_name: str
|
||||||
tool_preset: Optional[str] = None
|
|
||||||
messages: List[Dict[str, Any]] = field(default_factory=list)
|
messages: List[Dict[str, Any]] = field(default_factory=list)
|
||||||
|
tool_preset: Optional[str] = None
|
||||||
|
persona_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -295,7 +295,8 @@ class ConductorEngine:
|
|||||||
ticket_id=ticket.id,
|
ticket_id=ticket.id,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
messages=[],
|
messages=[],
|
||||||
tool_preset=self.tier_usage["Tier 3"]["tool_preset"]
|
tool_preset=self.tier_usage["Tier 3"]["tool_preset"],
|
||||||
|
persona_id=ticket.persona_id
|
||||||
)
|
)
|
||||||
context_files = ticket.context_requirements if ticket.context_requirements else None
|
context_files = ticket.context_requirements if ticket.context_requirements else None
|
||||||
|
|
||||||
@@ -410,6 +411,22 @@ def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files:
|
|||||||
ai_client.set_provider(ai_client.get_provider(), context.model_name)
|
ai_client.set_provider(ai_client.get_provider(), context.model_name)
|
||||||
ai_client.set_tool_preset(context.tool_preset)
|
ai_client.set_tool_preset(context.tool_preset)
|
||||||
|
|
||||||
|
# Apply Persona if specified
|
||||||
|
if context.persona_id:
|
||||||
|
from src.personas import PersonaManager
|
||||||
|
from src import paths
|
||||||
|
pm = PersonaManager(Path(paths.get_project_personas_path(Path.cwd())) if paths.get_project_personas_path(Path.cwd()).exists() else None)
|
||||||
|
try:
|
||||||
|
personas = pm.load_all()
|
||||||
|
if context.persona_id in personas:
|
||||||
|
persona = personas[context.persona_id]
|
||||||
|
if persona.system_prompt:
|
||||||
|
ai_client.set_custom_system_prompt(persona.system_prompt)
|
||||||
|
if persona.bias_profile:
|
||||||
|
ai_client.set_bias_profile(persona.bias_profile)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WARN] Failed to load persona {context.persona_id}: {e}")
|
||||||
|
|
||||||
# Check for abort BEFORE any major work
|
# Check for abort BEFORE any major work
|
||||||
if engine and hasattr(engine, "_abort_events"):
|
if engine and hasattr(engine, "_abort_events"):
|
||||||
abort_event = engine._abort_events.get(ticket.id)
|
abort_event = engine._abort_events.get(ticket.id)
|
||||||
|
|||||||
28
tests/test_persona_id.py
Normal file
28
tests/test_persona_id.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import pytest
|
||||||
|
from src.models import Ticket, WorkerContext
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticket_persona_id_serialization():
|
||||||
|
ticket = Ticket(
|
||||||
|
id="test-1", description="Test task", persona_id="SecuritySpecialist"
|
||||||
|
)
|
||||||
|
data = ticket.to_dict()
|
||||||
|
assert data["persona_id"] == "SecuritySpecialist"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticket_persona_id_deserialization():
|
||||||
|
data = {"id": "test-2", "description": "Test task 2", "persona_id": "CodeReviewer"}
|
||||||
|
ticket = Ticket.from_dict(data)
|
||||||
|
assert ticket.persona_id == "CodeReviewer"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticket_persona_id_default():
|
||||||
|
ticket = Ticket(id="test-3", description="Test")
|
||||||
|
assert ticket.persona_id is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_worker_context_persona_id():
|
||||||
|
ctx = WorkerContext(
|
||||||
|
ticket_id="test-1", model_name="gemini-2.5-flash", persona_id="DebugHelper"
|
||||||
|
)
|
||||||
|
assert ctx.persona_id == "DebugHelper"
|
||||||
Reference in New Issue
Block a user