Skip to content
This repository was archived by the owner on Jul 19, 2026. It is now read-only.
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
4 changes: 4 additions & 0 deletions crates/soft-agent/examples/lex/grid_budget.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
spec grid_budget {
forall current_kw :: Float, delta_kw :: Float, grid_kw :: Float, pv_kw :: Float:
under_budget(current_kw, delta_kw, grid_kw, pv_kw)
}
32 changes: 32 additions & 0 deletions crates/soft-agent/examples/lex/multi_depot_depot.lex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
fn config() -> AgentConfig {
agent_new("depot")
|> agent_peers(["vehicle"])
|> agent_effects(["a2a"])
|> agent_specs(["specs/grid_budget.spec"])
|> agent_handles([
{ topic: "RequestSession", fn_name: "on_request_session" },
])
}

fn within(current :: Float, delta :: Float, grid :: Float, pv :: Float) -> Bool {
current + delta <= grid + pv
}

fn on_request_session(
state :: { current_kw :: Float, budget_kw :: Float, pv_kw :: Float, requested_kw :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
if within(state.current_kw, state.requested_kw, state.budget_kw, state.pv_kw) {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: msg.from, a2a_topic: "GrantSession",
payload_json: "{\"charger_id\":\"c-1\"}", prompt: "",
}]
} else {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: msg.from, a2a_topic: "DenySession",
payload_json: "{\"reason\":\"grid_budget\"}", prompt: "",
}]
}
}
19 changes: 19 additions & 0 deletions crates/soft-agent/examples/lex/multi_depot_host.lex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
fn projected_load(current :: Float, delta :: Float) -> Float {
current + delta
}

fn budget_total(grid :: Float, pv :: Float) -> Float {
grid + pv
}

fn under_budget(current :: Float, delta :: Float, grid :: Float, pv :: Float) -> Bool {
projected_load(current, delta) <= budget_total(grid, pv)
}

fn soc_after(current :: Float, used :: Float) -> Float {
current - used
}

fn above_reserve(current :: Float, used :: Float, reserve :: Float) -> Bool {
soc_after(current, used) >= reserve
}
17 changes: 17 additions & 0 deletions crates/soft-agent/examples/lex/multi_depot_tms.lex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
fn config() -> AgentConfig {
agent_new("tms")
|> agent_peers(["vehicle"])
|> agent_effects(["a2a"])
|> agent_handles([
{ topic: "Acknowledge", fn_name: "terminate" },
{ topic: "Complete", fn_name: "terminate" },
{ topic: "Failed", fn_name: "terminate" },
])
}

fn terminate(
state :: { running :: Bool },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
[]
}
56 changes: 56 additions & 0 deletions crates/soft-agent/examples/lex/multi_depot_vehicle.lex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
fn config() -> AgentConfig {
agent_new("vehicle")
|> agent_peers(["depot", "tms"])
|> agent_effects(["a2a"])
|> agent_specs(["specs/soc_reserve.spec"])
|> agent_handles([
{ topic: "Dispatch", fn_name: "on_dispatch" },
{ topic: "GrantSession", fn_name: "on_grant" },
{ topic: "DenySession", fn_name: "on_deny" },
])
}

fn enough_soc(soc :: Float, energy :: Float, reserve :: Float) -> Bool {
soc - energy >= reserve
}

fn on_dispatch(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
if enough_soc(state.soc, state.energy_needed, state.reserve) {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: msg.from, a2a_topic: "Acknowledge",
payload_json: "{\"delivery_id\":\"d-1\"}", prompt: "",
}]
} else {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "depot", a2a_topic: "RequestSession",
payload_json: "{\"vehicle_id\":\"v-1\",\"power_kw\":50}", prompt: "",
}]
}
}

fn on_grant(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "tms", a2a_topic: "Complete",
payload_json: "{\"delivery_id\":\"d-1\"}", prompt: "",
}]
}

fn on_deny(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "tms", a2a_topic: "Failed",
payload_json: "{\"reason\":\"depot_denied\"}", prompt: "",
}]
}
58 changes: 58 additions & 0 deletions crates/soft-agent/examples/lex/single_vehicle.lex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
fn config() -> AgentConfig {
agent_new("vehicle")
|> agent_peers(["depot", "tms"])
|> agent_mcp_servers(["telemetry", "local_planner"])
|> agent_effects(["llm_local", "mcp", "a2a"])
|> agent_specs(["specs/soc_reserve.spec"])
|> agent_system_prompt("You are an autonomous truck.")
|> agent_handles([
{ topic: "Dispatch", fn_name: "on_dispatch" },
{ topic: "GrantSession", fn_name: "on_grant" },
{ topic: "DenySession", fn_name: "on_deny" },
])
}

fn enough_soc(soc :: Float, energy :: Float, reserve :: Float) -> Bool {
soc - energy >= reserve
}

fn on_dispatch(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
if enough_soc(state.soc, state.energy_needed, state.reserve) {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: msg.from, a2a_topic: "Acknowledge",
payload_json: "{\"delivery_id\":\"d-1\"}", prompt: "",
}]
} else {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "depot", a2a_topic: "RequestSession",
payload_json: "{\"vehicle_id\":\"v-1\",\"power_kw\":50}", prompt: "",
}]
}
}

fn on_grant(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "tms", a2a_topic: "Complete",
payload_json: "{\"delivery_id\":\"d-1\"}", prompt: "",
}]
}

