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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
matrix:
module:
- actor
- agent
- bootloader
- embeddings
- facade
Expand Down Expand Up @@ -55,6 +56,7 @@ jobs:
matrix:
module:
- actor
- agent
- bootloader
- embeddings
- facade
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ PACKAGES_FLAT = migration embeddings facade views security docs terminal test
PACKAGES_SRC = actor agent bootloader llm relay usage

# Modules that have test directories with wippy.lock
TEST_MODULES = actor bootloader embeddings facade llm migration relay usage views
TEST_MODULES = actor agent bootloader embeddings facade llm migration relay usage views

.PHONY: help check-manifests run-tests run-lint install publish-all \
publish-flat-% publish-src-%
Expand Down
8 changes: 5 additions & 3 deletions src/agent/src/agent.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type AgentRunner = {
description: string,
model: string,
max_tokens: number,
temperature: number,
temperature: number?,
thinking_effort: number,
tools: {[string]: UnifiedTool},
memory: {string},
Expand All @@ -67,7 +67,6 @@ local AGENT_CONFIG = {
defaults = {
model = "",
max_tokens = 512,
temperature = 0,
thinking_effort = 0
},
memory = {
Expand Down Expand Up @@ -415,7 +414,10 @@ function agent.new(compiled_spec: any): (any, string?)
description = compiled_spec.description,
model = compiled_spec.model or AGENT_CONFIG.defaults.model,
max_tokens = compiled_spec.max_tokens or AGENT_CONFIG.defaults.max_tokens,
temperature = compiled_spec.temperature or AGENT_CONFIG.defaults.temperature,
-- Left nil when the spec omits it: Claude 4.7+ and Opus/Sonnet 5 reject
-- temperature, top_p and top_k outright, and a value the author never
-- asked for is not ours to invent.
temperature = compiled_spec.temperature,
thinking_effort = compiled_spec.thinking_effort or AGENT_CONFIG.defaults.thinking_effort,
tools = compiled_spec.tools or {},
memory = compiled_spec.memory or {},
Expand Down
76 changes: 75 additions & 1 deletion src/agent/src/agent_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -587,13 +587,87 @@ local function define_tests()
test.not_nil(test_agent)
test.eq(test_agent.model, "")
test.eq(test_agent.max_tokens, 512)
test.eq(test_agent.temperature, 0)
test.is_nil(test_agent.temperature)
test.eq(test_agent.thinking_effort, 0)
test.is_nil(next(test_agent.tools)) -- Empty tools table
test.not_nil(test_agent.tool_wrappers)
test.eq(#test_agent.tool_wrappers, 0)
end)

it("should carry an explicit temperature through to the model options", function()
local spec = {
id = "explicit-temp-agent",
name = "Explicit Temp Agent",
description = "Sets its own temperature",
prompt = "You are explicit.",
temperature = 0.7
}

local test_agent = agent.new(spec)
test.eq(test_agent.temperature, 0.7)

local seen
agent._llm = {
generate = function(messages, options)
seen = options
return { result = "done", tokens = {} }
end
}
test_agent:step(mock_prompt.new())
test.eq(seen.temperature, 0.7)
end)

it("should carry an explicit zero temperature rather than treating it as unset", function()
local spec = {
id = "zero-temp-agent",
name = "Zero Temp Agent",
description = "Asks for greedy decoding on purpose",
prompt = "You are deterministic.",
temperature = 0
}

local test_agent = agent.new(spec)
test.eq(test_agent.temperature, 0)

local seen
agent._llm = {
generate = function(messages, options)
seen = options
return { result = "done", tokens = {} }
end
}
test_agent:step(mock_prompt.new())
test.eq(seen.temperature, 0)
end)

it("should omit temperature from model options when the spec does not set one", function()
-- Claude 4.7+ and Opus/Sonnet 5 reject temperature, top_p and
-- top_k outright, so a fabricated default makes every agent that
-- never asked for one unusable on those models.
local spec = {
id = "unset-temp-agent",
name = "Unset Temp Agent",
description = "Leaves sampling to the model",
prompt = "You are unset."
}

local test_agent = agent.new(spec)

local seen
agent._llm = {
generate = function(messages, options)
seen = options
return { result = "done", tokens = {} }
end
}
test_agent:step(mock_prompt.new())

test.not_nil(seen)
test.is_nil(seen.temperature)
test.is_nil(seen.top_p)
test.is_nil(seen.top_k)
end)

it("should preserve tool wrapper specs from compiled specification", function()
local test_agent = agent.new(compiled_spec_with_tool_wrappers)

Expand Down
46 changes: 34 additions & 12 deletions src/agent/src/compiler/compiler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,29 @@ local function normalize_binding_phases(raw_binding: any): {string}
return phases :: {string}
end

local function normalize_raw_binding(kind_hint: any, raw_binding: any): any?
-- Binding ids reach the compiler from registry data, so they carry whatever
-- type the author wrote. BindingSpec.id is a string, and tostring keeps the
-- authored value legible rather than discarding it.
local function optional_id(value: any): string?
if value == nil then
return nil
end
return tostring(value)
end

type NormalizedBinding = {
id: string?,
kind: string,
contract: string,
binding: string,
phases: {string},
context: table,
options: table,
priority: number,
strict: boolean,
}

local function normalize_raw_binding(kind_hint: any, raw_binding: any): NormalizedBinding?
if type(raw_binding) ~= "table" then
return nil
end
Expand All @@ -255,10 +277,10 @@ local function normalize_raw_binding(kind_hint: any, raw_binding: any): any?
end

return {
id = raw_binding.id or raw_binding.name,
kind = kind,
contract = contract_id,
binding = binding_id,
id = optional_id(raw_binding.id or raw_binding.name),
kind = kind :: string,
contract = contract_id :: string,
binding = binding_id :: string,
phases = normalize_binding_phases(raw_binding),
context = type(raw_binding.context) == "table" and raw_binding.context or {},
options = type(raw_binding.options) == "table" and raw_binding.options or {},
Expand All @@ -267,7 +289,7 @@ local function normalize_raw_binding(kind_hint: any, raw_binding: any): any?
}
end

local function collect_raw_bindings(out: {any}, kind_hint: any, raw_bindings: any)
local function collect_raw_bindings(out: {NormalizedBinding}, kind_hint: any, raw_bindings: any)
if type(raw_bindings) ~= "table" then
return
end
Expand All @@ -293,8 +315,8 @@ local function collect_raw_bindings(out: {any}, kind_hint: any, raw_bindings: an
end
end

local function raw_bindings_from_trait(trait_def: any): {any}
local out = {}
local function raw_bindings_from_trait(trait_def: any): {NormalizedBinding}
local out: {NormalizedBinding} = {}
if type(trait_def) ~= "table" then
return out
end
Expand Down Expand Up @@ -333,15 +355,15 @@ local function list_from_map_or_array(value: any): {string}
end

local function set_from_list(values: {string}): {[string]: boolean}
local out = {}
local out: {[string]: boolean} = {}
for _, value in ipairs(values) do
out[value] = true
end
return out
end

local function behaviors_from_trait(trait_def: any): {any}
local out = {}
local out: {any} = {}
if type(trait_def) ~= "table" or type(trait_def.behaviors) ~= "table" then
return out
end
Expand Down Expand Up @@ -668,7 +690,7 @@ local function append_behavior_lifecycle(
local context = deep_copy(base_context)
attach_options_to_context(context, options)
append_binding_spec(bindings, "lifecycle", {
id = behavior.id,
id = optional_id(behavior.id),
kind = "lifecycle",
trait_id = trait_id,
contract = "wippy.agent:lifecycle",
Expand Down Expand Up @@ -725,7 +747,7 @@ local function append_behavior_checkpoint(
local options = merge_agent_options(base_options, behavior.checkpoint)
attach_options_to_context(base_context, options)
append_binding_spec(bindings, "checkpoint", {
id = behavior.id,
id = optional_id(behavior.id),
kind = "checkpoint",
trait_id = trait_id,
contract = "wippy.agent:checkpoint",
Expand Down
7 changes: 7 additions & 0 deletions src/agent/test/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
test:
wippy run test \
-o wippy.llm:process_host:default=wippy.terminal:host \
-o wippy.llm:env_storage:default=app:env_storage

lint:
wippy lint --level error
8 changes: 8 additions & 0 deletions src/agent/test/_index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: "1.0"
namespace: app

entries:
- name: env_storage
kind: env.storage.os
meta:
type: envstorage
16 changes: 16 additions & 0 deletions src/agent/test/wippy.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
directories:
modules: .wippy
src: ..
modules:
- name: wippy/llm
version: 0.4.7
hash: 2e55afcf55c441a8e79e3737691e5721d06138b9a3a46c7fb87199db6a5dd426
- name: wippy/terminal
version: 0.4.2
hash: 34b99aac2eba9c5016fd02c783117fb2b0381ce14696c7b199265458b99a3271
- name: wippy/test
version: 0.4.10
hash: 5bc5ffe1b71889c0610133d8843451e5bf80e93ff43712b9017c2c0955fb023d
replacements:
- from: wippy/llm
to: ../../llm/src
7 changes: 7 additions & 0 deletions src/agent/test/wippy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
organization: wippy
module: agent-test
type: application
description: Test stub for agent module
exclude_meta:
type:
- e2e
Loading