Open source · Apache 2.0

Seven rules on every write.Enforced by the server, not the prompt.

OpenHR checks each change before it's saved. A change that breaks a rule is refused with a sentence your agent can read, and nothing is written.

Why a prompt isn't enough.

  1. 01

    Agents promise leave that isn't there

    A balance read at the start of a conversation is stale by the time the request is raised.

  2. 02

    Kit goes missing between owners

    A laptop issued twice in a spreadsheet has two holders on paper and none in fact.

  3. 03

    Checklists get ticked anyway

    A self-reported "assets returned" box says nothing about the access card still in a drawer.

The rules

Checked on every write.Inside one database transaction.

Leave

A tracked balance can't go negative.

Days are whole numbers of half-day units. A request reserves its days the moment it's raised, so pending and approved leave both count, and approving moves no balance at all.

  • Casual, sick, earned and comp-off are tracked
  • Two overlapping live requests are refused
  • Days can't exceed the span of the dates

Custody

An asset has exactly one holder.

A partial unique index in the schema allows one open assignment per asset, and the service checks first, so the caller gets a sentence instead of a database error.

  • Returning an asset closes the assignment row
  • Poor or damaged returns go to in_repair
  • Retired assets and exited people can't be issued kit

Exits

An exit gates on the record.

Asset clearance reads the custody table, not a tick box. The person becomes exited only when all five steps are signed off, dated on the last working day recorded at the start.

  • Five steps, from knowledge transfer to exit interview
  • assets_returned refused while kit is out
  • The final sign-off, the exit and the status change commit together

Audit

Every change writes an audit row.

The audit row commits in the same database transaction as the change it describes, so the log can't get ahead of the record or fall behind it.

  • Leave, custody, hiring, exits and status changes
  • Read it with get_audit_log, filtered by action or actor
  • Refused calls write nothing, so they leave no row

The other three rules.

  • State machines

    Employment status, leave status and candidate stage move only along declared edges. A refusal lists the legal moves.

  • Append-only history

    A decided leave request is never rewritten. A cancellation leaves the original visible.

  • Exact arithmetic

    Half-day units for leave, paise for money, basis points for rates. No floats anywhere.

  • Named approvers

    Every decision needs an approver, and nobody can decide their own leave.

  • Hires against an open role

    A hire needs an approved requisition with a position still open.

  • Read-only SQL

    run_query accepts SELECT only, on a connection that can't write.

See the rules work.

All three run against the open-source server.

  1. Connect Claude

    Add the MCP server to Claude Code from the openhr folder.

    claude mcp add openhr -s user -- $PWD/.venv/bin/python $PWD/run_mcp.py
  2. Ask for leave Dev doesn't have

    The server refuses it and says how much is left.

    > Book Dev two days of comp-off next week.
  3. Run the smoke test

    It calls every tool against the seeded company and checks that the rules hold.

    .venv/bin/python scripts/smoke_test.py

What each table holds.

Read from scripts/schema.sql in the openhr repository.

TableWhat it holdsHow it changes
employeesDirectory, reporting line, status, band and CTC in paiseStatus moves along the state machine
leave_entitlementsHalf-day units per person, leave type and yearSet with grant_leave_entitlement, never below what's used
leave_requestsDates, units, reason, status and approverDecided requests are never rewritten
asset_assignmentsWho held an asset, from when to whenClosed on return, never deleted. One open row per asset
exits, exit_clearanceReason, last working day and five signed stepsCompletes only when every step is done
audit_logActor, action, object and details, in sequenceAppended with every change

The rules are enforced in the server's write path; one-holder custody is also a database index. Writing to the SQLite file directly bypasses the rest.

Questions.

Can an agent edit a decided leave request?

No. No tool rewrites one. It can cancel a pending or approved request with a reason, and the original stays visible. run_query accepts SELECT statements only.

Does OpenHR run payroll?

No. CTC is recorded so leave liability can be valued, but salary isn't calculated, paid or taxed.

Is the audit log tamper-evident?

Not yet. Rows are written with each change and the tools only append, but a hash-chained log you can verify is planned for v0.3.

Which agent made a change?

Today every MCP write is recorded with the actor mcp. API keys with employee, manager and HR-admin roles, with the audit actor taken from the key, are planned for v0.2. Attri adds sign-in and roles in managed deployments.

Can the leave year start in April?

Not yet. One deployment uses one calendar leave year. A configurable leave year and carry-forward rules are planned for v0.3.

Let your agents handle the HR admin.Keep the rules on the server.

Run OpenHR on a demo company in minutes, or have Attri deploy it inside your environment with sign-in, roles and approval rules.