What it does

Every row checked against the release, including the ones that fail

Feature lists are written to be believed. This one is written to be checked. Every row below names the file it was verified against in v0.2.2 — the release this website publishes, pinned at 31959e11 — and not core's development branch, where more is true. Checked on .

Roughly half of these rows are not a clean pass. That is deliberate and it is the point of the page: a claim stated wider than the release supports is the failure mode this project keeps finding in its own drafts, so the narrower truth is written out in full rather than rounded up.

  • 11Holds
  • 12Narrower than it sounds
  • 4Not in this release

How to read a verdict

Holds
True at this release, with a live consumer rather than only a declaration.
Narrower than it sounds
Something real ships, but the obvious reading of the claim is wider than what this release does. The row states the narrower truth.
Not in this release
Does not hold at this release. Some of these are declared in the code and inert; some are later work; none of them are things you should rely on today.

Ownership and lifecycle

What you get, and what it costs you to be an early user.

  • Holds

    The code is under a permissive open-source license.

    MIT, with no additional field-of-use or branding conditions.

    LICENSEat v0.2.2
  • Not in this release

    Several people can each have their own account on one install.

    One owner per install. The gateway authenticates a single local token; there is no user-account model, no per-user separation of memory or knowledge, and nothing that would keep two people's work apart. Run one install per person.

    auth/modes.pyat v0.2.2
  • Not in this release

    Upgrading will not break the data you have already put in.

    This is a 0.x release under a clean-break doctrine: when a design is replaced the old path is removed in the same change. The release's own README says the next few 0.x releases may introduce breaking changes with no automatic migration, that sessions, memory, knowledge, config and app state may need to be recreated after an update, and that you should not make this your only system of record yet. Run `personalclaw snapshot` before every update.

    README.md, pre-1.0 heads-upat v0.2.2

What leaves your machine

The rows most likely to be overstated by a self-hosted project, including by us.

  • Narrower than it sounds

    Zero telemetry.

    No analytics, no crash reporting and no usage reporting of any kind - nothing about how you use it is sent anywhere. But the claim as stated is wider than the truth: by default PersonalClaw asks GitHub whether a newer release exists when it starts and then every twelve hours, identifying itself with a `personalclaw-update-check` user agent. You did not ask it to. Since v0.2.0 a setting turns that automatic check off (the row below), but it is on out of the box. If you grep the tree you will also find a `telemetry_salt` and a hashed owner id; at this release both stay on this machine - the salt is kept out of packs, exports and sync, the hash is shown only by the gateway's own status endpoint - and nothing transmits either.

    self_update.pyat v0.2.2
  • Holds

    You can turn the update check off.

    `updates.check_enabled` (Automatic Update Checks, in Settings under Updates) turns it off. Every check PersonalClaw makes on its own - at start, on the interval, when a page showing update status finds one due, and before a staged install - asks that setting first, read fresh each time, so switching it off stops the next check without a restart. With it off, PersonalClaw reaches GitHub for updates only when you ask: Check now, an Update you press, or `personalclaw update`. It is on by default.

    config/loader.pyat v0.2.2
  • Narrower than it sounds

    Outbound network access is deny-by-default.

    True about address ranges, not about destinations. The guard denies private, loopback and link-local ranges by default and pins the resolved address to defeat rebinding - so it will not reach your router, your NAS or a metadata endpoint. It is not a destination allow-list: the default profile reaches any public host. Read it as protecting your LAN, not as restricting where an agent may go on the internet.

    net/policy.pyat v0.2.2
  • Narrower than it sounds

    All network access goes through one guarded chokepoint.

    It is a real chokepoint, and it covers more than core's own fetches at this release: an app's requests through the SDK, the HTTP clients core hands model providers, remote MCP servers and the browser's top-level navigations are all judged by it, with the range checks and, where the client allows it, address pinning. It is not the whole machine. An app backend runs as a separate process with its own network stack and does not pass through it; a program an app's code starts is checked only for the hosts its command line names; and a page the browser has opened fetches its own subresources unchecked. If the egress settings cannot be read, it drops your allow-list and keeps the last deny list it saw, if it has seen one, rather than failing closed.

    net/guard.pyat v0.2.2
  • Narrower than it sounds

    You can restrict outbound access to an explicit list of hosts.

    For agent work, through one hand-edited file. The governance ceiling (`governance/ceiling.json` in the home, read once at start, with no setting in the app that writes it) can hold every run to an egress tier: `listed` reaches only your Allowed hosts, and nothing at all while that list is empty; `registry` reaches only the common package registries; `off` reaches nothing. A few surfaces are exclusive on their own: an automation's net-fetch step and a sync transport reach only hosts you named. What a tier does not narrow: the agent's own model, your own actions in the app and background jobs, and an app backend's own process. Without a ceiling, an Allowed hosts entry adds to what the default profile reaches rather than limiting it. `on_violation`, the warn-instead-of-block field on the egress policy, still has no consumer - do not read it as a mode.

    net/policy.pyat v0.2.2

