33from __future__ import annotations
44
55import re
6+ import time
67import uuid
78from dataclasses import dataclass , field
89from typing import Any , Callable , TYPE_CHECKING
@@ -27,11 +28,13 @@ class AgentProfile:
2728
2829
2930class SubAgentManager :
30- def __init__ (self , memory : MemoryBank , * , runner : Callable [[AgentProfile , str , list [dict [str , Any ]], set [str ]], str | dict [str , Any ]] | None = None ,
31- learning_engine : "LearningEngine | None" = None ):
31+ def __init__ (self , memory : MemoryBank , * , runner : Callable [..., str | dict [str , Any ]] | None = None ,
32+ learning_engine : "LearningEngine | None" = None ,
33+ provider_model : Callable [[], str ] | None = None ):
3234 self .memory = memory
3335 self .runner = runner
3436 self .learning_engine = learning_engine
37+ self .provider_model = provider_model
3538 self .profiles : dict [str , AgentProfile ] = {}
3639 self .register (AgentProfile ("researcher" , "Research the task, cite evidence, and separate facts from assumptions." , "readonly" ))
3740 self .register (AgentProfile ("coder" , "Inspect and modify code only when requested; verify every change." , "workspace" ))
@@ -71,10 +74,11 @@ def run(self, profile_name: str, task: str, *, workspace_id: str | None = None)
7174 task , n_results = 5 , profile = profile .name ,
7275 task_signature = self .learning_engine .task_signature (profile .name , task ) if self .learning_engine else None ,
7376 ) if profile .memory_scope != "none" else [])
77+ expected_provider_model = self .provider_model () if self .provider_model else "unknown"
7478 learning_run = None
7579 receipts : list [dict [str , Any ]] = []
7680 if self .learning_engine and profile .evolution_enabled and profile .name in {"coder" , "researcher" }:
77- learning_run = self .learning_engine .begin_run (profile .name , task )
81+ learning_run = self .learning_engine .begin_run (profile .name , task , provider_model = expected_provider_model )
7882 guidance , receipts = self .learning_engine .artifact_context (learning_run )
7983 if guidance :
8084 context .append ({"kind" : "learned_guidance" , "confidence" : 1.0 , "content" : guidance })
@@ -83,21 +87,51 @@ def run(self, profile_name: str, task: str, *, workspace_id: str | None = None)
8387 user_id = self .memory .user_id , workspace_id = workspace_id , session_id = run_id )
8488 tool_records : list [dict [str , Any ]] = []
8589 evidence : list [dict [str , Any ]] = []
90+ metrics : dict [str , Any ] = {
91+ "provider_model" : expected_provider_model or "unknown" , "provider_models" : [], "attempts" : None ,
92+ "prompt_tokens" : None , "completion_tokens" : None , "reasoning_tokens" : None , "tokens" : None ,
93+ "latency_ms" : None , "usage_status" : "unknown" ,
94+ }
8695 provider_error = False
96+ started = time .monotonic ()
8797 if self .runner is None :
8898 result = "Sub-agent runner is not configured."
8999 else :
90100 try :
91- raw_result = self .runner (profile , task , context , tools )
101+ raw_result = self .runner (profile , task , context , tools , run_id = run_id )
92102 if isinstance (raw_result , dict ):
93103 result = str (raw_result .get ("result" , "" ))
94104 tool_records = [item for item in raw_result .get ("tool_records" , []) if isinstance (item , dict )]
95105 evidence = [item for item in raw_result .get ("evidence" , []) if isinstance (item , dict )]
106+ if isinstance (raw_result .get ("metrics" ), dict ):
107+ metrics .update ({key : raw_result ["metrics" ][key ] for key in metrics
108+ if key in raw_result ["metrics" ]})
96109 else :
97110 result = str (raw_result )
98111 except Exception as exc :
99112 provider_error = True
100113 result = f"Sub-agent failed: { type (exc ).__name__ } : { exc } "
114+ if not isinstance (metrics .get ("provider_model" ), str ) or not metrics ["provider_model" ].strip ():
115+ metrics ["provider_model" ] = expected_provider_model or "unknown"
116+ metrics ["provider_model" ] = str (metrics ["provider_model" ]).strip ()[:192 ]
117+ metrics ["provider_models" ] = [str (item )[:192 ] for item in metrics ["provider_models" ]] if isinstance (
118+ metrics ["provider_models" ], (list , tuple , set )
119+ ) else []
120+ if metrics ["usage_status" ] not in {"authoritative" , "estimated" , "mixed" , "unknown" }:
121+ metrics ["usage_status" ] = "unknown"
122+ for field in ("attempts" , "prompt_tokens" , "completion_tokens" , "reasoning_tokens" , "tokens" ):
123+ try :
124+ metrics [field ] = None if metrics [field ] is None else max (0 , int (metrics [field ]))
125+ except (TypeError , ValueError ):
126+ metrics [field ] = None
127+ try :
128+ metrics ["latency_ms" ] = None if metrics ["latency_ms" ] is None else max (0.0 , float (metrics ["latency_ms" ]))
129+ except (TypeError , ValueError ):
130+ metrics ["latency_ms" ] = None
131+ if metrics .get ("latency_ms" ) is None :
132+ metrics ["latency_ms" ] = (time .monotonic () - started ) * 1000
133+ if learning_run :
134+ learning_run ["provider_model" ] = metrics ["provider_model" ]
101135
102136 for record in tool_records :
103137 payload = {"run_id" : run_id , ** record }
@@ -119,13 +153,16 @@ def run(self, profile_name: str, task: str, *, workspace_id: str | None = None)
119153 "result" : str (result )[:4000 ],
120154 "tool_receipts" : tool_records [:50 ],
121155 "acceptance_evidence" : evidence [:20 ],
156+ "metrics" : metrics ,
122157 "provider_error" : provider_error },
123158 user_id = self .memory .user_id , workspace_id = workspace_id , session_id = run_id )
124159 scoped_memory .add_log (str (result ), kind = "episodic" , status = "active" ,
125160 metadata = {"subagent_run_id" : run_id , "profile" : profile .name })
126161 if learning_run :
127162 self .learning_engine .complete_run (learning_run , result = str (result ), receipts = receipts ,
128- tools = tool_records , tokens = 0 , latency = 0.0 ,
163+ tools = tool_records , tokens = metrics .get ("tokens" ),
164+ latency = (float (metrics ["latency_ms" ]) / 1000
165+ if metrics .get ("latency_ms" ) is not None else None ),
129166 acceptance = evidence , provider_error = provider_error )
130167 if evidence :
131168 self .learning_engine .record_outcome (
@@ -135,4 +172,4 @@ def run(self, profile_name: str, task: str, *, workspace_id: str | None = None)
135172 )
136173 return {"run_id" : run_id , "profile" : profile .name , "result" : str (result ),
137174 "tools" : sorted (tools ), "memory_scope" : profile .memory_scope ,
138- "tool_receipts" : tool_records , "evidence" : evidence }
175+ "tool_receipts" : tool_records , "evidence" : evidence , "metrics" : metrics }
0 commit comments