Open-source runtime governance for AI agents
Ask before you act.
sayfirst is a local control plane that a program asks before an effect. The daemon answers allow, deny or suspend from a policy file a person can read, over a Unix socket that learns who is asking from the kernel; a suspension waits for a person, and every decision lands in a hash-chained record anyone can verify offline. No cloud account, no token, no port.
- allow
- deny
- suspend
- could not ask
$ uv tool install sayfirst-cli --with-executables-from sayfirst-control-plane --with-executables-from sayfirstd $ sayfirst-daemon up --quickstart $ sayfirst instrument run --pack subprocess --scope local -- python3 my_agent.py
sayfirst is the client's command, from sayfirst-cli; sayfirst-daemon is the control plane, from sayfirst-control-plane.
Apache-2.0 · version 0.3.3 on PyPI · Linux or macOS, Python 3.12, 3.13 or 3.14
The quickstart, recorded
A selection of the quickstart, run against 0.3.3 as published, in an empty account: an allow, the same run denied by one word of the policy, a suspension and the person who approves it, the chain exported with coverage unknown, and the control plane stopped.
Three answers, and one more that is never confused with them
The outcomes are closed at three. When nobody can be asked, that is said as such: it is never written as denied and never as allowed.
| Answer | What the program sees | Exit |
|---|---|---|
| allow | The effect goes ahead, and the decision is recorded. | 0 |
| deny | The effect never starts. The refusal is an exception raised inside the program, at the call — the program's to catch. | 1 |
| suspend | Nothing runs: the call raises, and the request waits for a person until the deadline the policy sets. An approval authorises one execution; a rejection is a denial until that deadline. | 5 |
| could not ask | No control plane answered. An effect that has not started does not start. | 4 |
Exit codes of sayfirst ask and of sayfirst instrument run when a program lets the outcome escape; 3 means the request itself was refused.
How it holds
A governed program puts one question to a local control plane before it does anything that matters: may I do this, with these arguments, as this account?
Who is asking comes from the kernel
The daemon listens on a Unix socket and nowhere else, and learns who is asking from the kernel's own peer credentials. No token, no port, nothing to leak.
The program never decides
It asks, and does only what was allowed. The answer is held as a grant for exactly that question, and stops being honoured when its connection ends, its lifetime runs out, the policy it was issued under changes, or the control plane goes silent for a lifetime.
A person in the loop by construction
An approval is one person's act, taken once, with the deadline the policy file sets. The allow it produces is the one execution that act authorised.
A record anyone can read back
Every decision lands in a hash-chained record that a third party can read back and check offline, with the contract distribution alone and no daemon.
Said plainly
What sayfirst is not, in its own words.
Not a sandbox
A governed program calls the boundary; a program that does not call it is not governed, and nothing here stops it. Pair it with operating-system sandboxing for code you do not trust.
Graded observability, and no higher
A deployment of this version is graded observability — the governed program can write or replace the evidence store, so the record it keeps is one that program could have forged — or unverified, which claims nothing. At neither grade is any claim of proof or of tamper detection made. A third grade, evidence, is defined, and no deployment of this version reaches it.
A mechanism, never a conformity
sayfirst puts the question, answers it from a policy file, and records the answer. It makes no deployment compliant with any law or standard; the EU AI Act page says what it supports and what it leaves to you.
One host, one approver, Python
No fleet, no tenancy, no console. One person approves or rejects a wait; approval by two people is not in the open core. The boundary, and the packs the client ships, are for Python programs.
Where things are
Three repositories, every distribution at 0.3.3 on the package index.
- sayfirst-control-plane The daemon, the contract, the boundary, the policy format, the evidence chain. GitHub · PyPI
- sayfirst-cli The
sayfirstcommand: ask, trace, explain, evidence, approvals, instrument, packs. GitHub · PyPI - sayfirst-governed-agent-demo An agent governed end to end, four capabilities and four answers set in one policy file — the demonstrator, not a product. GitHub