Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,16 @@ jobs:
go-version-file: go.mod
cache-dependency-path: go.sum

- name: Check Go formatting
run: |
unformatted="$(gofmt -l $(find . -name '*.go' -not -path './.git/*'))"
if [ -n "$unformatted" ]; then
echo "The following Go files are not gofmt-formatted:"
echo "$unformatted"
echo
echo "Run: gofmt -w \$(find . -name '*.go' -not -path './.git/*')"
exit 1
fi

- name: Run unit tests
run: go test ./...
1 change: 0 additions & 1 deletion cmd/atryum/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"runtime"
"strconv"
"strings"

)

const validMindSetupBaseURL = "https://app.dev.vm.validmind.ai"
Expand Down
14 changes: 7 additions & 7 deletions cmd/atryum/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,14 +527,14 @@ type evaluatorAdapter struct {

func (e *evaluatorAdapter) EvaluateToolCall(ctx context.Context, req invocation.EvaluateRequest) (invocation.EvaluateResponse, error) {
resp, err := e.client.EvaluateToolCall(ctx, backendclient.EvaluateRequest{
ModelConfigCUID: req.ModelConfigCUID,
OrgCUID: req.OrgCUID,
AgentVMCUID: req.AgentVMCUID,
ModelConfigCUID: req.ModelConfigCUID,
OrgCUID: req.OrgCUID,
AgentVMCUID: req.AgentVMCUID,
CharterFieldKey: req.CharterFieldKey,
ServerName: req.ServerName,
ToolName: req.ToolName,
ToolArgs: req.ToolArgs,
Context: req.Context,
ServerName: req.ServerName,
ToolName: req.ToolName,
ToolArgs: req.ToolArgs,
Context: req.Context,
})
if err != nil {
return invocation.EvaluateResponse{}, err
Expand Down
95 changes: 47 additions & 48 deletions internal/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,14 @@ type RuleListResponse struct {
// ─── Agent admin types ────────────────────────────────────────────────────────

type AdminAgent struct {
CUID string `json:"cuid"`
OrgName string `json:"org_name"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
AgentIDs []string `json:"agent_ids"`
SyncedAt time.Time `json:"synced_at"`
Enabled bool `json:"enabled"`
Charter string `json:"charter,omitempty"`
CUID string `json:"cuid"`
OrgName string `json:"org_name"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
AgentIDs []string `json:"agent_ids"`
SyncedAt time.Time `json:"synced_at"`
Enabled bool `json:"enabled"`
Charter string `json:"charter,omitempty"`
// Synced is true when this agent originated from a ValidMind sync
// (vm_organization_cuid is non-empty). Synced agents cannot be deleted
// manually — they are removed by re-syncing with a different org/record-type.
Expand Down Expand Up @@ -376,15 +376,15 @@ type AgentListResponse struct {
func toAdminAgent(a store.AgentRecord) AdminAgent {
ids := parseAgentIDs(a.AgentIDs)
return AdminAgent{
CUID: a.ID,
OrgName: a.VMOrganizationName,
Name: a.VMName,
Description: a.VMDescription,
AgentIDs: ids,
SyncedAt: a.SyncedAt,
Enabled: a.Enabled,
Charter: a.Charter,
Synced: a.VMOrganizationCUID != "",
CUID: a.ID,
OrgName: a.VMOrganizationName,
Name: a.VMName,
Description: a.VMDescription,
AgentIDs: ids,
SyncedAt: a.SyncedAt,
Enabled: a.Enabled,
Charter: a.Charter,
Synced: a.VMOrganizationCUID != "",
}
}

Expand Down Expand Up @@ -1179,7 +1179,6 @@ func apiMatchPatterns(patterns []string, value string) bool {
return false
}


// annotatedTool is the on-the-wire shape used for tools/list when atryum is
// able to compute a per-tool policy disposition. It mirrors mcp.Tool but adds
// an `annotations` block plus a description prefix so models that ignore
Expand Down Expand Up @@ -1463,22 +1462,22 @@ func (h *Handler) adminInvocationDetail(w http.ResponseWriter, r *http.Request)
if req.CreateRule.Enabled != nil {
enabled = *req.CreateRule.Enabled
}
newRule := store.Rule{
ID: "rule_" + newUUID(),
Action: req.CreateRule.Action,
ServerPatterns: normalizePatternSlice(req.CreateRule.ServerPatterns),
ToolPatterns: normalizePatternSlice(req.CreateRule.ToolPatterns),
ModelConfigCUID: req.CreateRule.ModelConfigCUID,
AgentCUIDs: normalizePatternSlice(req.CreateRule.AgentCUIDs),
Description: req.CreateRule.Description,
Enabled: enabled,
}
if err := h.rulesRepo.InsertBefore(r.Context(), anchorID, newRule); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
newRule := store.Rule{
ID: "rule_" + newUUID(),
Action: req.CreateRule.Action,
ServerPatterns: normalizePatternSlice(req.CreateRule.ServerPatterns),
ToolPatterns: normalizePatternSlice(req.CreateRule.ToolPatterns),
ModelConfigCUID: req.CreateRule.ModelConfigCUID,
AgentCUIDs: normalizePatternSlice(req.CreateRule.AgentCUIDs),
Description: req.CreateRule.Description,
Enabled: enabled,
}
if err := h.rulesRepo.InsertBefore(r.Context(), anchorID, newRule); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
}
}
if err := h.svc.Approve(r.Context(), id); err != nil {
if err := h.svc.Approve(r.Context(), id); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
Expand Down Expand Up @@ -1522,22 +1521,22 @@ func (h *Handler) adminInvocationDetail(w http.ResponseWriter, r *http.Request)
if req.CreateRule.Enabled != nil {
enabled = *req.CreateRule.Enabled
}
newRule := store.Rule{
ID: "rule_" + newUUID(),
Action: req.CreateRule.Action,
ServerPatterns: normalizePatternSlice(req.CreateRule.ServerPatterns),
ToolPatterns: normalizePatternSlice(req.CreateRule.ToolPatterns),
ModelConfigCUID: req.CreateRule.ModelConfigCUID,
AgentCUIDs: normalizePatternSlice(req.CreateRule.AgentCUIDs),
Description: req.CreateRule.Description,
Enabled: enabled,
}
if err := h.rulesRepo.InsertBefore(r.Context(), anchorID, newRule); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
newRule := store.Rule{
ID: "rule_" + newUUID(),
Action: req.CreateRule.Action,
ServerPatterns: normalizePatternSlice(req.CreateRule.ServerPatterns),
ToolPatterns: normalizePatternSlice(req.CreateRule.ToolPatterns),
ModelConfigCUID: req.CreateRule.ModelConfigCUID,
AgentCUIDs: normalizePatternSlice(req.CreateRule.AgentCUIDs),
Description: req.CreateRule.Description,
Enabled: enabled,
}
if err := h.rulesRepo.InsertBefore(r.Context(), anchorID, newRule); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
}
}
if err := h.svc.Deny(r.Context(), id, req.Message); err != nil {
if err := h.svc.Deny(r.Context(), id, req.Message); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
Expand Down
14 changes: 7 additions & 7 deletions internal/backend/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,14 @@ func (c *Client) FetchCustomFields(ctx context.Context, orgCUID, primaryRecordTy
// EvaluateRequest is sent to the VM backend to ask the LLM whether a tool
// call should be approved or denied.
type EvaluateRequest struct {
ModelConfigCUID string `json:"model_config_cuid"`
OrgCUID string `json:"org_cuid,omitempty"`
AgentVMCUID string `json:"agent_vm_cuid,omitempty"`
ModelConfigCUID string `json:"model_config_cuid"`
OrgCUID string `json:"org_cuid,omitempty"`
AgentVMCUID string `json:"agent_vm_cuid,omitempty"`
CharterFieldKey string `json:"charter_field_key,omitempty"`
ServerName string `json:"server_name"`
ToolName string `json:"tool_name"`
ToolArgs map[string]any `json:"tool_args,omitempty"`
Context string `json:"context,omitempty"`
ServerName string `json:"server_name"`
ToolName string `json:"tool_name"`
ToolArgs map[string]any `json:"tool_args,omitempty"`
Context string `json:"context,omitempty"`
}

// EvaluateResponse is the result returned by the VM backend after LLM evaluation.
Expand Down
1 change: 0 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"atryum/internal/auth"
)


type Config struct {
Server ServerConfig `toml:"server"`
Backend BackendConfig `toml:"backend"`
Expand Down
1 change: 0 additions & 1 deletion internal/invocation/agent_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ func TestInvokeWithoutAgentIDPersistsClientInfoFromRequest(t *testing.T) {
}
}


// External (non-MCP) callers like the amp-plugin example don't run behind
// the auth middleware today, so there is no identity in context. They can
// instead self-declare via the new `agent_id` field on ExternalSubmitRequest,
Expand Down
1 change: 0 additions & 1 deletion internal/invocation/policy/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,3 @@ type Provider interface {
DisplayName() string
Evaluate(ctx context.Context, call CallContext) (Decision, error)
}

1 change: 0 additions & 1 deletion internal/invocation/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,3 @@ func matchPatterns(patterns []string, value string) bool {
}
return false
}

24 changes: 12 additions & 12 deletions internal/invocation/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,19 @@ type SyncSettingsProvider interface {
// EvaluateRequest mirrors backend.EvaluateRequest so the service package does
// not import the backend package directly.
type EvaluateRequest struct {
ModelConfigCUID string `json:"model_config_cuid"`
OrgCUID string `json:"org_cuid,omitempty"`
AgentVMCUID string `json:"agent_vm_cuid,omitempty"`
CharterFieldKey string `json:"charter_field_key,omitempty"`
ModelConfigCUID string `json:"model_config_cuid"`
OrgCUID string `json:"org_cuid,omitempty"`
AgentVMCUID string `json:"agent_vm_cuid,omitempty"`
CharterFieldKey string `json:"charter_field_key,omitempty"`
// AtryumLLMConfigID references a local LLM config for native evaluation.
// When set, the local evaluator is used instead of the VM backend.
AtryumLLMConfigID string `json:"atryum_llm_config_id,omitempty"`
// Charter is the agent's governing text sent to the local LLM judge.
Charter string `json:"charter,omitempty"`
ServerName string `json:"server_name"`
ToolName string `json:"tool_name"`
ToolArgs map[string]any `json:"tool_args,omitempty"`
Context string `json:"context,omitempty"`
Charter string `json:"charter,omitempty"`
ServerName string `json:"server_name"`
ToolName string `json:"tool_name"`
ToolArgs map[string]any `json:"tool_args,omitempty"`
Context string `json:"context,omitempty"`
}

// EvaluateResponse mirrors backend.EvaluateResponse.
Expand Down Expand Up @@ -491,9 +491,9 @@ func (s *Service) runAIEvaluation(ctx context.Context, rule *ApprovalRule, serve
OrgCUID: orgCUID,
AgentVMCUID: agentVMCUID,
CharterFieldKey: charterFieldKey,
ServerName: serverName,
ToolName: toolName,
ToolArgs: toolArgs,
ServerName: serverName,
ToolName: toolName,
ToolArgs: toolArgs,
})
if err != nil {
slog.Error("ai_evaluation: LLM evaluation failed; falling back to human_approval",
Expand Down
14 changes: 7 additions & 7 deletions internal/store/agent_sync_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import (
// synchronisation from the ValidMind backend. All fields are empty strings
// when the user has not yet configured sync (i.e. no row exists yet).
type AgentSyncSettings struct {
OrgCUID string
AgentRecordTypeSlug string
CharterFieldKey string
SummaryModelConfigCUID string
SummaryAtryumLLMConfigID string
DefaultAgentVMCUID string
UpdatedAt time.Time
OrgCUID string
AgentRecordTypeSlug string
CharterFieldKey string
SummaryModelConfigCUID string
SummaryAtryumLLMConfigID string
DefaultAgentVMCUID string
UpdatedAt time.Time
}

var agentSyncSettingsColumns = []string{
Expand Down
2 changes: 1 addition & 1 deletion internal/store/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func (r *AgentsRepo) DeleteAll(ctx context.Context) error {
}

// DeleteSynced removes only agent records that originated from a ValidMind
// sync (vm_organization_cuid != ''). Manually-created agents (empty
// sync (vm_organization_cuid != ). Manually-created agents (empty
// vm_organization_cuid) are preserved.
func (r *AgentsRepo) DeleteSynced(ctx context.Context) error {
query, args, err := r.sb.Delete("agents").
Expand Down
2 changes: 1 addition & 1 deletion internal/store/migrations/011_agent_sync_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ func migration011() Definition {
Version: 11,
Name: "011_agent_sync_settings",
Steps: []Step{
RawDialect("create agent_sync_settings table", `
RawDialect("create agent_sync_settings table", `
CREATE TABLE IF NOT EXISTS agent_sync_settings (
id INTEGER PRIMARY KEY DEFAULT 1,
org_cuid TEXT NOT NULL DEFAULT '',
Expand Down
24 changes: 12 additions & 12 deletions internal/store/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ import (
// AgentCUIDs is a JSON-encoded list of Atryum agent CUIDs the rule applies to;
// an empty slice means "match all agents".
type Rule struct {
ID string
Action string
ServerPatterns []string
ToolPatterns []string
ModelConfigCUID string
AtryumLLMConfigID string
AgentCUIDs []string
Description string
Enabled bool
Order int
CreatedAt time.Time
UpdatedAt time.Time
ID string
Action string
ServerPatterns []string
ToolPatterns []string
ModelConfigCUID string
AtryumLLMConfigID string
AgentCUIDs []string
Description string
Enabled bool
Order int
CreatedAt time.Time
UpdatedAt time.Time
}

// RulesRepo provides CRUD and ordering operations for approval_rules.
Expand Down
2 changes: 1 addition & 1 deletion internal/store/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
// TestRulesRepo_CreateVMPathAIEvaluationRule is a regression test for the
// VM-backend ai_evaluation rule path. Such rules set only ModelConfigCUID and
// leave AtryumLLMConfigID empty. Migration 018 added atryum_llm_config_id as
// TEXT NOT NULL DEFAULT '', so writing SQL NULL (the old emptyToNil behavior)
// TEXT NOT NULL DEFAULT , so writing SQL NULL (the old emptyToNil behavior)
// for an empty value violated the NOT NULL constraint and made VM-path rules
// impossible to create. The empty string must be persisted directly instead.
func TestRulesRepo_CreateVMPathAIEvaluationRule(t *testing.T) {
Expand Down
Loading