fn on_deny(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "tms", a2a_topic: "Failed",
payload_json: "{\"reason\":\"depot_denied\"}", prompt: "",
}]
}
4 changes: 4 additions & 0 deletions crates/soft-agent/examples/lex/soc_reserve.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
spec soc_reserve {
forall current_soc :: Float, energy_used :: Float, reserve :: Float:
above_reserve(current_soc, energy_used, reserve)
}
150 changes: 6 additions & 144 deletions crates/soft-agent/examples/multi_depot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,155 +16,17 @@ use serde_json::{json, Value};
use soft_agent::{A2aMessage, Action, Gate, InProcessRouter, Mailbox, MailboxSender, Runner};
use tempfile::tempdir;

const HOST: &str = r#"
fn projected_load(current :: Float, delta :: Float) -> Float {
current + delta
}

fn budget_total(grid :: Float, pv :: Float) -> Float {
grid + pv
}

fn under_budget(current :: Float, delta :: Float, grid :: Float, pv :: Float) -> Bool {
projected_load(current, delta) <= budget_total(grid, pv)
}

fn soc_after(current :: Float, used :: Float) -> Float {
current - used
}

fn above_reserve(current :: Float, used :: Float, reserve :: Float) -> Bool {
soc_after(current, used) >= reserve
}
"#;

const GRID_BUDGET_SPEC: &str = r#"
spec grid_budget {
forall current_kw :: Float, delta_kw :: Float, grid_kw :: Float, pv_kw :: Float:
under_budget(current_kw, delta_kw, grid_kw, pv_kw)
}
"#;

const SOC_RESERVE_SPEC: &str = r#"
spec soc_reserve {
forall current_soc :: Float, energy_used :: Float, reserve :: Float:
above_reserve(current_soc, energy_used, reserve)
}
"#;

const VEHICLE_LEX: &str = r#"
fn config() -> AgentConfig {
agent_new("vehicle")
|> agent_peers(["depot", "tms"])
|> agent_effects(["a2a"])
|> agent_specs(["specs/soc_reserve.spec"])
|> agent_handles([
{ topic: "Dispatch", fn_name: "on_dispatch" },
{ topic: "GrantSession", fn_name: "on_grant" },
{ topic: "DenySession", fn_name: "on_deny" },
])
}

fn enough_soc(soc :: Float, energy :: Float, reserve :: Float) -> Bool {
soc - energy >= reserve
}

fn on_dispatch(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
if enough_soc(state.soc, state.energy_needed, state.reserve) {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: msg.from, a2a_topic: "Acknowledge",
payload_json: "{\"delivery_id\":\"d-1\"}", prompt: "",
}]
} else {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "depot", a2a_topic: "RequestSession",
payload_json: "{\"vehicle_id\":\"v-1\",\"power_kw\":50}", prompt: "",
}]
}
}

fn on_grant(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "tms", a2a_topic: "Complete",
payload_json: "{\"delivery_id\":\"d-1\"}", prompt: "",
}]
}
const HOST: &str = include_str!("lex/multi_depot_host.lex");

fn on_deny(
state :: { soc :: Float, reserve :: Float, energy_needed :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: "tms", a2a_topic: "Failed",
payload_json: "{\"reason\":\"depot_denied\"}", prompt: "",
}]
}
"#;
const GRID_BUDGET_SPEC: &str = include_str!("lex/grid_budget.spec");

const DEPOT_LEX: &str = r#"
fn config() -> AgentConfig {
agent_new("depot")
|> agent_peers(["vehicle"])
|> agent_effects(["a2a"])
|> agent_specs(["specs/grid_budget.spec"])
|> agent_handles([
{ topic: "RequestSession", fn_name: "on_request_session" },
])
}
const SOC_RESERVE_SPEC: &str = include_str!("lex/soc_reserve.spec");

fn within(current :: Float, delta :: Float, grid :: Float, pv :: Float) -> Bool {
current + delta <= grid + pv
}
const VEHICLE_LEX: &str = include_str!("lex/multi_depot_vehicle.lex");

fn on_request_session(
state :: { current_kw :: Float, budget_kw :: Float, pv_kw :: Float, requested_kw :: Float },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
if within(state.current_kw, state.requested_kw, state.budget_kw, state.pv_kw) {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: msg.from, a2a_topic: "GrantSession",
payload_json: "{\"charger_id\":\"c-1\"}", prompt: "",
}]
} else {
[{
kind: "send_a2a", server: "", tool: "", args_json: "",
peer: msg.from, a2a_topic: "DenySession",
payload_json: "{\"reason\":\"grid_budget\"}", prompt: "",
}]
}
}
"#;
const DEPOT_LEX: &str = include_str!("lex/multi_depot_depot.lex");

const TMS_LEX: &str = r#"
fn config() -> AgentConfig {
agent_new("tms")
|> agent_peers(["vehicle"])
|> agent_effects(["a2a"])
|> agent_handles([
{ topic: "Acknowledge", fn_name: "terminate" },
{ topic: "Complete", fn_name: "terminate" },
{ topic: "Failed", fn_name: "terminate" },
])
}

fn terminate(
state :: { running :: Bool },
msg :: { from :: Str, topic :: Str, payload_json :: Str },
) -> List[ActionRecord] {
[]
}
"#;
const TMS_LEX: &str = include_str!("lex/multi_depot_tms.lex");

#[derive(Clone)]
struct Scenario {
Expand Down
Loading
Loading