<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Castar Blog (English)</title>
  <link href="https://xoxown31.github.io/castar-landing/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://xoxown31.github.io/castar-landing/" rel="alternate" type="text/html" hreflang="en"/>
  <updated>2026-09-18T00:00:00+09:00</updated>
  <id>https://xoxown31.github.io/castar-landing/feed.xml</id>
  <icon>https://xoxown31.github.io/castar-landing/assets/img/castar-mark.svg</icon>
  <author><name>Castar Team</name></author>
  <entry xml:lang="en">
    <title type="html">From a Casting Call to Search Conditions</title>
    <link href="https://xoxown31.github.io/castar-landing/blog/2026/09/from-a-casting-call-to-search-conditions/" rel="alternate" type="text/html"/>
    <published>2026-09-18T00:00:00+09:00</published>
    <updated>2026-09-18T00:00:00+09:00</updated>
    <id>https://xoxown31.github.io/castar-landing/blog/2026/09/from-a-casting-call-to-search-conditions/</id>
    <author><name>Castar Team</name></author>
    <summary type="html">One LLM call turns a free-text casting call into strict filters and soft preferences. When nobody matches, we say so instead of loosening the conditions.</summary>
    <content type="html" xml:base="https://xoxown31.github.io/castar-landing/blog/2026/09/from-a-casting-call-to-search-conditions/">&lt;p&gt;A casting call is written for people. A typical one reads something like: &lt;em&gt;Supporting male role, early 40s to early 50s. Fluent English required. 178 cm or taller. Swimming a plus. A warm but weathered face, short hair.&lt;/em&gt; Most calls on Castar are written in Korean; the examples in this post are English translations.&lt;/p&gt;

&lt;p&gt;An actor profile is written for a database. On Castar it holds a small set of structured fields: gender, birth year, height, weight and skills picked from a fixed list. It also has photos.&lt;/p&gt;

&lt;p&gt;Search sits between the two. This post describes how Castar turns the first into something it can run against the second, and the decisions we made along the way. The most important one is about what the system should do when the honest answer is “nobody.”&lt;/p&gt;

&lt;p&gt;Every casting call, role and output in this post is made up for illustration.&lt;/p&gt;

&lt;h2 id=&quot;two-kinds-of-conditions&quot;&gt;Two kinds of conditions&lt;/h2&gt;

&lt;p&gt;Read a casting call closely and its conditions fall into two groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symbolic conditions&lt;/strong&gt; map onto a profile field. “Male” is a value of gender. “Early 40s to early 50s” is a range over age, which we can work out from birth year. “178 cm or taller” is a bound on height. “Fluent English” and “swimming” are entries in the skills list. For each of these, a profile either meets the condition or it doesn’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic conditions&lt;/strong&gt; have no field. “A warm but weathered face,” “sharp features” and “looks like he has worked outdoors all his life” describe appearance and impression, and the only place that information lives is the photos. How well an actor fits is a matter of degree, and partly of taste.&lt;/p&gt;

&lt;p&gt;We handle the two separately on purpose, because they fail differently. A wrong symbolic match is plainly wrong: a 21-year-old for a role written as “50s.” A semantic match can always be argued about. If both are folded into one similarity score, one can make up for the other, and a strong appearance match can lift an actor who doesn’t meet the age requirement above one who does. Keeping them apart lets the symbolic side be exact and the semantic side be graded.&lt;/p&gt;

&lt;p&gt;This post covers the symbolic side. Appearance goes through a separate image–text channel, which will get its own post.&lt;/p&gt;

&lt;h2 id=&quot;one-call-one-small-json&quot;&gt;One call, one small JSON&lt;/h2&gt;

&lt;figure class=&quot;figure figure--pipeline&quot; role=&quot;group&quot; aria-labelledby=&quot;fig1-cap&quot;&gt;
  &lt;div class=&quot;figure__scroll&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Figure 1, scrolls horizontally on small screens&quot;&gt;
    &lt;img src=&quot;/castar-landing/assets/posts/from-a-casting-call-to-search-conditions/pipeline.svg&quot; width=&quot;700&quot; height=&quot;640&quot; alt=&quot;Pipeline for a fictional casting call: “Supporting male role, early 40s to early 50s. Fluent English required. 178 cm or taller. Swimming a plus. A warm but weathered face, short hair.” One LLM call outputs JSON. The symbolic branch gives structured fields — gender = male, age 40–53, height ≥ 178, English required, swimming preferred — applied as a strict filter. The semantic branch gives the appearance text “a man with a warm, weathered face, short hair,” matched against photos in a shared image–text space and used to rank. Both lead to a shortlist.&quot; /&gt;
  &lt;/div&gt;
  &lt;figcaption id=&quot;fig1-cap&quot;&gt;&lt;b&gt;Figure 1.&lt;/b&gt; One LLM call splits a casting call into symbolic conditions, which become a strict filter over structured profile fields, and a short appearance description, which is matched against photos and used for ranking. &lt;a href=&quot;/castar-landing/assets/posts/from-a-casting-call-to-search-conditions/pipeline.png&quot;&gt;Open full size&lt;/a&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Each casting call goes to a language model once. Along with the text, the prompt carries the rules described below and the platform’s skill vocabulary, and asks for one compact JSON object. With field names simplified for this post, the object has these parts:&lt;/p&gt;

