From 816b11877826cce8849341eb18bd8f2e2f8c53e5 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 2 Aug 2026 23:40:24 +0530 Subject: [PATCH 1/3] fix(engine): replace len/4 token heuristic with tok tokenizer --- internal/engine/conversation_adapter_test.go | 12 ++++++++---- internal/engine/selective_rag.go | 7 +++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/internal/engine/conversation_adapter_test.go b/internal/engine/conversation_adapter_test.go index 004faad0..84e499bf 100644 --- a/internal/engine/conversation_adapter_test.go +++ b/internal/engine/conversation_adapter_test.go @@ -272,7 +272,7 @@ func TestExportMessagesFormat(t *testing.T) { func TestTokenEstimateRoughAccuracy(t *testing.T) { cm := NewConversationManager(ConversationConfig{}) - // estimateTokens uses len(text)/4 + // estimateTokens uses the BPE-based tok tokenizer. content := "this is a test string that should produce some tokens" cm.AddMessage("user", content) @@ -283,9 +283,13 @@ func TestTokenEstimateRoughAccuracy(t *testing.T) { t.Errorf("expected token estimate %d, got %d", expected, got) } - // Verify the formula: roughly 1 token per 4 characters. - if got != len(content)/4 { - t.Errorf("token estimate should be len/4 = %d, got %d", len(content)/4, got) + // The estimate must be non-zero and stay within a sane range for English + // text (BPE typically lands between ~3 and ~7 chars per token). + if got <= 0 { + t.Errorf("token estimate should be positive, got %d", got) + } + if cpt := float64(len(content)) / float64(got); cpt < 3 || cpt > 7 { + t.Errorf("chars-per-token %0.2f outside expected range (3-7) for %d chars and %d tokens", cpt, len(content), got) } } diff --git a/internal/engine/selective_rag.go b/internal/engine/selective_rag.go index 3efc3e99..20181c0c 100644 --- a/internal/engine/selective_rag.go +++ b/internal/engine/selective_rag.go @@ -4,6 +4,8 @@ import ( "strings" "sync" "time" + + "github.com/GrayCodeAI/hawk/internal/engine/token" ) // SelectiveRAG implements the Repoformer-style selective retrieval mechanism. @@ -171,8 +173,9 @@ func (r *SelectiveRAG) Stats() map[string]interface{} { // Helper functions for query classification func estimateTokens(text string) int { - // Rough estimate: 1 token per 4 chars - return len(text) / 4 + // BPE-based estimate via the tok tokenizer instead of the len/4 char + // heuristic, which systematically undercounts code-heavy text. + return token.CountTokensFast(text) } func isNavigationQuery(query string) bool { From 2002a0480a9d81bb68a9c2c066d62ba1394192ec Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 2 Aug 2026 23:40:26 +0530 Subject: [PATCH 2/3] fix(sandbox): scope seatbelt mach-lookup to service allowlist --- internal/sandbox/seatbelt.go | 21 +++++++++++++++++++-- internal/sandbox/seatbelt_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/internal/sandbox/seatbelt.go b/internal/sandbox/seatbelt.go index 9ffc3285..c1dd04a3 100644 --- a/internal/sandbox/seatbelt.go +++ b/internal/sandbox/seatbelt.go @@ -31,8 +31,25 @@ func GenerateSeatbeltProfile(policy *SeatbeltPolicy) string { b.WriteString("(version 1)\n") b.WriteString("(deny default)\n") - // Always allow basic mach-lookup for system functionality. - b.WriteString("(allow mach-lookup)\n") + // mach-lookup grants access to macOS XPC services. The legacy TierOff + // behavior allows every service; all other tiers are restricted to the + // minimal set of services required for normal tooling to work. Network + // resolution services are only reachable when network is allowed. + switch policy.Tier { + case TierOff: + b.WriteString("(allow mach-lookup)\n") + default: + b.WriteString("(allow mach-lookup\n") + b.WriteString(" (global-name \"com.apple.system.opendirectoryd.api\")\n") + b.WriteString(" (global-name \"com.apple.system.notification_center\")\n") + b.WriteString(" (global-name \"com.apple.cfprefsd\")\n") + b.WriteString(" (global-name \"com.apple.system.logger\")\n") + if policy.AllowNetwork { + b.WriteString(" (global-name \"com.apple.mDNSResponder\")\n") + b.WriteString(" (global-name \"com.apple.system.systemconfigurationd\")\n") + } + b.WriteString(")\n") + } // Sysctl read for basic system queries. if policy.AllowSysctl { diff --git a/internal/sandbox/seatbelt_test.go b/internal/sandbox/seatbelt_test.go index 9faa0bf1..e361fc53 100644 --- a/internal/sandbox/seatbelt_test.go +++ b/internal/sandbox/seatbelt_test.go @@ -169,6 +169,35 @@ func TestDefaultHawkPolicy_ProfileProducesValidSBPL(t *testing.T) { } } +func TestGenerateSeatbeltProfile_MachLookupTiered(t *testing.T) { + // TierOff keeps the legacy broad mach-lookup rule. + off := GenerateSeatbeltProfile(&SeatbeltPolicy{Tier: TierOff}) + if !strings.Contains(off, "(allow mach-lookup)\n") { + t.Error("TierOff profile should allow all mach-lookup services") + } + + // All other tiers restrict mach-lookup to the service allowlist. + strict := GenerateSeatbeltProfile(&SeatbeltPolicy{Tier: TierStrict}) + if strings.Contains(strict, "(allow mach-lookup)\n") { + t.Error("restricted profile should not contain the broad mach-lookup rule") + } + if !strings.Contains(strict, `(global-name "com.apple.system.opendirectoryd.api")`) { + t.Error("restricted profile should allow opendirectoryd.api") + } + if strings.Contains(strict, `(global-name "com.apple.mDNSResponder")`) { + t.Error("network service should not be reachable when AllowNetwork is false") + } + + // Network-enabled tiers additionally allow resolution services. + net := GenerateSeatbeltProfile(&SeatbeltPolicy{Tier: TierWorkspace, AllowNetwork: true}) + if !strings.Contains(net, `(global-name "com.apple.mDNSResponder")`) { + t.Error("network-enabled profile should allow mDNSResponder") + } + if !strings.Contains(net, `(global-name "com.apple.system.systemconfigurationd")`) { + t.Error("network-enabled profile should allow systemconfigurationd") + } +} + func TestRunSeatbelted_ReturnsCmd(t *testing.T) { policy := &SeatbeltPolicy{ AllowNetwork: false, From cb8d94cdc452acb991d90f940784733c4e9cd71f Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 2 Aug 2026 23:40:27 +0530 Subject: [PATCH 3/3] fix(multiagent): surface aggregate task failures from Pool.Run --- internal/multiagent/parallel/parallel.go | 21 ++++++++++++++++++- internal/multiagent/parallel/parallel_test.go | 12 +++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/internal/multiagent/parallel/parallel.go b/internal/multiagent/parallel/parallel.go index cef12618..227cb125 100644 --- a/internal/multiagent/parallel/parallel.go +++ b/internal/multiagent/parallel/parallel.go @@ -2,6 +2,7 @@ package parallel import ( "context" + "errors" "fmt" "sync" ) @@ -87,7 +88,9 @@ func (p *Pool) AddTask(description string) *Task { // Run executes all queued tasks in parallel, each in its own git worktree. // workFn receives the worktree path and task, and returns a result summary. -// Tasks that fail do not prevent other tasks from completing. +// Tasks that fail do not prevent other tasks from completing; the returned +// error aggregates every failed task so callers are not left with silent +// failures. func (p *Pool) Run(ctx context.Context, workFn func(ctx context.Context, worktreePath string, task *Task) (string, error)) error { p.mu.Lock() tasks := make([]*Task, len(p.tasks)) @@ -161,6 +164,22 @@ func (p *Pool) Run(ctx context.Context, workFn func(ctx context.Context, worktre } wg.Wait() + + // Aggregate failures instead of returning nil unconditionally, so a run + // where every task failed is distinguishable from a fully successful run. + var failed []*Task + for _, t := range tasks { + if t.Status == StatusFailed { + failed = append(failed, t) + } + } + if len(failed) > 0 { + errs := make([]error, 0, len(failed)) + for _, t := range failed { + errs = append(errs, fmt.Errorf("task %q failed: %w", t.Description, t.Error)) + } + return errors.Join(errs...) + } return nil } diff --git a/internal/multiagent/parallel/parallel_test.go b/internal/multiagent/parallel/parallel_test.go index 4f40e119..6e219360 100644 --- a/internal/multiagent/parallel/parallel_test.go +++ b/internal/multiagent/parallel/parallel_test.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "sync" "sync/atomic" "testing" @@ -202,8 +203,11 @@ func TestErrorHandling(t *testing.T) { } return "success", nil }) - if err != nil { - t.Fatalf("Run: %v", err) + if err == nil { + t.Fatal("Run should return an aggregate error when a task fails") + } + if !strings.Contains(err.Error(), "fail-1") || !strings.Contains(err.Error(), "intentional failure") { + t.Errorf("aggregate error should name the failed task and its error, got: %v", err) } defer pool.Cleanup() @@ -310,8 +314,8 @@ func TestContextCancellation(t *testing.T) { return "", ctx.Err() } }) - if err != nil { - t.Fatalf("Run: %v", err) + if err == nil { + t.Fatal("Run should report an aggregate error when tasks fail") } defer pool.Cleanup()