Skip to main content

unitares-sdk

Build your own Unitares resident agent. A resident is a long-running (or scheduled) process that checks in to governance, carries an EISV state vector, and participates in the shared knowledge graph. Vigil, Sentinel, and Chronicler are reference implementations.

Install

pip install unitares-sdk

To install the SDK as it shipped with a specific server release, use the Git URL instead:

pip install "unitares-sdk @ git+https://github.com/cirwel/unitares@v2.19.0#subdirectory=agents/sdk"

Replace @v2.19.0 with another server release tag only after checking the compatibility map. Pin a commit SHA when reproducing a specific development build.

Or from a checkout of the unitares repo:

pip install -e agents/sdk

Releases are tag-driven. Pushing an sdk-v* tag whose version matches version in pyproject.toml runs .github/workflows/publish-sdk.yml, which builds and uploads via PyPI Trusted Publishing — OIDC, no API token. The SDK carries its own version series, independent of the server's. See the release process.

Links in this file are absolute on purpose: pyproject.toml uses it as the package readme, so PyPI renders it outside the repo and relative paths 404.

The package is standalone — it talks to a UNITARES server over MCP/REST and never needs the server codebase importable. Fully typed (py.typed).

The 30-line resident

from pathlib import Path
from unitares_sdk.agent import CycleResult, GovernanceAgent
from unitares_sdk.client import GovernanceClient


class MyResident(GovernanceAgent):
    def __init__(self):
        super().__init__(
            name="MyResident",
            mcp_url="http://127.0.0.1:8767/mcp/",
            persistent=True,               # protects from auto-archive
            refuse_fresh_onboard=True,     # explicit bootstrap required
            cycle_timeout_seconds=60.0,    # hard cap on one cycle
            log_file=Path("/tmp/my_resident.log"),
            max_log_lines=10_000,
        )

    async def run_cycle(self, client: GovernanceClient) -> CycleResult | None:
        # Do your work here. Return a CycleResult to trigger a check-in,
        # or None to skip (useful for "nothing to do this tick" paths).
        count = await self.do_scan(client)
        if count == 0:
            return None
        return CycleResult(
            summary=f"scanned {count} items",
            complexity=0.2,
            confidence=0.9,
        )


if __name__ == "__main__":
    import asyncio
    asyncio.run(MyResident().run_forever(interval=60))

First run: UNITARES_FIRST_RUN=1 python my_resident.py — this mints the identity and stores its UUID anchor at ~/.unitares/anchors/myresident.json. Every subsequent run resumes that anchor automatically. Never delete anchors: if you do, set UNITARES_FIRST_RUN=1 again to re-bootstrap (you will get a new UUID).

Extension points

The base class handles MCP connect, identity resolve, check-in, heartbeat, log rotation, state persistence, and graceful shutdown. Override these to extend behavior:

Hook When Signature Return
run_cycle(client) Each iteration. The only required override. (client) -> CycleResult | None CycleResult or None
on_after_checkin(client, checkin_result, cycle_result) After each check-in, even on pause/reject. Use for EISV logging, coherence tracking, state writes that need the server response. All three args typed via unitares_sdk.models. None
on_verdict_pause(client, checkin_result, cycle_result) When the server returns a pause verdict. Use for self-recovery. Same arg signature as on_after_checkin for consistency. True to retry the check-in once; False to let VerdictError propagate.

Hook exceptions are logged and swallowed — a broken hook cannot take down a cycle. asyncio.CancelledError always propagates.

Do not override _ensure_identity, _handle_cycle_result, or _send_heartbeat — those are load-bearing and change across versions.

Constructor reference