&lt;div class=&quot;table-wrap&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Field&lt;/th&gt;
        &lt;th&gt;What goes in it&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gender&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;male&quot;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;female&quot;&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; if the call doesn’t say&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;age&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;a numeric range &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{min, max}&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height_cm&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;numeric bounds, only when the call gives a number&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skills.required&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;labels from the platform’s skill vocabulary&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skills.preferred&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;labels from the same vocabulary&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appearance&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;a short description of look and impression, for the image channel&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Anything the call doesn’t state stays &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; or empty. The model is told to fill a field only when the text supports it. A missing filter excludes nobody, while a guessed one quietly excludes people.&lt;/p&gt;

&lt;p&gt;We use a single call rather than a chain of them (one for age, one for skills, one for appearance, and so on). Latency and cost grow with the number of calls, and every extra call is another place for a timeout or a malformed response. The parts also depend on each other. Whether “swimming” is required or preferred depends on the sentence around it, and one pass sees the whole call at once. The cost is a longer prompt that has to be maintained as a single unit.&lt;/p&gt;

&lt;p&gt;The rest of this section goes through the fields one at a time.&lt;/p&gt;

&lt;h3 id=&quot;gender&quot;&gt;Gender&lt;/h3&gt;

&lt;p&gt;Gender is filled when the call states it, or when the role word itself carries it, as with “father” or “grandmother”. Otherwise it stays &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;. An occupation on its own, like “detective,” doesn’t count.&lt;/p&gt;

&lt;h3 id=&quot;age-decade-phrases-become-numbers&quot;&gt;Age: decade phrases become numbers&lt;/h3&gt;

&lt;p&gt;Casting calls usually give age as a decade, split into thirds: early, mid and late. We map each third to a fixed span of final digits: early is X0–X3, mid is X4–X6 and late is X7–X9.&lt;/p&gt;

&lt;div class=&quot;table-wrap table-wrap--nums&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Phrase&lt;/th&gt;
        &lt;th&gt;Range&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;40s&lt;/td&gt;
        &lt;td&gt;40–49&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;early 40s&lt;/td&gt;
        &lt;td&gt;40–43&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;mid 40s&lt;/td&gt;
        &lt;td&gt;44–46&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;late 40s&lt;/td&gt;
        &lt;td&gt;47–49&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;early 40s to early 50s&lt;/td&gt;
        &lt;td&gt;40–53&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;A span such as the last row takes the lower bound of its first phrase and the upper bound of its second.&lt;/p&gt;

