…e + spawn provenance (trinity-enterprise#69)
Disposable "ghost" agents: created with a hard budget (max_executions and/or
TTL — expiry ALWAYS stamped, ceiling 24h default), volume-less (container
writable layer; ghosts never recreate), hard-discarded at budget with no
soft-delete/retention/name-reservation. Creation is entitlement-gated
(ephemeral_agents; fail-closed — inert until the enterprise module registers);
all lifecycle mechanics are edition-agnostic OSS primitives.
Lifecycle:
- Schema: 5 additive agent_ownership columns (is_ephemeral, budget, expiry,
spawned_by_agent/key_id); dual-track migration (SQLite + Alembic 0016)
- Creation gates (crud.py): entitlement 403 → ephemeral-caller refusal
(chain-spawn kill) → per-parent spawn rate limit → TTL ceiling 400 →
server-suffixed name (hex8) → atomic per-owner Redis quota (INCR-with-cap,
NX reseed, DB fallback); ghosts skip volume/avatar/cred-injection/auto-sync,
default max_parallel_tasks=1
- Budget: gate at the TOP of CapacityManager.acquire (terminal+active >= max
or expired ⇒ EphemeralBudgetExhausted → 410 Gone / FAILED
ephemeral_exhausted; covers every admission surface); post-CAS-win
apply_result hook (backgrounded, fail-open) triggers discard at budget
- Hard discard (services/agent_service/ephemeral.py): SETNX-locked,
crash-convergent — intent marker → CAS-fail non-terminal rows
(ghost_discarded) → force-remove container → clear Redis state BEFORE purge
→ cascade purge (executions KEEP) → audit. DELETE routes ghosts here before
the container lookup (half-discarded state force-discardable)
- GC (cleanup_service._sweep_ephemeral_agents): DB pass + Docker-as-truth
orphan pass with 15-min newborn grace; capped per cycle
Part 2 — spawn provenance + parent control:
- Any agent-spawned creation persists spawned_by_agent/key_id and auto-grants
the agent_permissions parent→child edge (created_by="spawn:{parent}") so a
parent can immediately chat/list/info the child it spawned
- BEHAVIOR CHANGE: agent-scoped keys may start/stop/delete ONLY agents they
spawned (name AND key-id match; interim until #948); sharing, permission
grants, rename, and credential ops are now human-only (403 for agent keys)
- Ghost-key containment fence at the single auth entry point: a ghost's own
key reaches only heartbeat/result-callback/reports/notifications/self-info
Fleet hygiene: heartbeat watch + fleet health exclude ghosts; operator-queue
polling keeps them; exec/cost stats stay inclusive; schedules on ghosts → 400;
AgentStatus.ephemeral surfaced + GHOST badge.
Tests: tests/unit/test_69_ephemeral_agents.py (40, db_harness real-engine) —
accessors, facade delegations, acquire-gate matrix, key-fence matrix, Part 2
guard matrix, budget hook, discard idempotency/crash-convergence, atomic
quota. Full unit suite verified; residual order-dependent flakes reproduced
on clean dev (pre-existing, documented in learnings.md).
Refs abilityai/trinity-enterprise#69 (Phase 0 record on the issue; closed
manually at release — cross-repo keywords don't auto-close).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
max_executionsand/or TTL — expiry ALWAYS stamped, 24h default ceiling), volume-less (container writable layer; ghosts never recreate), and hard-discarded at budget — container removed, DB rows cascade-purged, Redis state cleared; no soft-delete, no 180-day name reservation. Creation is entitlement-gated (ephemeral_agents, fail-closed — the feature is inert until the enterprise module registers it); every lifecycle mechanic is an edition-agnostic OSS primitive (thesuspended_atpattern).spawned_by_agent/spawned_by_key_idand auto-grants theagent_permissionsparent→child edge, so an orchestrator can immediately chat/list/control the child it spawned.fan_out/ replica groups (ghost-clones is the target architecture's named anti-pattern).spawned_byname AND key-id match; interim until feat: workflow-scoped capability tokens for agent-to-agent calls #948 capability tokens). A durable agent key that previously start/stopped sibling agents via owner-equivalence now gets 403 on those three routes.Fleet-wide narrowing of the remaining agent-key breadth on other mutating routes is a recorded accepted-risk follow-up (per the issue's own AC).
Key design points
CapacityManager.acquire(no-enqueue invariant, beside the dispatch-breaker gate) — covers every admission surface; routers map to 410 Gone; pull-mode (feat: pull / work-stealing coordination (push→pull dispatch) #1081) must re-check the predicate at the claim endpoint (noted in the feature flow).expires_at=now), CAS-fail all non-terminal rows (canary L-03/E-01 stay green; defuses late-writer breaker resurrection), Redis clear BEFORE the purge frees the name (bug(lifecycle): stale transport circuit-breaker Redis key survives agent delete/recreate — a fresh healthy agent fast-fails as "unhealthy" #1560 ordering),schedule_executionsKEEP (ages out via 90d retention; admin-only visible post-purge — documented deviation).max_parallel_tasks=1default, no credential auto-injection.Changes
Backend:
models.py,dependencies.py(fence + 2 guards),services/agent_service/{crud,lifecycle,ephemeral(new)}.py,services/{capacity_manager,task_execution_service,cleanup_service,docker_service,heartbeat_service,monitoring_service,settings_service}.py,routers/{agents,chat,schedules,sharing,agent_rename,credentials,agent_files,monitoring}.py,db/{schema,tables,migrations,agents,schedules}.py,db/agent_settings/ephemeral.py(new mixin), Alembic0016,database.pyfacade. MCP:agents.ts(ephemeralparam, delete description),types.ts. Frontend: GHOST badge (Agents list + AgentHeader). Docs: requirements (lifecycle-observability), architecture.md subsystem block,feature-flows/ephemeral-agents.md, learnings.md.Test Plan
tests/unit/test_69_ephemeral_agents.py— 40 tests on the real-engine db_harness: mixin accessors round-trips, facade delegations, acquire-gate deny matrix (fires BEFORE slot work), key-fence allow/deny matrix, Part 2 guard matrix (name+key-id), budget hook fail-open + trigger, discard full-path/idempotent-rerun/half-discarded-resume/lock-contention, atomic quota + Redis-down fallbackdev(2 of 3 baseline worktree runs) — pre-existing, documented inlearnings.mdephemeral_agentsregistration lands (feature is 403/inert until then — fail-closed by design)Follow-ups (filed separately)
volume_removehas zero callers; purge never reclaims volumes (ghosts sidestep it by being volume-less)ephemeral_agentsmodule (activates the feature)Refs abilityai/trinity-enterprise#69 — private-tracker issue; closed manually at release (cross-repo keywords don't auto-close). Phase 0 design record: https://github.com/Abilityai/trinity-enterprise/issues/69#issuecomment-4952092419
🤖 Generated with Claude Code