package cli import ( "fmt" "github.com/spf13/cobra" ) // newThreadsCmd turns conversation storage on for a project, or off. // // A command rather than a configuration key because it is a decision about // data, not about deployment: someone has to choose to keep what a team types, // and choosing it should look like an act rather than like a default nobody // read. // // Until it is run for a project, llmgw stores no message content for that // project — usage rows carry token counts and costs, never text — and that is // how every project starts. func newThreadsCmd(open opener) *cobra.Command { var ( project string hours int off bool ) cmd := &cobra.Command{ Use: "threads", Short: "Keep conversations for a project, so a thread can move between models", Long: "With this on, a client sends x-llmgw-thread and only its new turn, and\n" + "llmgw replays the conversation to whichever model serves — including a\n" + "different one from the one it started on.\n\n" + "Off for every project until this is run. llmgw otherwise stores no\n" + "message content anywhere, which is a much easier thing to have deployed\n" + "in front of a team, so turning it on is deliberate.\n\n" + "The retention is required rather than optional: a conversation store with\n" + "no expiry is a transcript of everything a team ever typed, kept forever\n" + "because nobody chose a number.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { if !off && hours <= 0 { return fmt.Errorf("--retain-hours is required: say how long conversations are kept, or pass --off") } s, err := open(cmd.Context()) if err != nil { return err } defer s.Close() keep := hours if off { keep = 0 } p, err := resolveProject(cmd.Context(), s, project) if err != nil { return err } project = p.Path if err := s.SetThreadRetention(cmd.Context(), p.ID, keep); err != nil { return err } if keep == 0 { fmt.Fprintf(cmd.OutOrStdout(), "%s keeps no conversations. Existing ones are removed at the next prune.\n", project) return nil } fmt.Fprintf(cmd.OutOrStdout(), "%s keeps conversations for %d hours, measured from the last turn.\n", project, keep) return nil }, } cmd.Flags().StringVar(&project, "project", "", "project: id, path or unique name") cmd.Flags().IntVar(&hours, "retain-hours", 0, "how long a conversation is kept after its last turn") cmd.Flags().BoolVar(&off, "off", false, "stop keeping conversations for this project") return cmd }