&lt;figure class=&quot;figure&quot; role=&quot;group&quot; aria-labelledby=&quot;fig2-cap&quot;&gt;
  &lt;div class=&quot;figure__frame&quot;&gt;
    &lt;svg class=&quot;fig-capped&quot; viewBox=&quot;0 0 400 190&quot; role=&quot;img&quot; aria-labelledby=&quot;fig2-title fig2-desc&quot;&gt;
      &lt;title id=&quot;fig2-title&quot;&gt;Mapping “early 40s to early 50s” to ages 40 to 53&lt;/title&gt;
      &lt;desc id=&quot;fig2-desc&quot;&gt;Two rows of ten ages each, 40 to 49 and 50 to 59. Each decade is divided into early (0 to 3), mid (4 to 6) and late (7 to 9). Ages 40 through 53 are highlighted: all of the forties and the early fifties.&lt;/desc&gt;
      &lt;g font-size=&quot;14&quot; font-weight=&quot;600&quot; fill=&quot;#f2f1ec&quot;&gt;
        &lt;text x=&quot;4&quot; y=&quot;52&quot;&gt;40s&lt;/text&gt;
        &lt;text x=&quot;4&quot; y=&quot;132&quot;&gt;50s&lt;/text&gt;
      &lt;/g&gt;
      &lt;!-- 40s row: all in range --&gt;
      &lt;g fill=&quot;#2b2b29&quot; stroke=&quot;#ffffff&quot; stroke-width=&quot;1.1&quot;&gt;
        &lt;rect x=&quot;52&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;86&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;120&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;154&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;188&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;222&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;256&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;290&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;324&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;358&quot; y=&quot;30&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;!-- 50s row: 50–53 in range --&gt;
        &lt;rect x=&quot;52&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;86&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;120&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;154&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
      &lt;/g&gt;
      &lt;g fill=&quot;#161616&quot; stroke=&quot;#3d3d3a&quot; stroke-width=&quot;1.1&quot;&gt;
        &lt;rect x=&quot;188&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;222&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;256&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;290&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;324&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
        &lt;rect x=&quot;358&quot; y=&quot;110&quot; width=&quot;32&quot; height=&quot;34&quot; rx=&quot;4&quot; /&gt;
      &lt;/g&gt;
      &lt;g font-family=&quot;&apos;IBM Plex Mono&apos;, ui-monospace, monospace&quot; font-size=&quot;13&quot; text-anchor=&quot;middle&quot; fill=&quot;#ffffff&quot;&gt;
        &lt;text x=&quot;68&quot; y=&quot;52&quot;&gt;40&lt;/text&gt;&lt;text x=&quot;102&quot; y=&quot;52&quot;&gt;41&lt;/text&gt;&lt;text x=&quot;136&quot; y=&quot;52&quot;&gt;42&lt;/text&gt;&lt;text x=&quot;170&quot; y=&quot;52&quot;&gt;43&lt;/text&gt;&lt;text x=&quot;204&quot; y=&quot;52&quot;&gt;44&lt;/text&gt;
        &lt;text x=&quot;238&quot; y=&quot;52&quot;&gt;45&lt;/text&gt;&lt;text x=&quot;272&quot; y=&quot;52&quot;&gt;46&lt;/text&gt;&lt;text x=&quot;306&quot; y=&quot;52&quot;&gt;47&lt;/text&gt;&lt;text x=&quot;340&quot; y=&quot;52&quot;&gt;48&lt;/text&gt;&lt;text x=&quot;374&quot; y=&quot;52&quot;&gt;49&lt;/text&gt;
        &lt;text x=&quot;68&quot; y=&quot;132&quot;&gt;50&lt;/text&gt;&lt;text x=&quot;102&quot; y=&quot;132&quot;&gt;51&lt;/text&gt;&lt;text x=&quot;136&quot; y=&quot;132&quot;&gt;52&lt;/text&gt;&lt;text x=&quot;170&quot; y=&quot;132&quot;&gt;53&lt;/text&gt;
      &lt;/g&gt;
      &lt;g font-family=&quot;&apos;IBM Plex Mono&apos;, ui-monospace, monospace&quot; font-size=&quot;13&quot; text-anchor=&quot;middle&quot; fill=&quot;#8e8e88&quot;&gt;
        &lt;text x=&quot;204&quot; y=&quot;132&quot;&gt;54&lt;/text&gt;&lt;text x=&quot;238&quot; y=&quot;132&quot;&gt;55&lt;/text&gt;&lt;text x=&quot;272&quot; y=&quot;132&quot;&gt;56&lt;/text&gt;&lt;text x=&quot;306&quot; y=&quot;132&quot;&gt;57&lt;/text&gt;&lt;text x=&quot;340&quot; y=&quot;132&quot;&gt;58&lt;/text&gt;&lt;text x=&quot;374&quot; y=&quot;132&quot;&gt;59&lt;/text&gt;
      &lt;/g&gt;
      &lt;!-- brackets --&gt;
      &lt;g fill=&quot;none&quot; stroke=&quot;#8e8e88&quot; stroke-width=&quot;1.1&quot;&gt;
        &lt;path d=&quot;M54,70 V75 H184 V70&quot; /&gt;&lt;path d=&quot;M190,70 V75 H252 V70&quot; /&gt;&lt;path d=&quot;M258,70 V75 H388 V70&quot; /&gt;
        &lt;path d=&quot;M54,150 V155 H184 V150&quot; /&gt;&lt;path d=&quot;M190,150 V155 H252 V150&quot; /&gt;&lt;path d=&quot;M258,150 V155 H388 V150&quot; /&gt;
      &lt;/g&gt;
      &lt;g font-size=&quot;12.5&quot; text-anchor=&quot;middle&quot; fill=&quot;#b4b3ac&quot;&gt;
        &lt;text x=&quot;119&quot; y=&quot;92&quot;&gt;early&lt;/text&gt;&lt;text x=&quot;221&quot; y=&quot;92&quot;&gt;mid&lt;/text&gt;&lt;text x=&quot;323&quot; y=&quot;92&quot;&gt;late&lt;/text&gt;
        &lt;text x=&quot;119&quot; y=&quot;172&quot;&gt;early&lt;/text&gt;&lt;text x=&quot;221&quot; y=&quot;172&quot;&gt;mid&lt;/text&gt;&lt;text x=&quot;323&quot; y=&quot;172&quot;&gt;late&lt;/text&gt;
      &lt;/g&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
  &lt;figcaption id=&quot;fig2-cap&quot;&gt;&lt;b&gt;Figure 2.&lt;/b&gt; “Early 40s to early 50s” becomes &lt;code&gt;{&quot;min&quot;: 40, &quot;max&quot;: 53}&lt;/code&gt;: from the start of the early forties to the end of the early fifties. Highlighted ages are inside the range.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Not every call uses a decade phrase. Bare words such as “young man” or “middle-aged” are mapped to default ranges written into the prompt. The model doesn’t make up a range for each query. The defaults are one explicit, editable guess.&lt;/p&gt;

