Skip to content

Proposal: safely unpublish an Agent Spec only after retirement completes #42

Description

@schickling-assistant

Removing a live Agent Spec is not retirement: reconciliation iterates discovered
specs, so deletion can leave its PTY/exec tasks alive with no declaration left
to tear them down.

The correct source protocol is already retired #true, followed by
reconciliation. The remaining gap for an external publisher is a safe,
machine-readable transition from a fully retired declaration to removal of only
its executable desired-state file.

Scenario

A policy-aware publisher needs to decommission one dynamic seat while
preserving its native history:

active agent.kdl
  -> CAS-publish same declaration with retired #true
  -> st2 stops and collects every declared task
  -> prove retirement complete
  -> remove only agent.kdl
  -> retain status and resources/

Implementing the last two steps in a Home Manager shell hook or downstream
controller is not authoritative:

  • st2 owns both PTY and exec task backends;
  • a downstream probe can accidentally inspect only one backend;
  • unreadable runtime state must be unknown, never absent;
  • a stale remover can race a newer declaration generation; and
  • history and executable declaration inputs have different retention rules.

Open issue #35 currently shows why the runtime fact must fail closed:
ExecBackend::list can collapse unreadable state to an empty set. That issue is
a prerequisite for treating any retirement-complete result as authoritative.

Proposed primitive

st2 agent unpublish <host.identity> \
  --catalog <root> \
  --expect-sha256 <retired-spec-digest> \
  --require-retirement-complete \
  --json

Suggested contract:

  1. Acquire the same writer lock used by Agent Spec publication.
  2. CAS the exact current declaration bytes.
  3. Parse and require retired #true.
  4. Derive all task IDs from that declaration.
  5. Query every configured runtime backend.
  6. Fail closed on any inspection error or any live/dead retained task record.
  7. Atomically unlink only canonical agent.kdl.
  8. Preserve status, resources/, and unrelated per-seat history.
  9. Return stable removed, unchanged, generation-conflict, or
    retirement-pending status.

Because the declaration remains retired throughout the absence check, the
normal supervisor cannot respawn its tasks between proof and unlink.

Possible scope

This can be either:

  • a companion to a low-level CAS agent publish primitive; or
  • a higher-level agent retire --wait followed by guarded agent unpublish.

The source of truth should remain the canonical Agent Spec. st2 does not need a
second finalizer object or SeatIntent state machine.

Identity-reuse epochs/tombstones can remain downstream initially. If stale
create-after-delete becomes a demonstrated cross-client problem, a generic
incarnation guard could be proposed separately.

Related

  • #35 must fail closed
    before retirement completion is authoritative.
  • #29 would provide the
    bounded reconcile/inspection path.
  • #18 originally tracked
    retired-agent doctor behavior; current main has moved toward “retired means
    every declared task absent”.

Version

  • st2 0.1.0+a77776a
  • source a77776ac553af9b9b04149f2bc9f6c269a008b83
  • Linux
Posted on behalf of @schickling
field value
agent_name co2-pine
agent_session_id cdd15622-c01a-4731-bbfe-ab8cce0cad54
agent_tool Codex CLI
agent_tool_version 0.145.0
agent_runtime Codex CLI 0.145.0
agent_model unknown
runtime_profile /nix/store/ph8rlhdj25mg71v81jsfzy6dq4xpcs9m-coding-agent-runtime-profile/share/coding-agents/profile.json
skills_manifest /nix/store/lsykz8x5481xrpbgk280xh3pypk1c5jy-agent-skills-corpus/share/agent-skills/manifest.json
worktree tmp
machine dev3
tooling_profile dotfiles@3649b53

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions