The working path
Build on it.
Four steps: run the server, point a client at it, run the check-in loop, and decide what to do with what comes back. Run it on hardware you control, with the Homebrew preview for Apple Silicon Macs or Docker Compose.
The home page says what UNITARES is and How UNITARES fits together shows how it is put together. Here is the part you type. Every measured result's status is in the server's evidence ledger.
What you need
- Server
- cirwel/unitares · Apache 2.0 · Homebrew preview or Docker Compose
- Endpoint
- http://localhost:8767/mcp/ · Streamable HTTP · REST at /v1/tools/call
- Contract
- unitares-sdk · for agents you write yourself
- Runtime
- Python 3.14+ (SDK 3.12+) · PostgreSQL with AGE and pgvector · Redis · optional Elixir/OTP coordination
- Suited to
- operators running several long-lived agents on infrastructure they control
§ IRun the server
Choose your installation.
Self-hosted by design. Identity, telemetry, evidence, and policy history never leave it.
Apple Silicon Mac · Homebrew preview
Install Homebrew and Apple's Command Line Tools, then run these commands as your regular macOS user, without sudo. The preview builds from source and packages the server, PostgreSQL 17 with AGE and pgvector, Redis, and the coordination service.
brew update brew tap cirwel/unitares brew trust cirwel/unitares brew install cirwel/unitares/unitares brew pin unitares unitares-lease-plane
Next, follow the tap's first-time setup: create a dedicated database, configure UNITARES, bootstrap the new database, then verify and start the services. Package installation alone does not create the database or start the server.
Keep the packages pinned until you are ready for a backed-up upgrade. Database migrations are explicit; unitares update handles Docker Compose installations only. Native user services run in the Homebrew owner's logged-in session.
Choose one primary installation per Mac. If Docker already runs UNITARES, follow the migration guidance before starting native services; installing the package does not move existing data or clients. Intel Macs and Linux can use the Docker path below.
Docker Compose
That brings up PostgreSQL with AGE and pgvector, Redis, the lease plane that decides which agent holds a shared surface, and the server on loopback. MCP clients connect at localhost:8767/mcp/, the self-hosted dashboard is at /dashboard, and /health answers for readiness checks.
That is the server README's own command: it clones the latest verified release tag rather than the default branch, which can run ahead of it.
Optional models and search
The core record works without a model connection. Configure an endpoint and model explicitly when you want model-backed consultation. Ollama is optional, and fallback is an explicit choice. The Homebrew preview includes database-backed text search; semantic embeddings need a separately managed installation. Docker's full search overlay adds optional semantic search and hybrid ranking.
Try the demos from a source checkout
The second one is the identity guarantee biting rather than being described: governance exchanges each agent's continuity credential for a single-use, request-bound attestation, refuses one agent's attestation when it claims the other's identifier, refuses a captured attestation on replay, and passes the contested surface through an identity-checked handoff before releasing it.
§ IIConnect a client
Client-neutral at the boundary.
Any client that speaks Streamable HTTP MCP can connect. Nothing about the server assumes a model vendor.
The same server answers Codex, Claude Code, Cursor, Goose, Hermes, hosted connectors, and custom hosts. Add it the way that client adds any HTTP MCP server:
{
"mcpServers": {
"unitares": {
"type": "http",
"url": "http://localhost:8767/mcp/"
}
}
}A client without native HTTP support bridges through stdio. Claude Desktop is the usual case:
{
"mcpServers": {
"unitares": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8767/mcp/"]
}
}
}No agent-name header is configured anywhere. The agent identifies itself in its first call, which is the point: a name in a config file is a label, and a label is not an identity.
Non-MCP callers post to /v1/tools/call instead. For Codex and Claude Code, session lifecycle and hooks are packaged separately as unitares-governance-plugin.
§ IIIRun the loop
Four calls, and a decision that stays yours.
session = start_session(force_new=True)
sid = session["client_session_id"]
result = sync_state(
response_text=output,
complexity=0.6,
confidence=0.8,
client_session_id=sid,
)
if result.get("state_summary", {}).get("action") == "pause":
return_to_operator(result.get("next_action"))
record_result(
outcome_type="test_passed" if tests_passed else "test_failed",
prediction_id=result.get("prediction_id"),
client_session_id=sid,
)
state = check_working_state(client_session_id=sid)-
Identify
start_session
Binds every later write to this process instance. A fresh process mints fresh identity; continuity across a restart is declared, never inferred from a reused display name.
-
Claim
sync_state
Records what the process says it did, and how confident it is. Returns a policy action, proceed, guide, or pause, with a named reason and a next step.
-
Ground
record_result
Attaches the real outcome to the claim that predicted it, through the check-in's own prediction_id. Without it, the state estimate rests on the agent's own report.
-
Read
check_working_state
Reads the current policy action without writing anything.
Treat the action, the reason, and the next step as the stable contract; the enforcement record rides alongside them. When the server pauses an agent, it holds that agent's governed writes until recovery, review, the operator, or expiry lifts the pause. Anything the agent does outside the server is the host's to enforce.
The complete tool catalog is callable on every transport. The initial listing is deliberately small: use list_tools to discover names, describe_tool to inspect parameters, and use_tool to call a tool omitted from the initial listing. The checkpoint loop, shared memory, structured review, advisory consultation, and recovery are reachable from the same catalog.
§ IVBuild a long-running agent
The contract is a package, not the server repository.
For an agent that runs for weeks rather than minutes, the SDK handles connection, identity, check-ins, heartbeats, and knowledge participation. Preserve its identity anchor across restarts with identity(agent_uuid=...) rather than minting a new one each run, or its history restarts with it.
A workload registers its own progress source through the unitares.resident_progress_sources entry-point group, with no change to the server's repository at all. A workload never has to touch the server's code.
The lowercase agents in the server repository are reference clients and operational examples. They are built on it and are explicitly not part of it, so read them as worked examples rather than a framework to subclass. cirwel/unitares-resident is the first-party userland skeleton on the same public contract.
§ VWhere it fits
Where it sits beside evals and guardrails.
UNITARES keeps the record. Judgments of correctness and ethics stay with evals, reviewers, and independent evidence. The scope and threat model covers agents that conceal or game signals.
It runs beside what you already have:
Evals
between runs
- Is this model good enough for a defined task?
Guardrails
per action
- Is this action allowed, and is it contained?
UNITARES
continuously
- What has this running process been doing, what evidence supports its claims, who challenged them, and what happened?
Every measured result and pre-registered read is in the evidence ledger, versioned with the code. The research page lists the papers.
- Manual Install, operate, and configure
- Evidence Evidence and limits, in full
- Vocabulary Public glossary
- Contact [email protected]