&lt;p&gt;The output is always a pair of numbers, never a label. An earlier prototype of our search used two coarse age groups, “young” and “old,” and the line between them was never written down anywhere. A numeric range can be checked directly against a birth year, and when a range is wrong, you can see exactly how it’s wrong.&lt;/p&gt;

&lt;h3 id=&quot;height-only-when-there-is-a-number&quot;&gt;Height: only when there is a number&lt;/h3&gt;

&lt;p&gt;“178 cm or taller” becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;min&quot;: 178}&lt;/code&gt;. “Tall” becomes nothing. Turning a relative word into a cutoff would mean picking a number the producer never gave, and that number would silently remove everyone just below it.&lt;/p&gt;

&lt;h3 id=&quot;skills-only-from-our-own-vocabulary&quot;&gt;Skills: only from our own vocabulary&lt;/h3&gt;

&lt;p&gt;Skills on a Castar profile come from a fixed vocabulary: languages, sports, instruments and so on. The model gets that list and may only answer with labels from it. Along the way it normalizes synonyms. “Can ride horses” and “horseback riding” both become the platform skill Horse riding. “Spanish-speaking” becomes Spanish, and “fluent English” becomes English.&lt;/p&gt;

&lt;p&gt;The restriction matters because a filter is only useful if a profile can satisfy it. If the model wrote free-form skills, “horseback riding” would never match a profile that lists Horse riding, and a correct-looking filter would return nobody.&lt;/p&gt;

