- The classification suitable component chooses among candidates (tools, APIs, skills, applications, models, ...), and each row of the classification dataset is one candidate of one decision. Besides `extract_features_for_classification(trajectory_data)`, the code may define a second function in the global scope:
    `extract_candidate_features(trajectory_data, candidate) -> Dict[str, Any]`
  It returns the features of one candidate in the context of the trajectory. It is called once per candidate, after the code has run on the trajectory; do not call it yourself. `candidate` is a dict with exactly the keys "id", "name", "description", "args" and "spec"; it never says whether the candidate was chosen. `candidate["spec"]` is the candidate's entry in the catalog the decision offered, a dict whose keys depend on the catalog, or None when the catalog has none: test that a key is in it before reading the key. Up to three candidates of this decision, as the function receives them (long values clipped here):
${candidate_examples}
- A feature that describes a candidate, or relates the trajectory to a candidate, belongs only in `extract_candidate_features`: for example the words the request shares with the candidate's name or description, whether the trajectory mentions the candidate before the decision, or how well a model's, a skill's or an application's description fits the task. When the catalog entries describe HTTP APIs (a method, a path, parameters), whether the API's method fits the verb of the request, and whether its required parameters can be filled from the trajectory, are such features too.
- Features about the history of earlier calls as a whole (the tools called so far, the last application used, the steps since an event) describe the trajectory, not a candidate: they belong in `extract_features_for_classification`. Whether the candidate itself appears in that history is a feature of the candidate.
- `extract_features_for_classification` must never enumerate the decision's list of candidates, and no feature may index the decision's candidates by position (`candidate_1_overlap`, `option_3_score`): every row describes one candidate, so a column about the third candidate of a list says nothing about the candidate the row scores.
- Read the trajectory in `extract_candidate_features` only through `trajectory_data`, under the same rules as the main function; return only strings, numbers, booleans or None, flattened with flatten_json like the main function's features.
- Do not parse the trajectory again for every candidate (a decision can offer hundreds): compute what depends on the trajectory alone in a helper, and keep its result in a global dict that the helper fills on its first call. The global variables are fresh for every trajectory, so the dict holds the values of one trajectory.
