fix(claudewatcher): tag session-dump notes source_type: internal
brain-gardener#5: the audit's unsourced check was flagging 59 raw claudewatcher session dumps as high-severity hallucination risk, because it can't distinguish a first-party observation (a session transcript) from an external claim needing citation. Adds WriteNoteOptions.SourceType, emitted as source_type: <value> in the wing/hall frontmatter route. claudeSink (the claudewatcher sink) now always sets source_type: internal on the notes it writes.
This commit is contained in:
@@ -115,12 +115,13 @@ func (h *Handler) Query(w http.ResponseWriter, r *http.Request) {
|
||||
// When either is empty, the note falls back to brain/knowledge/<filename>
|
||||
// with optional type/domain frontmatter (legacy behaviour).
|
||||
type WriteNoteOptions struct {
|
||||
Content string
|
||||
Filename string
|
||||
Type string
|
||||
Domain string
|
||||
Wing string
|
||||
Hall string
|
||||
Content string
|
||||
Filename string
|
||||
Type string
|
||||
Domain string
|
||||
Wing string
|
||||
Hall string
|
||||
SourceType string // "internal" marks a first-party observation (e.g. claudewatcher) that needs no external citation
|
||||
}
|
||||
|
||||
// WriteNote writes a markdown note into the brain. Returns the path
|
||||
@@ -165,6 +166,9 @@ func writeHallNote(brainDir string, opts WriteNoteOptions) (string, error) {
|
||||
if opts.Domain != "" {
|
||||
fmt.Fprintf(&fm, "domain: %s\n", opts.Domain)
|
||||
}
|
||||
if opts.SourceType != "" {
|
||||
fmt.Fprintf(&fm, "source_type: %s\n", opts.SourceType)
|
||||
}
|
||||
fm.WriteString("---\n")
|
||||
|
||||
if err := os.WriteFile(dest, []byte(fm.String()+opts.Content), 0o644); err != nil {
|
||||
@@ -227,7 +231,14 @@ func (h *Handler) Write(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "invalid JSON")
|
||||
return
|
||||
}
|
||||
relPath, err := WriteNote(h.brainDir, WriteNoteOptions(req))
|
||||
relPath, err := WriteNote(h.brainDir, WriteNoteOptions{
|
||||
Content: req.Content,
|
||||
Filename: req.Filename,
|
||||
Type: req.Type,
|
||||
Domain: req.Domain,
|
||||
Wing: req.Wing,
|
||||
Hall: req.Hall,
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Error("write failed", "err", err)
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
|
||||
@@ -118,6 +118,40 @@ func TestWrite_IncludesFrontmatterWhenTypeProvided(t *testing.T) {
|
||||
assert.Contains(t, string(content), "Some learning.")
|
||||
}
|
||||
|
||||
func TestWriteNote_HallRouteIncludesSourceTypeWhenSet(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
rel, err := api.WriteNote(dir, api.WriteNoteOptions{
|
||||
Content: "# Claude session abc (koala)\n\nBody.\n",
|
||||
Filename: "session-koala-abc",
|
||||
Wing: "claude-sessions",
|
||||
Hall: "facts",
|
||||
Type: "source",
|
||||
SourceType: "internal",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(rel)))
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(got), "source_type: internal")
|
||||
assert.Contains(t, string(got), "wing: claude-sessions")
|
||||
}
|
||||
|
||||
func TestWriteNote_HallRouteOmitsSourceTypeWhenUnset(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
rel, err := api.WriteNote(dir, api.WriteNoteOptions{
|
||||
Content: "manually captured fact.\n",
|
||||
Wing: "agentsquad",
|
||||
Hall: "facts",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(rel)))
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, string(got), "source_type")
|
||||
}
|
||||
|
||||
func TestWrite_GeneratesFilenameIfAbsent(t *testing.T) {
|
||||
dir, h := setup(t)
|
||||
body, _ := json.Marshal(map[string]any{"content": "auto name"})
|
||||
|
||||
Reference in New Issue
Block a user