Constraints on autonomous work

What actually bounds an agent acting without you watching.

  • Holds

    The gateway requires authentication by default.

    A local token is the default mode. The one mode that removes the token, `none`, forces the bind to loopback, so switching auth off cannot silently expose the gateway to your network.

    auth/modes.pyat v0.2.2
  • Holds

    Credential handling fails closed.

    The credential store's stated failure posture is fail-closed: when it cannot decide, it refuses rather than proceeding without the credential.

    auth/credentials.pyat v0.2.2
  • Narrower than it sounds

    Autonomous actions leave an audit trail that would show tampering.

    An EDITED entry is caught; a DELETED one is not. Each entry carries an HMAC over its own payload, so altering a record in place makes its digest stop matching and the verifier names the entry. But the verifier deliberately ignores the chain itself: it 'tolerate[s] `prev_hash` mismatches (chain breaks) silently', because the gateway, channel and MCP processes all append to one log without IPC, so their writes interleave and a strict chain would false-alarm constantly. The consequence is that removing an entry leaves the surrounding entries individually valid and its gap unreported - the clean gap this row used to say was impossible. On Linux a row can also be missing with nothing removed: once the gateway rotates the log, the tool server an agent CLI runs can no longer append to it, and since v0.2.1 it writes that row, masked, to its own stderr instead of failing the call. Read it as per-record tamper-evidence, not as proof the log is complete. Default retention is 365 days, and by default only the most recent 5000 entries are checked.

    sel.py, verify_integrityat v0.2.2
  • Narrower than it sounds

    Per-tool grants restrict which tools an agent may call.

    Enforced at this release, below the agent you talk to. A subagent, a workflow or batch leaf, a room member, a sandboxed run, and a turn nobody is watching - a webhook's, or a result announced to an unwatched chat - each run under a grant tier (read-only, read-write, or a named list of tools) and are refused a call outside it. Before v0.2.0 these fields were declared with no consumer; since v0.2.0 they are checked at those seams. Two limits: the agent you chat with directly is not held to a per-tool list - its own calls are bounded by the approval mode, the command denylist and the egress check - and the only way to name the tools yourself is the governance ceiling file, written by hand and read once at start, which narrows those same seams.

    guardrails/policy.pyat v0.2.2

The app platform and supply chain

