Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
ae392a2
platform: add multi-tenant contracts
XnLemon Jul 8, 2026
0cddda9
platform/gateway: add text loop
XnLemon Jul 8, 2026
38636fd
platform/toolpolicy: add governance bridge
XnLemon Jul 8, 2026
e39271c
platform/channeladapter: add adapter outbox skeleton
XnLemon Jul 8, 2026
f053160
platform/gateway: enqueue outbound handoff
XnLemon Jul 8, 2026
c5e55e9
platform/storagerouter: add storage routing contracts
XnLemon Jul 8, 2026
5488171
platform/gateway: add session lease
XnLemon Jul 8, 2026
64d0a9a
platform: add gray routing helpers
XnLemon Jul 8, 2026
cf5af61
platform: add tenant budget helpers
XnLemon Jul 8, 2026
90072df
platform: validate storage migration modes
XnLemon Jul 8, 2026
2b86679
platform: validate audit policies
XnLemon Jul 8, 2026
08fbc12
platform: validate audit records
XnLemon Jul 8, 2026
b63b970
platform: add capacity estimator
XnLemon Jul 8, 2026
4193569
platform/channeladapter: replay dead letters
XnLemon Jul 8, 2026
78675ef
platform: populate audit record ids
XnLemon Jul 8, 2026
41b6b97
platform: validate audit sink writes
XnLemon Jul 8, 2026
b72383f
platform: add usage record contracts
XnLemon Jul 8, 2026
260b3a5
platform: add usage sink contracts
XnLemon Jul 8, 2026
c62e214
platform: add config version contracts
XnLemon Jul 8, 2026
a74ee58
platform: select config version by session gray bucket
XnLemon Jul 8, 2026
75d1af7
platform: add config version lifecycle helpers
XnLemon Jul 8, 2026
1a12b0f
platform: add usage summary contracts
XnLemon Jul 8, 2026
c44f477
platform: add audit query contracts
XnLemon Jul 8, 2026
8eefe55
platform: add config version diff contracts
XnLemon Jul 8, 2026
6b8b00c
platform: add config gray status summary
XnLemon Jul 8, 2026
c59acc4
platform: add operational action audit contracts
XnLemon Jul 8, 2026
644a03c
platform: add config cache invalidation contracts
XnLemon Jul 8, 2026
8219105
platform: add secret rotation status contracts
XnLemon Jul 8, 2026
e66bf9c
platform: add backend migration status contracts
XnLemon Jul 8, 2026
274747c
platform: add storage router status summary
XnLemon Jul 8, 2026
826a718
platform: add config operation summary contracts
XnLemon Jul 8, 2026
ce636fe
platform/toolpolicy: add approval summary contract
XnLemon Jul 8, 2026
bc201e0
fix(platform): enforce binding ACL and redact outbox errors
XnLemon Jul 9, 2026
3d57ce8
fix(platform): redact gateway audit error reasons
XnLemon Jul 9, 2026
597b46c
fix(toolpolicy): enforce auditable policy identity
XnLemon Jul 9, 2026
b8b802e
platform: harden identity and idempotency contracts
xianingawa-wq Jul 9, 2026
6d45186
Merge pull request #16 from XnLemon/feat/usage-record-contracts
XnLemon Jul 9, 2026
db7f629
platform: tighten routing identity contracts
xianingawa-wq Jul 9, 2026
8c34131
fix(platform): reconcile hardened contracts with gateway
XnLemon Jul 9, 2026
2db2997
Merge pull request #32 from XnLemon/feat/platform-contracts
XnLemon Jul 9, 2026
8c6ffc5
platform: add budget decision audit contracts
XnLemon Jul 8, 2026
1c775a1
Merge pull request #33 from XnLemon/feat/tool-approval-summary
XnLemon Jul 9, 2026
e29fb65
platform/gateway: add minimum loop acceptance test
XnLemon Jul 8, 2026
ac3a2d4
Merge pull request #34 from XnLemon/feat/budget-decision-audit
XnLemon Jul 9, 2026
b9bc7e3
platform/gateway: add outbound dispatch acceptance test
XnLemon Jul 9, 2026
9ea340f
Merge pull request #35 from XnLemon/feat/platform-minimum-loop-accept…
XnLemon Jul 9, 2026
3c337c6
Merge pull request #36 from XnLemon/feat/platform-outbound-dispatch-a…
XnLemon Jul 9, 2026
232e3bd
platform/gateway: correlate audit trace ids
XnLemon Jul 9, 2026
b2d5aff
platform/gateway: add trace skeleton spans
XnLemon Jul 9, 2026
eb4ec76
Merge pull request #37 from XnLemon/feat/platform-trace-audit-correla…
XnLemon Jul 9, 2026
36deb82
Merge pull request #38 from XnLemon/feat/platform-trace-skeleton-cont…
XnLemon Jul 9, 2026
6d42695
platform/gateway: enable runner session trace
XnLemon Jul 9, 2026
054d83b
platform/gateway: add message event trace contract
XnLemon Jul 9, 2026
3fa9c56
Merge pull request #40 from XnLemon/feat/platform-runner-session-trace
XnLemon Jul 9, 2026
94415bf
Merge pull request #41 from XnLemon/feat/platform-message-event-trace
XnLemon Jul 9, 2026
f05fa12
feat(platform): mark tool call trace spans
XnLemon Jul 9, 2026
5c52f3b
feat(platform): trace memory search spans
XnLemon Jul 9, 2026
19f09a0
feat(platform): trace memory write spans
XnLemon Jul 9, 2026
f919d4d
feat(platform): mark summary create trace spans
XnLemon Jul 9, 2026
dc28c70
fix(platform): address CI checks
XnLemon Jul 9, 2026
3daa913
fix(platform): address CodeRabbit review feedback
XnLemon Jul 9, 2026
6fb7ee8
fix(platform): bound in-memory sink records
XnLemon Jul 9, 2026
36896aa
storage: add tenant-aware backend router
XnLemon Jul 10, 2026
1b0147e
memory: add tenant-scoped memory knowledge abstraction
XnLemon Jul 10, 2026
177d609
artifact: add tenant-scoped object store
XnLemon Jul 10, 2026
fd77bf6
feat(platform): wire tenant storage into runtimes
xianingawa-wq Jul 10, 2026
f9a509b
feat(platform): enforce runtime tool governance
xianingawa-wq Jul 11, 2026
541dd09
fix lint failures in phase2 tool surface stack
xianingawa-wq Jul 11, 2026
a8099e4
fix remaining user tool tracking ineffassign
xianingawa-wq Jul 11, 2026
4cd8329
preserve runtime builder API compatibility
xianingawa-wq Jul 11, 2026
f1f4548
fix base lint issues in tool execution
xianingawa-wq Jul 11, 2026
dc0aa47
fix toolsurface lint ineffassign
xianingawa-wq Jul 11, 2026
133a545
feat(gateway): add budget preflight gate
xianingawa-wq Jul 11, 2026
3b80b0e
feat(gateway): record runner usage
xianingawa-wq Jul 11, 2026
76a1475
feat(gateway): apply model cost policy
xianingawa-wq Jul 11, 2026
d6cf2ab
feat(platform): add budget usage snapshots
xianingawa-wq Jul 11, 2026
86cabbb
reduce budget snapshot validation complexity
xianingawa-wq Jul 11, 2026
4755f64
feat(gateway): enforce text length channel limit
xianingawa-wq Jul 11, 2026
63d7f0f
feat(gateway): enforce file size channel limit
xianingawa-wq Jul 11, 2026
53ddb3a
feat(gateway): enforce channel rate limits
xianingawa-wq Jul 11, 2026
06cf699
feat(gateway): enforce mime type channel limit
xianingawa-wq Jul 11, 2026
b930618
feat(gateway): enforce user concurrency limits
xianingawa-wq Jul 11, 2026
38232d9
feat(gateway): enrich reject audit context
xianingawa-wq Jul 11, 2026
5afad20
feat(toolpolicy): enrich audit runtime context
xianingawa-wq Jul 11, 2026
5e64885
feat(platform): expand audit query dimensions
xianingawa-wq Jul 11, 2026
04f34db
reduce audit query filter complexity
xianingawa-wq Jul 11, 2026
7d40a54
feat(toolpolicy): audit budget remaining summary
xianingawa-wq Jul 11, 2026
762411d
feat(platform): query redacted audit details
xianingawa-wq Jul 11, 2026
ae9a3a8
feat(gateway): enrich budget audit runtime context
xianingawa-wq Jul 11, 2026
cbdb32d
feat(gateway): record redaction failed audit
xianingawa-wq Jul 11, 2026
4552555
feat(platform): add tool approval audit records
xianingawa-wq Jul 11, 2026
6849a5c
feat(approval): audit tool approval runtime decisions
xianingawa-wq Jul 11, 2026
c050a3a
feat(gateway): propagate approval audit context
xianingawa-wq Jul 11, 2026
1b9d19c
feat(worker): wire approval guardrail plugin
xianingawa-wq Jul 11, 2026
2d3787a
feat(approval): include tool metadata in approval context
xianingawa-wq Jul 11, 2026
7f543cd
feat(approval): return approval denied result
xianingawa-wq Jul 11, 2026
bfe5e93
feat(approval): record approval required metrics
xianingawa-wq Jul 11, 2026
f3a97fd
feat(audit): record audit write failure metrics
xianingawa-wq Jul 11, 2026
75fb239
fix runtime builder options calls in approval tests
xianingawa-wq Jul 11, 2026
f3b3ab1
feat(tool): record permission denied metrics
xianingawa-wq Jul 11, 2026
97a5fa9
feat(gateway): record budget denied metrics
xianingawa-wq Jul 11, 2026
fd732fa
feat(gateway): record rate limit metrics
xianingawa-wq Jul 11, 2026
359a9f4
feat(gateway): record idempotency hit metrics
xianingawa-wq Jul 11, 2026
a8819d3
fix(approval): redact reviewer decision text
xianingawa-wq Jul 11, 2026
fb85850
fix(platform): redact spaced secret assignments
xianingawa-wq Jul 11, 2026
75002b5
test(toolpolicy): cover deny audit records
xianingawa-wq Jul 11, 2026
83e8ed4
test(gateway): cover trace id tieouts
xianingawa-wq Jul 11, 2026
f9ff772
fix(langfuse): redact observation exports
xianingawa-wq Jul 11, 2026
ddb6fd8
fix(platform): redact config diff secret refs
xianingawa-wq Jul 11, 2026
50d976d
fix(artifactstore): redact storage errors
xianingawa-wq Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion agent/invocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,16 @@ func WithToolFilter(filter tool.FilterFunc) RunOption {
}
}

