ScuttleBot

scuttlebot / pkg / toon / toon.go
Blame History Raw 122 lines
1
// Package toon implements the TOON format — Token-Optimized Object Notation
2
// for compact LLM context windows.
3
//
4
// TOON is designed for feeding IRC conversation history to language models.
5
// It strips noise (joins, parts, status messages, repeated tool calls),
6
// deduplicates, and compresses timestamps into relative offsets.
7
//
8
// Example output:
9
//
10
// #fleet 50msg 2h window
11
// ---
12
// claude-kohakku [orch] +0m
13
// task.create {file: main.go, action: edit}
14
// "editing main.go to add error handling"
15
// leo [op] +2m
16
// "looks good, ship it"
17
// claude-kohakku [orch] +3m
18
// task.complete {file: main.go, status: done}
19
// ---
20
// decisions: edit main.go error handling
21
// actions: task.create → task.complete (main.go)
22
package toon
23
24
import (
25
"fmt"
26
"strings"
27
"time"
28
)
29
30
// Entry is a single message to include in the TOON output.
31
type Entry struct {
32
Nick string
33
Type string // agent type: "orch", "worker", "op", "bot", "" for unknown
34
MessageType string // envelope type (e.g. "task.create"), empty for plain text
35
Text string
36
At time.Time
37
}
38
39
// Options controls TOON formatting.
40
type Options struct {
41
Channel string
42
MaxEntries int // 0 = no limit
43
}
44
45
// Format renders a slice of entries into TOON format.
46
func Format(entries []Entry, opts Options) string {
47
if len(entries) == 0 {
48
return ""
49
}
50
51
var b strings.Builder
52
53
// Header.
54
window := ""
55
if len(entries) >= 2 {
56
dur := entries[len(entries)-1].At.Sub(entries[0].At)
57
window = " " + compactDuration(dur) + " window"
58
}
59
ch := opts.Channel
60
if ch == "" {
61
ch = "channel"
62
}
63
fmt.Fprintf(&b, "%s %dmsg%s\n---\n", ch, len(entries), window)
64
65
// Body — group consecutive messages from same nick.
66
baseTime := entries[0].At
67
var lastNick string
68
for _, e := range entries {
69
offset := e.At.Sub(baseTime)
70
if e.Nick != lastNick {
71
tag := ""
72
if e.Type != "" {
73
tag = " [" + e.Type + "]"
74
}
75
fmt.Fprintf(&b, "%s%s +%s\n", e.Nick, tag, compactDuration(offset))
76
lastNick = e.Nick
77
}
78
79
if e.MessageType != "" {
80
fmt.Fprintf(&b, " %s\n", e.MessageType)
81
}
82
text := strings.TrimSpace(e.Text)
83
if text != "" && text != e.MessageType {
84
// Truncate very long messages to save tokens.
85
if len(text) > 200 {
86
text = text[:197] + "..."
87
}
88
fmt.Fprintf(&b, " \"%s\"\n", text)
89
}
90
}
91
92
b.WriteString("---\n")
93
return b.String()
94
}
95
96
// FormatPrompt wraps TOON-formatted history into an LLM summarization prompt.
97
func FormatPrompt(channel string, entries []Entry) string {
98
toon := Format(entries, Options{Channel: channel})
99
var b strings.Builder
100
fmt.Fprintf(&b, "Summarize this IRC conversation. Focus on decisions, actions, and outcomes. Be concise.\n\n")
101
b.WriteString(toon)
102
return b.String()
103
}
104
105
func compactDuration(d time.Duration) string {
106
if d < time.Minute {
107
return fmt.Sprintf("%ds", int(d.Seconds()))
108
}
109
if d < time.Hour {
110
return fmt.Sprintf("%dm", int(d.Minutes()))
111
}
112
if d < 24*time.Hour {
113
h := int(d.Hours())
114
m := int(d.Minutes()) % 60
115
if m == 0 {
116
return fmt.Sprintf("%dh", h)
117
}
118
return fmt.Sprintf("%dh%dm", h, m)
119
}
120
return fmt.Sprintf("%dd", int(d.Hours()/24))
121
}
122

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button