Files
krow_backend/skills/create-employee-role.md
Suriyakumarvijayanayagam dc785b917c
Some checks failed
CI / test (push) Failing after 4m41s
CI / fixture (push) Failing after 8s
Separate what a worker does from what a company needs filled
Owliver could offer neither create. The Create Position flow worked and no chip
anywhere suggested it, because the chip row is entirely the backend's static
catalogue and no intent in it wrote anything. The gap was never in the
frontend's trigger matching — every phrasing already routed.

`employee_roles` is the supply side of `job_postings`. A posting is what the
ORGANIZATION needs filled; this is what a WORKER says they do. They share a
vocabulary and almost nothing else: "3 years" on a posting is a minimum an
applicant must clear, and the same words here are what the person has. There is
deliberately no foreign key between them — supply and demand already meet
through `job_applications`, which carries the funnel, the interview and the
outcome, and a second weaker link would disagree with it the first time
somebody withdrew.

NO NEW COMPANY ENTITY, AND THAT IS THE LOAD-BEARING DECISION. "Create a company
position" reads like it needs a client record. `organizations` is the TENANT —
absent from the resource table, absent from the policy map, written only by the
seeder — so creating a row there from a chat flow would provision a new tenant,
and the position would carry an org_id the operator's session cannot see. The
operator could never view the record they just created. That breaks I5 and I1
to add a feature nobody asked for. The client stays free text on the posting,
per blueprint decision D2, and the flow simply offers the clients this
organization already staffs for as chips. No schema change, no endpoint change.

Create is operators-only, and that is an I1 decision rather than a deferral.
The worker is named explicitly on the row and is deliberately NOT derived from
the session, because an operator recording a role on somebody's behalf is the
whole point of the flow. Granting talent the same Create would let a talent
caller write a role under any worker_email in the tenant — the attribution hole
Phase 3D closed elsewhere. Talent reads its own via a ScopeEmail predicate,
which is in place now so the grant is one line when a talent console exists.

`created_by` is in gen_resources.py's SERVER_OWNED as well as the policy's
Derived list. Both are required and the pairing is easy to miss: Derived fills
the column from the session, SERVER_OWNED is what makes the descriptor ReadOnly
so a request body cannot set it in the first place. Without it,
TestDerivedColumnsAreReadOnlyOrTalentScoped fails — verified by mutation, not
by reading.

The two catalogue intents carry PHRASE terms only. A bare "position" or "role"
term scores 10, the same as every reading on that page, and wins the tie on
declaration order — so a create chip would have arrived by evicting
`positions-attention` from the exact ordered result TestPositionsSuggestions
asserts. An offer to create something must not displace the reading a person
actually asked for. Neither declares a Subject, on the precedent of
`position-spec-steps`: a Subject would let the bare query "summarize" match
through matchShape and survive filterOnTopic. Neither declares a Signal, so an
empty composer still reports what the organization needs rather than proposing
paperwork.

Chip text is the coupling with nothing else holding it together: no page
context declares `capabilities`, so every server suggestion dispatches as its
own TEXT and is answered by whichever skill's trigger that text matches. A
renamed chip would open nothing, silently. Asserted on the frontend side.

The down migration drops `employee_role_status` and keeps `english_level`,
which is shared with job_postings.english_required and
job_applications.english_level. Rolled back and re-applied against the
database to prove it, not asserted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJvibeSc1JYXjatankqM1g
2026-09-02 15:29:25 +05:30

3.0 KiB
Raw Blame History

id, name, description, pages, status, version, prompt, flow, triggers, actions
id name description pages status version prompt flow triggers actions
create-employee-role Create Employee Role Record what a worker does — their role, experience, pay and availability — by answering a few questions in the chat.
talent-pool
positions
active 1 Create an employee role employee-role
create an employee role
create employee role
create employee roles
add an employee role
add employee role
new employee role
create a worker role
create worker role
record a role for
add a worker role
create_employee_role

Create Employee Role

Purpose

Record a worker's declared professional role without leaving the page. Owliver asks one question at a time, offers the answers as chips, and reads the whole thing back before anything is written.

This is not Create Position, and the difference is the point. A position is what the ORGANIZATION needs filled — a company, a title, a pay range it will pay. An employee role is what a WORKER says they do — the role they present themselves as, the experience they have, and the pay they are looking for. The two share a vocabulary and nothing else: "3 years" on a position is a minimum an applicant must clear, and the same words here are what this person has.

They are never joined by a column. Supply and demand meet through applications, which already carry the funnel, the interview and the outcome.

Capabilities

  • Understand requests to record what a worker does.
  • Ask who the role is for, and resolve the answer to a real worker profile.
  • Read the role, experience, English level, certifications, desired pay and availability out of a single sentence.
  • Ask only for what the request did not already answer.
  • Offer each answer as a suggestion, so the whole flow can be clicked.
  • Read the role back for confirmation before recording it.

Conversation

Each line is field | question | suggestions | required?. Suggestions beginning with @ come from the application's own data.

@workers is the worker profiles already on screen for this organization. Picking one records the role against that person's profile and email; typing an email address that has no profile yet also works, because a role can be declared before a profile exists. The worker is always asked for and is never assumed to be whoever is typing — an operator records this on somebody's behalf.

  • worker | Which worker is this role for? Type their name or email. | @workers | required
  • role_category | What role do they work as? | @roles | required
  • experience_years | How much experience do they have? | No experience; 1 year; 2 years; 3+ years | optional
  • english_level | What is their English level? | @english | optional
  • certifications | Any certifications they hold? | @certifications; None | optional
  • desired_pay | What pay are they looking for? | $18–$28/hr; $25–$35/hr; $30–$40/hr; Custom | optional
  • availability | When are they available? | @availability | optional
  • notes | Anything else worth recording? | | optional

Actions

  • create_employee_role