// WithMandatoryToolFilter sets a non-negotiable tool visibility boundary for
// this run. Unlike WithToolFilter, it applies to the complete invocation tool
// surface, including framework-managed tools, and is preserved across derived
// child invocations.
func WithMandatoryToolFilter(filter tool.FilterFunc) RunOption {
return func(opts *RunOptions) {
opts.MandatoryToolFilter = filter
}
}

// WithAdditionalTools appends tools that are visible only for this run.
//
// Additional tools are treated as user tools, so WithToolFilter can still
Expand Down Expand Up @@ -997,6 +1007,71 @@ func WithToolPermissionPolicyFunc(fn tool.PermissionPolicyFunc) RunOption {
return WithToolPermissionPolicy(fn)
}

// WithMandatoryToolPermissionPolicy sets a non-negotiable permission policy
// that derived child invocations must preserve. It is checked before the
// ordinary per-run ToolPermissionPolicy.
func WithMandatoryToolPermissionPolicy(policy tool.PermissionPolicy) RunOption {
return func(opts *RunOptions) {
opts.MandatoryToolPermissionPolicy = policy
}
}

// WithMandatoryToolPermissionPolicyFunc adapts fn into a mandatory per-run
// tool permission policy.
func WithMandatoryToolPermissionPolicyFunc(fn tool.PermissionPolicyFunc) RunOption {
return WithMandatoryToolPermissionPolicy(fn)
}

