@@ -29,6 +29,7 @@ import (
2929 "github.com/docker/cagent/pkg/remote"
3030 "github.com/docker/cagent/pkg/runtime"
3131 "github.com/docker/cagent/pkg/session"
32+ "github.com/docker/cagent/pkg/team"
3233 "github.com/docker/cagent/pkg/teamloader"
3334)
3435
3839 attachmentPath string
3940 workingDir string
4041 useTUI bool
42+ remoteAddress string
4143)
4244
4345// NewRunCmd creates a new run command
@@ -60,6 +62,7 @@ func NewRunCmd() *cobra.Command {
6062 cmd .PersistentFlags ().BoolVar (& autoApprove , "yolo" , false , "Automatically approve all tool calls without prompting" )
6163 cmd .PersistentFlags ().StringVar (& attachmentPath , "attach" , "" , "Attach an image file to the message" )
6264 cmd .PersistentFlags ().BoolVar (& useTUI , "tui" , true , "Run the agent with a Terminal User Interface (TUI)" )
65+ cmd .PersistentFlags ().StringVar (& remoteAddress , "remote" , "" , "Use remote runtime with specified address (only supported with TUI)" )
6366 addGatewayFlags (cmd )
6467
6568 return cmd
@@ -130,71 +133,114 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
130133 slog .Debug ("Working directory set" , "dir" , absWd )
131134 }
132135
133- // Determine how to obtain the agent definition
134- ext := strings .ToLower (filepath .Ext (agentFilename ))
135- if ext == ".yaml" || ext == ".yml" || strings .HasPrefix (agentFilename , "/dev/fd/" ) {
136- // Treat as local YAML file: resolve to absolute path so later chdir doesn't break it
137- if ! strings .Contains (agentFilename , "\n " ) {
138- if abs , err := filepath .Abs (agentFilename ); err == nil {
139- agentFilename = abs
136+ // Skip agent file loading when using remote runtime
137+ var agents * team.Team
138+ var err error
139+ if remoteAddress == "" {
140+ // Determine how to obtain the agent definition
141+ ext := strings .ToLower (filepath .Ext (agentFilename ))
142+ if ext == ".yaml" || ext == ".yml" || strings .HasPrefix (agentFilename , "/dev/fd/" ) {
143+ // Treat as local YAML file: resolve to absolute path so later chdir doesn't break it
144+ if ! strings .Contains (agentFilename , "\n " ) {
145+ if abs , err := filepath .Abs (agentFilename ); err == nil {
146+ agentFilename = abs
147+ }
140148 }
141- }
142- if ! fileExists (agentFilename ) {
143- return fmt .Errorf ("agent file not found: %s" , agentFilename )
144- }
145- } else {
146- // Treat as an OCI image reference. Try local store first, otherwise pull then load.
147- a , err := fromStore (agentFilename )
148- if err != nil {
149- fmt .Println ("Pulling agent " , agentFilename )
150- if _ , pullErr := remote .Pull (agentFilename ); pullErr != nil {
151- return fmt .Errorf ("failed to pull OCI image %s: %w" , agentFilename , pullErr )
149+ if ! fileExists (agentFilename ) {
150+ return fmt .Errorf ("agent file not found: %s" , agentFilename )
151+ }
152+ } else {
153+ // Treat as an OCI image reference. Try local store first, otherwise pull then load.
154+ a , err := fromStore (agentFilename )
155+ if err != nil {
156+ fmt .Println ("Pulling agent " , agentFilename )
157+ if _ , pullErr := remote .Pull (agentFilename ); pullErr != nil {
158+ return fmt .Errorf ("failed to pull OCI image %s: %w" , agentFilename , pullErr )
159+ }
160+ // Retry after pull
161+ a , err = fromStore (agentFilename )
162+ if err != nil {
163+ return fmt .Errorf ("failed to load agent from store after pull: %w" , err )
164+ }
152165 }
153- // Retry after pull
154- a , err = fromStore (agentFilename )
166+
167+ // Write the fetched content to a temporary YAML file
168+ tmpFile , err := os .CreateTemp ("" , "agentfile-*.yaml" )
155169 if err != nil {
156- return fmt .Errorf ("failed to load agent from store after pull: %w" , err )
170+ return err
171+ }
172+ defer os .Remove (tmpFile .Name ())
173+ if _ , err := tmpFile .WriteString (a ); err != nil {
174+ tmpFile .Close ()
175+ return err
176+ }
177+ if err := tmpFile .Close (); err != nil {
178+ return err
157179 }
180+ agentFilename = tmpFile .Name ()
158181 }
159182
160- // Write the fetched content to a temporary YAML file
161- tmpFile , err := os .CreateTemp ("" , "agentfile-*.yaml" )
183+ agents , err = teamloader .Load (ctx , agentFilename , runConfig )
162184 if err != nil {
163185 return err
164186 }
165- defer os . Remove ( tmpFile . Name ())
166- if _ , err := tmpFile . WriteString ( a ); err != nil {
167- tmpFile . Close ( )
168- return err
169- }
170- if err := tmpFile . Close (); err != nil {
171- return err
172- }
173- agentFilename = tmpFile . Name ( )
187+ defer func () {
188+ if err := agents . StopToolSets ( ); err != nil {
189+ slog . Error ( "Failed to stop tool sets" , "error" , err )
190+ }
191+ }()
192+ } else {
193+ // For remote runtime, just store the original agent filename
194+ // The remote server will handle agent loading
195+ slog . Debug ( "Skipping local agent file loading for remote runtime" , "filename" , agentFilename )
174196 }
175197
176- agents , err := teamloader . Load ( ctx , agentFilename , runConfig )
177- if err != nil {
178- return err
198+ // Validate remote flag usage
199+ if remoteAddress != "" && ( ! useTUI || exec ) {
200+ return fmt . Errorf ( "--remote flag can only be used with TUI mode" )
179201 }
180- defer func () {
181- if err := agents .StopToolSets (); err != nil {
182- slog .Error ("Failed to stop tool sets" , "error" , err )
183- }
184- }()
185202
186203 tracer := otel .Tracer (APP_NAME )
187204
188- rt , err := runtime .New (agents ,
189- runtime .WithCurrentAgent (agentName ),
190- runtime .WithAutoRunTools (autoApprove ),
191- runtime .WithTracer (tracer ),
192- )
193- if err != nil {
194- return fmt .Errorf ("failed to create runtime: %w" , err )
195- }
205+ var sess * session.Session
206+
207+ // Create runtime based on whether remote flag is specified
208+ var rt runtime.Runtime
209+ if remoteAddress != "" && useTUI && ! exec {
210+ // Create remote runtime for TUI mode
211+ remoteClient , err := runtime .NewClient (remoteAddress )
212+ if err != nil {
213+ return fmt .Errorf ("failed to create remote client: %w" , err )
214+ }
196215
197- sess := session .New ()
216+ sess , err = remoteClient .CreateSession (ctx )
217+ if err != nil {
218+ return err
219+ }
220+
221+ remoteRt , err := runtime .NewRemoteRuntime (remoteClient ,
222+ runtime .WithRemoteCurrentAgent ("root" ),
223+ runtime .WithRemoteAgentFilename ("pirate.yaml" ),
224+ )
225+ if err != nil {
226+ return fmt .Errorf ("failed to create remote runtime: %w" , err )
227+ }
228+ rt = remoteRt
229+ slog .Debug ("Using remote runtime" , "address" , remoteAddress , "agent" , agentName )
230+ } else {
231+ // Create local runtime
232+ localRt , err := runtime .New (agents ,
233+ runtime .WithCurrentAgent (agentName ),
234+ runtime .WithAutoRunTools (autoApprove ),
235+ runtime .WithTracer (tracer ),
236+ )
237+ if err != nil {
238+ return fmt .Errorf ("failed to create runtime: %w" , err )
239+ }
240+ rt = localRt
241+ sess = session .New ()
242+ slog .Debug ("Using local runtime" , "agent" , agentName )
243+ }
198244
199245 // For `cagent exec`
200246 if exec {
@@ -204,12 +250,12 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
204250 } else {
205251 execArgs = append (execArgs , "Follow the default instructions" )
206252 }
207- return runWithoutTUI (ctx , agentFilename , rt , sess , execArgs )
253+ return runWithoutTUI (ctx , agentFilename , rt , session . New () , execArgs )
208254 }
209255
210256 // For `cagent run --tui=false`
211257 if ! useTUI {
212- return runWithoutTUI (ctx , agentFilename , rt , sess , args )
258+ return runWithoutTUI (ctx , agentFilename , rt , session . New () , args )
213259 }
214260
215261 // The default is to use the TUI
@@ -228,7 +274,7 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
228274 }
229275 }
230276
231- a := app .New (agentFilename , rt , sess , firstMessage )
277+ a := app .New (agentFilename , rt , agents , sess , firstMessage )
232278 m := tui .New (a )
233279
234280 progOpts := []tea.ProgramOption {
@@ -251,7 +297,7 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
251297 return err
252298}
253299
254- func runWithoutTUI (ctx context.Context , agentFilename string , rt * runtime.Runtime , sess * session.Session , args []string ) error {
300+ func runWithoutTUI (ctx context.Context , agentFilename string , rt runtime.Runtime , sess * session.Session , args []string ) error {
255301 sess .Title = "Running agent"
256302 // If the last received event was an error, return it. That way the exit code
257303 // will be non-zero if the agent failed.
@@ -442,7 +488,7 @@ func runWithoutTUI(ctx context.Context, agentFilename string, rt *runtime.Runtim
442488 return lastErr
443489}
444490
445- func runUserCommand (userInput string , sess * session.Session , rt * runtime.Runtime , ctx context.Context ) (bool , error ) {
491+ func runUserCommand (userInput string , sess * session.Session , rt runtime.Runtime , ctx context.Context ) (bool , error ) {
446492 yellow := color .New (color .FgYellow ).SprintfFunc ()
447493 switch userInput {
448494 case "/exit" :
0 commit comments