From 4fe80b860cdebef24ac87986de81636dfa9ade6f Mon Sep 17 00:00:00 2001 From: BaaRai <130491490+BaaRai@users.noreply.github.com> Date: Mon, 9 Nov 2026 19:19:42 +0100 Subject: [PATCH 1/2] chore: bump norma to v0.4.6 and enable untrusted-output boundary Bump the Norma replace to v0.4.6, which adds the opt-in UntrustedOutputBoundary option: tool results are framed as data between nonce-tagged markers, and the output cap leaves room for the framing. Enable the option in the four agentcore.Options literals that set ToolOutputDir: chat, mainagent, planner and worker. --- agent/chat.go | 3 +++ agent/mainagent.go | 3 +++ agent/planner.go | 3 +++ agent/worker.go | 3 +++ go.mod | 2 +- go.sum | 6 ++---- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/agent/chat.go b/agent/chat.go index ae52ee8..05128b3 100644 --- a/agent/chat.go +++ b/agent/chat.go @@ -160,6 +160,9 @@ func (c *ChatAgent) Chat(ctx context.Context, agentKey, sessionID, message strin Settlement: wrapupSettlement(agentKey, nil), NonStreaming: c.nonStreaming(), // when this profile selects non-streaming, use Provider.Complete MaxTokens: c.maxTokens(), // 0 = send no cap, the server's default decides + + // Frame tool results as untrusted data (nonce-tagged); see harness/untrusted.go. + UntrustedOutputBoundary: true, } if c.guard != nil { opts.Hooks = c.guard.Hooks() diff --git a/agent/mainagent.go b/agent/mainagent.go index 94585fb..b05d60c 100644 --- a/agent/mainagent.go +++ b/agent/mainagent.go @@ -169,6 +169,9 @@ func (m *MainAgent) Chat(ctx context.Context, taskID int64, mainSeg int, as *db. Settlement: wrapupSettlement("mainagent", nil), NonStreaming: m.nonStreaming(), // when this profile selects non-streaming, use Provider.Complete MaxTokens: m.maxTokens(), // 0 = send no cap, the server's default decides + + // Frame tool results as untrusted data (nonce-tagged); see harness/untrusted.go. + UntrustedOutputBoundary: true, } if m.tx != nil { // persist raw human↔AI conversation; one accumulating file per segment opts.Transcript = m.tx diff --git a/agent/planner.go b/agent/planner.go index 7f53f80..637e04f 100644 --- a/agent/planner.go +++ b/agent/planner.go @@ -457,6 +457,9 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts Settlement: settle, NonStreaming: p.nonStreaming(), // when this profile selects non-streaming, use Provider.Complete MaxTokens: p.maxTokens(), // 0 = send no cap; the server default decides + + // Frame tool results as untrusted data (nonce-tagged); see harness/untrusted.go. + UntrustedOutputBoundary: true, } if p.tx != nil { // persist raw LLM conversation; one accumulating file per task's planner opts.Transcript = p.tx diff --git a/agent/worker.go b/agent/worker.go index 93b5863..77a0842 100644 --- a/agent/worker.go +++ b/agent/worker.go @@ -526,6 +526,9 @@ func (w *Worker) execute(ctx context.Context, name string, taskID int64, as *db. Todos: actool.NewTodoStore(), // session-level scratch todos (TodoWrite), pure planning, discarded on exit NonStreaming: w.nonStreaming(), // when this profile selects non-streaming, use Provider.Complete MaxTokens: w.maxTokens(), // 0 = send no cap; the server default decides + + // Frame tool results as untrusted data (nonce-tagged); see harness/untrusted.go. + UntrustedOutputBoundary: true, } if hooks != nil { // typed-nil guard: only set when concrete (avoids harness panic) opts.Hooks = hooks diff --git a/go.mod b/go.mod index 717efd7..0ce2218 100644 --- a/go.mod +++ b/go.mod @@ -61,4 +61,4 @@ require ( modernc.org/memory v1.11.0 // indirect ) -replace github.com/Autumn-27/norma => github.com/BaaRai/Norma v0.4.5 +replace github.com/Autumn-27/norma => github.com/BaaRai/Norma v0.4.6 diff --git a/go.sum b/go.sum index 0da0865..fc83fe3 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -github.com/BaaRai/Norma v0.4.4 h1:x5UAeaTuhREI1GiIYuwIEybRxr/pLCeCUvXmUbwv5Is= -github.com/BaaRai/Norma v0.4.4/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw= -github.com/BaaRai/Norma v0.4.5 h1:k6xgLsR308CSYPrrcCzqQJDfyzMI7VjAUN5CDX+i0sY= -github.com/BaaRai/Norma v0.4.5/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw= +github.com/BaaRai/Norma v0.4.6 h1:sEm58Gx7gw+UU/05aHZBZ+Xqm5dMYiuSQpc1aXnbElc= +github.com/BaaRai/Norma v0.4.6/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw= github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= From 62571c9b341ad52dcdae2508f3acba2bf03df11c Mon Sep 17 00:00:00 2001 From: BaaRai <130491490+BaaRai@users.noreply.github.com> Date: Mon, 9 Nov 2026 19:24:24 +0100 Subject: [PATCH 2/2] chore: keep untrusted-output boundary off until isolated smoke test Revert the four UntrustedOutputBoundary: true lines. The bump to Norma v0.4.6 stays; the boundary is available there but remains disabled until an isolated smoke run is done (no throwaway database, cheap profile or test key yet). --- agent/chat.go | 3 --- agent/mainagent.go | 3 --- agent/planner.go | 3 --- agent/worker.go | 3 --- 4 files changed, 12 deletions(-) diff --git a/agent/chat.go b/agent/chat.go index 05128b3..ae52ee8 100644 --- a/agent/chat.go +++ b/agent/chat.go @@ -160,9 +160,6 @@ func (c *ChatAgent) Chat(ctx context.Context, agentKey, sessionID, message strin Settlement: wrapupSettlement(agentKey, nil), NonStreaming: c.nonStreaming(), // when this profile selects non-streaming, use Provider.Complete MaxTokens: c.maxTokens(), // 0 = send no cap, the server's default decides - - // Frame tool results as untrusted data (nonce-tagged); see harness/untrusted.go. - UntrustedOutputBoundary: true, } if c.guard != nil { opts.Hooks = c.guard.Hooks() diff --git a/agent/mainagent.go b/agent/mainagent.go index b05d60c..94585fb 100644 --- a/agent/mainagent.go +++ b/agent/mainagent.go @@ -169,9 +169,6 @@ func (m *MainAgent) Chat(ctx context.Context, taskID int64, mainSeg int, as *db. Settlement: wrapupSettlement("mainagent", nil), NonStreaming: m.nonStreaming(), // when this profile selects non-streaming, use Provider.Complete MaxTokens: m.maxTokens(), // 0 = send no cap, the server's default decides - - // Frame tool results as untrusted data (nonce-tagged); see harness/untrusted.go. - UntrustedOutputBoundary: true, } if m.tx != nil { // persist raw human↔AI conversation; one accumulating file per segment opts.Transcript = m.tx diff --git a/agent/planner.go b/agent/planner.go index 637e04f..7f53f80 100644 --- a/agent/planner.go +++ b/agent/planner.go @@ -457,9 +457,6 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts Settlement: settle, NonStreaming: p.nonStreaming(), // when this profile selects non-streaming, use Provider.Complete MaxTokens: p.maxTokens(), // 0 = send no cap; the server default decides - - // Frame tool results as untrusted data (nonce-tagged); see harness/untrusted.go. - UntrustedOutputBoundary: true, } if p.tx != nil { // persist raw LLM conversation; one accumulating file per task's planner opts.Transcript = p.tx diff --git a/agent/worker.go b/agent/worker.go index 77a0842..93b5863 100644 --- a/agent/worker.go +++ b/agent/worker.go @@ -526,9 +526,6 @@ func (w *Worker) execute(ctx context.Context, name string, taskID int64, as *db. Todos: actool.NewTodoStore(), // session-level scratch todos (TodoWrite), pure planning, discarded on exit NonStreaming: w.nonStreaming(), // when this profile selects non-streaming, use Provider.Complete MaxTokens: w.maxTokens(), // 0 = send no cap; the server default decides - - // Frame tool results as untrusted data (nonce-tagged); see harness/untrusted.go. - UntrustedOutputBoundary: true, } if hooks != nil { // typed-nil guard: only set when concrete (avoids harness panic) opts.Hooks = hooks