Typed manifest parameter values (beyond string → string)
Motivated by #1432 (per-component-executor-binding), but independent and non-blocking — see "Relationship to #1432" below.
Summary
Manifest parameter values are modelled everywhere as IReadOnlyDictionary<string, string>. That is correct for the current templating use (${param} text substitution) but blocks first-class support for parameter kinds whose value is not a string — e.g. the executor kind (a structured selection), and future numeric/boolean/enum/secret-reference/object parameters. This item introduces a richer, typed parameter-value model with lossless persistence and back-compat, so parameter kinds can carry structured values without stringly-typed encoding.
Relationship to #1432
The per-component-executor feature does not depend on this item. Per the M7 cohesion decision, the executor parameter's selection is recorded in a dedicated typed channel (parameter-selections: { <name>: <JsonElement> }) that feeds executor-bindings, leaving the string templating parameter-values map untouched. This item generalises that idea to all parameter kinds and would later let the executor selection migrate from the dedicated channel into the unified typed map. It can be scheduled independently.
Root Cause / Current State
AgentDefinitionParameterSubstitutor.Substitute(AgentManifest, IReadOnlyDictionary<string,string>?) (Phantom.Workspaces.Llm.Core/AgentDefinitionParameterSubstitutor.cs:15-17) performs ${name} regex substitution — inherently string-valued.
- The
string→string shape is threaded through ~20 sites, including CreateAgentChatRequest.Parameters, CreateAgentDefinitionRequest.Parameters, AcquireAgentChatRequest.Parameters, AgentDefinitionResolver.Parameters, AgentDefinitionToolExtractor, AgentChat.UpdateParameterValues (AgentChat.cs:671), SlashCommandContext.CurrentParameterValues, the Launchpad/StartAgentSession VMs, OpenAgentSessionShortcutHandler (reads/merges a string dict), and the persistence path AgentSessionEntityFactory.CreateEntityData(... IReadOnlyDictionary<string,string>? parameterValues ...) (Data.Core/AgentSessionEntityFactory.cs:46) plus the Mongo/Web persistence DTOs.
Affected Files (indicative)
| File |
Contribution |
Phantom.Workspaces.Llm.Core/AgentDefinitionParameterSubstitutor.cs |
Project a typed value to its string form for ${} substitution (text params only). |
Phantom.Workspaces.Llm.Core/CreateAgentChatRequest.cs, CreateAgentDefinitionRequest.cs, Services/AgentDefinitionResolver.cs, Services/AcquireAgentChatRequest.cs |
Widen Parameters to the typed value model. |
Phantom.Workspaces.Data.Core/AgentSessionEntityFactory.cs + Mongo/Web persistence DTOs |
Persist typed values; read legacy string values as ParameterValue.String. |
Phantom.Workspaces/ViewModels/*Launchpad*, OpenAgentSessionShortcutHandler.cs, SlashCommands/* |
Produce/consume typed values; string params unaffected. |
Design / Fix (sketch)
Introduce a discriminated ParameterValue (e.g. String, Number, Bool, Json) with:
string AsTemplateString() — used by the substitutor for ${} (text/directory params only; structured params are not substitutable and error if referenced in a template).
- Lossless JSON round-trip for persistence.
- Back-compat readers: a persisted bare-string value deserialises as
ParameterValue.String; the writer may continue emitting bare strings for string params to avoid churn, or emit a tagged form — decide and test both read paths.
Thread IReadOnlyDictionary<string, ParameterValue> through the parameter pipeline, keeping string params behaviourally identical.
Considered / Background: (a) Overloading parameter-values with JSON-encoded strings — rejected as stringly-typed and concern-mixing. (b) A dedicated per-kind side channel (what #1432 uses for executor now) — good for one kind, but does not generalise; this item is the general solution.
Expected Tests
| Test Name |
Class |
What It Verifies |
ParameterValue_LegacyStringMap_ReadsAsStringValues |
ParameterValueTests (Phantom.Workspaces.Llm.Core.Tests) |
A persisted legacy string→string parameter-values block loads with each entry as ParameterValue.String (back-compat). |
ParameterValue_RoundTrips_AllKinds |
ParameterValueTests |
String/number/bool/json values persist and reload losslessly. |
Substitutor_StringParam_UnchangedBehaviour |
AgentDefinitionParameterSubstitutorTests |
${text-param} substitution is identical to the current string-only behaviour. |
Substitutor_StructuredParamInTemplate_Errors |
AgentDefinitionParameterSubstitutorTests |
Referencing a non-string parameter inside a ${} template fails clearly. |
Typed manifest parameter values (beyond
string → string)Motivated by #1432 (per-component-executor-binding), but independent and non-blocking — see "Relationship to #1432" below.
Summary
Manifest parameter values are modelled everywhere as
IReadOnlyDictionary<string, string>. That is correct for the current templating use (${param}text substitution) but blocks first-class support for parameter kinds whose value is not a string — e.g. theexecutorkind (a structured selection), and future numeric/boolean/enum/secret-reference/object parameters. This item introduces a richer, typed parameter-value model with lossless persistence and back-compat, so parameter kinds can carry structured values without stringly-typed encoding.Relationship to #1432
The per-component-executor feature does not depend on this item. Per the M7 cohesion decision, the
executorparameter's selection is recorded in a dedicated typed channel (parameter-selections: { <name>: <JsonElement> }) that feedsexecutor-bindings, leaving the string templatingparameter-valuesmap untouched. This item generalises that idea to all parameter kinds and would later let theexecutorselection migrate from the dedicated channel into the unified typed map. It can be scheduled independently.Root Cause / Current State
AgentDefinitionParameterSubstitutor.Substitute(AgentManifest, IReadOnlyDictionary<string,string>?)(Phantom.Workspaces.Llm.Core/AgentDefinitionParameterSubstitutor.cs:15-17) performs${name}regex substitution — inherently string-valued.string→stringshape is threaded through ~20 sites, includingCreateAgentChatRequest.Parameters,CreateAgentDefinitionRequest.Parameters,AcquireAgentChatRequest.Parameters,AgentDefinitionResolver.Parameters,AgentDefinitionToolExtractor,AgentChat.UpdateParameterValues(AgentChat.cs:671),SlashCommandContext.CurrentParameterValues, the Launchpad/StartAgentSession VMs,OpenAgentSessionShortcutHandler(reads/merges a string dict), and the persistence pathAgentSessionEntityFactory.CreateEntityData(... IReadOnlyDictionary<string,string>? parameterValues ...)(Data.Core/AgentSessionEntityFactory.cs:46) plus the Mongo/Web persistence DTOs.Affected Files (indicative)
Phantom.Workspaces.Llm.Core/AgentDefinitionParameterSubstitutor.cs${}substitution (text params only).Phantom.Workspaces.Llm.Core/CreateAgentChatRequest.cs,CreateAgentDefinitionRequest.cs,Services/AgentDefinitionResolver.cs,Services/AcquireAgentChatRequest.csParametersto the typed value model.Phantom.Workspaces.Data.Core/AgentSessionEntityFactory.cs+ Mongo/Web persistence DTOsParameterValue.String.Phantom.Workspaces/ViewModels/*Launchpad*,OpenAgentSessionShortcutHandler.cs,SlashCommands/*Design / Fix (sketch)
Introduce a discriminated
ParameterValue(e.g.String,Number,Bool,Json) with:string AsTemplateString()— used by the substitutor for${}(text/directory params only; structured params are not substitutable and error if referenced in a template).ParameterValue.String; the writer may continue emitting bare strings for string params to avoid churn, or emit a tagged form — decide and test both read paths.Thread
IReadOnlyDictionary<string, ParameterValue>through the parameter pipeline, keeping string params behaviourally identical.Considered / Background: (a) Overloading
parameter-valueswith JSON-encoded strings — rejected as stringly-typed and concern-mixing. (b) A dedicated per-kind side channel (what #1432 uses forexecutornow) — good for one kind, but does not generalise; this item is the general solution.Expected Tests
ParameterValue_LegacyStringMap_ReadsAsStringValuesParameterValueTests(Phantom.Workspaces.Llm.Core.Tests)string→stringparameter-valuesblock loads with each entry asParameterValue.String(back-compat).ParameterValue_RoundTrips_AllKindsParameterValueTestsSubstitutor_StringParam_UnchangedBehaviourAgentDefinitionParameterSubstitutorTests${text-param}substitution is identical to the current string-only behaviour.Substitutor_StructuredParamInTemplate_ErrorsAgentDefinitionParameterSubstitutorTests${}template fails clearly.