refactor(layers): the last upward edges close

- guardian moves from safety to runtime: it runs an agent to review a
  turn, so it is a turn-engine client, not a subsystem.
- jobs moves from runtime to tools: it imports nothing above the
  subsystem layer, and builtin's bash and background-job tools own it.
- recovery.Metrics becomes hostaudit.RecoveryMetrics. The telemetry sink
  reads a counter record and no longer imports the recovery gate, which
  depends on the agent.
- RedactMessage and RedactMessages move from base/secrets to
  contract/provider, built on secrets.Redact, so base imports nothing
  from contract.

layer-order reaches 0: every kernel import now points at its own layer or
below. Baseline carried with -allow-widen for one line: recovery/gate.go
gains the hostaudit import.
This commit is contained in:
yhh committed 2026-09-23 20:26:33 -07:00
1 parent 493504224a
commit fc3a44dbcc
79 files changed
+200 -200

No files matched your search

+1 -1
View File
@@ -45,8 +45,8 @@ import (
"reasonix/internal/platform/lsp"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/capability"
"reasonix/internal/runtime/guardian"
"reasonix/internal/runtime/recovery"
"reasonix/internal/safety/guardian"
"reasonix/internal/safety/permission"
"reasonix/internal/safety/sandbox"
"reasonix/internal/session/control"
+1 -1
View File
@@ -7,9 +7,9 @@ import (
"reasonix/internal/contract/config"
"reasonix/internal/contract/event"
"reasonix/internal/runtime/jobs"
"reasonix/internal/session/control"
"reasonix/internal/state/workspacelease"
"reasonix/internal/tools/jobs"
)
// sessionRuntime is the session-scoped machinery a build hands to the
-42
View File
@@ -6,8 +6,6 @@ import (
"strings"
"sync"
"sync/atomic"
"reasonix/internal/contract/provider"
)
var (
@@ -338,43 +336,3 @@ func mask(value string) string {
}
return value[:head] + strings.Repeat("*", len(value)-head-tail) + value[len(value)-tail:]
}
// RedactMessage returns a storage-safe copy of m with textual secret surfaces
// masked. Images are left untouched because they are opaque data URLs.
// ToolCalls and MemoryCitations are cloned before masking: m is passed by
// value but its slices share backing arrays with the caller, and the save
// path hands in live session messages — writing through would silently mutate
// the model-visible history mid-conversation and churn the prompt cache.
func RedactMessage(m provider.Message) provider.Message {
m.Content = Redact(m.Content)
m.ReasoningContent = Redact(m.ReasoningContent)
m.Original = Redact(m.Original)
if len(m.ToolCalls) > 0 {
calls := make([]provider.ToolCall, len(m.ToolCalls))
copy(calls, m.ToolCalls)
for i := range calls {
calls[i].Arguments = Redact(calls[i].Arguments)
calls[i].Diff = Redact(calls[i].Diff)
}
m.ToolCalls = calls
}
if len(m.MemoryCitations) > 0 {
cites := make([]provider.MemoryCitation, len(m.MemoryCitations))
copy(cites, m.MemoryCitations)
for i := range cites {
cites[i].Note = Redact(cites[i].Note)
}
m.MemoryCitations = cites
}
return m
}
// RedactMessages returns a redacted copy of msgs. The input slice and its
// messages are never mutated.
func RedactMessages(msgs []provider.Message) []provider.Message {
out := make([]provider.Message, len(msgs))
for i, m := range msgs {
out[i] = RedactMessage(m)
}
return out
}
-36
View File
@@ -6,8 +6,6 @@ import (
"strings"
"sync"
"testing"
"reasonix/internal/contract/provider"
)
func TestRedactMasksCommonSecretShapes(t *testing.T) {
@@ -302,37 +300,3 @@ func TestProcessEnvAlwaysFiltersRegisteredCredentialKeys(t *testing.T) {
t.Fatalf("ordinary env was removed with opt-in filtering off:\n%s", joined)
}
}
func TestRedactMessagesDoesNotMutateInput(t *testing.T) {
const secret = "sk-real-secret-value-123456"
msgs := []provider.Message{
{
Role: provider.RoleAssistant,
Content: "checking",
ToolCalls: []provider.ToolCall{
{ID: "call_1", Name: "bash", Arguments: `{"command":"echo DEEPSEEK_API_KEY=` + secret + `"}`},
},
MemoryCitations: []provider.MemoryCitation{{Note: "token " + secret}},
},
{Role: provider.RoleTool, ToolCallID: "call_1", Content: "DEEPSEEK_API_KEY=" + secret},
}
out := RedactMessages(msgs)
// The redacted copy must not carry the raw secret...
if strings.Contains(out[0].ToolCalls[0].Arguments, secret) || strings.Contains(out[1].Content, secret) {
t.Fatalf("redacted copy leaked secret: %+v", out)
}
// ...and the input — live session history the model still replays — must
// be untouched, including through the shared ToolCalls/MemoryCitations
// backing arrays.
if !strings.Contains(msgs[0].ToolCalls[0].Arguments, secret) {
t.Fatalf("RedactMessages mutated the caller's ToolCalls: %q", msgs[0].ToolCalls[0].Arguments)
}
if !strings.Contains(msgs[0].MemoryCitations[0].Note, secret) {
t.Fatalf("RedactMessages mutated the caller's MemoryCitations: %q", msgs[0].MemoryCitations[0].Note)
}
if !strings.Contains(msgs[1].Content, secret) {
t.Fatalf("RedactMessages mutated the caller's Content: %q", msgs[1].Content)
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
// Sync wraps a Sink so concurrent Emit calls are serialized. The base Sink
// contract assumes serial emission — the agent's run loop emits one event at a
// time. Background jobs (internal/runtime/jobs) emit from their own goroutines, which can
// time. Background jobs (internal/tools/jobs) emit from their own goroutines, which can
// overlap a running turn's emission; wrapping the session sink once in Sync keeps
// the serial-Emit invariant every sink relies on (an SSE writer, a webview
// EventsEmit, a TUI channel) without each having to lock. A nil sink yields
@@ -0,0 +1,23 @@
package hostaudit
// RecoveryMetrics are content-free counters for release observation.
// They never record parameters, paths, or error bodies.
type RecoveryMetrics struct {
FailureEvents int64
RuleContinues int64
ReviewContinues int64
HumanPrompts int64
HumanContinues int64
TaskGrantContinues int64
TaskGrantUses int64
HumanRevises int64
ReviewErrors int64
ReviewLatencyMsSum int64
ReviewLatencyCount int64
RepeatPrompts int64
// Episode / generation counters (content-free).
ModeResets int64
EpisodeRotations int64
StaleObservationsIgnored int64
}
+42
View File
@@ -0,0 +1,42 @@
package provider
import "reasonix/internal/base/secrets"
// RedactMessage returns a storage-safe copy of m with textual secret surfaces
// masked; images are opaque data URLs and stay untouched. ToolCalls and
// MemoryCitations are cloned first: the save path hands in live session
// messages, and masking through shared slices would rewrite the model-visible
// history mid-conversation.
func RedactMessage(m Message) Message {
m.Content = secrets.Redact(m.Content)
m.ReasoningContent = secrets.Redact(m.ReasoningContent)
m.Original = secrets.Redact(m.Original)
if len(m.ToolCalls) > 0 {
calls := make([]ToolCall, len(m.ToolCalls))
copy(calls, m.ToolCalls)
for i := range calls {
calls[i].Arguments = secrets.Redact(calls[i].Arguments)
calls[i].Diff = secrets.Redact(calls[i].Diff)
}
m.ToolCalls = calls
}
if len(m.MemoryCitations) > 0 {
cites := make([]MemoryCitation, len(m.MemoryCitations))
copy(cites, m.MemoryCitations)
for i := range cites {
cites[i].Note = secrets.Redact(cites[i].Note)
}
m.MemoryCitations = cites
}
return m
}
// RedactMessages returns a redacted copy of msgs. The input slice and its
// messages are never mutated.
func RedactMessages(msgs []Message) []Message {
out := make([]Message, len(msgs))
for i, m := range msgs {
out[i] = RedactMessage(m)
}
return out
}
+40
View File
@@ -0,0 +1,40 @@
package provider
import (
"strings"
"testing"
)
func TestRedactMessagesDoesNotMutateInput(t *testing.T) {
const secret = "sk-real-secret-value-123456"
msgs := []Message{
{
Role: RoleAssistant,
Content: "checking",
ToolCalls: []ToolCall{
{ID: "call_1", Name: "bash", Arguments: `{"command":"echo DEEPSEEK_API_KEY=` + secret + `"}`},
},
MemoryCitations: []MemoryCitation{{Note: "token " + secret}},
},
{Role: RoleTool, ToolCallID: "call_1", Content: "DEEPSEEK_API_KEY=" + secret},
}
out := RedactMessages(msgs)
// The redacted copy must not carry the raw secret...
if strings.Contains(out[0].ToolCalls[0].Arguments, secret) || strings.Contains(out[1].Content, secret) {
t.Fatalf("redacted copy leaked secret: %+v", out)
}
// ...and the input — live session history the model still replays — must
// be untouched, including through the shared ToolCalls/MemoryCitations
// backing arrays.
if !strings.Contains(msgs[0].ToolCalls[0].Arguments, secret) {
t.Fatalf("RedactMessages mutated the caller's ToolCalls: %q", msgs[0].ToolCalls[0].Arguments)
}
if !strings.Contains(msgs[0].MemoryCitations[0].Note, secret) {
t.Fatalf("RedactMessages mutated the caller's MemoryCitations: %q", msgs[0].MemoryCitations[0].Note)
}
if !strings.Contains(msgs[1].Content, secret) {
t.Fatalf("RedactMessages mutated the caller's Content: %q", msgs[1].Content)
}
}
+1 -1
View File
@@ -23,8 +23,8 @@ import (
"reasonix/internal/ext/skill"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/agent/testutil"
"reasonix/internal/runtime/jobs"
"reasonix/internal/session/control"
"reasonix/internal/tools/jobs"
)
// fakes: a Factory wrapping a behavior-driven runner in a real Controller
+1 -1
View File
@@ -21,10 +21,10 @@ import (
"reasonix/internal/ext/extension/uihub"
"reasonix/internal/ext/plugin"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/session/control"
"reasonix/internal/state/store"
"reasonix/internal/tools/builtin"
"reasonix/internal/tools/jobs"
)
// SessionParams is everything a Factory needs to assemble one ACP session's
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"strings"
"time"
"reasonix/internal/runtime/jobs"
"reasonix/internal/tools/jobs"
)
type machineTask struct {
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"reasonix/internal/base/testenv"
"reasonix/internal/contract/config"
"reasonix/internal/contract/event"
"reasonix/internal/runtime/jobs"
"reasonix/internal/tools/jobs"
)
func TestTaskMachineListUsesContentFreePersistedMetadata(t *testing.T) {
+3 -3
View File
@@ -256,7 +256,7 @@ func redactSessionTranscript(path string, dryRun bool) (int64, int64, error) {
if dryRun {
return files, redactedEncodedSize(s.Messages), nil
}
s.Replace(secrets.RedactMessages(s.Messages))
s.Replace(provider.RedactMessages(s.Messages))
// Redaction is an intentional rewrite, but it must still be CAS-protected:
// the loaded transcript may have gone stale while the doctor inspected it.
// SaveRewrite preserves the newer external transcript and reports a conflict
@@ -304,7 +304,7 @@ func eventLogNeedsRedaction(path string) bool {
// storage encoding of msgs. Comparing encoded forms (not struct equality)
// matches exactly what a rewrite would put on disk.
func messagesNeedRedaction(msgs []provider.Message) bool {
redacted := secrets.RedactMessages(msgs)
redacted := provider.RedactMessages(msgs)
for i := range msgs {
before, errB := json.Marshal(msgs[i])
after, errA := json.Marshal(redacted[i])
@@ -317,7 +317,7 @@ func messagesNeedRedaction(msgs []provider.Message) bool {
func redactedEncodedSize(msgs []provider.Message) int64 {
var n int64
for _, m := range secrets.RedactMessages(msgs) {
for _, m := range provider.RedactMessages(msgs) {
if b, err := json.Marshal(m); err == nil {
n += int64(len(b)) + 1
}
+1 -1
View File
@@ -28,10 +28,10 @@ import (
"reasonix/internal/contract/provider"
"reasonix/internal/contract/surface"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/session/control"
"reasonix/internal/state/stats"
"reasonix/internal/state/store"
"reasonix/internal/tools/jobs"
)
//go:embed logo-wordmark.svg
+1 -1
View File
@@ -11,8 +11,8 @@ import (
"time"
"reasonix/internal/contract/config"
"reasonix/internal/runtime/jobs"
"reasonix/internal/session/control"
"reasonix/internal/tools/jobs"
)
// lockProbeController wraps a real controller but intercepts the two blocking
+1 -1
View File
@@ -22,9 +22,9 @@ import (
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/permission"
"reasonix/internal/session/control"
"reasonix/internal/tools/jobs"
)
func TestTitlePromptRequiresUserMessageLanguage(t *testing.T) {
+2 -2
View File
@@ -8,6 +8,7 @@ import (
"net/http"
"os"
"path/filepath"
"reasonix/internal/contract/hostaudit"
"runtime"
"strings"
"sync"
@@ -18,7 +19,6 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/surface"
"reasonix/internal/runtime/recovery"
)
type roundTripFunc func(*http.Request) (*http.Response, error)
@@ -397,7 +397,7 @@ func TestEverySignalTheSinkRecordsSurvivesTheUploadAllowList(t *testing.T) {
sink.Emit(event.Event{Kind: event.CompactionStarted, Compaction: event.Compaction{Trigger: "auto"}})
event.RecordProtocolRecovery(sink, event.ProtocolRecoveryAudit{Kind: event.ProtocolRecoveryMissingReasoningRetryReplaced})
sink.Emit(event.Event{Kind: event.TurnDone})
reporter.RecordRecovery(recovery.Metrics{FailureEvents: 1, ReviewLatencyMsSum: 400, ReviewLatencyCount: 1})
reporter.RecordRecovery(hostaudit.RecoveryMetrics{FailureEvents: 1, ReviewLatencyMsSum: 400, ReviewLatencyCount: 1})
entries, err := os.ReadDir(filepath.Join(home, pendingDirName))
if err != nil || len(entries) == 0 {
+2 -2
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"net"
"reasonix/internal/contract/hostaudit"
"regexp"
"runtime"
"slices"
@@ -14,7 +15,6 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/surface"
"reasonix/internal/runtime/recovery"
)
type Options struct {
@@ -83,7 +83,7 @@ func (r *Reporter) Wrap(inner event.Sink) event.Sink {
return &sink{AuditForwarder: event.AuditForwarder{Inner: inner}, inner: inner, reporter: r, counts: countersFrom(r.static)}
}
func (r *Reporter) RecordRecovery(m recovery.Metrics) {
func (r *Reporter) RecordRecovery(m hostaudit.RecoveryMetrics) {
if r == nil {
return
}
+1 -1
View File
@@ -23,7 +23,6 @@ import (
"reasonix/internal/contract/tool"
"reasonix/internal/ext/extension/dispatch"
"reasonix/internal/runtime/capability"
"reasonix/internal/runtime/jobs"
"reasonix/internal/runtime/plancontract"
"reasonix/internal/runtime/taskpolicy"
"reasonix/internal/safety/evidence"
@@ -31,6 +30,7 @@ import (
"reasonix/internal/state/checkpoint"
"reasonix/internal/state/instruction"
"reasonix/internal/state/memory"
"reasonix/internal/tools/jobs"
)
// maxToolOutputBytes caps a single tool result before it goes into the model's
+1 -1
View File
@@ -7,13 +7,13 @@ import (
"reasonix/internal/contract/tool"
"reasonix/internal/ext/extension/dispatch"
"reasonix/internal/runtime/capability"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/safety/sandbox"
"reasonix/internal/state/checkpoint"
"reasonix/internal/state/instruction"
"reasonix/internal/state/memory"
"reasonix/internal/state/workspacelease"
"reasonix/internal/tools/jobs"
)
// Options configures an Agent.
@@ -18,8 +18,8 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/state/execjournal"
"reasonix/internal/tools/jobs"
)
// A lone delegation reaches the same runner a fan-out item does and, until this
+1 -1
View File
@@ -14,8 +14,8 @@ import (
"reasonix/internal/contract/ablation"
"reasonix/internal/contract/agentgraph"
"reasonix/internal/contract/event"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/tools/jobs"
)
const (
+1 -1
View File
@@ -14,8 +14,8 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/state/checkpoint"
"reasonix/internal/tools/jobs"
"reasonix/internal/base/testenv"
"reasonix/internal/contract/agentgraph"
+1 -1
View File
@@ -16,9 +16,9 @@ import (
"reasonix/internal/contract/planmode"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/runtime/taskpolicy"
"reasonix/internal/safety/evidence"
"reasonix/internal/tools/jobs"
)
// streamedTurn is one provider completion collected by stream. Keeping the
+1 -1
View File
@@ -6,11 +6,11 @@ import (
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/ext/extension/dispatch"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/sandbox"
"reasonix/internal/state/checkpoint"
"reasonix/internal/state/memory"
"reasonix/internal/state/workspacelease"
"reasonix/internal/tools/jobs"
)
// agentServices are the collaborators an Agent talks to, separated from the
@@ -13,7 +13,7 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/tools/jobs"
)
// fakeProgressClock drives the merger's pacing deterministically: tests advance
+1 -1
View File
@@ -20,12 +20,12 @@ import (
"reasonix/internal/contract/planmode"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/state/checkpoint"
"reasonix/internal/state/memory"
"reasonix/internal/state/sessiontemp"
"reasonix/internal/state/workspacelease"
"reasonix/internal/tools/jobs"
)
// withSubagentSessionTemp installs a fresh session-private temporary directory
@@ -12,8 +12,8 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/state/checkpoint"
"reasonix/internal/tools/jobs"
)
// TestBackgroundTaskReturnsBeforeSlotFrees ensures run_in_background returns a
+1 -1
View File
@@ -15,8 +15,8 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/tools/jobs"
)
func testTaskContext() context.Context {
+1 -1
View File
@@ -5,11 +5,11 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/safety/sandbox"
"reasonix/internal/state/instruction"
"reasonix/internal/state/memory"
"reasonix/internal/tools/jobs"
)
// toolCallContext assembles the context one tool call runs under: the host
File renamed without changes.
File renamed without changes.
+7 -6
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"reasonix/internal/contract/hostaudit"
"strings"
"sync"
"time"
@@ -56,7 +57,7 @@ type Gate struct {
mu sync.Mutex
opts Options
tasks map[string]*taskRuntime
metrics Metrics
metrics hostaudit.RecoveryMetrics
waiters map[string]chan resolvePayload // keyed by approval id
taskOf map[string]string // approval id -> task id
pending map[string]PendingProposal // approval id -> transient proposal scope
@@ -270,9 +271,9 @@ func (g *Gate) finishDismissed(dismissed []dismissedWaiter) {
// Metrics returns a copy of content-free counters accumulated since gate
// construction or the most recent DrainMetrics call.
func (g *Gate) Metrics() Metrics {
func (g *Gate) Metrics() hostaudit.RecoveryMetrics {
if g == nil {
return Metrics{}
return hostaudit.RecoveryMetrics{}
}
g.mu.Lock()
defer g.mu.Unlock()
@@ -282,14 +283,14 @@ func (g *Gate) Metrics() Metrics {
// DrainMetrics atomically returns and clears recovery counters accumulated
// since the last drain. Desktop telemetry uses this delta API at TurnDone so a
// historical event is never counted again on later turns.
func (g *Gate) DrainMetrics() Metrics {
func (g *Gate) DrainMetrics() hostaudit.RecoveryMetrics {
if g == nil {
return Metrics{}
return hostaudit.RecoveryMetrics{}
}
g.mu.Lock()
defer g.mu.Unlock()
out := g.metrics
g.metrics = Metrics{}
g.metrics = hostaudit.RecoveryMetrics{}
return out
}
-22
View File
@@ -139,28 +139,6 @@ type Snapshot struct {
Tasks map[string]*TaskState `json:"tasks,omitempty"`
}
// Metrics are content-free counters for release observation.
// They never record parameters, paths, or error bodies.
type Metrics struct {
FailureEvents int64
RuleContinues int64
ReviewContinues int64
HumanPrompts int64
HumanContinues int64
TaskGrantContinues int64
TaskGrantUses int64
HumanRevises int64
ReviewErrors int64
ReviewLatencyMsSum int64
ReviewLatencyCount int64
RepeatPrompts int64
// Episode / generation counters (content-free).
ModeResets int64
EpisodeRotations int64
StaleObservationsIgnored int64
}
// ApprovalKindRecovery is the Approval.Kind value for recovery cards.
const ApprovalKindRecovery = "recovery"
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"sync/atomic"
"time"
"reasonix/internal/runtime/jobs"
"reasonix/internal/tools/jobs"
)
// TaskRecorder bridges jobs.Manager lifecycle events into the Task Store. It
@@ -8,7 +8,7 @@ import (
"time"
"reasonix/internal/base/testenv"
"reasonix/internal/runtime/jobs"
"reasonix/internal/tools/jobs"
)
func newRecorderForTest(t *testing.T, projectDir string) (*TaskRecorder, *FileStore) {
@@ -12,7 +12,7 @@ import (
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/agent"
"reasonix/internal/safety/guardian"
"reasonix/internal/runtime/guardian"
"reasonix/internal/safety/permission"
)
+2 -2
View File
@@ -44,18 +44,18 @@ import (
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/capability"
"reasonix/internal/runtime/goaleval"
"reasonix/internal/runtime/jobs"
"reasonix/internal/runtime/guardian"
"reasonix/internal/runtime/promptrefine"
"reasonix/internal/runtime/recovery"
"reasonix/internal/runtime/taskmonitor"
"reasonix/internal/safety/evidence"
"reasonix/internal/safety/guardian"
"reasonix/internal/safety/permission"
"reasonix/internal/safety/sandbox"
"reasonix/internal/state/checkpoint"
"reasonix/internal/state/memory"
"reasonix/internal/state/sessiontemp"
"reasonix/internal/state/workspacelease"
"reasonix/internal/tools/jobs"
"reasonix/internal/tools/shellrun"
)
+2 -2
View File
@@ -13,13 +13,13 @@ import (
"reasonix/internal/model/billing"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/goaleval"
"reasonix/internal/runtime/jobs"
"reasonix/internal/runtime/guardian"
"reasonix/internal/runtime/promptrefine"
"reasonix/internal/runtime/recovery"
"reasonix/internal/safety/guardian"
"reasonix/internal/safety/permission"
"reasonix/internal/safety/sandbox"
"reasonix/internal/state/workspacelease"
"reasonix/internal/tools/jobs"
)
// controllerDeps is what assembly hands the controller and never rebinds. The
+2 -2
View File
@@ -31,12 +31,12 @@ import (
"reasonix/internal/ext/pluginpkg"
"reasonix/internal/ext/skill"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/guardian"
"reasonix/internal/runtime/guardian"
"reasonix/internal/safety/permission"
"reasonix/internal/state/checkpoint"
"reasonix/internal/state/memory"
"reasonix/internal/state/store"
"reasonix/internal/tools/jobs"
)
type typedNilControllerSink struct{}
+1 -1
View File
@@ -7,9 +7,9 @@ import (
"path/filepath"
"strings"
"reasonix/internal/runtime/jobs"
"reasonix/internal/runtime/taskmonitor"
"reasonix/internal/state/sessioninbox"
"reasonix/internal/tools/jobs"
)
// hostContinuationSource labels inbox items the runtime authored for itself.
+1 -1
View File
@@ -16,8 +16,8 @@ import (
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/state/sessioninbox"
"reasonix/internal/tools/jobs"
)
func backgroundJobEvent(id string) jobs.CompletionEvent {
+1 -1
View File
@@ -14,10 +14,10 @@ import (
"reasonix/internal/model/billing"
"reasonix/internal/platform/browser"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/state/checkpoint"
"reasonix/internal/state/memory"
"reasonix/internal/tools/jobs"
)
// This file defines the driving port: the typed, segregated interface surface
+7 -6
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log/slog"
"reasonix/internal/contract/hostaudit"
"reasonix/internal/state/sessionstore"
"slices"
"strings"
@@ -256,15 +257,15 @@ func (c *Controller) saveRecoveryState(path string) {
}
// RecoveryMetrics returns content-free recovery counters for export/observation.
func (c *Controller) RecoveryMetrics() recovery.Metrics {
func (c *Controller) RecoveryMetrics() hostaudit.RecoveryMetrics {
if c == nil {
return recovery.Metrics{}
return hostaudit.RecoveryMetrics{}
}
c.mu.Lock()
gate := c.recoveryGate
c.mu.Unlock()
if gate == nil {
return recovery.Metrics{}
return hostaudit.RecoveryMetrics{}
}
return gate.Metrics()
}
@@ -272,15 +273,15 @@ func (c *Controller) RecoveryMetrics() recovery.Metrics {
// DrainRecoveryMetrics returns only counters recorded since the previous
// drain. Desktop calls this once per completed turn to avoid re-emitting the
// controller's cumulative lifetime totals.
func (c *Controller) DrainRecoveryMetrics() recovery.Metrics {
func (c *Controller) DrainRecoveryMetrics() hostaudit.RecoveryMetrics {
if c == nil {
return recovery.Metrics{}
return hostaudit.RecoveryMetrics{}
}
c.mu.Lock()
gate := c.recoveryGate
c.mu.Unlock()
if gate == nil {
return recovery.Metrics{}
return hostaudit.RecoveryMetrics{}
}
return gate.DrainMetrics()
}
@@ -5,6 +5,7 @@ import (
"encoding/json"
"os"
"path/filepath"
"reasonix/internal/contract/hostaudit"
"reasonix/internal/state/sessionstore"
"testing"
@@ -89,7 +90,7 @@ func TestRecoveryCheckpointScriptedE2E(t *testing.T) {
if delta.FailureEvents == 0 || delta.HumanPrompts != 0 || delta.HumanContinues != 0 {
t.Fatalf("drained metrics = %+v", delta)
}
if next := c.DrainRecoveryMetrics(); next != (recovery.Metrics{}) {
if next := c.DrainRecoveryMetrics(); next != (hostaudit.RecoveryMetrics{}) {
t.Fatalf("second metrics drain = %+v, want zero delta", next)
}
}
@@ -8,7 +8,7 @@ import (
"reasonix/internal/base/testenv"
"reasonix/internal/contract/provider"
"reasonix/internal/safety/guardian"
"reasonix/internal/runtime/guardian"
"reasonix/internal/state/store"
)
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"reasonix/internal/state/sessionstore"
"time"
"reasonix/internal/runtime/jobs"
"reasonix/internal/tools/jobs"
)
// SessionDestroyHandle separates cancelled-job waiting from ending the destroy
@@ -13,7 +13,7 @@ import (
"reasonix/internal/ext/extension"
"reasonix/internal/ext/extension/dispatch"
"reasonix/internal/platform/browser"
"reasonix/internal/safety/guardian"
"reasonix/internal/runtime/guardian"
"reasonix/internal/state/sessiontemp"
)
+2 -2
View File
@@ -17,10 +17,10 @@ import (
"reasonix/internal/ext/extension"
"reasonix/internal/ext/extension/dispatch"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/guardian"
"reasonix/internal/runtime/guardian"
"reasonix/internal/state/sessioninbox"
"reasonix/internal/state/store"
"reasonix/internal/tools/jobs"
)
// Snapshot writes the executor's conversation to the active session file. No-op
+1 -1
View File
@@ -18,7 +18,7 @@ import (
"reasonix/internal/ext/extension"
"reasonix/internal/ext/skill"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/tools/jobs"
)
func turnOutcome(err error) string {
@@ -15,8 +15,8 @@ import (
"reasonix/internal/contract/tool"
"reasonix/internal/ext/skill"
"reasonix/internal/runtime/agent"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/tools/jobs"
)
// turnOrchestrator owns foreground turn execution while Controller keeps the
+1 -1
View File
@@ -4,7 +4,7 @@
// This first slice owns the session-artifact sidecars — the files and
// directories that live beside a session's .jsonl (branch metadata, goal state,
// checkpoints, background-job artifacts, the cleanup-pending marker). They were
// previously derived independently in internal/runtime/agent, internal/runtime/jobs,
// previously derived independently in internal/runtime/agent, internal/tools/jobs,
// internal/session/control and internal/frontend/acp, each re-spelling the suffix convention; a
// layout change meant hunting across packages. Centralizing them here makes
// store the one place that knows where a session's artifacts go.
+1 -1
View File
@@ -23,9 +23,9 @@ import (
"reasonix/internal/base/secrets"
"reasonix/internal/base/shellparse"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/sandbox"
"reasonix/internal/state/sessiontemp"
"reasonix/internal/tools/jobs"
"reasonix/internal/tools/shellrun"
)
@@ -8,8 +8,8 @@ import (
"time"
"reasonix/internal/contract/event"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/sandbox"
"reasonix/internal/tools/jobs"
)
type fakeSandboxEscapeApprover struct {
+1 -1
View File
@@ -10,8 +10,8 @@ import (
"reasonix/internal/contract/planmode"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/tools/jobs"
)
// bash_output / kill_shell / wait operate the background jobs registered by
+1 -1
View File
@@ -9,8 +9,8 @@ import (
"reasonix/internal/contract/event"
"reasonix/internal/contract/planmode"
"reasonix/internal/runtime/jobs"
"reasonix/internal/safety/evidence"
"reasonix/internal/tools/jobs"
)
// End-to-end through the actual tools: a background bash job runs under a manager
@@ -6,7 +6,7 @@ import (
"reasonix/internal/contract/planmode"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/jobs"
"reasonix/internal/tools/jobs"
)
type visibilityRecorder struct{}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+24 -32
View File
@@ -10,12 +10,12 @@
"doc-owner": 43,
"doc-prose": 2324,
"error-text": 9,
"essay": 2891,
"file-size": 23694,
"essay": 2890,
"file-size": 23695,
"flat-view": 0,
"frontend-parity": 32,
"function-size": 4348,
"layer-order": 5,
"layer-order": 0,
"layering": 0,
"marker": 0,
"narrative": 0,
@@ -284,9 +284,7 @@
"essay": 5
},
"internal/base/secrets/redact.go": {
"complexity": 9,
"essay": 1,
"layer-order": 1
"complexity": 9
},
"internal/base/shellparse/bash.go": {
"essay": 8
@@ -965,9 +963,6 @@
"internal/platform/repair/update_test.go": {
"essay": 2
},
"internal/platform/telemetry/sink.go": {
"layer-order": 1
},
"internal/platform/update/apply_darwin.go": {
"complexity": 42,
"essay": 1,
@@ -1129,19 +1124,11 @@
"internal/runtime/capability/catalog.go": {
"essay": 1
},
"internal/runtime/jobs/artifacts_test.go": {
"essay": 5
"internal/runtime/guardian/guardian.go": {
"essay": 25
},
"internal/runtime/jobs/jobs.go": {
"essay": 42,
"file-size": 1135,
"orphan": 1
},
"internal/runtime/jobs/jobs_extra_test.go": {
"essay": 1
},
"internal/runtime/jobs/jobs_security_test.go": {
"essay": 4
"internal/runtime/guardian/guardian_policy.md": {
"doc-prose": 1
},
"internal/runtime/plancontract/project.go": {
"orphan": 1
@@ -1154,7 +1141,7 @@
},
"internal/runtime/recovery/gate.go": {
"essay": 4,
"file-size": 393
"file-size": 394
},
"internal/runtime/recovery/rules.go": {
"complexity": 19,
@@ -1205,13 +1192,6 @@
"internal/safety/evidence/verification_summary_test.go": {
"essay": 1
},
"internal/safety/guardian/guardian.go": {
"essay": 25,
"layer-order": 1
},
"internal/safety/guardian/guardian_policy.md": {
"doc-prose": 1
},
"internal/safety/permission/bash_decompose.go": {
"essay": 23
},
@@ -1514,15 +1494,13 @@
},
"internal/tools/builtin/bash.go": {
"essay": 11,
"layer-order": 1,
"orphan": 1
},
"internal/tools/builtin/bash_env_test.go": {
"essay": 4
},
"internal/tools/builtin/bgjobs.go": {
"essay": 10,
"layer-order": 1
"essay": 10
},
"internal/tools/builtin/bgjobs_test.go": {
"essay": 6
@@ -1582,6 +1560,20 @@
"internal/tools/builtin/writefile.go": {
"essay": 2
},
"internal/tools/jobs/artifacts_test.go": {
"essay": 5
},
"internal/tools/jobs/jobs.go": {
"essay": 42,
"file-size": 1135,
"orphan": 1
},
"internal/tools/jobs/jobs_extra_test.go": {
"essay": 1
},
"internal/tools/jobs/jobs_security_test.go": {
"essay": 4
},
"internal/tools/shellrun/runner.go": {
"essay": 4
},
+1 -1
View File
@@ -48,7 +48,7 @@ var docClassByPrefix = []struct {
{".github/", classMachine},
{".reasonix/", classMachine},
{"internal/ext/skill/builtincontent/", classMachine},
{"internal/safety/guardian/", classMachine},
{"internal/runtime/guardian/", classMachine},
}
// docSkippedPrefixes are corpora and fixtures: text a benchmark or a test