Skip to content

feat: auto-update system for Conductor - #17

Merged
Jason Robert (jrob5756) merged 5 commits into
mainfrom
feature/release-management
Mar 4, 2026
Merged

feat: auto-update system for Conductor#17
Jason Robert (jrob5756) merged 5 commits into
mainfrom
feature/release-management

Conversation

@jrob5756

Copy link
Copy Markdown
Collaborator

Summary

Implements a complete auto-update lifecycle for Conductor, enabling automated releases, passive update notifications, and one-command upgrades.

Changes

Epic 1: GitHub Release Workflow

  • New .github/workflows/release.yml — tag-triggered CI/CD workflow that runs quality gates (lint, typecheck, test) and creates GitHub Releases with build artifacts on v* tag pushes
  • Supports pre-release tags (e.g., v0.2.0-beta.1)

Epic 2: Update Check Module

  • New src/conductor/cli/update.py — core update-check logic:
    • get_cache_path(), read_cache(), write_cache() — 24h cache at ~/.conductor/update-check.json
    • fetch_latest_version() — GitHub API with 2s timeout, silent failure
    • parse_version(), has_prerelease(), is_newer() — semver comparison with pre-release support
    • check_for_update_hint() — TTY-only, non-silent one-line hint
    • run_update() — version-pinned uv tool install --force upgrade
  • New tests/test_cli/test_update.py — comprehensive tests for all functions

Epic 3: CLI Integration

  • Modified src/conductor/cli/app.pyconductor update command + update hint in main() callback
  • Guards: TTY-only, non-silent, skips when subcommand is update

Epic 4: Documentation & Skill Updates

  • Updated AGENTS.md, .claude/skills/conductor/SKILL.md, .claude/skills/conductor/references/execution.md
  • Added conductor update to common commands, quick reference, and execution docs

Design

  • Zero new dependencies — uses urllib.request (stdlib) and tuple version comparison
  • Non-intrusive — 2s network timeout, 24h cache, TTY-only hints, respects --silent
  • Version-pinned installs — upgrades pin to exact release tag (e.g., git+...@v0.3.0)

Plan

See docs/projects/releases/release-management.plan.md for full solution design.

