@@ -16,13 +16,13 @@ mod request_context;
1616
1717use std:: path:: { Path , PathBuf } ;
1818
19- use bob_runtime:: agent_loop :: { AgentLoop , AgentLoopOutput , help_text } ;
19+ use bob_runtime:: { Agent , Session } ;
2020use clap:: { Parser , Subcommand } ;
2121use eyre:: WrapErr ;
2222
2323use crate :: {
24- bootstrap:: { CliRuntimeHandles , SkillsRuntimeContext , build_runtime} ,
25- request_context :: build_request_context ,
24+ bootstrap:: { CliRuntimeHandles , build_runtime} ,
25+ config :: AgentConfig ,
2626} ;
2727
2828/// Bob CLI — a general-purpose AI agent framework CLI.
@@ -140,34 +140,51 @@ fn print_help() {
140140 CLI session commands:
141141 /help, /h Show this help message
142142 /new, /reset Start a new session context
143-
144- {}
145- " ,
146- help_text( )
143+ /quit, /q Exit the REPL
144+
145+ Available slash commands:
146+ /tools List available tools
147+ /tool <name> Describe a specific tool
148+ /tape search <query> Search tape history
149+ /tape info Show tape statistics
150+ /anchors List tape anchors
151+ /handoff [name] Create handoff checkpoint
152+ /usage Show session token usage
153+ "
147154 ) ;
148155}
149156
157+ /// Build an Agent from configuration.
158+ async fn build_agent ( cfg : & AgentConfig ) -> eyre:: Result < Agent > {
159+ let CliRuntimeHandles { runtime, tools, store, tape, skills_context : _ } =
160+ build_runtime ( cfg) . await ?;
161+
162+ let mut builder = Agent :: from_runtime ( runtime, tools) . with_store ( store) . with_tape ( tape) ;
163+
164+ // Load system prompt from workspace file if it exists
165+ if let Ok ( prompt) = std:: fs:: read_to_string ( ".agent/system-prompt.md" ) {
166+ builder = builder. with_system_prompt ( prompt) ;
167+ }
168+
169+ Ok ( builder. build ( ) )
170+ }
171+
150172/// Run the interactive REPL loop.
151173#[ expect(
152174 clippy:: print_stdout,
153175 clippy:: print_stderr,
154176 reason = "CLI REPL must use stdout/stderr for user interaction"
155177) ]
156- async fn repl (
157- agent_loop : AgentLoop ,
158- model : & str ,
159- skills_context : Option < & SkillsRuntimeContext > ,
160- policy : Option < & config:: PolicyConfig > ,
161- ) {
178+ async fn repl ( mut session : Session , model : & str , cfg : & AgentConfig ) {
162179 use tokio:: io:: { AsyncBufReadExt , BufReader } ;
163180
164181 let stdin = BufReader :: new ( tokio:: io:: stdin ( ) ) ;
165182 let mut lines = stdin. lines ( ) ;
166183
167- let mut session_seq: u64 = 1 ;
168- let mut session_id = format ! ( "cli-session-{session_seq}" ) ;
184+ let skills_context = bootstrap:: build_skills_composer ( cfg) . ok ( ) . flatten ( ) ;
169185
170186 eprintln ! ( "Bob agent ready (model: {model})" ) ;
187+ eprintln ! ( "Session: {}" , session. session_id( ) ) ;
171188 eprintln ! ( "Type a message and press Enter. /help for commands.\n " ) ;
172189
173190 loop {
@@ -195,31 +212,37 @@ async fn repl(
195212 continue ;
196213 }
197214 ReplCommand :: NewSession => {
198- session_seq = session_seq. saturating_add ( 1 ) ;
199- session_id = format ! ( "cli-session-{session_seq}" ) ;
200- eprintln ! ( "Started new session: {session_id}" ) ;
215+ session = session. new_session ( ) ;
216+ eprintln ! ( "Started new session: {}" , session. session_id( ) ) ;
201217 continue ;
202218 }
203219 }
204220 }
205221
206- let context = build_request_context ( & input, skills_context, policy) ;
207- match agent_loop. handle_input_with_context ( & input, & session_id, context) . await {
208- Ok ( AgentLoopOutput :: Response ( bob_runtime:: core:: types:: AgentRunResult :: Finished (
209- resp,
210- ) ) ) => {
211- println ! ( "{}" , resp. content) ;
212- println ! (
213- "\n [usage] prompt={} completion={} total={}" ,
214- resp. usage. prompt_tokens,
215- resp. usage. completion_tokens,
216- resp. usage. total( )
217- ) ;
218- }
219- Ok ( AgentLoopOutput :: CommandOutput ( output) ) => {
220- println ! ( "{output}" ) ;
222+ // Build request context with skills if available
223+ let context = request_context:: build_request_context (
224+ & input,
225+ skills_context. as_ref ( ) ,
226+ cfg. policy . as_ref ( ) ,
227+ ) ;
228+
229+ match session. chat_with_context ( & input, context) . await {
230+ Ok ( response) => {
231+ if response. is_quit {
232+ break ;
233+ }
234+ if !response. content . is_empty ( ) {
235+ println ! ( "{}" , response. content) ;
236+ }
237+ if response. usage . total ( ) > 0 {
238+ println ! (
239+ "\n [usage] prompt={} completion={} total={}" ,
240+ response. usage. prompt_tokens,
241+ response. usage. completion_tokens,
242+ response. usage. total( )
243+ ) ;
244+ }
221245 }
222- Ok ( AgentLoopOutput :: Quit ) => break ,
223246 Err ( err) => {
224247 eprintln ! ( "Error: {err}" ) ;
225248 }
@@ -584,17 +607,11 @@ async fn main() -> eyre::Result<()> {
584607 let cfg = config:: load_config ( & cli. config )
585608 . wrap_err_with ( || format ! ( "failed to load config from '{}'" , cli. config) ) ?;
586609
587- let CliRuntimeHandles { runtime, tools, store, tape, skills_context } =
588- build_runtime ( & cfg) . await ?;
589- let agent_loop =
590- AgentLoop :: new ( runtime. clone ( ) , tools. clone ( ) ) . with_store ( store) . with_tape ( tape) ;
591- repl (
592- agent_loop,
593- & cfg. runtime . default_model ,
594- skills_context. as_ref ( ) ,
595- cfg. policy . as_ref ( ) ,
596- )
597- . await ;
610+ // Build agent using the new simplified API
611+ let agent = build_agent ( & cfg) . await ?;
612+ let session = agent. start_session ( ) ;
613+
614+ repl ( session, & cfg. runtime . default_model , & cfg) . await ;
598615 }
599616 Commands :: Skills ( skills_cmd) => match skills_cmd {
600617 SkillsCommands :: List {
0 commit comments