Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,45 @@ ENGRAPHIS_LLM_API_KEY=sk-your-key-here
# ENGRAPHIS_CLOUD_TOKEN_SUBJECT=member
# ENGRAPHIS_CLOUD_ACCESS_TOKEN=

# Persistent customer state (cloud session and optional saved scoped relay bearer).
# Which plan the dashboard shows, and which capabilities it draws as unlocked. You should
# not need either of these.
#
# The plan is resolved automatically: once this installation has reached the control plane
# it reads the authoritative entitlement (plan and feature list) and caches it beside the
# cloud session, so a Pro or Team badge is correct on every later boot including offline
# ones. The refresh is opportunistic and runs on a background thread — it never blocks or
# delays startup, and it never fails the dashboard when the cloud is unreachable. Before
# the very first successful contact a connected installation is presented as Pro, the
# smallest paid plan, so a paying customer is never shown the free local core.
#
# ENGRAPHIS_CLOUD_PLAN is an override for the cases automatic resolution cannot cover: an
# air-gapped deployment, or one pinned to a short-lived ENGRAPHIS_CLOUD_ACCESS_TOKEN that
# cannot refresh. It takes precedence over the cached entitlement. Accepts pro, team, or
# free. It is presentation only — setting it grants nothing, because Engraphis Cloud
# authorizes every paid call regardless of what this client displays.
# ENGRAPHIS_CLOUD_PLAN=team
#
# Set to 0 to stop the client contacting the control plane for the entitlement at all. The
# badge then falls back to ENGRAPHIS_CLOUD_PLAN, or to the last cached answer.
# ENGRAPHIS_CLOUD_ENTITLEMENT_REFRESH=0

# Persistent customer state (cloud session, the cached hosted entitlement, and an optional
# saved scoped relay bearer).
# Locally defaults to ~/.engraphis; in a container use a private persistent volume.
# ENGRAPHIS_STATE_DIR=/data/.engraphis

# Hosted entitlements may report a separate local-only write grace capped at 24 hours.
# It never extends the exact 3-day trial, subscription expiry, or any cloud access.

# Managed compute consent. Set to 1 to allow the open package to upload workspace
# snapshots to the Engraphis Cloud service for managed analytics, dreaming, and
# consolidation. Without this flag, managed compute is off and no cloud upload
# occurs. The cloud service is authoritative for all paid computation.
# ENGRAPHIS_MANAGED_COMPUTE_CONSENT=1
# Managed compute consent is decided automatically and needs no customer action: a
# local-only installation (no cloud session) is never allowed to upload workspace
# snapshots, while an installation connected to Engraphis Cloud is allowed by default,
# because connecting already accepts the terms covering managed analytics, dreaming, and
# consolidation. This variable is an explicit operator override, not a customer-facing
# setting — set it to 0 to opt a connected installation back out, or to 1 to force
# managed compute on regardless of session state. The cloud service remains authoritative
# for all paid computation.
# ENGRAPHIS_MANAGED_COMPUTE_CONSENT=0

# Optional credential-redacted JSON logs for hosted customer deployments.
# ENGRAPHIS_JSON_LOGS=1
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ All notable changes to Engraphis are documented here. Format loosely follows

## [Unreleased]

No unreleased changes.
### Changed

- Managed compute consent now travels with the cloud account: an installation connected to
Engraphis Cloud is enabled for managed analytics, dreaming, and consolidation **by
default**, because connecting already accepts the terms that cover it. A local-only
installation with no cloud session is still never allowed.
`ENGRAPHIS_MANAGED_COMPUTE_CONSENT` remains as an explicit operator override (`=0` opts a
connected installation back out, `=1` forces it on regardless of session state) and is no
longer surfaced anywhere in the UI.

## [1.0.1] - 2026-07-24

Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,14 @@ a color palette and layout preset; or change the colors used for each type of no
### Managed compute

The open package can upload bounded workspace snapshots to the Engraphis Cloud service
for managed analytics, dreaming, and consolidation. This is **off by default** and
requires explicit opt-in by setting `ENGRAPHIS_MANAGED_COMPUTE_CONSENT=1`. The cloud
service is authoritative for all paid computation; no local setting turns this package
into a compute worker or relay.
for managed analytics, dreaming, and consolidation. A local-only installation with no
cloud session is **never** allowed to upload. Connecting an installation to Engraphis
Cloud accepts the terms that cover managed compute, so a **connected installation is
enabled by default** — there is no separate opt-in step to complete.
`ENGRAPHIS_MANAGED_COMPUTE_CONSENT` remains as an operator override (`=0` opts a
connected installation back out, `=1` forces it on); it is not a customer-facing
setting. The cloud service is authoritative for all paid computation; no local setting
turns this package into a compute worker or relay.

The dashboard is powered by the v2 engine — the same `MemoryService` that backs the MCP server
and the Python library. What you see in the UI is what your agents get.
Expand Down Expand Up @@ -616,9 +620,11 @@ background loop, cron wrapper, or worker.

Secret-class and session-scoped memories are excluded before a managed snapshot is serialized;
secret-class rows are rejected again by the hosted service. The encoded payload is capped at
16 MiB. Managed compute remains off until the customer explicitly sets
`ENGRAPHIS_MANAGED_COMPUTE_CONSENT=1`; cloud entitlement is also required. A managed proposal
never silently rewrites the local database.
16 MiB and travels over HTTPS without end-to-end encryption. Managed compute is enabled by
default once an installation is connected to Engraphis Cloud — connecting accepts the terms
that cover it — and stays off for a local-only installation with no cloud session; cloud
entitlement is also required. `ENGRAPHIS_MANAGED_COMPUTE_CONSENT=0` opts a connected
installation back out. A managed proposal never silently rewrites the local database.

Manual consolidation can also use schema-validated LLM output through
`MemoryService.consolidate`, `POST /api/consolidate`, `engraphis_consolidate`, or
Expand Down Expand Up @@ -667,7 +673,7 @@ All via environment (or `.env`):
| `ENGRAPHIS_CLOUD_REFRESH_CREDENTIAL` | — | Bootstrap-only rotating hosted credential; after first use the owner-only cloud session replacement takes precedence |
| `ENGRAPHIS_CLOUD_TOKEN_SUBJECT` | `member` | Subject fixed during hosted bootstrap (`device` or `member`); set explicitly with an environment-only refresh credential |
| `ENGRAPHIS_CLOUD_ACCESS_TOKEN` | — | Optional short-lived access token for ephemeral jobs |
| `ENGRAPHIS_MANAGED_COMPUTE_CONSENT` | `0` | Explicitly set to `1` before uploading a bounded workspace snapshot for hosted Analytics or Automation |
| `ENGRAPHIS_MANAGED_COMPUTE_CONSENT` | *(auto)* | Operator override only; default follows whether a cloud session is configured (connected = allowed, local-only = never). `0` opts a connected installation out, `1` forces it on |

See `.env.example` for the full customer-runtime and managed-service client options.

Expand Down
11 changes: 8 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,14 @@ them back as `expected_head` / `expected_count` when independent evidence is req
- **Server authority:** every hosted and cost-bearing operation is authorized by the private
control plane; local plan labels and upgrade URLs are presentation metadata only.
- **Managed-compute consent:** Analytics, Auto Dreaming, and Auto Consolidation upload a
bounded snapshot only after explicit customer opt-in via
`ENGRAPHIS_MANAGED_COMPUTE_CONSENT=1`. Secret-class memories are excluded before
serialization and rejected again by the hosted service.
bounded snapshot. Consent travels with the cloud account: connecting an installation to
Engraphis Cloud accepts the terms covering managed compute, so a connected installation is
allowed and an installation with no cloud session is never allowed. Operators may override
with `ENGRAPHIS_MANAGED_COMPUTE_CONSENT` (`0` opts a connected installation back out).
The snapshot carries normal and sensitive memory content, excludes secret-class and
session-scoped rows, is capped at 16 MiB, and travels over HTTPS without end-to-end
encryption. Secret-class memories are excluded before serialization and rejected again by
the hosted service.
- **Trial and grace are separate:** an email-confirmed trial lasts exactly 3 active days. A
separately bounded, maximum-24-hour local workspace-write grace never extends the trial,
subscription, Cloud Sync, managed compute, Team access, seats, or credentials.
Expand Down
4 changes: 2 additions & 2 deletions deploy/railway-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"required": false
},
"ENGRAPHIS_MANAGED_COMPUTE_CONSENT": {
"value": "0",
"prompt": "Change to 1 only after reviewing the managed snapshot upload boundary.",
"value": "",
"prompt": "Optional override. Leave blank: managed compute follows the cloud account, so a connected deployment is allowed and a local-only one is not. Set 0 to opt this deployment out.",
"required": false
}
}
Expand Down
8 changes: 5 additions & 3 deletions docs/HOSTING_RAILWAY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ ENGRAPHIS_CLOUD_REFRESH_CREDENTIAL=<secret>

Prefer mounting the owner-only cloud session file rather than placing a rotating refresh
credential directly in deployment configuration. An injected environment credential is only the
bootstrap value; after rotation, the owner-only saved replacement takes precedence. Enabling
managed compute for an authorized customer may upload a snapshot capped at 16 MiB; secret-class
and session-scoped rows are excluded client-side, and secret-class rows are rejected server-side.
bootstrap value; after rotation, the owner-only saved replacement takes precedence. Once
connected, managed compute is enabled by default for an authorized customer and may upload a
snapshot capped at 16 MiB over HTTPS without end-to-end encryption; secret-class and
session-scoped rows are excluded client-side, and secret-class rows are rejected server-side.
Set `ENGRAPHIS_MANAGED_COMPUTE_CONSENT=0` to opt the deployed installation back out.

## Persistence and recovery

Expand Down
7 changes: 5 additions & 2 deletions docs/RAILWAY_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ issuer, relay, managed compute, Auto Dreaming, Auto Consolidation, or Team ident
- No vendor signer, billing, mail, Team-admin, relay-storage, or worker secrets.

Hosted customer endpoint variables may be exposed as optional inputs, but a refresh credential
must be injected as a secret or mounted owner-only state file. Managed compute requires
explicit opt-in via `ENGRAPHIS_MANAGED_COMPUTE_CONSENT=1`.
must be injected as a secret or mounted owner-only state file. Managed compute is enabled by
default once the deployed installation is connected to Engraphis Cloud; connecting accepts the
terms that cover it, and a local-only node with no cloud session is never allowed. Expose
`ENGRAPHIS_MANAGED_COMPUTE_CONSENT` only as an optional operator override (`0` to opt a
connected installation back out), not as a required opt-in input.

## Publish gate

Expand Down
90 changes: 76 additions & 14 deletions engraphis/cloud_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
import urllib.error
import urllib.request
import uuid
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Any, Optional
from urllib.parse import quote

from engraphis.cloud_session import CloudSessionError, access_for_workspace
from engraphis.hosted_client import build_pinned_https_opener
from engraphis.cloud_session import configured as cloud_session_configured
from engraphis.hosted_client import build_pinned_https_opener, upgrade_url

SNAPSHOT_SCHEMA = "engraphis-managed-snapshot/v1"
MAX_RESPONSE_BYTES = 16 * 1024 * 1024
Expand Down Expand Up @@ -49,13 +50,26 @@ def redirect_request(self, req, fp, code, msg, headers, newurl):


def managed_compute_consent() -> bool:
"""Return whether the customer explicitly enabled managed snapshot uploads.
"""Return whether this installation may upload workspace content for managed work.

Entitlement is enforced by the cloud service, but it is not consent to upload local
workspace content. The public client therefore remains off unless the customer sets
``ENGRAPHIS_MANAGED_COMPUTE_CONSENT=1``.
Consent travels with the cloud account: connecting an installation to Engraphis Cloud
accepts the terms that cover managed compute, so a connected installation is allowed by
default and the customer is never asked to hand-edit an environment variable.

A local installation with no cloud session is never allowed — there is no account, so
there is no agreement to rely on.

``ENGRAPHIS_MANAGED_COMPUTE_CONSENT`` remains an explicit override for operators who want
to force the answer either way; ``=0`` opts a connected installation back out.
"""
return _truthy(os.environ.get("ENGRAPHIS_MANAGED_COMPUTE_CONSENT"))
override = os.environ.get("ENGRAPHIS_MANAGED_COMPUTE_CONSENT")
if override is not None and override.strip() != "":
return _truthy(override)
try:
return bool(cloud_session_configured(require_compute=False))
except Exception:
# Consent must never be the reason a dashboard fails to render.
return False


def _public_http_error(status: int) -> tuple[str, bool]:
Expand All @@ -67,7 +81,14 @@ def _public_http_error(status: int) -> tuple[str, bool]:
if status in {401, 403}:
return "Engraphis Cloud authorization was rejected.", False
if status == 402:
return "This hosted feature is not available for the current plan.", False
# 402 is the control plane's "no active paid entitlement", which a lapsed or
# past_due subscription reaches just as often as a genuine free plan. Name the
# billing page so a paying customer can fix it instead of reading a dead end.
return (
"This hosted feature needs an active Engraphis Cloud subscription. Check "
"billing or upgrade at %s." % upgrade_url(),
False,
)
if status == 404:
return "The hosted workspace or feature was not found.", False
if status == 409:
Expand All @@ -81,6 +102,43 @@ def _public_http_error(status: int) -> tuple[str, bool]:
return "Engraphis Cloud rejected the request.", False


def _public_session_error(status: int) -> tuple[str, bool]:
"""Map a session-acquisition failure to fixed, actionable public copy.

``CloudSessionError`` text is never forwarded across this boundary (it can quote local
state paths), but the bare status alone reads as an outage for every cause. A customer
whose subscription lapsed, whose session was revoked, or who is simply offline each
need a different next step, and only the transient ones are worth retrying.
"""

if status == 401:
return (
"Connect this installation to Engraphis Cloud to use hosted features.",
False,
)
if status == 402:
return (
"This hosted feature needs an active Engraphis Cloud subscription. Check "
"billing or upgrade at %s." % upgrade_url(),
False,
)
if status == 403:
return ("Engraphis Cloud authorization was rejected.", False)
if status == 409:
return (
"The saved cloud session is unusable; connect this installation again.",
False,
)
if status == 429:
return ("Engraphis Cloud is temporarily busy. Try again shortly.", True)
if status >= 500:
return (
"Engraphis Cloud is unreachable; hosted features resume once it responds.",
True,
)
return ("The cloud session is unavailable.", False)


def _metadata(value: Any) -> dict:
if isinstance(value, dict):
return value
Expand Down Expand Up @@ -191,8 +249,9 @@ def _build_managed_snapshot_locked(service: Any, workspace: str, *,
"""Build the bounded client-side transport document for one local workspace.

Secret-classified rows are omitted before serialization. ``consent`` allows an
already-confirmed caller to pass its decision explicitly; otherwise the environment
opt-in is required.
already-confirmed caller to pass its decision explicitly; otherwise
:func:`managed_compute_consent` decides, which allows cloud-connected installations and
denies purely local ones.
"""

clean_workspace = service._clean_ws(workspace)
Expand All @@ -202,8 +261,8 @@ def _build_managed_snapshot_locked(service: Any, workspace: str, *,
allowed = managed_compute_consent() if consent is None else bool(consent)
if not allowed:
raise CloudFeatureError(
"Managed compute is off. Opt in before uploading workspace content by setting "
"ENGRAPHIS_MANAGED_COMPUTE_CONSENT=1.",
"Managed compute is turned off for this installation, so no workspace content "
"was uploaded. Connect this installation to Engraphis Cloud to use it.",
status=409,
code="consent_required",
)
Expand Down Expand Up @@ -297,7 +356,9 @@ def _build_managed_snapshot_locked(service: Any, workspace: str, *,
class CloudFeatureClient:
base_url: str
organization_id: str
access_token: str
# A dataclass ``__repr__`` prints every field, so the default would put a live bearer
# token into any traceback, log line, or debugger frame that renders this client.
access_token: str = field(repr=False)
timeout_seconds: float = 15.0

@classmethod
Expand All @@ -306,8 +367,9 @@ def from_environment(cls, workspace_id: str) -> "CloudFeatureClient":
access_token, organization_id, base_url = access_for_workspace(workspace_id)
except CloudSessionError as exc:
status = exc.status if 400 <= exc.status <= 599 else 503
message, transient = _public_session_error(status)
raise CloudFeatureError(
"The cloud session is unavailable.", status=status
message, status=status, transient=transient
) from exc
except ValueError as exc:
raise CloudFeatureError(
Expand Down
Loading
Loading