@@ -1101,9 +1101,25 @@ def run_agent(self, args):
11011101 if not quiet :
11021102 _progress .print_summary (self , response )
11031103
1104- # Display explain trace (tool reasoning)
1105- if getattr (args , 'explain' , False ) and response .execution_trace :
1106- self .print_header ("Execution Trace (Explain Mode)" )
1104+ _explain = getattr (args , 'explain' , False )
1105+ _trace = getattr (args , 'trace' , False )
1106+
1107+ # A per-step timeline (bars + durations) shows where the
1108+ # wall-clock went across the run's steps.
1109+ if _trace and response .execution_trace :
1110+ self .print_header ("Timeline" )
1111+ _tl = _progress .execution_timeline_lines (response .execution_trace )
1112+ if not _tl :
1113+ self .print ("(no timed steps recorded for this run)" )
1114+ for _style , _text in _tl :
1115+ if self .console :
1116+ self .console .print (f"[{ _style } ]{ _text } [/{ _style } ]" )
1117+ else :
1118+ print (_text )
1119+
1120+ # Display the step trace (tool reasoning + per-step timing).
1121+ if (_explain or _trace ) and response .execution_trace :
1122+ self .print_header ("Execution Trace" )
11071123 _lines = _progress .execution_trace_lines (response .execution_trace )
11081124 if not _lines :
11091125 self .print ("(no detailed steps recorded for this run)" )
@@ -1113,8 +1129,18 @@ def run_agent(self, args):
11131129 else :
11141130 print (_text )
11151131
1132+ # On a multi-step run without an explicit trace flag, point
1133+ # the user at the timeline rather than leaving it hidden.
1134+ elif not quiet and not _explain and int (getattr (response , "tool_calls" , 0 ) or 0 ) >= 1 :
1135+ _steps = int (getattr (response , "tool_calls" , 0 ) or 0 )
1136+ _hint = f"{ _steps } tool step{ 's' if _steps != 1 else '' } — run with --trace to see the timeline"
1137+ if self .console :
1138+ self .console .print (f"[effgen.muted]{ _hint } [/effgen.muted]" )
1139+ else :
1140+ print (_hint )
1141+
11161142 # Display execution statistics
1117- if getattr (args , 'verbose' , False ) or getattr ( args , 'explain' , False ) :
1143+ if getattr (args , 'verbose' , False ) or _explain or _trace :
11181144 self .print_header ("Execution Statistics" )
11191145 stats_table = self ._create_stats_table ({
11201146 "Mode" : response .mode .value ,
@@ -2947,6 +2973,8 @@ def create_parser():
29472973 )
29482974 run_parser .add_argument ('--explain' , action = 'store_true' ,
29492975 help = 'Show why the agent chose each tool' )
2976+ run_parser .add_argument ('--trace' , action = 'store_true' ,
2977+ help = 'Show a step-by-step timeline with per-step durations' )
29502978 run_parser .add_argument ('--checkpoint-dir' , help = 'Directory to write agent checkpoints' )
29512979 run_parser .add_argument ('--checkpoint-interval' , type = int , default = 0 ,
29522980 help = 'Checkpoint every N iterations (requires --checkpoint-dir)' )
@@ -3254,11 +3282,20 @@ def create_parser():
32543282 'workflow entry node(s) (alternative to --input)' )
32553283 workflow_run .add_argument ('--json' , dest = 'output_json' , action = 'store_true' ,
32563284 help = 'Emit the workflow result as JSON to stdout (for CI gating)' )
3285+ workflow_run .add_argument ('--diagram' , action = 'store_true' ,
3286+ help = 'Draw the workflow as a dependency graph (nodes by '
3287+ 'level, edges, per-node status/duration/cost)' )
3288+ workflow_run .add_argument ('-q' , '--quiet' , action = 'store_true' , default = argparse .SUPPRESS ,
3289+ help = 'Quiet output (errors only); --json still emits to stdout' )
32573290
32583291 workflow_validate = workflow_subparsers .add_parser ('validate' , help = 'Validate a workflow YAML file' )
32593292 workflow_validate .add_argument ('file' , help = 'Path to workflow YAML file' )
32603293 workflow_validate .add_argument ('--json' , dest = 'output_json' , action = 'store_true' ,
32613294 help = 'Emit the validation result as JSON to stdout' )
3295+ workflow_validate .add_argument ('--diagram' , action = 'store_true' ,
3296+ help = 'Draw the workflow dependency graph (nodes by level, edges)' )
3297+ workflow_validate .add_argument ('-q' , '--quiet' , action = 'store_true' , default = argparse .SUPPRESS ,
3298+ help = 'Quiet output (errors only); --json still emits to stdout' )
32623299
32633300 # Batch command
32643301 batch_parser = subparsers .add_parser (
@@ -4129,6 +4166,24 @@ def _handle_workflow_command(args, cli) -> int:
41294166 json_mode = getattr (args , 'output_json' , False )
41304167 if json_mode :
41314168 cli ._human_to_stderr = True
4169+ show_diagram = getattr (args , 'diagram' , False )
4170+
4171+ def _print_diagram (dag , node_results = None ):
4172+ from effgen .ui .workflow_viz import workflow_diagram_lines
4173+ order = dag .topological_order ()
4174+ levels = dag ._compute_levels (order )
4175+ lines = workflow_diagram_lines (
4176+ dag .name ,
4177+ [n .id for n in dag .nodes ],
4178+ [e .to_dict () for e in dag .edges ],
4179+ levels ,
4180+ node_results = node_results ,
4181+ )
4182+ for style , text in lines :
4183+ if cli .console and style :
4184+ cli .console .print (f"[{ style } ]{ text } [/{ style } ]" )
4185+ else :
4186+ cli .print (text )
41324187
41334188 if wf_cmd == 'validate' :
41344189 try :
@@ -4147,6 +4202,9 @@ def _handle_workflow_command(args, cli) -> int:
41474202 cli .print (f" Nodes: { len (dag .nodes )} " )
41484203 cli .print (f" Edges: { len (dag .edges )} " )
41494204 cli .print (f" Execution order: { ' -> ' .join (order )} " )
4205+ if show_diagram :
4206+ cli .print ("" )
4207+ _print_diagram (dag )
41504208 return 0
41514209 except Exception as e :
41524210 if json_mode :
@@ -4197,8 +4255,10 @@ def _agent_factory(nd):
41974255 )
41984256 return Agent (config )
41994257
4258+ quiet = getattr (args , 'quiet' , False )
42004259 dag = WorkflowDAG .from_yaml (args .file , agent_factory = _agent_factory )
4201- cli .print (f"Running workflow '{ dag .name } ' ({ len (dag .nodes )} nodes)..." )
4260+ if not quiet :
4261+ cli .print (f"Running workflow '{ dag .name } ' ({ len (dag .nodes )} nodes)..." )
42024262
42034263 # Per-node ``task:`` strings declared in the YAML become each node's
42044264 # default input (so `effgen workflow run workflow.yaml` works with no
@@ -4238,17 +4298,23 @@ def _agent_factory(nd):
42384298 print (json .dumps (result .to_dict (), indent = 2 , default = str , ensure_ascii = False ))
42394299 return 0 if result .success else 1
42404300
4241- cli .print (f"\n Workflow { 'succeeded' if result .success else 'FAILED' } "
4242- f"in { result .execution_time :.2f} s" )
4243- for nr in result .node_results :
4244- status = nr ['status' ]
4245- cli .print (f" [{ status :>9s} ] { nr ['id' ]} ({ nr ['execution_time' ]:.2f} s)" )
4246-
4247- if result .success :
4248- # Show final outputs
4249- cli .print ("\n Outputs:" )
4250- for key , val in result .outputs .items ():
4251- cli .print (f" { key } : { str (val )[:200 ]} " )
4301+ if not quiet :
4302+ cli .print (f"\n Workflow { 'succeeded' if result .success else 'FAILED' } "
4303+ f"in { result .execution_time :.2f} s" )
4304+
4305+ if show_diagram :
4306+ cli .print ("" )
4307+ _print_diagram (dag , node_results = result .node_results )
4308+ else :
4309+ for nr in result .node_results :
4310+ status = nr ['status' ]
4311+ cli .print (f" [{ status :>9s} ] { nr ['id' ]} ({ nr ['execution_time' ]:.2f} s)" )
4312+
4313+ if result .success :
4314+ # Show final outputs
4315+ cli .print ("\n Outputs:" )
4316+ for key , val in result .outputs .items ():
4317+ cli .print (f" { key } : { str (val )[:200 ]} " )
42524318
42534319 return 0 if result .success else 1
42544320
0 commit comments