How capabilities are added, and what is checked when they are.

  • Holds

    Capabilities install and uninstall as app bundles rather than being built in.

    Twenty-one provider types at this release - model, agent, task, channel, inbox, skills, knowledge, memory, notification, tool, workflow, search, action, prompt, duty_gate, sync, sandbox, trigger_source, ocr, trigger and vector_store - each one an installable bundle can provide, declaring its type and permissions in a manifest. Counted from PROVIDER_TYPES at the tag, which is why this row counts the release rather than describing a branch.

    apps/manifest.pyat v0.2.2
  • Narrower than it sounds

    The core contains no vendor-specific logic.

    Vendor integrations live in app bundles, and two checks hold the line: apps may import core only through the published SDK, per file, and a sweep fails any vendor SDK import or credential literal in core that is not a listed exception. But the exceptions exist, and the release names them: wire-protocol clients for the Anthropic and OpenAI formats many providers reuse, secret-detection patterns for vendor tokens, and import and export code that reads and writes other agent tools' files - Claude Code's agents folder and MCP config among them. Read it as no vendor integration in core, not as no vendor name.

    docs/architecture/provider-boundary.mdat v0.2.2
  • Narrower than it sounds

    Apps are sandboxed, with a per-app network policy.

    An app can be confined at this release, but it is not by default. An app backend that names a sandbox tier in its manifest - a container, or a Lima virtual machine - launches inside it, and refuses to launch rather than fall back to the host when that tier is unavailable. In a container, a backend without the `network` permission gets no network at all, and one without `storage` is given no data folder to write; in a Lima machine the network is whatever that machine was created with. A backend that names no tier, the default, runs on the host as an ordinary process with its own network stack, and there `network` is a disclosure shown at install, not a boundary. A program an app's in-gateway code starts is refused a host its command line names unless the manifest declares it or it is on your Allowed hosts. Install a third-party app with the same care you would give any program you run.

    apps/backend_runtime.pyat v0.2.2
  • Not in this release

    A vulnerable dependency cannot reach the default branch.

    The audit can fail a run, but nothing requires it to pass. Since v0.2.2 the dependency audit is no longer report-only: on every pull request, and every night on the default branch, it audits the locks core ships and fails on a high or critical finding that no live waiver covers - the Python audit reports no severity, so every Python finding counts as high. But no status check is required to merge into the default branch, so a red audit is a finding to act on rather than a barrier. The job's own comment says a new advisory stops merging until it is fixed or waived; that holds only once a check is required, and the same file records that the default branch has none, while the CI guide at this tag leaves the rules that would require one to a later change. It also fails on less than every vulnerable dependency: a moderate or low finding in the JavaScript lock is only listed, a waiver lets a finding through for up to 30 days, an advisory published after a merge is caught by the next night's run rather than before it, and the images' OS packages are not audited.

    .github/workflows/ci.ymlat v0.2.2

What it does

