<< All versions

Skill v1.0.1

currentAutomated scan100/100
radiantlogicinc/fastworkflow/validate-command-parameters

+3 new

──Details
PublishedOctober 27, 2026 at 04:02 AM
Content Hashsha256:d966f237199028a7...
Git SHA1daddb8bac19
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 7.3 KB)
SKILL.md7.3 KBactive
SKILL.md · 175 lines · 7.3 KB

version: "1.0.1" name: validate-command-parameters description: >- Implement the Signature.validate_extracted_parameters hook on a fastWorkflow command so whole-call rules, cross-field constraints and context preconditions are checked and corrected before the command body runs, and so a bad call stays a conversation instead of becoming an exception. Covers the exact contract and where it sits in the validation order, normalising values in place, writing error messages an agent can act on, and what belongs here versus in the field declaration. Use when a command has rules spanning more than one field, needs a precondition on the current context, must normalise input before executing, or when authoring or reviewing any command Signature.


Validating extracted parameters

Pydantic answers one field at a time: right type, matches the pattern, was supplied. It cannot answer the questions that only exist once the whole call is on the table — is this end date before its own start date, is anything actually selected for this bulk command to operate on, does this decision value exist in the backend's vocabulary. validate_extracted_parameters is where those go.

The contract

A static method on the command's Signature class:

python
class Signature:
class Input(BaseModel):
...
@staticmethod
def validate_extracted_parameters(
workflow: fastworkflow.Workflow,
command: str,
cmd_parameters: "Signature.Input",
) -> tuple[bool, str]:
...
return (True, '')

Return (True, '') to proceed, or (False, message) to send message back to the caller. The hook is optional — a command without it simply skips this stage.

Where it runs in the order

The framework validates in this sequence, and stops at the first failure:

1. type coercion per field str/int/float/bool/Enum/list, plus any `pattern`
2. missing required fields sentinel values count as missing; empty list counts as missing
3. db_lookup per field fuzzy resolution against a live key set
4. validate_extracted_parameters <- only reached if 1-3 all passed

So this hook may assume every field is present and of the declared type. Do not re-check types, required-ness or regex here — declare those on the field and let stage 1 do it. What is left is exactly what the field declarations cannot express.

Before validation, in agent mode, the values are bound in code when the call's form allows it: XML tags, name=value pairs, or command_name value (positional). The positional form binds to the field declared json_schema_extra={'positional': True} (several words allowed), or else to the class's only str field (one token only); it declines on anything doubtful and the LLM extracts instead. Mark at most one field, and not a free-text field of a command that changes state. A lone command_name value that binds to no field is refused with a message naming the parameters, so the command never runs on its defaults instead. Whatever bound the values, the sequence above still runs.

It is a correction hook, not just a check

The cmd_parameters object it receives is the one the command body will read, so writing to it is the intended way to normalise:

python
@staticmethod
def validate_extracted_parameters(workflow, command, cmd_parameters):
if not cmd_parameters.order_id.startswith('#'):
cmd_parameters.order_id = f'#{cmd_parameters.order_id}'
return (True, '')

Prefer normalising to rejecting whenever the intent is unambiguous. Every rejection costs a round trip; a value that can only have meant one thing should just be fixed.

Return, never raise

Exceptions are caught, logged at critical, and converted into a failure whose message names the command and the exception. That is strictly worse than a message written for the caller: the caller gets a stack-trace-flavoured string instead of an instruction. Return (False, message).

Write the message for an agent

The message is the only thing the caller has to work from. Every message should name the field, say what is wrong with the value that arrived, and say where an acceptable one comes from.

python
return (False,
"decision 'approve-all' is not one of the accepted values. "
"Use one of: approve, revoke, defer.")

Vague messages ("invalid input") cause the agent to retry the same value.

Accumulate errors, report once

Check everything, then report together, so one round trip tells the caller all of it:

python
def report(errors):
"""The (is_valid, message) pair the framework expects, from a list of
error strings of which most are normally empty."""
message = "\n".join(error for error in errors if error)
return (False, message) if message else (True, "")

Three helper shapes are worth distinguishing at the call site:

ShapeReturnsUse for
normalising(value, error) — store the value backa code to upper-case, a uid to strip
relationalerrortwo or more fields that must agree
readingvalueturning an intent the caller can express into the value the body wants
python
@staticmethod
def validate_extracted_parameters(workflow, command, cmd_parameters):
errors = []
cmd_parameters.decision, error = one_of(cmd_parameters.decision, DECISIONS)
errors.append(error)
errors.append(in_order(cmd_parameters.begin_date, cmd_parameters.end_date))
return report(errors)

Keep it local and cheap

The hook runs on every attempt at the command, including the ones an agent is guessing at, and including retries after a correction. Do not call a backend from here. Check against values already in the workflow context, or against a constant vocabulary. If a check genuinely needs a live key set, that is what db_lookup is for — see the resolve-parameter-values skill.

Context preconditions belong here

This is the only hook that sees the workflow before the body runs, so it is where "is this command meaningful right now" goes:

python
selection = getattr(workflow.command_context_for_response_generation, 'selection', None)
if not selection:
errors.append("Nothing is selected. Use select_items first, then retry.")

Note the message names the producing command, the same job available_from does for a parameter that never arrived.

Do not treat it as a security boundary

The hook runs on both the NLU path and the direct-action path, but it is a command's own declaration of what it needs, not an enforcement layer — a caller reaching the body another way bypasses it. Invariants that must hold for correctness or safety belong in the command body as well.

Authoring checklist

- [ ] Type, pattern and required-ness are declared on the field, not re-checked here
- [ ] Unambiguous values are normalised in place rather than rejected
- [ ] Every failure returns (False, message); nothing raises
- [ ] Each message names the field, the bad value, and where a good one comes from
- [ ] Errors accumulate into one message
- [ ] No backend calls
- [ ] Safety-critical invariants are also enforced in the command body

Related

  • Handle parameters that never arrived: the declare-parameter-producers skill.
  • Values that arrived nearly right and should be fuzzy-resolved: the resolve-parameter-values skill.
← v1.0.0All versions