Skip to content

Commit 50dca8e

Browse files
committed
new: in interactive mode continue after flow complete
1 parent f4f27af commit 50dca8e

File tree

4 files changed

+38
-14
lines changed

4 files changed

+38
-14
lines changed

nerve/runtime/flow.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,11 @@ async def from_path(
9898

9999
async def _setup_if_needed(self, task_override: str | None = None) -> None:
100100
if self.started_at is None:
101+
logger.debug("setting started at")
101102
self.started_at = time.time()
102103

103104
if self.curr_actor is None:
105+
logger.debug(f"setting curr actor to {self.curr_actor_idx}")
104106
self.curr_actor = self.actors[self.curr_actor_idx]
105107

106108
if task_override:
@@ -112,9 +114,12 @@ async def _setup_if_needed(self, task_override: str | None = None) -> None:
112114
state.on_task_started(self.curr_actor)
113115

114116
async def step(self) -> None:
117+
logger.debug("flow.step")
118+
115119
await self._setup_if_needed()
116120

117121
if self.done():
122+
logger.debug("flow done")
118123
state.on_event("flow_complete", {"steps": self.curr_step, "usage": state.get_usage()})
119124
return
120125

@@ -130,30 +135,39 @@ async def step(self) -> None:
130135
if state.is_active_task_done():
131136
logger.debug(f"task {self.curr_actor.runtime.name} complete") # type: ignore
132137
self.curr_actor_idx += 1
133-
self.curr_actor = None
134138
state.reset()
135139

136140
self.curr_step += 1
137141

138142
def done(self) -> bool:
139143
if self.curr_actor_idx >= len(self.actors):
144+
logger.debug("all actors done")
140145
return True
141146

142147
if self.max_steps > 0 and self.curr_step > self.max_steps:
148+
logger.debug("max steps reached")
143149
state.on_max_steps_reached()
144150
return True
145151

146152
usage = state.get_usage()
147153
if self.max_cost > 0 and usage.cost is not None and usage.cost > self.max_cost:
154+
logger.debug("max cost reached")
148155
state.on_max_cost_reached()
149156
return True
150157

151158
if self.timeout is not None and self.started_at is not None and time.time() - self.started_at > self.timeout:
159+
logger.debug("timeout reached")
152160
state.on_timeout()
153161
return True
154162

155163
return False
156164

165+
async def _reset(self) -> None:
166+
logger.debug("flow reset")
167+
state.reset()
168+
await self.shell.reset()
169+
self.curr_actor_idx = 0
170+
157171
async def run(self, task_override: str | None = None) -> None:
158172
state.on_event(
159173
"flow_started",
@@ -165,10 +179,22 @@ async def run(self, task_override: str | None = None) -> None:
165179

166180
while not self.done():
167181
await self._setup_if_needed(task_override)
182+
168183
if self.curr_actor:
184+
logger.debug("interact if needed")
169185
await self.shell.interact_if_needed(self.curr_actor)
186+
else:
187+
logger.debug("no actor, can't interact")
188+
170189
await self.step()
171190

191+
# in interactive mode, we reset and restart when we're done
192+
# to let the user quit or change the task
193+
if self.done() and state.is_interactive():
194+
await self._reset()
195+
196+
logger.debug("flow complete")
197+
172198
state.on_event(
173199
"flow_complete",
174200
{

nerve/runtime/logging.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@
99
from nerve.runtime.events import Event
1010

1111

12-
def _log_message_format(record: dict[str, t.Any]) -> str:
13-
return "{time} <level>{message}</level>\n"
14-
15-
1612
def init(log_path: pathlib.Path | None = None, level: str = "INFO", litellm_debug: bool = False) -> None:
1713
"""
1814
Initialize the logging system.
@@ -28,7 +24,7 @@ def init(log_path: pathlib.Path | None = None, level: str = "INFO", litellm_debu
2824
logger.add(
2925
sys.stdout,
3026
colorize=True,
31-
format=_log_message_format,
27+
format="{time} <level>{message}</level>",
3228
level=level,
3329
)
3430

@@ -142,10 +138,6 @@ def log_event_to_terminal(event: Event) -> None:
142138
logger.info(f"💬 {colored(data['response'], 'black', 'on_white')}")
143139

144140
elif event.name == "step_started":
145-
# avoid logging step usage in interactive mode
146-
if state.is_interactive():
147-
return
148-
149141
if isinstance(data["usage"], dict):
150142
data["usage"] = DictWrapper(data["usage"])
151143

nerve/runtime/shell.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,20 @@ async def _show_welcome(self) -> None:
133133
print()
134134
print(colored("👋 Welcome to the interactive shell!", "white", attrs=["bold"]))
135135
print()
136+
await self._handle_view()
136137
await self._handle_help()
137138

138-
async def interact_if_needed(self, actor: Agent) -> None:
139-
if not state.is_interactive() or self._keep_going:
140-
return
139+
async def reset(self) -> None:
140+
logger.debug("shell reset")
141+
self._keep_going = False
141142

143+
async def interact_if_needed(self, actor: Agent) -> None:
142144
# wait for all events to be logged
143145
state.wait_for_events_logs()
144146

147+
if not state.is_interactive() or self._keep_going:
148+
return
149+
145150
# show the welcome message if this is the first step
146151
if self._first_step:
147152
self._first_step = False
@@ -157,6 +162,8 @@ async def interact_if_needed(self, actor: Agent) -> None:
157162
logger.debug(f"unhandled command: {command}")
158163
# add the command to the conversation
159164
actor.add_extra_message(command)
165+
# continue until complete
166+
self._keep_going = True
160167
elif not done:
161168
# keep running the interaction shell
162169
await self.interact_if_needed(actor)

nerve/runtime/state.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,6 @@ def reset() -> None:
362362
global _task_status, _reason, _knowledge
363363
_task_status = Status.RUNNING
364364
_reason = None
365-
_knowledge = {}
366365

367366

368367
def on_user_input_needed(input_name: str, prompt: str) -> str:

0 commit comments

Comments
 (0)