Jason Robertand others added 5 commits March 3, 2026 18:49
Add planning documents for the auto-update system:
- release-management.brainstorm.md: initial design notes covering
tag-triggered releases, update checks, and conductor update command
- release-management.plan.md: detailed solution design with architecture,
data flow, security considerations, and phased implementation plan
- Add .github/workflows/release.yml with tag-triggered release pipeline
- Workflow runs lint, typecheck, and test quality gates before release
- Pre-release tags (containing hyphen) produce pre-release GitHub Releases
- Build artifacts (.whl, .tar.gz) attached via gh release create --generate-notes
- Concurrency group cancels in-progress runs for same tag ref
- Update Epic 1 status to DONE in plan document
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add src/conductor/cli/update.py with full update-check logic:
get_cache_path, read_cache, write_cache (24-hour TTL),
fetch_latest_version (GitHub releases API, 2s timeout),
parse_version, has_prerelease, is_newer (semver + pre-release),
check_for_update_hint (TTY/silent/subcommand guards),
run_update (uv tool install --force git+...@{tag_name})
- Add 46 comprehensive tests in tests/test_cli/test_update.py
- Update plan document: Epic 2 status DONE, acceptance criteria checked
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add check_for_update_hint() call in main() callback guarded by TTY,
non-SILENT verbosity, and subcommand != 'update' with deferred import
- Add conductor update command (@app.command()) with error handling
- Add 7 CLI-level tests: TestUpdateCommand (3) and TestUpdateHintCLI (4)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add update.py to AGENTS.md cli/ architecture bullet list
- Verify AGENTS.md Common Commands includes conductor update
- Verify SKILL.md Quick Reference includes conductor update
- Verify execution.md has conductor update reference section
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jrob5756
Jason Robert (jrob5756) merged commit 2d75d00 into mainMar 4, 2026
7 checks passed
@jrob5756
Jason Robert (jrob5756) deleted the feature/release-management branch March 4, 2026 00:28
Haitao Feng (fenghaitao) added a commit to fenghaitao/conductor that referenced this pull request Jun 4, 2026
…onductor
Route conditions: conductor's simpleeval evaluates Python-style booleans,
so route `when` expressions require `True`/`False` (capitalised), not
`true`/`false`. Fixed in mission-plan.yaml and orchestrator-review.yaml.
Output templates: replace `| default(X)` with `if Y is defined else X`
for outputs that reference steps that may not have run (idempotent skip
paths). The `default()` filter only suppresses Undefined errors on the
value itself — it does not guard against the step being absent. Affected:
plan-architecture.yaml, orchestrator-review.yaml, write-contract-area.yaml,
write-skill.yaml.
Error handling: wrap the YAML parse validation in save_services_yaml
(plan-write-artifacts.yaml) in try/except so a malformed LLM-generated
services.yaml logs the error to stderr and exits cleanly instead of
crashing the script with an unhandled exception.
Cleanup: remove internal gap reference labels (gap microsoft#10, microsoft#17, microsoft#19) from
prompt text and comments — these were planning artefacts not useful to
workers reading the files at runtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Jason Robert (jrob5756) pushed a commit that referenced this pull request Aug 24, 2026
Blocking fixes (PR #484 review):
- _restart_spawned_runtime no longer publishes the rebuilt client until
it has actually started: _client/_started are invalidated first, so a
failed start() (e.g. OOM at spawn) leaves the provider correctly
believing no client is started, instead of silently disabling
dead-runtime recovery for the rest of the process.
- The consecutive-restart cap is now checked before incrementing the
counter and is never left stale: the cap can no longer be tripped
after zero actual restarts, the giving-up message reports the real
restart count, and close() resets the counter so a cached provider
isn't permanently wedged after a workflow crash-loops once.
- Replaced the unfalsifiable cap-message assertion in
test_copilot_runtime_recovery.py with one that pins the rendered
clause and asserts the cap actually prevents the next rebuild.
- Added tests/test_providers/conftest.py: an autouse fixture clearing
COPILOT_PROVIDER_RUNTIME_URL/TOKEN so the runtime-recovery tests pass
regardless of the developer's/CI runner's environment.
- Added a regression test covering the corrupted-state bug: when the
rebuilt client's start() raises, _started must end up False and a
later _ensure_client_started() must re-attempt start().
Recommendations applied:
- _runtime_unavailable_error now distinguishes a confirmed-dead process
(poll() returned an exit code) from a broken connection to a still-
alive process, instead of always claiming the process "died" and
suggesting NODE_OPTIONS.
- Client teardown during restart, and session.disconnect() in the
per-agent finally block, now log a warning on failure instead of
silently swallowing the exception (a leaked child / stranded session
is diagnostically useful, especially given this PR's own OOM focus).
- The session.error ProviderError path is now also routed through dead-
runtime classification when retryable, instead of always surfacing a
generic "Copilot SDK error" message that hides an exit-code 137 OOM
kill.
- Narrowed _spawned_runtime_process's return type from Any | None to
subprocess.Popen[bytes] | None, matching the isinstance check the
body already performs and the SDK's own annotation.
- Added a one-time warning when a spawned, started client has no usable
_cli_process handle, so a future SDK rename surfaces instead of
silently degrading recovery to a no-op.
- Fixed the inverted _FakeClient docstring/comments describing mock
auto-vivification as looking "live" when it in fact reads as dead.
- Scoped the restart-counter-reset comment to agent execution (several
auxiliary paths increment without resetting).
- Updated CHANGELOG.md, docs/configuration.md and AGENTS.md to name the
restart cap (2, fixed, non-configurable), correct the "endlessly
retrying" overstatement, and scope the SDK-boundary claim to
agent-execution; documented the _cli_process vs _process split.
Recommendations skipped (not applied): #5 (_interrupted_session reset +
disclosure wording), #6 (max_session pre-flight), #12 (Liveness enum),
#13 (_RestartBudget value type), #17 (per-generation client tracking
for parallel groups), #18 (additional missing tests beyond the one
added for finding #1), #19 (collapsing except clauses), #20 (extracting
shared helpers) -- all correctness-neutral hardening/refactors judged
to grow the diff beyond what this pass should touch; pyproject.toml
dependency cap was also left alone as an unrelated, broader change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Jason Robert (jrob5756) pushed a commit that referenced this pull request Aug 24, 2026
Blocking fixes (PR #484 review):
- _restart_spawned_runtime no longer publishes the rebuilt client until
it has actually started: _client/_started are invalidated first, so a
failed start() (e.g. OOM at spawn) leaves the provider correctly
believing no client is started, instead of silently disabling
dead-runtime recovery for the rest of the process.
- The consecutive-restart cap is now checked before incrementing the
counter and is never left stale: the cap can no longer be tripped
after zero actual restarts, the giving-up message reports the real
restart count, and close() resets the counter so a cached provider
isn't permanently wedged after a workflow crash-loops once.
- Replaced the unfalsifiable cap-message assertion in
test_copilot_runtime_recovery.py with one that pins the rendered
clause and asserts the cap actually prevents the next rebuild.
- Added tests/test_providers/conftest.py: an autouse fixture clearing
COPILOT_PROVIDER_RUNTIME_URL/TOKEN so the runtime-recovery tests pass
regardless of the developer's/CI runner's environment.
- Added a regression test covering the corrupted-state bug: when the
rebuilt client's start() raises, _started must end up False and a
later _ensure_client_started() must re-attempt start().
Recommendations applied:
- _runtime_unavailable_error now distinguishes a confirmed-dead process
(poll() returned an exit code) from a broken connection to a still-
alive process, instead of always claiming the process "died" and
suggesting NODE_OPTIONS.
- Client teardown during restart, and session.disconnect() in the
per-agent finally block, now log a warning on failure instead of
silently swallowing the exception (a leaked child / stranded session
is diagnostically useful, especially given this PR's own OOM focus).
- The session.error ProviderError path is now also routed through dead-
runtime classification when retryable, instead of always surfacing a
generic "Copilot SDK error" message that hides an exit-code 137 OOM
kill.
- Narrowed _spawned_runtime_process's return type from Any | None to
subprocess.Popen[bytes] | None, matching the isinstance check the
body already performs and the SDK's own annotation.
- Added a one-time warning when a spawned, started client has no usable
_cli_process handle, so a future SDK rename surfaces instead of
silently degrading recovery to a no-op.
- Fixed the inverted _FakeClient docstring/comments describing mock
auto-vivification as looking "live" when it in fact reads as dead.
- Scoped the restart-counter-reset comment to agent execution (several
auxiliary paths increment without resetting).
- Updated CHANGELOG.md, docs/configuration.md and AGENTS.md to name the
restart cap (2, fixed, non-configurable), correct the "endlessly
retrying" overstatement, and scope the SDK-boundary claim to
agent-execution; documented the _cli_process vs _process split.
Recommendations skipped (not applied): #5 (_interrupted_session reset +
disclosure wording), #6 (max_session pre-flight), #12 (Liveness enum),
#13 (_RestartBudget value type), #17 (per-generation client tracking
for parallel groups), #18 (additional missing tests beyond the one
added for finding #1), #19 (collapsing except clauses), #20 (extracting
shared helpers) -- all correctness-neutral hardening/refactors judged
to grow the diff beyond what this pass should touch; pyproject.toml
dependency cap was also left alone as an unrelated, broader change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Jason Robert (jrob5756) added a commit that referenced this pull request Aug 24, 2026
* fix(copilot): recover from a dead spawned Copilot runtime process
Detect when the nested Copilot runtime subprocess has died (broken
pipe/connection reset, or a check before sending an idle-recovery
prompt) and transparently restart it on the next attempt instead of
surfacing a confusing stuck-agent error. Externally-owned runtimes
(runtime_url) are never restarted here -- that failure is reported as
non-retryable so the owning orchestrator can act. A consecutive
restart counter (reset on any successful SDK call) caps restart
attempts so a runtime that keeps dying before ever succeeding fails
fast rather than looping forever.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(copilot): address review findings on runtime restart state machine
Blocking fixes (PR #484 review):
- _restart_spawned_runtime no longer publishes the rebuilt client until
it has actually started: _client/_started are invalidated first, so a
failed start() (e.g. OOM at spawn) leaves the provider correctly
believing no client is started, instead of silently disabling
dead-runtime recovery for the rest of the process.
- The consecutive-restart cap is now checked before incrementing the
counter and is never left stale: the cap can no longer be tripped
after zero actual restarts, the giving-up message reports the real
restart count, and close() resets the counter so a cached provider
isn't permanently wedged after a workflow crash-loops once.
- Replaced the unfalsifiable cap-message assertion in
test_copilot_runtime_recovery.py with one that pins the rendered
clause and asserts the cap actually prevents the next rebuild.
- Added tests/test_providers/conftest.py: an autouse fixture clearing
COPILOT_PROVIDER_RUNTIME_URL/TOKEN so the runtime-recovery tests pass
regardless of the developer's/CI runner's environment.
- Added a regression test covering the corrupted-state bug: when the
rebuilt client's start() raises, _started must end up False and a
later _ensure_client_started() must re-attempt start().
Recommendations applied:
- _runtime_unavailable_error now distinguishes a confirmed-dead process
(poll() returned an exit code) from a broken connection to a still-
alive process, instead of always claiming the process "died" and
suggesting NODE_OPTIONS.
- Client teardown during restart, and session.disconnect() in the
per-agent finally block, now log a warning on failure instead of
silently swallowing the exception (a leaked child / stranded session
is diagnostically useful, especially given this PR's own OOM focus).
- The session.error ProviderError path is now also routed through dead-
runtime classification when retryable, instead of always surfacing a
generic "Copilot SDK error" message that hides an exit-code 137 OOM
kill.
- Narrowed _spawned_runtime_process's return type from Any | None to
subprocess.Popen[bytes] | None, matching the isinstance check the
body already performs and the SDK's own annotation.
- Added a one-time warning when a spawned, started client has no usable
_cli_process handle, so a future SDK rename surfaces instead of
silently degrading recovery to a no-op.
- Fixed the inverted _FakeClient docstring/comments describing mock
auto-vivification as looking "live" when it in fact reads as dead.
- Scoped the restart-counter-reset comment to agent execution (several
auxiliary paths increment without resetting).
- Updated CHANGELOG.md, docs/configuration.md and AGENTS.md to name the
restart cap (2, fixed, non-configurable), correct the "endlessly
retrying" overstatement, and scope the SDK-boundary claim to
agent-execution; documented the _cli_process vs _process split.
Recommendations skipped (not applied): #5 (_interrupted_session reset +
disclosure wording), #6 (max_session pre-flight), #12 (Liveness enum),
#13 (_RestartBudget value type), #17 (per-generation client tracking
for parallel groups), #18 (additional missing tests beyond the one
added for finding #1), #19 (collapsing except clauses), #20 (extracting
shared helpers) -- all correctness-neutral hardening/refactors judged
to grow the diff beyond what this pass should touch; pyproject.toml
dependency cap was also left alone as an unrelated, broader change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Jason Robert <jasonrobert@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto 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

@jrob5756