OpenProgram Docs

Upgrade installed releases#

Upgrade behavior depends on the installation type. A stable installation always moves between published versions; it never follows origin/main.

Version 0.7.0 is the one-time transition from v0.6.6 to the updater-enabled release line: Desktop users install the v0.7.0 DMG manually; CLI/server users rerun the complete release installer once:

curl -fsSL https://openprogram.io/install | sh

After installing v0.7.0, the Desktop settings and openprogram upgrade commands below handle later stable releases.

Desktop release#

The Desktop checks the latest stable GitHub Release automatically. You can also use Settings → General → Application → Check now.

  • macOS: when a release is available, choose Download and open DMG. OpenProgram selects the architecture-matched complete DMG, downloads it to the location you choose, verifies its byte count and SHA-256, and opens it. Quit OpenProgram and replace OpenProgram.app; older unsigned packages may require Privacy & Security → Open Anyway again.
  • Linux: rerun the release installer from the target immutable tag. Linux currently has no published desktop package.

The application shell and complete product runtime are replaced together. State under ~/.openprogram remains unchanged.

macOS development and public releases#

Local development and public distribution use separate signing modes:

  • Local iteration: scripts/refresh-local-app.sh rebuilds the default installed development App with its persistent local certificate. It does not submit each edit to Apple. Keep the local signing state outside the repository and reuse it; do not delete it to fix a permission prompt.
  • Public release: the Release GitHub Actions workflow runs for a v* tag, or manually for an existing immutable tag. It signs the application and embedded native components with Developer ID, waits for Apple notarization, staples and validates tickets, runs the packaged-runtime checks, and only then publishes checksummed artifacts. Ordinary branch pushes do not trigger this workflow.