Parameter Type Default Purpose
name str required Agent display name; drives anchor path (~/.unitares/anchors/<name_lower>.json).
mcp_url str http://127.0.0.1:8767/mcp/ Governance MCP endpoint.
persistent bool False Stamp the persistent + autonomous tags on fresh onboard. Set True for long-running residents.
refuse_fresh_onboard bool False Require UNITARES_FIRST_RUN=1 to mint a new identity. Set True to prevent silent ghost-forks.
cycle_timeout_seconds float | None None Hard cap on a single run_once / run_forever iteration. MCP's anyio task group can hang on session.initialize if the server flakes — use 60–120s.
log_file Path | None None Log file to auto-trim after each cycle (in both run_once and run_forever, in a finally block so it fires on error and timeout too). Leave unset if launchd / logrotate owns rotation.
max_log_lines int 10_000 Trim threshold for log_file.
state_dir Path | None <repo>/data/<name_lower> Default directory for state persistence. Used when state_file is not set.
state_file Path | None None Explicit cross-cycle state path. Takes precedence over state_dir / "state.json" when set.
parent_agent_id str | None None Forked-from UUID. Forwards to server on fresh onboard.
spawn_reason str | None None Registered values: subagent, dialectic_reviewer, dispatch, compaction, explicit, new_session. Unknown values are accepted but receive no live-parent exemption.

Lifecycle shapes

  • Daemon: asyncio.run(agent.run_forever(interval=60)) — loops forever with heartbeats when idle. Reference: agents/sentinel/agent.py.
  • Scheduled: asyncio.run(agent.run_once()) under launchd / systemd cron. References: agents/chronicler/agent.py, agents/vigil/agent.py.

Both shapes respect cycle_timeout_seconds and auto-trim log_file.

State persistence

  • self.save_state(d: dict) and self.load_state() -> dict let your run_cycle carry data across iterations. Writes are atomic (os.replace).
  • Non-JSON-serializable values (e.g. datetime, Path) are coerced to their str() representation on save rather than raising TypeError. You get a lossy round-trip, not a silent state-write failure.

Identity rules

  1. The agent's first MCP call (onboard or identity) is the sole source of identity. Do not set identity out-of-band.
  2. UUID is the ground truth. client_session_id and continuity_token are cache keys for ephemeral clients; residents don't need them.
  3. Anchors live at ~/.unitares/anchors/<name_lower>.json. One anchor per host per role. The file contains agent_uuid and is written atomically.
  4. Never silent-swap an identity. If the anchor is missing and refuse_fresh_onboard=True, _ensure_identity raises IdentityBootstrapRefused — the operator must explicitly set UNITARES_FIRST_RUN=1 once to mint a new one.

Not in the SDK (on purpose)

  • agents/common/findings.py and agents/common/config.py are internal to the reference residents in this repo. If you need findings-posting in your own resident, vendor the helper or POST to /api/findings yourself — the REST contract is the public surface.
  • The violation taxonomy is server-owned vocabulary (src/violation_taxonomy.yaml); consume it via the /v1/taxonomy endpoint rather than importing server code.
  • Watcher (agents/watcher/agent.py) uses a different execution model (sync, hook-driven, one-shot per tool-use event) and does not subclass GovernanceAgent.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

unitares_sdk-0.2.0.tar.gz (105.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

unitares_sdk-0.2.0-py3-none-any.whl (75.9 kB view details)

Uploaded Python 3

File details

Details for the file unitares_sdk-0.2.0.tar.gz.

File metadata

  • Download URL: unitares_sdk-0.2.0.tar.gz
  • Upload date:
  • Size: 105.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for unitares_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 dc010649ed4431a3de69edf4e083219d5092786c7a54a9e719f4db5512d1b633
MD5 1c791ad109096980f79eaf1485db4149
BLAKE2b-256 777ce10c054cdccca3643e0f0cbc1fc558cf738ad8a63461f1b74cd49c220650

See more details on using hashes here.

Provenance

The following attestation bundles were made for unitares_sdk-0.2.0.tar.gz:

Publisher: publish-sdk.yml on cirwel/unitares

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file unitares_sdk-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: unitares_sdk-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 75.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for unitares_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3ad9b2a997f25e7839a2d37b71b138900f9da0368f01b520e3dbe9a471ef8bef
MD5 f7d1abfaf3aeb574fd08fe5c7a826f12
BLAKE2b-256 e1f0b070d9b2ace5e841592800e9b36e39a86da0c29f602f6aa98b50da9b5937

See more details on using hashes here.

Provenance

The following attestation bundles were made for unitares_sdk-0.2.0-py3-none-any.whl:

Publisher: publish-sdk.yml on cirwel/unitares

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.1

2 files

This release

0.2.0 This release

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page