orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package tools
2
3// Searching earlier conversations.
4//
5// The other half of memory. Memory keeps one sentence facts about Isaac, this
6// reaches what was actually said, which matters because a tool result dies with
7// the turn that fetched it and only the answer survives. Without it a question
8// about something settled last week had nothing to reach for.
9
10import (
11 "context"
12 "fmt"
13 "strings"
14 "time"
15)
16
17// PastExchange is one earlier question and its answer.
18type PastExchange struct {
19 ConvID string `json:"conversation_id"`
20 Title string `json:"title"`
21 When time.Time `json:"when"`
22 Question string `json:"question"`
23 Answer string `json:"answer"`
24}
25
26// History is chat's own conversation store, an interface for the same reason
27// Memory is: this package cannot see the database the site owns.
28type History interface {
29 SearchPast(query string, limit int) ([]PastExchange, error)
30}
31
32var ChatHistory = Tool{
33 Name: "chat_history",
34 Description: "Search earlier conversations with Isaac for what was actually said. Use it when he " +
35 "refers to something from another chat, asks what was decided or discussed before, or when a " +
36 "question only makes sense against something already settled. It returns whole exchanges, so " +
37 "quote the answer as something said earlier rather than as a fresh fact, and check anything " +
38 "that has since changed. It does not search the web. Read only.",
39 Schema: obj(map[string]any{
40 "query": str("what to look for, the subject words rather than the whole question"),
41 "n": integer("how many exchanges to return, default 6"),
42 }, "query"),
43 Run: func(ctx context.Context, d *Deps, a map[string]any) (any, error) {
44 if d.History == nil {
45 return nil, fmt.Errorf("the conversation history is not available in this turn")
46 }
47 q := strings.TrimSpace(argStr(a, "query"))
48 if q == "" {
49 return nil, fmt.Errorf("query is required, and it should be the subject rather than the whole question")
50 }
51 n := int(argNum(a, "n", 6))
52 if n < 1 || n > 15 {
53 n = 6
54 }
55 hits, err := d.History.SearchPast(q, n)
56 if err != nil {
57 return nil, err
58 }
59 if len(hits) == 0 {
60 return map[string]any{"query": q, "exchanges": []any{}, "count": 0,
61 "note": "Nothing earlier matched. Say so rather than inventing what was said, " +
62 "and answer the question on its own."}, nil
63 }
64 return map[string]any{"query": q, "exchanges": hits, "count": len(hits),
65 "note": "These are earlier exchanges, not current facts. Anything dated or priced in " +
66 "them may have moved since, so look it up again before repeating it."}, nil
67 },
68}