The workflow requires repository Secrets MAC_CSC_LINK (base64 encrypted PKCS#12), MAC_CSC_KEY_PASSWORD, APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID, plus repository variable MAC_SIGNING_IDENTITY. Store them in GitHub settings, never in source files. CI imports them into a temporary keychain and removes it when the signing step exits.

To rebuild an existing reviewed release tag, use the Release → Run workflow UI or gh workflow run release.yml -f tag=vX.Y.Z. The tag, package versions and release notes must agree. Do not recreate a certificate or app password for each release. If a credential expires or is revoked, update the corresponding Secret before retrying.

A notarization submission ID appears in the signing job log. In Progress means Apple has not finished; Accepted permits publication; other states stop publication. A 45-minute wait timeout does not cancel the submission. Use xcrun notarytool info SUBMISSION_ID --keychain-profile PROFILE to check it and xcrun notarytool log SUBMISSION_ID --keychain-profile PROFILE for rejection details. After resolving the cause, rerun the failed workflow for the same immutable tag; a new build may create a new notarization submission.

Keep the development App on machines used for frequent source iteration. A public Developer ID App has a different identity; local refresh intentionally refuses to overwrite it with a development signature. Switching between those modes can require fresh macOS permission consent. Agent bypass settings do not control these macOS permissions.

CLI and server release#

Check or upgrade to the latest stable release:

openprogram upgrade --check
openprogram upgrade

To select a specific immutable release instead:

curl -fsSL https://openprogram.io/install | OPENPROGRAM_VERSION=X.Y.Z sh

On Windows, the equivalent command is:

$env:OPENPROGRAM_VERSION = "X.Y.Z"
irm https://openprogram.io/install.ps1 | iex

The command downloads the versioned installer from the immutable release tag. The installer downloads the platform runtime archive, verifies its checksum and complete capability manifest in a staging directory, and cold-starts the worker before publishing or activating that version. macOS and Linux serialize upgrades and atomically change the current symlink; Windows atomically replaces the PowerShell launcher and retains its previous launcher. A failure before activation leaves the previous version selected and removes an unpublished staging runtime. Version directories are retained, so rollback uses the same command with the previous OPENPROGRAM_VERSION. A running worker is not restarted automatically.

On Windows, both PowerShell and CMD launchers are prepared before activation. Each file is replaced atomically; if the second replacement fails, the installer restores the first entry point. If restoration also fails, the installer stops with a manual-recovery error and retains the original launcher at the reported path. Keep that recovery file until both entry points work again; a later successful installation does not delete it. A validated runtime is retained for retry even when launcher activation fails.

Restart a login service after upgrading:

openprogram worker restart

Recovering a conversational self-update#

The source-based conversational update controller currently requires macOS. On Windows and Linux, the chat prepare and retry tools report this before creating an update request or reserving its active slot. This does not disable published-version upgrades: use openprogram upgrade for a release-installed CLI, or the Desktop release updater. Existing update status and cancellation remain available; a worker service alone does not provide the source-update controller's build, activation, verification and recovery workflow.

During an approved update, ordinary queued Jobs retain their identities and wait for maintenance to end. Running executions that support durable pause stop at their next checkpoint. The update waits for active attempts and resource claims to finish before replacing the App. After a verified commit or rollback, the worker resumes only executions paused by that update. Tasks already paused by the user or waiting for input stay paused. A task without a safe pause must finish before the update deadline; otherwise activation is aborted. Existing checkpoint validation still rejects changed tool contracts instead of replaying an old operation against a different implementation. When that validation rejects an update-owned continuation, the worker preserves the old checkpoint and creates one fresh planning Job from the original task and history, retaining its tool selection and permission rules. Unresolved side effects or a user cancellation prevent automatic replanning.

Newly prepared updates also save an original-session follow-up with a stable Job identity. Once maintenance and any automatic repair sequence finish, it uses the original conversation and update evidence to continue checking the requested behavior. This is a new background turn and does not grant approval for another installation. Older update records do not receive retroactive follow-ups. Installation success and task continuation are separate outcomes.

Conversational packaging is offline. Its dependency base must have exactly the candidate's uv.lock and scripts/release/product-runtime.json; the controller uses the saved runtime's pinned build tools and private copies of existing npm/uv/Electron-builder/node-gyp caches. The Electron platform archive must match the trusted controller's pinned version, architecture, and release SHA-256; the candidate receives it as a read-only electronDist input. It still builds the candidate's Web, docs, wheel and Desktop archive. Missing caches or a different dependency base stop the update before activation; they do not authorize downloads or reuse of mismatched dependencies. This build path and real installed acceptance remain release gates.

The packaged worker smoke test uses one controller-selected private loopback port and never the default port 18100. Browser automation is checked separately by the revalidated saved controller runtime, which is outside every candidate-writable path, against browser assets whose tree hash matches that runtime. The writable build-runtime copy is never executed by the controller. Both checks must complete before activation. The fixed macOS icon render check also runs under the trusted controller instead of granting the candidate build access to host graphics services.

The source-checkout self_update_prepare chat tool accepts an optional verification_plan argument. The plan is included in the mandatory owner approval and stored with the immutable request. For example:

{"schema":1,"checks":[{"id":"diagnostics","assertion_id":"acceptance-1","entry":"/api/diagnostics","timeout_seconds":10,"max_output_bytes":65536}]}

Provide exactly one check for each assertion (acceptance-1, acceptance-2, and so on), from 1 to 32 checks. All fields shown are required; schema must be the integer 1. Check IDs must be unique alphanumeric/underscore/hyphen identifiers starting with an alphanumeric character, at most 64 characters. Each check requires an integer timeout of 1–60 seconds and an integer output limit of 1–262144 bytes (1–1572864 for ui:main). Supported entries are /api/commands, /api/diagnostics, /api/doctor, /healthz, /chat, cli:version, cli:help, test:python and ui:main. Arbitrary URLs, query strings and unsupported fields are rejected before creating the update; only test:python additionally requires argv as described below.

The restarted verifier receives the same plan and calls self_update_observe(check_id="diagnostics"), without supplying entry or execution arguments. The execution layer applies the approved limits and the original overall deadline. Signed evidence must match that check and assertion; reusing evidence for another assertion does not pass. Omitting the plan preserves the previous HTTP-only verifier behavior and grants no new permissions.

For a planned update, verification, post-rollback diagnosis and source-repair Jobs receive the same approved plan and iteration policy, together with the attempt's timeout. A repaired child candidate keeps the original goal, assertions, check IDs, limits, model and authority; it cannot refresh the overall iteration deadline. Diagnosis and source repair still have only their read tools: including the verification plan in their prompts does not authorize them to execute checks.

The fixed CLI entries run the installed App's Python with -I -B -m openprogram and respectively --version or --help, never a model-supplied command or PATH lookup. Preparation rejects a CLI plan without the native sandbox or a compatible installed runtime manifest and matching package/App build-revision markers. Execution uses private temporary directories, blocks network access and App/source writes, and denies owner-home reads outside the required runtime and scratch paths. Native verification is single-process: the sandbox denies process creation, including ordinary or detached subprocesses, fork and posix_spawn. A check requiring a child process cannot pass through this adapter. This restriction applies to these CLI checks and the candidate checks below, not the separate source-repair required-test or build execution paths. Evidence binds the runtime identity, exact invocation and exit status; nonzero exit, timeout, cancellation, changed identity, excess output or failed cleanup cannot pass. These entries check CLI startup/help only, not arbitrary feature behavior.

For a candidate source test, add a check such as:

{"id":"source-test","assertion_id":"acceptance-1","entry":"test:python","argv":["tests/verify_feature.py","expected"],"timeout_seconds":30,"max_output_bytes":65536}

Here argv contains 1–32 strings of at most 4096 characters each, with no NUL. The first item is a committed regular .py file relative to the candidate root, not a symlink, absolute path, parent traversal or interpreter option. Script paths start with an ASCII letter, digit or underscore and otherwise use letters, digits, underscore, slash, dot or hyphen, with at most 511 characters. The remaining items are literal script arguments, approved before the update; the verifier cannot change them. The installed candidate Python runs -I -B SCRIPT ARGS from the registered candidate worktree root. Isolated mode does not automatically add that root to Python's import path; a test importing candidate code must do so explicitly. The registered candidate path is an additional allowed read location, including when it is under the owner's HOME; other owner-home data stays inaccessible. The candidate remains read-only. Temporary test data belongs in the private TMPDIR or HOME, and no dependency installation is performed. Native CLI prerequisites and limits also apply. Missing/dirty/unregistered source or a changed script blocks verification; evidence records the source revision, script digest and invocation. A candidate_test result proves source-test execution, not installed-App behavior.

For a read-only capture of the original session's main App window, use:

{"id":"main-capture","assertion_id":"acceptance-1","entry":"ui:main","timeout_seconds":30,"max_output_bytes":1048576}

Preparation requires a compatible packaged UI-verification descriptor, runtime identity and exactly one connected main Desktop window. The candidate and rollback packages must both contain matching capture/backend/frontend capability bindings before installation. An older package without this capability cannot use this plan. The verifier receives a PNG image and accessibility tree, not merely a file path; the approved output limit covers the entire capture JSON, including base64 image data. PNGs must be non-interlaced 8-bit RGB/RGBA, with each dimension at most 16384 and at most 32 million pixels. The resolved verifier model must declare image input support. Preparation rejects a text-only model; restart recovery checks the capability again before creating the verifier Job. If image support is no longer available, startup records an error instead of running a text-only verifier. An already queued Job repeats this check at execution, before calling the model.

Capture is bound to the active verification Job, candidate revision, worker, original session route and exact main-window connection. Expired, cancelled, replayed or identity-mismatched requests cannot produce passing evidence. User input, navigation, window changes, conflicting capture resources, excess output or incomplete cleanup also prevent successful capture. No arbitrary URL, JavaScript, target window, click, navigation or data mutation is authorized. During capture, the current Desktop adapter rejects new page network requests, native IPC operations and external-link navigation from that main window. The restriction is released before evidence upload, including on failure, and does not apply to other windows. It does not revoke already running requests. The native adapter marks the main renderer's HTTP requests; the backend rejects marked requests, including authentication bootstrap and stale markers. During the check, that window's existing WebSocket accepts only observation replies, cancellation of the exact verifier Job, not ordinary application commands.

To capture the conversation after an approved scroll, add interaction:

{"id":"scroll-history","assertion_id":"acceptance-1","entry":"ui:main","timeout_seconds":30,"max_output_bytes":1048576,"interaction":{"kind":"scroll","delta_y":-400}}

delta_y is a nonzero integer from -1200 to 1200 CSS pixels; the target is always the original main conversation. No selector or script is accepted. This requires UI protocol 2 in both candidate and rollback packages, binding the actual native scroll adapter, backend guards and compiled UI persistence guard. Protocol 1 packages remain capture-only and are rejected for scroll plans before installation. The screenshot and accessibility tree show the after-scroll state; signed evidence also records before, after and restored scroll metrics. The position is restored before success without persisting the temporary position. Failed capture attempts restore their own scroll while the original target and deadline remain valid. User interruption cancels the check without restoring over the user's new position. Target changes or failed restoration cannot pass. At a scroll boundary the position may remain unchanged; this alone does not prove an assertion requiring movement.

For the original conversation's context graph, use a frozen perspective check:

{"id":"context-view","assertion_id":"acceptance-1","entry":"ui:main","timeout_seconds":30,"max_output_bytes":1048576,"interaction":{"kind":"view","target":"dag"}}

target is exactly session or dag, not a URL or another session. The check starts with the original conversation visible and invokes its actual perspective control. It captures the requested perspective and restores the conversation and scroll position before success. An already selected DAG, a missing/replaced control, user interruption or failed restoration cannot pass. A session target does not change the perspective and cannot prove that a switch occurred. No temporary perspective is persisted, including through background tab updates. On user interruption the adapter does not force the original perspective or scroll position over the user's selection. Failed restoration may leave the requested perspective visible; the check reports failure, not successful cleanup. Both candidate and rollback packages require UI protocol 3, which additionally binds the compiled perspective support. Protocols 1 and 2 keep their previous capture and scroll capabilities; they cannot accept perspective checks.

Temporary test-object rename dialogs are not supported. New verification plans and legacy execution requests using test_object are rejected before requesting UI interaction. Existing protocol 4 packages remain readable for rollback compatibility; new packages advertise only supported capture, scroll and perspective capabilities.

The screenshot supports only assertions about the captured state; interactions that were not observed remain inconclusive. HTTP responses, including /chat HTML, do not prove rendered App behavior. Other side-effect operations are not supported. Complete verification and actual installed-App acceptance remain pending.

This source-checkout capability is separate from stable-release upgrades. On macOS, if a conversational self-update leaves the default App in maintenance, inspect it from your local terminal without starting an Agent:

openprogram self-update status --json
openprogram self-update status UPDATE_ID --json
openprogram self-update repair UPDATE_ID

Replace UPDATE_ID with the ID reported by status. Repair requires an interactive terminal and the exact confirmation displayed with the action, revision and plan digest. There is no --yes or force-clear option. An existing approved attempt can resume only within its original ten-minute window; a failed or expired attempt requires fresh confirmation.

Repair uses the controller saved before the update. It restores the previous App when rollback remains possible, or completes an already-started irreversible commit only with the original accepted verification evidence. An aborted, unactivated transaction retains the old App. Missing or changed evidence leaves maintenance enabled. Repair restarts the default worker, checks the App identity and live service, then clears maintenance. It creates no new verifier Job and does not change the original update verdict. The separate repair result records which version was recovered; service recovery is not proof that a failed feature meets its original goal.

New conversational update requests also freeze a read-only diagnostic stage. After a verified rollback, the restored worker creates one Post-rollback diagnosis Job in the original session. It uses the approved model and profile, reads failure evidence, and reports a cause and proposed corrections. It cannot edit source, run shell commands, install software, or authorize another update. The original verification and rollback verdicts are retained.

The Job has at most five minutes after rollback, shortened by any earlier approved iteration deadline. Restart does not reset that limit or repeat a terminal Job. Use the ordinary Job cancel action to stop diagnosis; self_update_cancel still only cancels a pre-activation update. A new update supersedes pending diagnosis. Unavailable models or invalid evidence stop diagnosis without restricting the restored service. self-update status --json includes diagnosis_result when one exists. Older requests without frozen diagnostic configuration keep their prior behavior. A diagnostic report does not itself modify or install code.

New requests separately freeze a Post-rollback source repair stage. Initial approval includes isolated repair and the listed required_tests. Default mode requires a separate approval before another installation; explicitly approved bounded_auto also permits subsequent installations within its original limits. An implementation/test diagnosis can trigger one read-only model Job that proposes text edits. The controller validates each edit, creates a new linked worktree and commit, and runs the frozen tests in a native sandbox without network access or App write permission. Your original worktree is unchanged. Default scope is the original changed files; bounded_auto uses its approved path patterns. Protected runtime, approval, installer, dependency and Git files are not automatically modified. Invalid paths or non-unique old text stop repair.

Repair and tests share at most ten minutes after rollback, shortened by an earlier approved deadline. A new update, cancellation or expiry stops this stage and its test processes. Use the ordinary Job cancel action while the model runs; after the model finishes, call self_update_repair_cancel(update_id) in the original conversation to cancel tests. Restart does not replay partial edits, commits or tests. Failed worktrees and evidence remain available for inspection. Old requests without frozen repair configuration do not gain this capability.

self_update_status and self-update status --json expose source_repair_result. The chat tool uses the same read-only projection as the owner-authenticated Web history (GET /api/self-updates?session_id=…) and detail (GET /api/self-updates/{update_id}?session_id=…) endpoints. History includes completed attempts and supports bounded limit and cursor pagination. candidate_revision and target_app describe the requested installation; last_verified_runtime contains the last matching verification's SHA, PID, timestamp and source, or is null when unknown. It does not prove the worker is still online. state_revision is a state counter, not a Git revision. Verifier evidence can be read with GET /api/self-updates/{update_id}/evidence?session_id=…&evidence_id=…, using the verifier's evidence_id or an assertion's evidence_refs value from that projection. Owner authentication and the original session are required. The response contains only observations cited by the validated signed result, not arbitrary files, credentials or configuration. Stored HTTP/HTML response text is not proof of a rendered App window. Changed or invalid evidence returns an error.

Queries do not initialize or repair update state. Invalid state returns an error rather than an empty history. Running reports self_update_error if its update snapshot is unavailable. The projection excludes credentials, raw logs and configuration; repair summaries include status and the new candidate SHA when present. It does not replace the separate CLI recovery inspection output.

Conversations display ordinary messages and tool results. They do not mount update history, update controls, evidence viewers or recovery notices, and do not poll update-history APIs. Explicit status tools and the authenticated APIs remain available.

candidate_ready means the new commit and all configured tests passed validation; awaiting_tests means no required tests were configured. Missing/failed tests or source drift produce failed; cancellation and expiry produce cancelled and expired. None means installed.

For a tested repaired candidate, call self_update_retry(update_id, candidate_sha) in the original owner conversation. It always asks for one-shot approval, even in bypass mode. The approval displays the exact SHA, changed paths, tests and remaining budget. Git contents and test logs are checked again after approval; changing either invalidates submission. An awaiting_tests candidate cannot be approved through this entry: start a new owner request with explicit tests.

New bounded_auto requests must include a future total deadline, allowed path patterns and non-empty required_tests. Only requests with the separately frozen iteration authorization can automatically submit another candidate. An old request's mode field alone never grants that permission. Each child preserves the original goal, assertions, source baseline, model/profile and policy. The first update counts as attempt 1; the maximum of three includes it. Reserving a child consumes one attempt, including failed submissions. Restart reuses the same child and deadline rather than resetting the budget.

self_update_iteration_cancel(update_id) stops the whole sequence, including pending approval, diagnosis, source repair and candidate tests. A child already being activated or verified must finish its transaction or safe rollback; no subsequent attempt is allowed. This does not cancel unrelated jobs.

The chat self_update_status result includes an iteration section with root, parent, attempt limits, deadline and submission status. submitted means the request was handed to the external supervisor, not that installation succeeded. The child still goes through packaging, system checks, a new verification Job and commit or rollback. Full installed-App acceptance remains a separate release requirement; fixture tests do not establish that the current App was updated.

The worker also persists finite update results in the original session, even if the window is closed or the update stops before a verification Job is created. Repeated worker reconciliation reuses the same update, attempt and result-type identity; it does not start another model turn or move the conversation's HEAD. An interrupted write is retried. If the original session or initiating assistant node no longer exists, delivery remains pending without recreating the session or sending the result elsewhere. Persisting a result does not reopen the App or prove that installation succeeded; the update phase and verified runtime evidence remain the source of that conclusion.

Desktop now has a receiver for a trusted, update-specific conversation recovery request. It resolves only the original session through owner authentication and acknowledges it after the transcript loads in the main window. An expired request, a deleted session or an authentication failure leaves normal startup available without adding a recovery notice. Changing pages stops automatic relocation; recovery reasons remain internal status. Neither recovery nor its loading confirmation starts another verification Job or proves the update succeeded. The controller now persists the recovery intent before activation and binds the opaque update ID to the installer transaction. If the App was open, both activation and rollback reopen it with that ID; an originally closed App stays closed. Ordinary App launches do not consume these recovery requests.

Both the candidate and installed App must contain matching packaged recovery protocol declarations. Missing, incompatible or changed declarations, a mismatched transaction ID, or invalid frozen owner configuration stop activation before the old App is replaced. The controller rechecks these inputs after waiting and after resuming a prepared update. Packaging and the local refresh script generate the declaration from the actual Desktop archive, installer, runtime manifest, backend and compiled Web files. An older App without it needs an explicit complete update before conversational recovery is available. The source integration is covered by fixture tests; real installed-App restart and session/tab restoration acceptance remain pending, so this is not yet a release-ready end-to-end feature.

If the App or normal CLI cannot start, use the entry saved for that update:

"$HOME/.openprogram/self-updates/UPDATE_ID/recover.sh" status
"$HOME/.openprogram/self-updates/UPDATE_ID/recover.sh" repair

The script uses the original saved runtime outside the App. status is also the default when no argument is given. repair still requires interactive owner confirmation; it does not bypass failed evidence or expired authorization. recover.sh resume invokes the original supervisor within its existing authority and deadlines, without approving a new update or recreating a verifier Job.

Before activation, OpenProgram also publishes the update's user-owned ai.openprogram.self-update.recovery.UPDATE_ID.plist under ~/Library/LaunchAgents/. It runs once per subsequent user login, independently of the App. There is no resident process or periodic retry, and writing the file does not start another controller immediately. New recovery jobs use launchd's Standard process class so runtime integrity reads are not classified as discretionary background work. Previously saved Background entries retain their exact bindings and remain supported; recovery does not rewrite their scheduling policy. Recovery does not run before login or disk unlock. If both the App and controller stop in the current login session, use the saved script explicitly. Completed updates validate the saved runtime and remove only their unchanged login file within the same state lock, without hashing the entire runtime a second time for cleanup. Recovery writes progress to bootstrap.log before runtime validation and controller resumption. The saved runtime, script and evidence remain. Missing or damaged trusted recovery files require manual intervention rather than reconstruction from an unverified App.

Development checkout#

For the locally installed macOS App, run scripts/refresh-local-app.sh from the current checkout after committing a change. The script rebuilds the wheel and Desktop assets, builds the GUI, Research and Wiki Program wheels before the App is quit (a failed source build leaves the installed App unchanged), updates both the PATH and embedded Python installations, and refreshes bundled Node and Ink resources and the App icon. It probes the complete embedded runtime before regenerating its capability manifest and restarting the default worker. This also upgrades older App runtime manifests when a newly required capability is introduced; a failed probe stops the refresh instead of declaring the capability verified.

The refreshed worker uses the embedded App Python with -I -B; an existing launchd service is rebound to that same interpreter, and detached startup uses it as well. The script verifies the actual worker process executable and flags after the health check. A matching package revision alone does not prove interpreter identity. Existing checkpoints retain their strict tool implementation contract; switching Python installation paths can invalidate implementation fingerprints.

The copied Node executable must work outside its original installation. The script checks this before modifying the App. If your PATH selects a Node build that needs adjacent libraries, set OPENPROGRAM_NODE_BIN to a standalone Node executable when running the refresh script.

In a source checkout, openprogram upgrade uses the development pipeline instead of the release installer. It validates a Git target, updates dependencies and built assets when their source files changed, probes the new checkout, and restarts the worker only after the probe succeeds:

openprogram upgrade --check
openprogram upgrade --dry-run
openprogram upgrade

The historical openprogram update command is a compatibility alias for openprogram upgrade.

See Server upgrading for source-checkout recovery details. The maintained architecture, trust boundaries, UI states, and implementation evidence are in Automatic updates.

Last updated · 2026-10-10