Skip to content

Data structure ​

Accepted direction · 5 October 2026, roles decided 11 October 2026 · ten tables built 11 October 2026: users, companies, owners, stores, employments, invitations, schedules, shifts, change requests and inbox items; schedules, stores and people are also edited by hand until the assistant does it · store and person descriptions and a store's hourly load built 14 October 2026, read by nothing yet · shift breaks, shift templates and the manager-on-duty check built 14 October 2026 · several shifts a day for one person (split shifts) built 14 October 2026 · a reason with every decline, and who approved a change, built 14 October 2026 · human-readable ids instead of UUIDs built 15 October 2026 · the API's shared core built 15 October 2026, not deployed: the company became a team with roles, and sign-in sessions, password resets, API keys, ownership transfers and a job queue were added, on Prisma 8 · ids in the shared format of every Agentfy product (two words, twelve characters) built 15 October 2026 · break start times, a store's receipts per cashier per hour, and the counts behind the schedule's day view and month sheet built 16 October 2026, not deployed · a manager's rights in their own store, and a record of who changed people, invitations and stores, built 19 October 2026, not deployed · a manager optional for an employee, the owner deciding such an employee's requests, the manager-on-duty check only in a store with a manager, and a leaving manager's waiting requests handed to the owner, built 23 October 2026, not deployed · a catalogue of positions for each team, from which a person's position is chosen instead of a typed job title, built 30 October 2026, not deployed · the rules read from a description stored next to it, built 10 November 2026, not deployed

Shiffty would keep everything about a store — its people, rules, schedules and change requests — in its own database, grouped by company and store; the agent keeps nothing and is given the facts each time it needs them.

A person is one thing, their place in a store is another. Everything a person may see or ask for follows from that place, not from the person.

How it is grouped ​

A team is the owner's business — called a company until 15 October 2026 — and has stores. A store has managers, employees, rules, schedules and one agent. A user is a person who can sign in; an employment is that person's place in one store, with a role, and an employee's employment names the manager they report to. The outline — an intention, not a set of tables:

text
team ────┬─ team_member (user, role owner|admin|member)
         ├─ position (name, description, active|archived)
         └─ store ─┬─ employment (user, role manager|employee, manager, position, hours, skills, status)
                   │     ├─ availability_rule / availability_exception / absence / preference
                   │     └─ agent_conversation (agentfy_conversation_id, grant)
                   ├─ store_rules, shift_template, coverage_requirement, traffic
                   ├─ schedule (period, draft|published, version)
                   │     ├─ shift (employment, date, start, end, template, origin)
                   │     └─ change_request (kind, status, proposal, colleague, decision)
                   ├─ agentfy_agent (agent_id, provisioning_id, status)
                   └─ invitation (name, email, role, link, expires, status)
user ─── inbox_item ("waiting for you")

What each thing is ​

ThingIn one line
TeamThe customer: the owner's business, with one or more stores (a company until 15 October 2026)
Team memberA person in the team with a role there: its one owner, an admin or a member — everyone who works in its stores is a member
PositionOne entry of the team's own list of positions ("Cashier", "Stock handler"): a name, a description for the assistant and whether it is still given to people
Rules of a descriptionWhat the assistant read from the store's, a position's or a person's description, kept until the description changes (since 10 November 2026)
StoreOne shop; almost everything else belongs to a store
UserA person who can sign in
EmploymentA person's place in one store: manager or employee, the manager an employee reports to, a position from the team's list, contracted hours, skills and a status — invited, active or inactive
Availability rule, availability exceptionWhen the person can work: the recurring pattern and the one-off changes to it
AbsenceLeave or sickness: days the person is never scheduled
PreferenceWhat the person would like; honoured when possible
Store rulesOpening hours, rest, maximum hours, shift length, breaks
Shift templateThe standard shifts a schedule is built from
Coverage requirementHow many people of which role each hour needs
TrafficHow busy the store is by hour, and where the number came from
ScheduleOne period for one store, as a draft or as published, with a version
ShiftOne person working one stretch of one day, and whether the solver, the manager or a change request put it there
Change requestWhat was asked, the proposed change, the colleague involved, the decision, and its status
Agent conversationThe link between one employment and its chat with the store's agent, with the current permission to act
Store agent recordWhich agent on Agentfy belongs to this store, and whether it is ready
InvitationAn offer of a role in a store to a named person, with a one-time link, an expiry and a status
Inbox itemOne thing waiting for one person
SessionOne signed-in device: it lets the person stay signed in and can be ended from the server
Password resetA one-time link, sent by email, to choose a new password
API keyA key a program uses instead of a person's sign-in, for one team, with what it may do
Ownership transferThe owner's offer to hand the team to another member, until it is accepted or cancelled
JobWork the API does in the background, at a time or on a schedule

