CI / test (push) Successful in 1m44s
SetThreadRetention existed and nothing called it, so the feature was unreachable: a project could never opt in and every request carrying a thread was refused. A command rather than a configuration key, because it is a decision about data rather than 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. --retain-hours is required. Without a number the command would create exactly the thing the design avoids: a conversation store with no expiry, holding everything a team ever typed because nobody chose one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
71 lines
2.4 KiB
Go
71 lines
2.4 KiB
Go
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
|
|
}
|
|
if err := s.SetThreadRetention(cmd.Context(), project, 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", "default", "project id")
|
|
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
|
|
}
|