&lt;figure class=&quot;figure&quot; role=&quot;group&quot; aria-labelledby=&quot;fig3-cap&quot;&gt;
  &lt;div class=&quot;figure__frame&quot;&gt;
    &lt;svg class=&quot;fig-capped&quot; viewBox=&quot;0 0 420 196&quot; role=&quot;img&quot; aria-labelledby=&quot;fig3-title fig3-desc&quot;&gt;
      &lt;title id=&quot;fig3-title&quot;&gt;Free-form skill phrases mapped to platform skills&lt;/title&gt;
      &lt;desc id=&quot;fig3-desc&quot;&gt;On the left, phrases as written in casting calls: “can ride horses”, “horseback riding”, “Spanish-speaking” and “fluent English”. Arrows map both horse phrases to the platform skill Horse riding, “Spanish-speaking” to Spanish and “fluent English” to English.&lt;/desc&gt;
      &lt;defs&gt;
        &lt;marker id=&quot;f3a&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;9&quot; refY=&quot;5&quot; markerWidth=&quot;6&quot; markerHeight=&quot;6&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L10,5 L0,10 z&quot; fill=&quot;#8e8e88&quot; /&gt;&lt;/marker&gt;
      &lt;/defs&gt;
      &lt;g font-size=&quot;10.5&quot; font-weight=&quot;600&quot; letter-spacing=&quot;1.6&quot; fill=&quot;#8e8e88&quot;&gt;
        &lt;text x=&quot;0&quot; y=&quot;14&quot;&gt;IN THE CASTING CALL&lt;/text&gt;
        &lt;text x=&quot;420&quot; y=&quot;14&quot; text-anchor=&quot;end&quot;&gt;PLATFORM SKILL&lt;/text&gt;
      &lt;/g&gt;
      &lt;g font-size=&quot;13.5&quot; fill=&quot;#b4b3ac&quot;&gt;
        &lt;text x=&quot;0&quot; y=&quot;50&quot;&gt;“can ride horses”&lt;/text&gt;
        &lt;text x=&quot;0&quot; y=&quot;90&quot;&gt;“horseback riding”&lt;/text&gt;
        &lt;text x=&quot;0&quot; y=&quot;140&quot;&gt;“Spanish-speaking”&lt;/text&gt;
        &lt;text x=&quot;0&quot; y=&quot;180&quot;&gt;“fluent English”&lt;/text&gt;
      &lt;/g&gt;
      &lt;g fill=&quot;none&quot; stroke=&quot;#8e8e88&quot; stroke-width=&quot;1.2&quot;&gt;
        &lt;path d=&quot;M134,46 C210,46 214,68 286,68&quot; marker-end=&quot;url(#f3a)&quot; /&gt;
        &lt;path d=&quot;M134,86 C210,86 214,68 286,68&quot; marker-end=&quot;url(#f3a)&quot; /&gt;
        &lt;path d=&quot;M134,136 L286,136&quot; marker-end=&quot;url(#f3a)&quot; /&gt;
        &lt;path d=&quot;M134,176 L286,176&quot; marker-end=&quot;url(#f3a)&quot; /&gt;
      &lt;/g&gt;
      &lt;g fill=&quot;#2b2b29&quot; stroke=&quot;#ffffff&quot; stroke-width=&quot;1.1&quot;&gt;
        &lt;rect x=&quot;292&quot; y=&quot;53&quot; width=&quot;128&quot; height=&quot;30&quot; rx=&quot;15&quot; /&gt;
        &lt;rect x=&quot;292&quot; y=&quot;121&quot; width=&quot;128&quot; height=&quot;30&quot; rx=&quot;15&quot; /&gt;
        &lt;rect x=&quot;292&quot; y=&quot;161&quot; width=&quot;128&quot; height=&quot;30&quot; rx=&quot;15&quot; /&gt;
      &lt;/g&gt;
      &lt;g font-size=&quot;13.5&quot; font-weight=&quot;600&quot; text-anchor=&quot;middle&quot; fill=&quot;#f2f1ec&quot;&gt;
        &lt;text x=&quot;356&quot; y=&quot;73&quot;&gt;Horse riding&lt;/text&gt;
        &lt;text x=&quot;356&quot; y=&quot;141&quot;&gt;Spanish&lt;/text&gt;
        &lt;text x=&quot;356&quot; y=&quot;181&quot;&gt;English&lt;/text&gt;
      &lt;/g&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
  &lt;figcaption id=&quot;fig3-cap&quot;&gt;&lt;b&gt;Figure 3.&lt;/b&gt; Different wordings of the same skill collapse onto one label from the platform’s vocabulary, so the filter asks for something a profile can actually contain.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;required-or-preferred&quot;&gt;Required or preferred&lt;/h3&gt;

&lt;p&gt;Casting calls separate what a role needs from what would be nice to have, and the separation is usually marked in the wording. Phrases like “a plus,” “preferred,” “nice to have” or “bonus points for” mark a condition as optional. “Swimming a plus” puts swimming under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preferred&lt;/code&gt;. Skills stated as plain requirements, like “Fluent English required,” go under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;required&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;appearance-text&quot;&gt;Appearance text&lt;/h3&gt;

&lt;p&gt;Whatever describes look and impression goes into a short &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appearance&lt;/code&gt; string, which is passed to the image–text channel. Nothing in it becomes a filter.&lt;/p&gt;

&lt;h2 id=&quot;strict-means-strict&quot;&gt;Strict means strict&lt;/h2&gt;

&lt;p&gt;Once the conditions are extracted, the required ones are applied as a filter: gender, the age range (compared with the age we compute from birth year), height bounds and required skills. An actor who fails any of them is out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If nobody passes, the search stops and tells the producer there are no matching actors yet.&lt;/strong&gt; It doesn’t loosen the age range or drop the English requirement and try again.&lt;/p&gt;

&lt;p&gt;We did it the other way at first. When the filtered pool came back empty, the earlier prototype dropped the weakest condition and retried until something came back. That looks helpful, but it changes the question without telling anyone. A producer who wrote “50s” and sees a 21-year-old at the top of the list will either waste time on a candidate who was never going to fit, or will stop trusting the rest of the list. It’s worse because the result looks like a match. Nothing on the screen says “we ignored your age requirement.”&lt;/p&gt;

&lt;figure class=&quot;figure&quot; role=&quot;group&quot; aria-labelledby=&quot;fig4-cap&quot;&gt;
  &lt;div class=&quot;figure__frame&quot;&gt;
    &lt;svg class=&quot;fig-capped&quot; viewBox=&quot;0 0 440 236&quot; role=&quot;img&quot; aria-labelledby=&quot;fig4-title fig4-desc&quot;&gt;
      &lt;title id=&quot;fig4-title&quot;&gt;Relax-and-retry versus strict filtering&lt;/title&gt;
      &lt;desc id=&quot;fig4-desc&quot;&gt;Two panels for a fictional call asking for a man in his 50s who speaks English. Left, relax and retry: the age condition is silently dropped and the list comes back with actors aged 21, 26 and 34. Right, strict: all three conditions stay, and the result is “No matching actors yet”, leaving the producer to decide what to loosen.&lt;/desc&gt;
      &lt;defs&gt;
        &lt;marker id=&quot;f4a&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;9&quot; refY=&quot;5&quot; markerWidth=&quot;6&quot; markerHeight=&quot;6&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L10,5 L0,10 z&quot; fill=&quot;#8e8e88&quot; /&gt;&lt;/marker&gt;
      &lt;/defs&gt;
      &lt;g fill=&quot;#161616&quot; stroke=&quot;rgba(255,255,255,0.12)&quot;&gt;
        &lt;rect x=&quot;0.5&quot; y=&quot;0.5&quot; width=&quot;211&quot; height=&quot;235&quot; rx=&quot;10&quot; /&gt;
        &lt;rect x=&quot;228.5&quot; y=&quot;0.5&quot; width=&quot;211&quot; height=&quot;235&quot; rx=&quot;10&quot; /&gt;
      &lt;/g&gt;
      &lt;g font-size=&quot;10.5&quot; font-weight=&quot;600&quot; letter-spacing=&quot;1.6&quot; fill=&quot;#8e8e88&quot;&gt;
        &lt;text x=&quot;16&quot; y=&quot;26&quot;&gt;RELAX AND RETRY&lt;/text&gt;
        &lt;text x=&quot;244&quot; y=&quot;26&quot;&gt;STRICT&lt;/text&gt;
      &lt;/g&gt;
      &lt;!-- condition chips --&gt;
      &lt;g font-size=&quot;12.5&quot; text-anchor=&quot;middle&quot;&gt;
        &lt;rect x=&quot;16&quot; y=&quot;40&quot; width=&quot;50&quot; height=&quot;26&quot; rx=&quot;13&quot; fill=&quot;#2b2b29&quot; stroke=&quot;#ffffff&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;text x=&quot;41&quot; y=&quot;57&quot; fill=&quot;#f2f1ec&quot;&gt;male&lt;/text&gt;
        &lt;rect x=&quot;72&quot; y=&quot;40&quot; width=&quot;46&quot; height=&quot;26&quot; rx=&quot;13&quot; fill=&quot;none&quot; stroke=&quot;#8e8e88&quot; stroke-width=&quot;1&quot; stroke-dasharray=&quot;3 3&quot; /&gt;
        &lt;text x=&quot;95&quot; y=&quot;57&quot; fill=&quot;#8e8e88&quot;&gt;50s&lt;/text&gt;
        &lt;line x1=&quot;80&quot; y1=&quot;53&quot; x2=&quot;110&quot; y2=&quot;53&quot; stroke=&quot;#b4b3ac&quot; stroke-width=&quot;1.2&quot; /&gt;
        &lt;rect x=&quot;124&quot; y=&quot;40&quot; width=&quot;68&quot; height=&quot;26&quot; rx=&quot;13&quot; fill=&quot;#2b2b29&quot; stroke=&quot;#ffffff&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;text x=&quot;158&quot; y=&quot;57&quot; fill=&quot;#f2f1ec&quot;&gt;English&lt;/text&gt;

        &lt;rect x=&quot;244&quot; y=&quot;40&quot; width=&quot;50&quot; height=&quot;26&quot; rx=&quot;13&quot; fill=&quot;#2b2b29&quot; stroke=&quot;#ffffff&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;text x=&quot;269&quot; y=&quot;57&quot; fill=&quot;#f2f1ec&quot;&gt;male&lt;/text&gt;
        &lt;rect x=&quot;300&quot; y=&quot;40&quot; width=&quot;46&quot; height=&quot;26&quot; rx=&quot;13&quot; fill=&quot;#2b2b29&quot; stroke=&quot;#ffffff&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;text x=&quot;323&quot; y=&quot;57&quot; fill=&quot;#f2f1ec&quot;&gt;50s&lt;/text&gt;
        &lt;rect x=&quot;352&quot; y=&quot;40&quot; width=&quot;68&quot; height=&quot;26&quot; rx=&quot;13&quot; fill=&quot;#2b2b29&quot; stroke=&quot;#ffffff&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;text x=&quot;386&quot; y=&quot;57&quot; fill=&quot;#f2f1ec&quot;&gt;English&lt;/text&gt;
      &lt;/g&gt;
      &lt;text x=&quot;95&quot; y=&quot;82&quot; font-size=&quot;11&quot; text-anchor=&quot;middle&quot; fill=&quot;#8e8e88&quot; font-style=&quot;italic&quot;&gt;dropped, silently&lt;/text&gt;
      &lt;g stroke=&quot;#8e8e88&quot; stroke-width=&quot;1.2&quot;&gt;
        &lt;line x1=&quot;106&quot; y1=&quot;90&quot; x2=&quot;106&quot; y2=&quot;110&quot; marker-end=&quot;url(#f4a)&quot; /&gt;
        &lt;line x1=&quot;334&quot; y1=&quot;90&quot; x2=&quot;334&quot; y2=&quot;110&quot; marker-end=&quot;url(#f4a)&quot; /&gt;
      &lt;/g&gt;
      &lt;!-- relaxed results --&gt;
      &lt;g fill=&quot;#101010&quot; stroke=&quot;rgba(255,255,255,0.12)&quot;&gt;
        &lt;rect x=&quot;16&quot; y=&quot;118&quot; width=&quot;180&quot; height=&quot;30&quot; rx=&quot;6&quot; /&gt;
        &lt;rect x=&quot;16&quot; y=&quot;154&quot; width=&quot;180&quot; height=&quot;30&quot; rx=&quot;6&quot; /&gt;
        &lt;rect x=&quot;16&quot; y=&quot;190&quot; width=&quot;180&quot; height=&quot;30&quot; rx=&quot;6&quot; /&gt;
      &lt;/g&gt;
      &lt;g fill=&quot;#3d3d3a&quot;&gt;
        &lt;circle cx=&quot;34&quot; cy=&quot;133&quot; r=&quot;8&quot; /&gt;&lt;circle cx=&quot;34&quot; cy=&quot;169&quot; r=&quot;8&quot; /&gt;&lt;circle cx=&quot;34&quot; cy=&quot;205&quot; r=&quot;8&quot; /&gt;
      &lt;/g&gt;
      &lt;g font-size=&quot;12.5&quot; fill=&quot;#f2f1ec&quot;&gt;
        &lt;text x=&quot;52&quot; y=&quot;137&quot;&gt;Actor · age &lt;tspan font-weight=&quot;600&quot;&gt;21&lt;/tspan&gt;&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;173&quot;&gt;Actor · age &lt;tspan font-weight=&quot;600&quot;&gt;26&lt;/tspan&gt;&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;209&quot;&gt;Actor · age &lt;tspan font-weight=&quot;600&quot;&gt;34&lt;/tspan&gt;&lt;/text&gt;
      &lt;/g&gt;
      &lt;g font-size=&quot;11&quot; font-family=&quot;&apos;IBM Plex Mono&apos;, ui-monospace, monospace&quot; fill=&quot;#8e8e88&quot; text-anchor=&quot;end&quot;&gt;
        &lt;text x=&quot;186&quot; y=&quot;137&quot;&gt;#1&lt;/text&gt;&lt;text x=&quot;186&quot; y=&quot;173&quot;&gt;#2&lt;/text&gt;&lt;text x=&quot;186&quot; y=&quot;209&quot;&gt;#3&lt;/text&gt;
      &lt;/g&gt;
      &lt;!-- strict result --&gt;
      &lt;rect x=&quot;244&quot; y=&quot;118&quot; width=&quot;176&quot; height=&quot;102&quot; rx=&quot;8&quot; fill=&quot;none&quot; stroke=&quot;#b4b3ac&quot; stroke-width=&quot;1&quot; stroke-dasharray=&quot;4 3&quot; /&gt;
      &lt;text x=&quot;332&quot; y=&quot;160&quot; font-size=&quot;13.5&quot; font-weight=&quot;600&quot; text-anchor=&quot;middle&quot; fill=&quot;#f2f1ec&quot;&gt;No matching actors yet&lt;/text&gt;
      &lt;g font-size=&quot;11.5&quot; text-anchor=&quot;middle&quot; fill=&quot;#b4b3ac&quot;&gt;
        &lt;text x=&quot;332&quot; y=&quot;184&quot;&gt;The producer decides&lt;/text&gt;
        &lt;text x=&quot;332&quot; y=&quot;200&quot;&gt;what to loosen.&lt;/text&gt;
      &lt;/g&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
  &lt;figcaption id=&quot;fig4-cap&quot;&gt;&lt;b&gt;Figure 4.&lt;/b&gt; The same fictional call, “a man in his 50s, English required,” against a fictional pool with no such actor. Relaxing quietly answers a different question; the strict filter answers the one that was asked.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;An empty result carries information. It tells the producer that the platform doesn’t have this person yet, and the producer can decide which condition to loosen, since they know which ones actually matter for the role. It also tells us where the actor pool is thin.&lt;/p&gt;

&lt;p&gt;Preferred conditions never remove anyone. They only affect the order of the actors who passed the strict filter: someone who meets a preferred condition moves up, and someone who doesn’t stays in the list.&lt;/p&gt;

&lt;p&gt;We also don’t show numbers next to results: no “92% match” and no impression score. A similarity value from a model isn’t the probability that someone is right for a role, but a number on screen invites people to read it that way and to treat 87 versus 84 as a real difference. Results are shown in order, without scores.&lt;/p&gt;

&lt;h2 id=&quot;a-worked-example&quot;&gt;A worked example&lt;/h2&gt;

&lt;p&gt;Here is the fictional casting call from Figure 1 in full.&lt;/p&gt;

&lt;div class=&quot;casting-call&quot;&gt;
  &lt;dl&gt;
    &lt;dt&gt;Role&lt;/dt&gt;&lt;dd&gt;Supporting male role, early 40s to early 50s.&lt;/dd&gt;
    &lt;dt&gt;Required&lt;/dt&gt;&lt;dd&gt;Fluent English required.&lt;/dd&gt;
    &lt;dt&gt;Height&lt;/dt&gt;&lt;dd&gt;178 cm or taller.&lt;/dd&gt;
    &lt;dt&gt;Preferred&lt;/dt&gt;&lt;dd&gt;Swimming a plus.&lt;/dd&gt;
    &lt;dt&gt;Look&lt;/dt&gt;&lt;dd&gt;A warm but weathered face, short hair.&lt;/dd&gt;
  &lt;/dl&gt;
&lt;/div&gt;

&lt;p&gt;The single LLM call returns (field names and labels simplified):&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;filters&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;gender&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;male&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;max&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;53&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;height_cm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;178&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;max&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;skills&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;English&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;preferred&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Swimming&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;appearance&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;a man with a warm, weathered face, short hair&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Line by line:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“Male” becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;gender&quot;: &quot;male&quot;&lt;/code&gt;. “Supporting” describes the size of the part, not the actor, so it doesn’t become a condition.&lt;/li&gt;
  &lt;li&gt;“Early 40s to early 50s” becomes 40–53, using the rule from Figure 2.&lt;/li&gt;
  &lt;li&gt;“178 cm or taller” has a number, so it becomes a lower bound on height. There is no upper bound.&lt;/li&gt;
  &lt;li&gt;“Fluent English required” is normalized to the platform skill English and, because the call says “required,” it’s a required skill.&lt;/li&gt;
  &lt;li&gt;“Swimming a plus” is marked as a plus, so Swimming is preferred.&lt;/li&gt;
  &lt;li&gt;“A warm but weathered face, short hair” becomes the appearance text. None of it becomes a filter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The search then keeps only male actors aged 40 to 53, at least 178 cm tall, with English in their skills. If that set is empty, the producer sees “no matching actors yet.” If it isn’t, actors who swim move up, and the appearance channel orders the rest.&lt;/p&gt;

&lt;h2 id=&quot;what-this-doesnt-solve-yet&quot;&gt;What this doesn’t solve yet&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Vague age words depend on defaults.&lt;/strong&gt; “Young man” becomes whatever range the prompt says it is. Those defaults are explicit and easy to change, but they’re still our guess about what a producer means, and producers won’t all agree with it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The vocabulary has to be maintained.&lt;/strong&gt; Every new skill and every new way of saying an old one (“horse riding,” “horseback riding,” “can ride a horse”) needs a home in the vocabulary. A skill that isn’t in the vocabulary can’t become a filter at all.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Birth year is coarse.&lt;/strong&gt; Age computed from a birth year can be off by one near a birthday, so an actor at the edge of a range can land on either side of it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Strictness cuts both ways.&lt;/strong&gt; Because required conditions exclude people, an extraction mistake (a misread age, or a preferred skill marked as required) also excludes people without saying so. Strict filtering makes the quality of this one LLM call matter more, not less.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Evaluation is ongoing.&lt;/strong&gt; We’re still measuring how reliably the extraction handles real-world phrasing, and we’ll write about it when we have results we trust.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;next&quot;&gt;Next&lt;/h2&gt;

&lt;p&gt;This post covered the symbolic half of a casting call: conditions that map onto fields and can be checked exactly. The other half, “a warm but weathered face,” “sharp features” and the rest of the appearance vocabulary, can’t be checked that way. It goes through a separate image–text channel, and that will be the subject of our next post.&lt;/p&gt;</content>
  </entry>
</feed>