The fields behind the team, the rules and the traffic are listed on Input data; they are a draft there too.

The rules that matter ​

  • A user is a person; an employment is that person's place in one store. What they can see and do comes from the employment — or, for the owner, from owning the team.
  • Each person has exactly one place (11 October 2026): the owner their team, everyone else one store. There is no list of teams or stores to pick from in the application.
  • An employee reports to a manager of the same store, or to no manager. A manager is optional since 23 October 2026 (owner's decision; until then an employee could go without one only while the store had no manager). Only the owner leaves an employee without a manager; a manager moves their store's employees to another manager of it, never to none. A manager's people are the employments that name them; managers answer to the owner.
  • Managers and employees join only through an invitation from the owner (or, since 15 October 2026, an admin of the team), which arrives by email. Since 19 October 2026 a store's manager also invites employees to their own store. Owners are the only people who sign up on their own.
  • Access to an agent is by employment, not by user. A person talks to the agent of the store they work in, with the rights of their role there.
  • A team is linked to an Agentfy team and a store to an Agentfy agent by Shiffty's own identifiers. See Agentfy integration.
  • All state lives in Shiffty. Schedules, requests and decisions are kept here; the agent is handed the facts through tools every time, and Shiffty relies on nothing the agent remembers.
  • Agentfy credentials live in the API's environment, never in the database.
  • The local stand-in for Agentfy is gone (14 October 2026, the owner's decision). From 13 October the API could play Agentfy itself on a developer's machine and kept that stand-in's operations, conversations, runs, events and messages in five tables of its own; the migration 20261016T1038_drop_fake_agentfy (16 October 2026) drops them. The tables for the real integration stay. See Agentfy integration.
  • Who decides a change request (11 October 2026, from the decision that each employee has a specific manager): an employee's request goes to their own manager; if that manager has not joined yet, to any manager of the store. An employee with no manager, an employee of a store with no active manager, and a manager send their requests to the owner — since 23 October 2026, when a manager became optional; until then an employee with no manager went to any manager of the store. When a manager is deactivated, the requests waiting for them go to the owner, and if they were the store's last active manager so do those waiting for any manager. So a request always has someone to decide it. Nobody decides their own request.
  • Times are the store's. A store has a time zone (11 October 2026). Opening hours and the date and times of a shift are the store's wall clock; "today", the current week and which week is live are worked out in the store's time zone — never the server's and never the viewer's. A shift's hours are the time that really passes, so a shift on the night the clocks change is an hour shorter or longer.
  • Employee data is sensitive. Availability, absences and the reasons people give are personal. Where this data lives and who sees it is still an open question that has to be settled before any pilot.

Where it would be kept ​

PostgreSQL on Neon, reached through Prisma — Prisma 8 since 15 October 2026, with the schema written next to each part of the API and its migrations in TypeScript. The API in the repository is set up this way; on a developer's machine it uses a local PostgreSQL instead.

The shared core (15 October 2026, SHIF-25; not deployed). The API now stands on the same core as our other products: the tables have lower-case names, every time is stored with its time zone, and the company and its owner became a team and its members. One migration moved every existing row into the new tables, keeping every id; secrets that the old key had sealed — an agent's credential, a chat's stored context — could not be carried over and are issued again when next needed. The tables the core adds:

  • User — also has a platform role (admin opens the Settings of the whole platform, not a role in any team) and a status.
  • Team — the owner's business: a name and a status.
  • Team member — a person in a team: owner (exactly one), admin or member, and whether they are still in it or have left. Everyone who works in the team's stores is a member; an admin may do what the owner does with the team's people and invitations, but not hand the team over.
  • Session — one signed-in device. Signing in returns a short-lived access token and a refresh token; each refresh replaces the refresh token, and using an old one again ends the whole session, since someone else has it. Signing out, or setting a new password, ends sessions on the server.
  • Password reset — a one-time link for one hour, sent by email; only its fingerprint is kept.
  • API key — for a program instead of a person: one team, a name, what it may do (reading the team, for now), when it expires and when it was last used; only a fingerprint is kept, and the key itself is shown once. Handing the team over ends the old owner's keys.
  • Ownership transfer — the owner offers the team to another member; when they accept, they become the owner and the old owner an admin.
  • Job — background work, kept in the database so that several copies of the API do not do it twice: reloading Settings and keeping the agents' memberships in step run on it.

Since 11 October 2026 ten tables exist, created by migrations, and nothing else on this page does. Six of them replace the five of 6 October (companies with members without a role, and employees who could not sign in); the test data of that time was dropped, as the owner allowed. Schedules and shifts were added the same day, then change requests and inbox items. On 14 October 2026 a table for a store's hourly load was added.

  • User — a name, an email and the password, kept only as a hash. An invited person has no password until they accept.

  • Team (a company until 15 October 2026) — the owner's business: a name.

  • Team member (until 15 October 2026 only the owner, as the company member) — see above.

  • Store — a store of the team: a name, an address, opening and closing time (one opening per day, closing the same day), its time zone (a standard name such as Europe/London, required), a description, a sort order and a status, active or inactive. A new store goes to the end of the owner's list. The description (since 14 October 2026) is plain text of up to 4,000 characters, meant as context for the assistant later — the store's format, location, customers, quirks; nothing reads it yet.

  • Store hourly load (14 October 2026) — the usual number of till receipts in one hour of one day of the week, for one store: Monday to Sunday, one value per hour slot from opening to closing time, each slot named by its start hour (17 is 17:00–18:00; a part hour at the end is a slot too). A whole number, zero or more; a slot nobody filled in is unknown and not stored. Values outside the current opening hours are kept but not shown, so changing the hours back brings them back. Since 16 October 2026 the schedule's day view reads them (below); there is still no forecast, and nothing builds a schedule from them. Next to them the store keeps its receipts per cashier per hour (16 October 2026): how many till receipts one cashier handles in an hour, a whole number from 1 to 10,000, 80 unless changed, edited by the same people as the hourly load. One row per store, day and hour, rather than one block of numbers per store, so the database itself checks each value and a later reader can ask for one hour.

  • Employment — a person's place in one store: manager or employee, the manager an employee reports to (the database itself refuses a manager from another store), a position from the team's list (a free-text job title until 30 October 2026, see Position below), weekly contracted hours, a description and a status, invited, active or inactive. One per person. No skills yet. The description (since 14 October 2026) is plain text of up to 4,000 characters, meant as context for the assistant later — strengths, skills, limits; nothing reads it yet. Only those who may change it see it: the owner, and a manager for the employees of their store. A person never sees their own description (a privacy choice made while building, for the owner to confirm; see open questions).

  • Position (30 October 2026, the owner's request) — one entry of the team's own list of positions, which the owner fills in: a name of up to 100 characters, unique in the team whatever the case and the spaces around it ("Cashier" and " cashier" are one position), an optional description of up to 2,000 characters — context for the assistant, as a person's is — and a status, active or archived. One list for the whole business, shared by all its stores (the owner's answer of the same day). Only the owner adds, renames, describes and archives positions; managers and employees read the active ones. There is no delete: an archived position is not given to anyone new and stays with the people who hold it. A person has one position or none; it is chosen from the list, never typed — in the invitation and in the person's dialog. The assistant's people tools give each person's position with its description, and the solver hands a position's description to the agent once, with the people who hold it, as it does a person's description. The titles typed before were kept: when the list was added, each team's distinct non-empty titles became its active positions — spaces trimmed, the same word in a different case counted once, the spelling most people had kept — every person was given theirs, and the free-text title was removed. An empty title gave no position.

  • Rules of a description (10 November 2026, the owner's request) — for one description, the store's, a position's or a person's: the rules the assistant read from it, each with the words it came from, or "no rules" with a reason, and a fingerprint of the text they were read from. One per description, replaced whenever the assistant saves it again; rules whose fingerprint is not the description's current one are stale and read again before the next plan. The API never writes them on its own: it has no language model (Agent and solver).

  • Invitation — to a team and one of its stores, for a named person and email, as manager or employee, with an expiry seven days out and a status: pending, accepted or withdrawn (revoked, since 15 October 2026); an expired one is shown as expired. Only a fingerprint (hash) of the link is kept. Since 15 October 2026 the link is sent by email, and still shown once to the person who invited, to hand over if the email does not arrive; inviting the same address again replaces the pending invitation. Inviting creates the person and their employment as invited; accepting sets the password, makes the employment active and the person a member of the team; withdrawing removes the invited person again.

  • Schedule — one store's week, Monday to Sunday (the outline's "period" is a week, and there is no version), as a draft or published, with a short note such as "Made by hand · not sent yet" and the moment it was published. One per store and week.

  • Shift — one person on one day of a schedule's week: start and end time, the area (sales floor, checkout or manager on duty) and who put it there (the demo data, a manager, a request or the assistant). The person must work in the schedule's store, and the database itself refuses anything else. A shift has to fall within the store's opening hours and end after it starts, so no shift runs past midnight. Since 14 October 2026 a person may have several shifts on one day — split shifts, such as 12:00–14:00 and 18:00–20:00 — up to three, and one person's shifts on one day may not overlap: one ending at 14:00 and the next starting at 14:00 is allowed. The database itself refuses an overlap; the limit of three is the API's. Since 14 October 2026 a shift also has a break in minutes — 0 to 180, in steps of 5, shorter than the shift; existing shifts got none. Since 16 October 2026 a break may also have a start time: then the whole break, from its start for its minutes, has to lie inside the shift, and a shift without a break has no start — the API refuses anything else, and the database itself refuses a start outside the shift. A break without a start is "time not set", as every break was before that day; the screens say "1 h break 13:00–14:00" where the time is known. An approved change of hours keeps the break's start while the break still fits the new times and drops it otherwise; a swap carries it with the shift.

  • Shift template (14 October 2026) — a store's named preset for a shift, such as "Morning 06:00–10:00" or "Day 09:00–18:00 · 60 min break": a name, start and end, a break, an area or none, and its place in the store's list. Up to ten per store, within the store's opening hours when saved. It is only a preset: a shift made from it copies its values and keeps no link back, so changing or deleting a template changes no shift. If the store's hours change later, a template left outside them is kept, marked, and not offered until it is edited. Since 16 October 2026 a template's break may have a start time too, under the same rule as a shift's, and a shift made from the template copies it.

  • Change request — one person's request to change one of their own shifts, in their store: the kind (time off, time off with cover, swap, change hours), the change itself as a structured proposal — remove the shift, give it to a colleague, move its times, or swap two shifts, each with the shift as it was when asked; time off and time off with cover are about the day, so on a day with several shifts the request takes every one of them (14 October 2026), while a swap and a change of hours stay about one shift — the colleague asked (if any) and their answer, who decides (a named manager, any manager of the store, or the owner), a title, a one-line summary, the requester's own words, a chip ("Covered", "Fits rules", "No overtime", "Needs a call", "No cover"), the lines that were checked, who decided, when and how — by a person, automatically or, later, by the assistant — and, for a decline, the reason they gave (14 October 2026). The status goes waiting for the colleague → waiting for a decision → approved or declined; while it waits, the requester can withdraw it (withdrawn, with when). "Now" and "If approved" are worked out from the proposal, not stored. The outline's change request belongs to a schedule; the built one belongs to a store and can span two weeks (a swap).

  • Inbox item — one thing waiting for one person's answer: a colleague's ask to cover or swap (linked to the request), or a published week of their own shifts to confirm. It keeps the answer and when it was given. In the outline it belongs to the user; the built one belongs to the employment.

What happens to a request. A request that needs a colleague waits for them first. A colleague can say no without a reason; the request then ends as declined, with nobody having decided, and the requester sees that in My requests. What happens after that is not designed, so nothing more is built. A request nobody covers goes straight to whoever decides. Approving checks the shifts again and changes them in one step, all or nothing: if the shifts are no longer what the request was about, or a rule would now break, nothing changes and the person deciding is told the schedule changed. Declining changes nothing on the schedule and always says why (the owner's rule, 14 October 2026): the manager or the owner writes a reason of 3 to 500 characters, the requester sees it on their request, and undoing the decline clears it. A colleague's no still needs no reason. Every decision says who made it (the owner's rule, 14 October 2026): it records whether a person decided — and who — or it was automatic or the assistant's, and the request, the lists of requests, the schedule's approved changes, the shift dialog and My shifts say "Approved by Daniel Okafor" or "Approved automatically". Today every decision is a person's: nothing approves automatically and the assistant decides nothing; the other two ways are recorded so that such a decision, once it exists, is never shown as someone's. Decisions made before 14 October 2026 count as a person's when one is recorded, otherwise as automatic. A decision can be undone while the week is not over: undoing an approval puts the shifts back as they were, and is refused if they were changed again since.

What is checked is not the assistant yet. The demo requests carry the prototypes' check lines word for word. A request made through the API gets the API's own fixed checks: within opening hours, at least 11 hours' rest (from the last shift of one day to the first shift of the next; the gap inside a split day is not rest), weekly hours against the contract, no overlap with the person's other shifts that day and at most three a day, the same store — and, since 14 October 2026, a warning when approving would leave part of a day's opening hours with no manager on duty that is covered now ("Approving leaves no manager on duty on Wed, Oct 21, 16:00–20:00"). The lines are worked out when the request is made and kept with it; they are not worked out again when it is decided. There is no solver and no coverage rule behind them; the assistant that would write these lines comes later.

Whether a week is live or past is not stored. It follows from the dates and the store's today: a week whose Sunday is over is past, a published week that contains today is live, a later published one is waiting, and a draft stays a draft until its week is over. A shift's hours are the hours worked: end minus start, minus the break (14 October 2026); a day's hours are the sum of its shifts. Everything that counts hours uses them — a person's week against their contract, the warnings and request checks about weekly hours, the totals of a week, a store and a person, and what the assistant's tools return (until 19 October 2026 also the assistant demo's texts). A person's overtime in a week is the hours above their contracted weekly hours. Nobody without contracted hours has overtime.

A manager on duty for all opening hours (14 October 2026). For every day of a week — every day counts as open, since a store has one opening time for all days — the shifts in the manager on duty area together have to cover the store's opening hours; breaks are ignored, because the person is in the store. The API works out the stretches that are not covered, in the store's wall clock, and returns them with the week to the owner and the managers ("Tue: no manager on duty 06:00–08:00, 20:00–22:00"). It is a warning only: nothing is refused because of it, publishing included — the owner's principle that rules here guide and do not forbid. It counts the shift's area, not the person's role: the area says who is in charge for that stretch, so a manager working a checkout shift does not count and anyone put in the manager-on-duty area does. That choice is ours, for the owner to confirm. Since 23 October 2026 the check applies only to a store with an active manager: a store the owner runs alone with employees shows no gaps (rather than a gap on every day), its requests get no manager-on-duty warning, and the solver plans no duty there. Also our choice, for the owner to confirm.

The day view's counts (16 October 2026). For every hour slot of every day of a week — the same slots as the hourly load — the API returns with the week four numbers for the whole store, whatever a screen filters: the usual receipts for that weekday and hour (unknown stays unknown); the tills needed, the receipts divided by the store's receipts per cashier and rounded up, at least one while the store is open, unknown when the receipts are; how many people are on checkout — in shifts of the checkout area — and how many are on shift at all. People are counted in person-hours: someone counts in an hour for the part of it they are there (a shift from 09:30 is 0.5 in the 09:00 hour), minus the part of a break with a known start that falls in it. A break whose start is not set is taken off nowhere, so that person counts as there for the whole shift, and the day says how many such breaks it has. The counts are rounded to one decimal, and an hour with fewer on checkout than tills needed is marked. The counting rule is ours, for the owner to confirm. It only shows: nothing is refused and nothing is scheduled because of it.

The month sheet (16 October 2026). For a calendar month the API returns every Monday-to-Sunday week that touches it — four to six — each with its status (published, draft or no schedule); a row for each person, as in the week, with their shifts day by day and the approved changes; and for each person within the calendar month only: shifts, worked hours, days off — days in a week that has a schedule on which they have no shift — and the contract to compare with: weekly hours × days of the month ÷ 7, so a week the month covers only partly counts by its days in it (October 2026: 31 days, 4.43 weeks). That rule is ours, for the owner to confirm. The month is read only; the owner and the store's managers see it, an employee does not.

What the API shows. The weeks of a store, one week as one grid of everyone the asker may see — managers first, then employees, each row with the person's role, whom they report to and whether the asker may edit it (14 October 2026; until then the employees and the managers were two separate grids) — in which a person's day lists all of its shifts, with the requests drawn on them, the week containing today, each person's own shifts, and "this week's" hours for stores and people. With each week it also returns the days without a manager on duty, and for each store its shift templates (the owner and the store's managers only). For the assistant's scripted demo (removed from the app on 19 October 2026; no screen calls this since) the API also answers, read-only, which colleagues could take one of the asker's shifts — cover it (a cover takes every shift the asker has that day, and a colleague who works that day at other times can take it), or swap it for one of theirs that week (offered only for a day with one shift, since a swap moves one shift) — under the same fixed checks as a request, with no warning. An employee sees their own shifts in published weeks only. A manager sees the schedules of their store, and the owner sees those of every store.

Editing by hand ​

The owner decided on 11 October 2026 that every change would later go through the assistant, and that until the assistant is real every change must also be possible by hand. What follows is built (11 October 2026, not deployed) and is that stopgap: the assistant is meant to take these actions over, under the same rules.

  • Who edits schedules. The owner edits every row of any store's weeks, the employees' and the managers'. A manager edits their own store's employees only: the managers' rows are the owner's, so a manager's own changes go to the owner as requests. A manager sees the managers' rows read only, as before the one grid. An employee edits nothing. Someone of another company, or an employee, gets "not found"; a manager on a manager's row is told it is the owner's.
  • Weeks. A week is created empty or as a copy of the week before (each shift a week later); a manager's copy brings only the employees' shifts. Publishing is one action for the whole week, managers and employees alike. People who have left, and shifts outside today's opening hours, are left out of a copy. A week is edited while it is a draft and published once (which records when). A published or live week can still be edited by the same people. A past week cannot be changed.
  • Shifts. A shift is added, changed or deleted in the week's grid: the person is the row and the day the column; the times, the break — and, if wanted, when it starts — and the area change. The store's shift templates are offered: picking one fills the times, the break and the area, which can still be changed, and an empty day can be filled with a template in two clicks. A day of a person shows all its shifts, each opening on its own, and offers another one — also from a template — while the day has fewer than three. The same rules as everywhere else hold, in the store's time zone: within opening hours, inside the week, someone working in the store, up to three shifts a day that do not overlap. Less than 11 hours' rest and more hours than the contract are shown as warnings and do not block. Anything written by hand is recorded as put there by a manager, the owner's edits too.
  • A shift with an open request is locked. While a request about it waits for a colleague or a decision, it cannot be edited or deleted; deciding or withdrawing the request frees it. So a decision never meets a shift that changed under it without noticing.
  • Requests by hand. An employee — and a manager, for their own shifts — makes a request from a form: one of their shifts this week or next, what they need (time off, a colleague to cover, a swap with a colleague's shift that week, different hours) and an optional reason, kept as their own words. Time off and a cover take every shift of that day, and the form says so. The colleagues offered are the store's active people with the same role. It goes through the same checks and the same rule of who decides as before. A request can be withdrawn until it is decided: nothing on the schedule changes, the colleague's open ask disappears, and it leaves the approver's list and the counts.
  • Shift templates (14 October 2026). The owner, for any store, and a store's managers, for their own, add, change, reorder and delete its templates — up to ten — in a "Shift templates" section: on the store's page for the owner, on Schedules for a manager. An employee has none to see.
  • Stores. The owner edits a store's name, address, opening hours, time zone, status and description; since 19 October 2026 (the owner's decision) the store's managers edit its opening hours and description too, and nothing else of it. The hourly load is edited by the owner and by the store's managers, because they know their store's traffic (14 October 2026, for the owner to confirm), and so is the receipts per cashier per hour next to it (16 October 2026). New hours are refused while any live or upcoming shift would fall outside them, and the shifts in the way are named. A new time zone keeps every shift at its wall-clock time — 08:00 stays 08:00, now in the new zone.
  • People. The owner edits a person's position, weekly hours, manager (a manager of the same store, as decided on 11 October 2026) and status. A manager edits the position and weekly hours of the people who report to them; since 19 October 2026 (the owner's decision) also the manager — another manager of the same store — and the status of any employee of their store, never of a manager and never their own, and they invite employees to their store and withdraw those invitations. The description (14 October 2026): the owner writes it for any manager or employee, also when inviting them; a manager for any employee of their store, not for managers and not for themselves; the API refuses anything else. Role and store do not change by hand. Making someone inactive keeps their history: their shifts up to today and their requests stay. Their shifts after today are listed first and removed only after the owner confirms. It is refused while a request is open on one of those shifts, and for a manager while people still report to them. Positions (30 October 2026): the owner keeps the team's list on People → Positions — adds a position, changes its name or description, archives it and brings it back; a person's position is then picked from the active ones, or none. A manager picks one for the people who report to them, as they changed the title before, and cannot add or change positions.
  • Who changed it (19 October 2026). Every change to a person, an invitation or a store — by the owner or a manager, on a screen or later through the assistant — is recorded: who, their role, the kind of change (deactivated, reactivated, new manager, invited, withdrawn, edited) and the names of the fields that changed, never their values. Since 30 October 2026 so is every change to a position (added, edited, archived, brought back). Nothing shows the record on screen yet.

They are not deployed anywhere; see What is built.

Identifiers ​

Every record a person or a partner can point at has a readable id, and that id is its primary key (15 October 2026, SHIF-26; not deployed): store-calm-rivers-k8m4p2d7n9x3, not 0b5c…-…. There is no second id next to it: the readable one is the row's key and every reference to the row uses it.

The form is prefix – adjective – noun – twelve characters, lower-case ASCII only, the same in every Agentfy product since 15 October 2026 (SHIF-28; the format below). The prefix says what the record is: it is the name of the record's model in the database schema, whole, in lower case with its words joined by hyphens, never shortened (15 October 2026, SHIF-27, the same rule in every Agentfy product). Before SHIF-27 some prefixes were abbreviations (org, emp, sched, tmpl, req and others); one migration replaced each of them by the full name and kept the rest of every id, words and characters, as it was. One word is the exception: Organization is written org (24 October 2026, SHIF-40, the owner's decision for every Agentfy product), so OrganizationConversation gets org-conversation; the migration 20261024T1456_org_id_prefixes renamed the organization-… ids that existed, again keeping the rest of each id. An organization-… id is no longer recognised: it is simply not found.

RecordModelPrefix
Team (a company until 15 October 2026)Teamteam
Team memberTeamMemberteam-member
UserUseruser
StoreStorestore
EmploymentEmploymentemployment
InvitationTeamInvitationteam-invitation
ScheduleScheduleschedule
ShiftShiftshift
Shift templateShiftTemplateshift-template
Store hourly loadStoreHourlyLoadstore-hourly-load
Change requestChangeRequestchange-request
Inbox itemInboxIteminbox-item
SettingSettingsetting
Agent conversationOrganizationConversationorg-conversation
Chat messageOrganizationChatMessageorg-chat-message
SessionAuthSessionauth-session
Password resetPasswordResetpassword-reset
API keyTeamApiKeyteam-api-key
Ownership transferTeamOwnershipTransferteam-ownership-transfer
JobJobjob
Rules of a descriptionDescriptionRuledescription-rule

The last five came with the API's core (15 October 2026, SHIF-25).

  • The words are for people, not for uniqueness. They come from the lists of the human-id package, version 4.1.3 (MIT licence), as they are — 200 adjectives and 300 nouns, the same in every Agentfy product. The last twelve characters are random, drawn by nanoid 3.3.11 (MIT) from a cryptographic source over 32 letters and digits that cannot be confused (no i, l, o, u): 60 bits, about 76 with the words. With a million records of one kind a new id meets an existing one about once in 10¹⁷.
  • Uniqueness is the database's. The primary key refuses a duplicate; the API then draws a new id and writes again, up to five times. An id is drawn once, by the API, when the record is created, and never changes; it holds no name, date, role or secret.
  • A prefix can contain hyphens, so an id is never cut at its hyphens to find it. The API compares the start of an id with the list of prefixes, the longest first: store-hourly-load-… is a store hourly load, never a store.
  • The API checks the kind of every id it is given. A store's address with an employment's id in it, or anything that is not an id at all, is answered 400 before anything is looked up. A well-formed id still goes through the usual permissions: someone else's store looks the same as one that does not exist.
  • Records made before 15 October 2026 got new ids in one migration, which rewrote every reference to them, including those inside a request's stored proposal. Ids that had already left Shiffty were kept: a company, store, employment or owner that Agentfy knows by its id, a chat whose stored context is sealed to its id, and a request made through the assistant. Those few keep their old UUID and are still accepted; nothing new gets one. An old address with a rewritten UUID in it now finds nothing.
  • The SHIF-27 rename changed the prefix of every id that had an old one, also of those Agentfy already knew. Shiffty has no production, so such ids existed only in local databases; Agentfy (or, until 14 October 2026, its local stand-in) still knows such a record by its old id. For a local database the way back is make seed. A conversation context sealed to an old id is cleared.
  • Company became Team (15 October 2026, SHIF-25), so the prefixes followed the renamed models: the core migration turned company-…, company-member-… and invitation-… into team-…, team-member-… and team-invitation-…, in every id and every reference to it, the way SHIF-27 did, and kept the rest of each id. As there, Agentfy's own records and the replayed provisioning request were left as they were, and a local database connected to Agentfy is reset with make seed.
  • Not changed: what Agentfy calls things (its team, agent and chat ids), the keys that make a repeated call safe (idempotency keys), audit records, access grants and the agent's credentials keep their own identifiers.

The format of an id ​

Built 15 October 2026 (SHIF-28), not deployed · the same format in Agentfy, Bonsite, Securify, Shiffty and Store Deputy, by the owner's rule of 15 October 2026

PartWhat it is
Form<prefix>-<adjective>-<noun>-<suffix>, e.g. user-calm-rivers-k8m4p2d7n9x3
Prefixthe model's whole name in kebab-case, the word Organization written org, from the table above
Adjective, nounone word each from the adjectives and nouns lists of human-id 4.1.3, in lower case; no verbs or adverbs, no words of our own; a word with anything but a–z would be dropped (4.1.3 has none)
Suffixexactly 12 characters of 0123456789abcdefghjkmnpqrstvwxyz (Crockford's base32 in lower case)
Randomnesscryptographic: crypto.randomBytes, the suffix through customRandom of nanoid 3.3.11
Versionshuman-id 4.1.3 and nanoid 3.3.11, pinned exactly in api/package.json
Whereapi/src/slices/setup/id/domain/idFormat.ts: formatId, isCanonicalId, isLegacyId, LEGACY_ID_FORMATS

New ids are made only in this form, by formatId, and nothing generates an id any other way. The API accepts an id given to it in this form with the record's own prefix and both words from the lists, or in one of Shiffty's earlier forms, which it only reads:

Earlier form (LEGACY_ID_FORMATS)ExampleIssued
prefix – adjective – noun – six characters, with Shiffty's own word lists (human-id minus 13 adjectives and 6 nouns, idWords.ts)store-calm-rivers-k8m4p215 October 2026, SHIF-26 and SHIF-27, until SHIF-28
a UUID kept by the SHIF-26 migration0b5c…-…before 15 October 2026

Ids already issued are not rewritten: an old one keeps working and a new record gets the new form. A form with a verb, a suffix of another length or of hex digits, or a word that is not in the lists is refused with 400, unless it is one of the earlier forms above.

The same check in every product: with a source that gives the bytes 0, 1, 2, … in order, formatId('user', source) is user-afraid-swans-nmkjhgfedcba. The adjective is drawn first, then the noun (each by one byte or two read as a number, masked to the bits the list needs and drawn again when out of range), then the suffix (19 bytes in one request, read by nanoid from the last one back).

Not decided ​

  • Where employee data is hosted and who may see which part of it.
  • How long requests, decisions and old schedules are kept.
  • Whether a person may later work in more than one store, or a manager run several, and what that does to "one person, one agent".
  • Decided 20 October 2026: the owner has an agent of their own, one per company, keyed by the company's id; its conversation names the owner by their user id instead of an employment (Agentfy integration). Not decided: what happens to the owner's conversation when the company changes hands.
  • Legal break rules and shifts that run past midnight: neither can be stored yet. When a break falls can be stored since 16 October 2026, but nothing proposes it.
  • Whether shifts on one day need a minimum gap between them (none now), whether three a day is the right limit, and whether a person may take only part of a split day off (time off and a cover take the whole day now).
  • Whether the manager-on-duty check should count the area (as now) or the person's role, whether a store can be closed on some days (every day counts as open now), and whether a request's checks should be worked out again when it is decided.
  • Whether overtime is counted per week against contracted hours (as now) or by another rule, such as a legal maximum.
  • How long decided and withdrawn requests and answered inbox items are kept (answered items leave the list after seven days for now), and whether other requests may change more than one shift (time off and a cover of a split day already do).
  • Whether a manager's copy of a week should also bring the managers' shifts, what an inactive store means for its schedules, and whether a person may move to another store or role (not possible by hand now).
  • How long a sign-in lasts. Sign-in is Shiffty's own (decided 6 October 2026), with password hashes and, since 15 October 2026, sessions in this database; the lifetimes — 15 minutes for an access token, 30 days for a session without use — are the core's defaults, not the owner's choice. See Screens.

Where to go next ​

  • Screens: what people would see of this data, and where it is edited by hand.
  • Agentfy integration: the agent, its conversations and the permission it acts with.
  • Input data: the fields a store would provide.
  • Overview: the application and the API.