Skip to content

v0.5.0: Type-safe state tracking, lazy startup, and non-destructive cleanup - #1

Merged
ashish16052 merged 19 commits into
mainfrom
asm/0.5
Jul 10, 2026
Merged

v0.5.0: Type-safe state tracking, lazy startup, and non-destructive cleanup#1
ashish16052 merged 19 commits into
mainfrom
asm/0.5

Conversation

@ashish16052

Copy link
Copy Markdown
Owner

Summary

  • Replace string state literals with ServerState enum for type safety
  • Lazy server startup — start on first session() call, not on context entry
  • Non-destructive runtime.close() — safe for multi-process deployments

Changes

  • start() removed; servers start lazily on first session()
  • stop() replaced with close() (non-destructive)
  • All state tracking uses ServerState enum
  • SQLite registry with atomic claims
  • Health probes and inspect command

Fixes

  • One runtime exiting no longer kills servers from other processes

It serialized only event.text, silently dropping tool calls, permission
requests, and status events for anyone proxying the stream to a
frontend. No replacement — callers should serialize event.raw directly.
close() only cleared the local session_id and did no server-side
cleanup, so the name implied more than it did. reset() matches actual
behavior: the next ask()/stream() call starts a new conversation.
Prevents race conditions when multiple callers request the same server.
Exactly one succeeds in claiming the startup slot; others wait for it
to finish. Automatic cleanup of stale entries after 90s.
Store server lifecycle states (starting, running, stopping, failed) in registry
instead of just starting/ready. Display status is derived separately from state
+ observed health, so the registry only tracks what we intentionally did.
Add three new functions to compute server status from state + health observation:
- _is_process_alive(pid): check if PID is running
- _is_health_ok(client): call /health endpoint with timeout
- _compute_display_status(state, alive, health): derive user-facing status

These will be used by CLI and tests. No behavior changes yet.
Populate registry fields on server startup:
- runtime_version: set to current opencode-runtime version
- last_used_at: set when server reaches running state

Add ServerManager.touch(key) to update last_used_at when a session accesses
the server. Called from OpenCodeRuntime.session() after get_or_start().
New 'opencode-runtime inspect <id>' shows full server details:
- ID, Status (computed), Project, Workspace, User
- PID, Port, Uptime, Last used, Runtime version, Log file

Status display uses icons and colors (● running, ◐ starting, ▲ unhealthy, ○ stale).
Uptime/idle computed from timestamps in the registry.
- Update RegistryEntry.state type hint from str to ServerState
- Replace all state comparisons (state == "running") with enum values
- Replace all state assignments (state="starting") with enum values
- Update tests to use ServerState enum in fixtures and assertions
- Import ServerState in cli.py, server.py, and test files
- Update registry.claim_starting() to use ServerState.STARTING.value in SQL

Type-safe state tracking throughout the codebase. No functional changes.
Do not start a default server on context entry. Servers start only when
the user explicitly requests a session via runtime.session().

Changes:
- Remove start() method (was just calling session())
- __aenter__ now returns self without starting a server
- Prevents wasting resources on unused runtimes
- Better for multi-workspace apps that may never use default workspace

Example:
  async with OpenCodeRuntime() as runtime:
      session = await runtime.session(workspace="acme")  # starts here

This aligns with the "started on first use" design principle and prevents
creating unnecessary servers in applications that create a runtime at
startup but don't immediately use it.
@ashish16052
ashish16052 force-pushed the asm/0.5 branch 2 times, most recently from 8927cdb to e32b663 Compare July 10, 2026 11:36
@ashish16052 ashish16052 changed the title v0.4.5: Type-safe state tracking, lazy startup, and non-destructive cleanup v0.5.0: Type-safe state tracking, lazy startup, and non-destructive cleanup Jul 10, 2026
@ashish16052
ashish16052 force-pushed the asm/0.5 branch 2 times, most recently from 653fd0a to dd944da Compare July 10, 2026 16:02
close() now stops only servers this OpenCodeRuntime instance spawned
itself, tracked via ServerManager._owned. Servers it merely attached
to — already running, started by another process or the CLI — are
left alone.

- __aexit__ calls close()
- get_or_start() records ownership only when it actually spawns a
  process, not when attaching to an existing one
- close() calls the new stop_owned(), which stops and clears owned keys
- Remove stop() (was calling stop_all())

To stop servers this runtime didn't start, use the CLI:
  opencode-runtime stop <key> / stop-all
- Remove calls to removed start() method; use close() instead
- Lazy startup: tests call session() to trigger server creation
- test_context_manager_with_server now asserts close() stops the
  server this runtime started
- Add test_close_leaves_attached_server_running: a second runtime
  attached to the same server must not stop it via its own close()
- Remove test_stop_all_terminates_all_tenant_servers (old behavior)
@ashish16052
ashish16052 force-pushed the asm/0.5 branch 2 times, most recently from 6703390 to 6181986 Compare July 10, 2026 17:24
cmd_ps, cmd_health, and cmd_inspect each duplicated the same
process-alive check, health probe, and display-status computation
(plus, for cmd_ps/cmd_inspect, the same icon/color lookup tables).
Move it all into ServerManager as status()/list_statuses(), backed by
a new DisplayStatus enum, so the CLI only renders what ServerManager
computes instead of re-deriving it three times.
Stamp the registry database with a schema version (via SQLite's
PRAGMA user_version, no extra table needed) and add a migration
runner that walks a database up to the current version on open.
Unversioned (pre-existing) databases are treated as version 0 and
migrated in place; a database newer than this code understands fails
loudly (RegistrySchemaError) instead of being silently misread.

No column changes yet — this just lands the mechanism now, while the
schema is new and there's no real migration debt to carry forward.
status_coloured's own reset code cancelled the outer dim wrapper early, so every column after STATUS lost its dim styling.
manager.find() filters to RUNNING only, so a STARTING entry
(e.g. an orphaned claim) couldn't be stopped by key; read the registry entry directly instead.
Password is sent as HTTP Basic auth, not Bearer, per _headers().
- SQLite registry with atomic claims
- ServerState enum for type safety
- Health probes and detailed status
- Inspect command for server details
- Lazy startup (no auto-create default server)
- Non-destructive runtime cleanup (safe multi-process)
@ashish16052
ashish16052 merged commit 67d17e9 into main Jul 10, 2026
1 check passed
@ashish16052
ashish16052 deleted the asm/0.5 branch July 10, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant