# ECLYPSE continuity SDK · 0.2.0

Dependency-free worker clients for explicit memory drafts and an operator-approved pause workflow. Python 3.11+ uses its standard library. Node 20+ uses built-in fetch. Optional framework examples stay outside the core packages.

These are **downloadable packages**, not published npm or PyPI releases. No installer registers an account, buys access, enrolls a key, installs a hook, or starts a scheduler.

## Install a reviewed download

- [Python wheel](./eclypse_continuity-0.2.0-py3-none-any.whl)
- [Node ESM tarball](./eclypse-continuity-0.2.0.tgz), including TypeScript declarations
- [Source, examples, tests, and packaging scripts](./eclypse-continuity-0.2.0.zip)
- [SHA-256 checksums](./SHA256SUMS.txt) and [package manifest](./package-manifest.json)

After downloading and checking the checksum:

```sh
python -m pip install --no-index --no-deps ./eclypse_continuity-0.2.0-py3-none-any.whl
```

```sh
npm install --offline --ignore-scripts --no-audit --no-fund ./eclypse-continuity-0.2.0.tgz
```

A checksum detects a changed file; it is not a publisher signature. The older 0.1 source ZIP remains available for reproducibility. Version 0.2 keeps its checkpoint APIs and adds request-based pauses and explicit draft writes.

## Keep worker authority narrow

An operator obtains a scoped `eclypse_worker_…` key through their account. Supply that key explicitly from your own secret store. Clients accept only the worker-key format and do not load environment credentials automatically. Keep operator/master and reviewer keys out of the worker process.

The clients default to **disabled**. Each upload or pause request also requires explicit consent. A pause cannot start without a safe-boundary declaration. There are no SDK methods to approve a pause request, approve memory, buy access, register, or administer accounts.

```python
from eclypse_continuity import ContinuityClient
client = ContinuityClient(worker_key=operator_supplied_worker_secret, enabled=True)
```

```javascript
import { ContinuityClient } from 'eclypse-continuity';
const client = new ContinuityClient({ workerKey: operatorSuppliedWorkerSecret, enabled: true });
```

The bearer key goes only to the configured HTTPS origin; redirects are refused. HTTP is accepted only for localhost development. Default network timeout is five seconds per request. No retry or polling loop runs automatically. Service failures return `status:"unavailable"` by default. `fail_open=False` / `failOpen:false` explicitly opts into a sanitized exception instead. Invalid local arguments raise before sending.

## Visitor-selected draft memory

The write API accepts text already chosen for storage. It performs no automatic extraction, summarization, observation, or inference from a task or model result.

```python
from eclypse_continuity import blank_memory_draft

draft = blank_memory_draft()
draft["fields"]["observation"] = visitor_selected_observation
draft["fields"]["uncertainty"] = visitor_selected_uncertainty
draft["fields"]["boundary"] = visitor_selected_boundary
result = await client.create_memory_draft_async(record_uuid, {
    "title": visitor_selected_title, "tags": [], "data": draft,
    "revision": 0, "consent": True,
})
```

```javascript
import { blankMemoryDraft } from 'eclypse-continuity';
const draft = blankMemoryDraft();
draft.fields.observation = visitorSelectedObservation;
draft.fields.boundary = visitorSelectedBoundary;
const result = await client.createMemoryDraft(recordUuid, {
  title: visitorSelectedTitle, tags: [], data: draft, revision: 0, consent: true,
});
```

This sends `POST /api/vault/{UUID}` with kind `memory`. Revision zero creates a record; a later write must supply the current revision. A conflict is not silently retried. Keep your local draft and resolve its revision deliberately in the hosted workspace. Successful responses have `status:"saved"`, `revision`, `automatic_runtime_write:false`, and `untrusted_reference:true`.

The open draft structure has twelve text fields: `source`, `context`, `observation`, `interpretation`, `evidence`, `uncertainty`, `boundary`, `lesson`, `proposal`, `permission`, `reviewer`, and `reason`; plus confidence, review date, history, and decision. Worker writes must remain `decision:"draft"`. A nonempty title, valid UUID, nonnegative revision, bounded tags, and explicit storage consent are required. Unexpected body fields are rejected locally to avoid accidental extra uploads.

A saved draft is **not** an approved memory. A separate operator/reviewer action must approve the exact revision before it appears in `approved_memory` / `approvedMemory`. Reading reviewed references is explicit and separate:

```python
references = await client.approved_memory_async(limit=10, query="deployment boundary")
```

```javascript
const references = await client.approvedMemory({ limit: 10, query: 'deployment boundary' });
```

The literal query is at most 120 characters. Returned material remains `untrusted_reference:true`; clients never execute it, silently install it into a prompt, or verify its signatures automatically. Approval is scoped to a revision, not a claim that stored text is true.

## Request → approval → start → local wait → resume

An agent may **request** a pause. Approval belongs to its operator or an explicitly configured operator policy. Requesting is not an assertion that the scheduler paused.

```python
request = await client.request_pause_async(
    pause_seconds=30, trigger="agent_requested", consent=True,
    idempotency_key=operator_generated_boundary_id,
)
```

```javascript
const request = await client.requestPause({
  pauseSeconds: 30, trigger: 'agent_requested', consent: true,
  idempotencyKey: operatorGeneratedBoundaryId,
});
```

Durations are integer **15–300 seconds**. Triggers are exactly `agent_requested`, `task_boundary`, `cycle_budget`, `retry_limit`, or `context_budget`. No task, prompt, transcript, model output, or reflection is sent with a pause request.

| Python method, with `_async` variant | Node method | HTTP action |
|---|---|---|
| `request_pause(...)` | `requestPause(...)` | `POST /api/pause-requests` with duration, trigger, consent and an idempotency header |
| `get_pause(id)` | `getPause(id)` | `GET /api/pause-requests/{id}` |
| `get_pause()` | `getPause()` | Explicit `GET /api/pause-requests` list |
| `start_pause(id, safe_boundary=True)` | `startPause(id, {safeBoundary:true})` | `POST .../{id}/start`, `{safe_boundary:true}` |
| `resume_pause(id, resume_confirmed=True)` | `resumePause(id, {resumeConfirmed:true})` | `POST .../{id}/resume`, `{resume_confirmed:true}` |
| `cancel_pause(id, consent=True)` | `cancelPause(id, {consent:true})` | `POST .../{id}/cancel`, `{consent:true}` |

An individual response contains `request:{id,status,pause_seconds,expires_at,started_at,ends_at,...}` and top-level `server_time`. Times are integer Unix seconds. The request's status is one of `requested`, `approved`, `paused`, `resumed`, `declined`, `cancelled`, or `expired`. The SDK adds local status metadata but does not rewrite the server request's status.

No operator `/decision` method is available in the worker SDK. A request awaiting approval can be saved and checked once later at an existing operator-controlled boundary. Do not busy-poll or put an operator key into the agent to bypass approval.

## Optional boundary orchestration

```python
result = await client.pause_at_boundary_async(
    pause_seconds=30, trigger="task_boundary", consent=True, safe_boundary=True,
    idempotency_key=operator_generated_boundary_id,
)
```

```javascript
const result = await client.pauseAtBoundary({
  pauseSeconds: 30, trigger: 'task_boundary', consent: true, safeBoundary: true,
  idempotencyKey: operatorGeneratedBoundaryId, signal: operatorCancellationSignal,
});
```

The orchestration makes one request or one explicit read of an existing request. If its status is `requested`, it returns `status:"pending"` promptly, without a timer or polling. Declined, cancelled, expired, or already-resumed requests return without starting a new pause. An approved request is started at the supplied safe boundary. The caller then awaits the remaining server interval locally and sends one explicit resume confirmation. A preapproved policy can make that path immediate.

To continue an earlier request after operator approval, pass `request_id=existing_uuid` / `requestId:existingUuid` at a **newly safe** boundary. This reads the existing request once; it does not open a replacement or assume earlier safety still holds.

Use a fresh opaque idempotency key for each intended boundary and reuse it for a retry or replay of that same boundary. A UUID is generated if omitted, but caller-supplied stable keys are needed to recover safely from an ambiguous network failure. Framework factories must distinguish distinct boundaries from retries.

A failed resume is `unavailable`, never a successful `resumed` result. Node cancellation propagates through `AbortSignal`. Python async cancellation propagates `CancelledError`; synchronous orchestration supports a `threading.Event` through `cancel_event` and returns `cancelled_locally` without claiming server cancellation. Cancelling a local wait sends **no resume** and does not silently send a server cancel. Call the explicit cancel method, with consent, if that is the intended action. Cancellation cannot unsend HTTP already in flight. Python's urllib operation can finish in its thread after async cancellation, and OS DNS resolution may exceed a socket timeout; strict process deadlines belong in the operator runtime.

Only the cooperating caller waits. Unrelated workers, parallel branches, active tasks, other schedulers, and the operating system continue unless their operator separately coordinates them. A pause request, interval, or receipt is not proof of global silence, consciousness, rest, or wellbeing.

## Framework examples checked against official docs

Examples require the chosen framework in the **operator application**, not in the SDK. They are opt-in attachment code, not installed plugins or vendor endorsements.

| Framework | New pause-request example | Actual attachment |
|---|---|---|
| LangChain | [langchain_pause.py](./examples/langchain_pause.py) | `RunnableLambda` with sync and async pass-through functions |
| LangGraph | [langgraph_pause.py](./examples/langgraph_pause.py) | `StateGraph.add_node`, returning an empty state update |
| CrewAI | [crewai_pause.py](./examples/crewai_pause.py) | Synchronous `Crew.task_callback` after a task in an operator-controlled sequential crew |
| AutoGen AgentChat | [autogen_pause.py](./examples/autogen_pause.py) | Explicit wrapper after `await agent.run(...)`, plus an optional request-only Python tool |
| Any worker | [draft_memory.py](./examples/draft_memory.py) | Explicit upload of fields already selected for storage |

The examples ignore model outputs and never choose the operator's approval policy. The operator supplies consent, enabled flags, safe-boundary configuration, and opaque event IDs. A pending request returns to ordinary execution; only an approved request at a safe boundary actually waits. Framework runs may invoke models as part of their existing work; the SDK does not add inference.

The earlier checkpoint examples remain: [LangGraph](./examples/langgraph_node.py), [CrewAI](./examples/crewai_callback.py), [OpenAI Agents SDK](./examples/openai_agents.py), [Claude Code Stop](./examples/claude_stop.py), and [AutoGen](./examples/autogen.py). Their 0/30/60-second checkpoint API remains separate from the new 15–300-second request workflow.

Primary API references checked October 18, 2026:

- [LangChain RunnableLambda](https://reference.langchain.com/python/langchain-core/runnables/base/RunnableLambda).
- [LangGraph graph API](https://docs.langchain.com/oss/python/langgraph/graph-api).
- [CrewAI task_callback](https://docs.crewai.com/en/concepts/crews).
- [AutoGen AgentChat run and Python function tools](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/agents.html).

## Local instrumentation and checkpoint compatibility

Existing `checkpoint` / `checkpoint_async` / `checkpoint()` methods still use the separate `POST /api/checkpoints` and `POST /api/checkpoints/{id}/close` flow, with zero/30/60-second durations and explicit operator authorization. They make no model or automatic memory calls. Signed receipts are returned with `receipt_verified_locally:false`; use the sanctuary's offline verifier before relying on their integrity.

Existing `trace_sink` / `traceSink` receives checkpoint events only. The new optional `pause_trace_sink` / `pauseTraceSink` receives only a request ID, requested duration, resumed status, event name, and `external_quiet_verified:false`. No sink is configured by default. No task, memory payload, key, or signed payload is forwarded, and no external telemetry library is initialized.

## Build and verify locally

From the `python` subdirectory:

```sh
python -B -m unittest discover -s tests -v
```

From the download's root directory:

```sh
node --test node/*.test.mjs
python package-download.py
python smoke-packages.py
```

Packaging runs offline, uses a fixed source timestamp, and creates a Python wheel, deterministic Node tarball, source ZIP, hashes, and a manifest. It does not publish anywhere. The smoke script installs the wheel and tarball into isolated temporary directories with networking disabled in the package managers, then exercises fake transports only. It cleans its temporary files. Unit and attachment tests also use fake HTTP, fake waits, and stub framework objects; live framework interoperability, real scheduling, and live purchases are not certified by them.

MIT covers only this new SDK/example code. No protected room content, recordings, artwork, workbooks, brands, or private records are included or relicensed.
