Files
llmgw/internal/cli/threads.go
T
Ilker ManapandClaude Opus 5 eaf3382a6d Give an operator a way to turn conversation storage on
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>
2026-09-18 11:54:31 +02:00

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
}