feat(audit): add append-only agent audit trail (.beads/interactions.jsonl)

Implements audit logging for agent interactions to support auditing and
dataset generation (fixes #649).

New features:
- .beads/interactions.jsonl (append-only audit log)
- bd audit record: log LLM calls, tool calls, or pipe JSON via stdin
- bd audit label <id>: append labels (good/bad) for dataset curation
- bd compact --audit: optionally log LLM prompt/response during compaction
- bd init: creates empty interactions.jsonl
- bd sync: includes interactions.jsonl in staging

Audit entries are append-only - labeling creates new entries that
reference parent entries by ID.

Closes #649

Co-authored-by: Dmitry Chichkov <dchichkov@nvidia.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-20 03:24:51 -08:00
parent 3c08e5eb9d
commit 7b758271ed
10 changed files with 483 additions and 45 deletions

View File

@@ -15,9 +15,11 @@ const (
// Config holds configuration for the compaction process.
type Config struct {
APIKey string
Concurrency int
DryRun bool
APIKey string
Concurrency int
DryRun bool
AuditEnabled bool
Actor string
}
// Compactor handles issue compaction using AI summarization.
@@ -53,6 +55,10 @@ func New(store *sqlite.SQLiteStorage, apiKey string, config *Config) (*Compactor
}
}
}
if haikuClient != nil {
haikuClient.auditEnabled = config.AuditEnabled
haikuClient.auditActor = config.Actor
}
return &Compactor{
store: store,

View File

@@ -13,6 +13,7 @@ import (
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
"github.com/steveyegge/beads/internal/audit"
"github.com/steveyegge/beads/internal/types"
)
@@ -32,6 +33,8 @@ type HaikuClient struct {
tier1Template *template.Template
maxRetries int
initialBackoff time.Duration
auditEnabled bool
auditActor string
}
// NewHaikuClient creates a new Haiku API client. Env var ANTHROPIC_API_KEY takes precedence over explicit apiKey.
@@ -67,7 +70,23 @@ func (h *HaikuClient) SummarizeTier1(ctx context.Context, issue *types.Issue) (s
return "", fmt.Errorf("failed to render prompt: %w", err)
}
return h.callWithRetry(ctx, prompt)
resp, callErr := h.callWithRetry(ctx, prompt)
if h.auditEnabled {
// Best-effort: never fail compaction because audit logging failed.
e := &audit.Entry{
Kind: "llm_call",
Actor: h.auditActor,
IssueID: issue.ID,
Model: string(h.model),
Prompt: prompt,
Response: resp,
}
if callErr != nil {
e.Error = callErr.Error()
}
_, _ = audit.Append(e)
}
return resp, callErr
}
func (h *HaikuClient) callWithRetry(ctx context.Context, prompt string) (string, error) {