diff --git a/README.md b/README.md index 2f61a94..9e7e81a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,40 @@ rossoctl login rossoctl agents list ``` +## Agent context infrastructure + +`rossoctl context` creates, lists, and attaches the context resources provided +by Rosso's optional Context Service integration. See Rosso's canonical +[Context Service documentation](https://github.com/rossoctl/rossoctl/blob/main/docs/concepts/context-service.md) +for the resource model, storage behavior, and lifecycle. The underlying service +is maintained in the +[context-service repository](https://github.com/rossoctl/context-service). + +```sh +# Create and inspect a shared workspace. +rossoctl context create research --shared --size 10Gi \ + --storage-class ibm-scale-csi +rossoctl context list + +# Mount it when importing an agent. +rossoctl agents import --deployment-type sandbox \ + --context research:/workspace \ + from-image --name researcher --containerImage IMAGE +``` + +Context commands require a Rosso server containing the context resource API +introduced by [rossoctl/rossoctl#2392](https://github.com/rossoctl/rossoctl/pull/2392). +An older server returns an actionable compatibility error from `context list`. + +To try the commands from the latest source: + +```sh +git clone https://github.com/rossoctl/rossoctl-cli.git +cd rossoctl-cli +make build +./bin/rossoctl context --help +``` + ## Running a command behind an AuthBridge pipeline Rossoctl can be used to test how an agent runs under an AuthBridge configuration on your laptop. It provides an in-process implementation of AuthBridge. diff --git a/cmd/contexts.go b/cmd/contexts.go index 42e2e72..a6d0a94 100644 --- a/cmd/contexts.go +++ b/cmd/contexts.go @@ -2,7 +2,9 @@ package cmd import ( "encoding/json" + "errors" "fmt" + "net/http" "text/tabwriter" "github.com/spf13/cobra" @@ -12,6 +14,14 @@ import ( var contextsNamespace string +func contextListError(err error) error { + var statusErr *apiclient.StatusError + if errors.As(err, &statusErr) && statusErr.StatusCode == http.StatusNotFound { + return fmt.Errorf("this Rosso server does not support context infrastructure; context commands require the context resource API introduced by rossoctl/rossoctl#2392: %w", err) + } + return err +} + func contextNamespace() (string, error) { if contextsNamespace != "" { return contextsNamespace, nil @@ -128,7 +138,7 @@ func newContextsListCmd() *cobra.Command { } result, err := client.ListContexts(cmd.Context(), namespace) if err != nil { - return err + return contextListError(err) } if jsonOutput { encoded, err := json.MarshalIndent(result.Items, "", " ") @@ -194,8 +204,17 @@ func printContextResource(cmd *cobra.Command, value *apiclient.ContextResource, } func init() { - contextsCmd := newGroup("contexts", "Manage named agent context resources") + contextsCmd := newGroup("contexts", "Manage durable context infrastructure for agents") contextsCmd.Aliases = []string{"context"} + contextsCmd.Long = `Manage durable context infrastructure for agents. + +Context resources make files available to agents as workspaces, memory, +knowledge, or artifacts. They are distinct from rossoctl configuration +contexts and from an LLM's finite context window. The current backend is +PVC-backed storage mounted into StatefulSet or Sandbox agents. + +Learn more: +https://github.com/rossoctl/rossoctl/blob/main/docs/concepts/context-service.md` contextsCmd.PersistentFlags().StringVar(&contextsNamespace, "namespace", "", "namespace (overrides current context)") contextsCmd.AddCommand(newContextsCreateCmd(), newContextsListCmd(), newContextsGetCmd(), newContextsDeleteCmd()) rootCmd.AddCommand(contextsCmd) diff --git a/cmd/contexts_test.go b/cmd/contexts_test.go index a6e693b..9863819 100644 --- a/cmd/contexts_test.go +++ b/cmd/contexts_test.go @@ -87,6 +87,33 @@ func TestContextsList(t *testing.T) { } } +func TestContextsListExplainsUnsupportedServer(t *testing.T) { + isolateHome(t) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api/v1/namespaces": + _, _ = w.Write([]byte(`{"namespaces":["team1"]}`)) + case "/api/v1/contexts/team1": + http.Error(w, `{"detail":"Not Found"}`, http.StatusNotFound) + default: + t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path) + } + })) + defer srv.Close() + setupImportContext(t, srv, "team1") + + _, err := execute(t, "contexts", "list") + if err == nil { + t.Fatal("expected an unsupported-server error") + } + for _, expected := range []string{"does not support context infrastructure", "rossoctl/rossoctl#2392"} { + if !strings.Contains(err.Error(), expected) { + t.Errorf("error missing %q: %v", expected, err) + } + } +} + func TestContextAlias(t *testing.T) { command, _, err := rootCmd.Find([]string{"context", "list"}) if err != nil { @@ -133,3 +160,21 @@ func TestContextCreateHelpIncludesExamples(t *testing.T) { } } } + +func TestContextGroupHelpDefinesContextInfrastructure(t *testing.T) { + out, err := execute(t, "context", "--help") + if err != nil { + t.Fatal(err) + } + for _, expected := range []string{ + "durable context infrastructure for agents", + "distinct from rossoctl configuration", + "LLM's finite context window", + "PVC-backed storage", + "docs/concepts/context-service.md", + } { + if !strings.Contains(out, expected) { + t.Errorf("context help missing %q:\n%s", expected, out) + } + } +} diff --git a/docs/README.md b/docs/README.md index 4415281..32792b1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -136,4 +136,3 @@ assert on the command strings they would run rather than invoking a real runtime to `main`, plus a `go mod tidy` check. Shuffled order is included because the suite mutates process state (`HOME`, cobra flag values), so an order-dependent test is a real risk — see the pflag hazard documented in `cmd/root_test.go`. -