Shipped code paths with live consumers at this release.

  • Holds

    It can run inference against a model hosted on your own machine.

    A local-model contract ships and is live: the model download and model-registry handlers list, fetch and remove models through it. A bundled chat app built on it lets a fresh install hold a conversation on this machine with no provider bound - a small model, downloaded once when you click for it, then run offline on the CPU. It is a floor, not a recommendation: bind a real model, local or hosted, for real work.

    local_models/provider.pyat v0.2.2
  • Holds

    It can connect to external MCP servers.

    A long-lived MCP client ships and is consumed by the MCP and tools handlers at this release: the agent calls tools on servers you configure, over stdio or at a URL (Streamable HTTP or SSE, with OAuth sign-in for a remote server), and a server at a URL is reached through the egress guard. Serving in the other direction - PersonalClaw acting as an MCP server for something else - is a separate module and is not what this row claims.

    mcp_client.pyat v0.2.2
  • Holds

    It can run work on a schedule with nobody watching.

    One clock engine, started by the gateway, fires schedules, intervals and one-off times from the trigger store at this release. Read this row together with the autonomy rows above: an agent a trigger starts asks before it acts unless that automation was allowed to run on its own, and a run nobody watches is held to its grant tier.

    triggers/loop.pyat v0.2.2
  • Narrower than it sounds

    There is a desktop application you can install.

    Three desktop artifacts are attached to this release: PersonalClaw-0.2.2-arm64.dmg for Apple-silicon Macs, and PersonalClaw-0.2.2.AppImage and personalclaw-desktop_0.2.2_amd64.deb for x86-64 Linux. None carries a publisher's signature: the dmg is ad-hoc signed and not notarized, so macOS asks you to approve it once under Privacy & Security on first launch, and the Linux files are unsigned. There is no Intel Mac or Windows build, and no in-app update: a new version is a download you install over the old one. Otherwise you install the package and reach the gateway in a browser.

    docs/guides/desktop.mdat v0.2.2
  • Narrower than it sounds

    It can retrieve relevant passages from your own documents to ground its answers (RAG).

    A hybrid retriever fuses SQLite FTS5 keyword search with a knowledge-graph layer and reciprocal rank fusion, and it is a live path at this release, not a module without callers. But vector search is not on by default: it only joins that fusion once an embedding provider is bound. With none bound, embeddings are off and retrieval runs keyword plus graph only, not semantic.

    knowledge/retrieval.pyat v0.2.2
  • Holds

    You can upload documents, including large files, and attach them to a conversation.

    A resumable, tus-like chunked-upload protocol (init/part/status/complete) streams to disk in bounded chunks rather than buffering a file in memory, is size-policed per file type before a byte lands, and the assembled file routes to a chat attachment, knowledge ingest, or the workspace - the same protocol behind all three.

    uploads/store.pyat v0.2.2
  • Holds

    You can back up your data and restore it.

    `personalclaw snapshot` and `restore` ship as CLI commands, and Settings > Backups runs them too: by default a nightly snapshot and a monthly restore drill that restores the newest one into a temporary directory to prove it reads. A snapshot holds every store whole, each database copied through SQLite's backup API, and carries no credential value. `backup export` is a different thing - a deterministic, SHA-256-manifested export of your records that sync carries, which nothing restores from.

    docs/reference/cli.mdat v0.2.2
  • Not in this release

    It supports SSO - SAML or OIDC login.

    Not shipped, by design: PersonalClaw is single-owner and self-hosted, so there is no directory to federate with. The auth modes at this release are `local_token`, the default, and `none`. `api_key` and `oauth2`, declared at earlier releases with nothing able to select them, are deleted; asking for either is named at startup, and the local token stays in force.

    auth/modes.pyat v0.2.2
  • Narrower than it sounds

    It can send and receive webhooks.

    Both directions ship, and the inbound one is narrower than it sounds: it answers only programs on this machine. A program can POST to a webhook automation's own address with a sender token made for it, or start an agent turn with the owner's webhook token (at least 32 characters), and a request from any other address is refused - a sender elsewhere reaches it through an SSH tunnel or a relay you run here. The turn a webhook starts runs unattended with read tools only. Outbound, the HTTP Webhook Hooks app makes a POST an action a hook or automation can run, held to the run's egress rules.

    inbound/webhook.pyat v0.2.2
  • Holds

    It has memory that persists and learns across sessions, not just within one.

    Semantic memory (key-value facts with confidence gating and conflict resolution), episodic memory (conversation fragments with embeddings and time-decay retrieval, falling back to FTS5 without them) and procedural memory (after-turn mining of tool-outcome priors) all ship and are live at this release.

    vector_memory.pyat v0.2.2
  • Narrower than it sounds

    It can search the web.

    The web_search/web_fetch tools and a provider registry ship, resolved through the same active-binding path the model providers use. But nothing is bound out of the box: no search provider ships bundled at this release, so it is a seam you fill by installing a search-provider app - a keyless one, such as DuckDuckGo or Wikipedia, needs no key - not a batteries-included web search feature.

    search_providers/registry.pyat v0.2.2

Check it yourself

Every link above points into the tagged release, so a row is falsifiable in one step: open the file and read it. If a row overstates what the code does, that is a defect in this page and it will be fixed or the row will be removed.

What this page does not do is compare PersonalClaw to other projects. Doing that responsibly means auditing somebody else's software to the same standard applied here, and this project has not earned that standing while it is still finding overstatements in its own drafts. An accurate account of our own scope is more useful to you than our characterisation of anyone else's.

The controls behind the autonomy rows are written up in full elsewhere, including a limitations page that exists for the same reason this one does.

Read from v0.2.2 (31959e11de37) · 11 holding · 12 narrower than stated · 4 not in this release · checked

If a row here is wrong or has gone stale, say so and it will be corrected or the row will be removed.

Open an issue