// CheckToolPermission applies the non-negotiable policy followed by the
// ordinary per-run policy. The first non-allow decision terminates the chain.
func (opts *RunOptions) CheckToolPermission(
ctx context.Context,
req *tool.PermissionRequest,
) (tool.PermissionDecision, error) {
if opts == nil {
return tool.AllowPermission(), nil
}
policies := [...]tool.PermissionPolicy{
opts.MandatoryToolPermissionPolicy,
opts.ToolPermissionPolicy,
}
for _, policy := range policies {
if isNilToolPermissionPolicy(policy) {
continue
}
decision, err := policy.CheckToolPermission(ctx, req)
if err != nil {
return tool.PermissionDecision{}, err
}
decision, err = tool.NormalizePermissionDecision(decision)
if err != nil {
return tool.PermissionDecision{}, err
}
if decision.Action != tool.PermissionActionAllow {
return decision, nil
}
}
return tool.AllowPermission(), nil
}

func isNilToolPermissionPolicy(policy tool.PermissionPolicy) bool {
if policy == nil {
return true
}
value := reflect.ValueOf(policy)
switch value.Kind() {
case reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Pointer,
reflect.Slice:
return value.IsNil()
default:
return false
}
}

func appendRunTools(opts *RunOptions, tools []tool.Tool) {
if opts == nil || len(tools) == 0 {
return
Expand Down Expand Up @@ -1336,7 +1411,12 @@ type RunOptions struct {
// StructuredOutputType is the Go type to unmarshal the final JSON into for this run.
StructuredOutputType reflect.Type

// ToolFilter is a custom function to filter tools for this run.
// MandatoryToolFilter is a non-negotiable visibility boundary applied to
// the complete invocation tool surface, including framework-managed tools.
// Derived child invocations must preserve it.
MandatoryToolFilter tool.FilterFunc

// ToolFilter is a custom function to filter user tools for this run.
// If set, only tools for which the filter returns true will be available to the model.
// If nil, all registered tools will be available (default behavior).
//
Expand Down Expand Up @@ -1389,6 +1469,10 @@ type RunOptions struct {
// externally and later provide tool results (RoleTool messages).
ToolExecutionFilter tool.FilterFunc

// MandatoryToolPermissionPolicy is checked before ToolPermissionPolicy and
// is preserved across derived child invocations.
MandatoryToolPermissionPolicy tool.PermissionPolicy

// ToolPermissionPolicy checks whether a tool call may run after the model
// has requested it, after argument repair, and after before-tool callbacks
// have finalized arguments.
Expand Down
18 changes: 18 additions & 0 deletions agent/invocation_surface.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ type InvocationToolSurfaceProvider interface {
) ([]tool.Tool, map[string]bool)
}

// InvocationToolActivationProvider is an optional interface implemented by
// agents that apply invocation-scoped activation after run-option tools have
// been appended to the base surface.
//
// The provider must return the activated tool surface together with updated
// user and external tool classifications. Callers provide private slice/map
// copies, so implementations may mutate the inputs without affecting the
// invocation's configured surface.
type InvocationToolActivationProvider interface {
ApplyInvocationToolActivation(
ctx context.Context,
inv *Invocation,
tools []tool.Tool,
userToolNames map[string]bool,
externalToolNames map[string]bool,
) ([]tool.Tool, map[string]bool, map[string]bool)
}

// InvocationSkillRepositoryProvider is an optional interface implemented by
// agents that can expose the effective, invocation-scoped skill repository.
//
Expand Down
68 changes: 68 additions & 0 deletions agent/invocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,74 @@ func TestWithToolPermissionPolicy(t *testing.T) {
require.Equal(t, tool.PermissionActionDeny, decision.Action)
}

func TestRunOptionsCheckToolPermissionAppliesMandatoryPolicyFirst(
t *testing.T,
) {
var calls []string
opts := NewRunOptions(
WithMandatoryToolPermissionPolicyFunc(
func(
context.Context,
*tool.PermissionRequest,
) (tool.PermissionDecision, error) {
calls = append(calls, "mandatory")
return tool.DenyPermission("tenant policy"), nil
},
),
WithToolPermissionPolicyFunc(
func(
context.Context,
*tool.PermissionRequest,
) (tool.PermissionDecision, error) {
calls = append(calls, "ordinary")
return tool.AllowPermission(), nil
},
),
)

decision, err := opts.CheckToolPermission(
context.Background(),
&tool.PermissionRequest{ToolName: "shell"},
)
require.NoError(t, err)
require.Equal(t, tool.PermissionActionDeny, decision.Action)
require.Equal(t, []string{"mandatory"}, calls)
}

func TestRunOptionsCheckToolPermissionAllowsOrdinaryPolicyToTighten(
t *testing.T,
) {
var calls []string
opts := NewRunOptions(
WithMandatoryToolPermissionPolicyFunc(
func(
context.Context,
*tool.PermissionRequest,
) (tool.PermissionDecision, error) {
calls = append(calls, "mandatory")
return tool.AllowPermission(), nil
},
),
WithToolPermissionPolicyFunc(
func(
context.Context,
*tool.PermissionRequest,
) (tool.PermissionDecision, error) {
calls = append(calls, "ordinary")
return tool.DenyPermission("child policy"), nil
},
),
)

decision, err := opts.CheckToolPermission(
context.Background(),
&tool.PermissionRequest{ToolName: "shell"},
)
require.NoError(t, err)
require.Equal(t, tool.PermissionActionDeny, decision.Action)
require.Equal(t, []string{"mandatory", "ordinary"}, calls)
}

func TestWithInstruction(t *testing.T) {
opts := &RunOptions{}
WithInstruction(testRunInstruction)(opts)
Expand Down
8 changes: 6 additions & 2 deletions agent/llmagent/llm_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -1645,8 +1645,12 @@ func (a *LLMAgent) resolveBaseModel(inv *agent.Invocation) baseModelResolution {
// setupInvocation sets up the invocation.
func (a *LLMAgent) setupInvocation(invocation *agent.Invocation) {
// Set agent identity before resolving node-scoped surfaces.
invocation.Agent = a
invocation.AgentName = a.name
if invocation.Agent != a {
invocation.Agent = a
}
if invocation.AgentName != a.name {
invocation.AgentName = a.name
}

// Set the base model once for compatibility with existing callbacks.
resolution := a.resolveBaseModel(invocation)
Expand Down
67 changes: 67 additions & 0 deletions agent/llmagent/surface_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,73 @@ func TestLLMAgent_Run_AgentToolFilterStillAppliesWithInvocationToolSurface(
require.Contains(t, m.got.Tools, testTransferToolName)
}

func TestLLMAgent_Run_MandatoryToolFilterAppliesToFrameworkTools(
t *testing.T,
) {
m := &captureModel{}
agt := New(
"test-agent",
WithModel(m),
WithTools([]tool.Tool{
dummyTool{decl: &tool.Declaration{Name: "allowed_user_tool"}},
}),
WithSubAgents([]agent.Agent{&mockAgent{name: "child"}}),
WithAwaitUserReplyTool(true),
)
inv := agent.NewInvocation(
agent.WithInvocationMessage(model.NewUserMessage("hello")),
agent.WithInvocationRunOptions(agent.NewRunOptions(
agent.WithMandatoryToolFilter(
func(_ context.Context, tl tool.Tool) bool {
switch tl.Declaration().Name {
case testTransferToolName, testAwaitReplyToolName:
return false
default:
return true
}
},
),
)),
)

ch, err := agt.Run(context.Background(), inv)
require.NoError(t, err)
for range ch {
}

require.NotNil(t, m.got)
require.Contains(t, m.got.Tools, "allowed_user_tool")
require.NotContains(t, m.got.Tools, testTransferToolName)
require.NotContains(t, m.got.Tools, testAwaitReplyToolName)
}

func TestLLMAgent_SetupInvocationDoesNotRewritePreinitializedIdentity(
t *testing.T,
) {
agt := New("test-agent", WithModel(&captureModel{}))
inv := agent.NewInvocation(agent.WithInvocationAgent(agt))
const iterations = 10000
start := make(chan struct{})
done := make(chan struct{})
go func() {
<-start
for i := 0; i < iterations; i++ {
_ = inv.Agent
_ = inv.AgentName
}
close(done)
}()

close(start)
for i := 0; i < iterations; i++ {
agt.setupInvocation(inv)
}
<-done

require.Same(t, agt, inv.Agent)
require.Equal(t, "test-agent", inv.AgentName)
}

func TestLLMAgent_Run_SurfacePatch_OverridesToolDeclarations(t *testing.T) {
m := &captureModel{}
agt := New(
Expand Down
17 changes: 17 additions & 0 deletions agent/llmagent/tool_activation.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,23 @@ func (a *LLMAgent) applyToolActivation(
)
}

// ApplyInvocationToolActivation implements agent.InvocationToolActivationProvider.
func (a *LLMAgent) ApplyInvocationToolActivation(
ctx context.Context,
inv *agent.Invocation,
tools []tool.Tool,
userToolNames map[string]bool,
externalToolNames map[string]bool,
) ([]tool.Tool, map[string]bool, map[string]bool) {
return a.applyToolActivation(
ctx,
inv,
tools,
userToolNames,
externalToolNames,
)
}

func (a *LLMAgent) toolActivationInputs() (
[]tool.ToolSet,
[]toolActivationRule,
Expand Down
Loading