This is Part 12 of the fourteen-part Local-First Agent Operations series. In Part 11, I worked downward through model and runtime performance. This time I am moving back up the stack to a question that sounds simpler than it is: once I have a reliable operating procedure, how do I make it available to an agent without accidentally building another control plane inside a prompt?

I arrived at that question through two failures that looked unrelated. In one, I told a stack to stop and discovered that some of it was still running. In the other, I repaired a memory integration, confirmed that the package and configuration were present, and still did not have memory in the live agent. Both failures were useful because they exposed the same mistake from opposite directions. I had confused a declared intention with an observed result.

That is the trap an operational skill has to avoid. A useful skill can package judgment, evidence requirements, explanations, and approval boundaries. It can help an agent decide what to inspect and when to stop. It should not quietly grow its own ideas about dependency order, process identity, remote execution, secrets, readiness, or rollback. Those belong to the operation system that already owns them.

The Stop Command That Did Not Stop the Stack

The first incident was wonderfully blunt. The stack-wide stop returned, but the machine did not agree. Several related processes remained active.

At first glance, this is the sort of problem that invites another shell loop. Find the remaining processes, stop each one, and move on. I have written enough shell scripts to know exactly how attractive that answer can be at two in the morning. I have also lived with enough of those scripts to know what happens next: the second loop develops its own list of services, its own ordering rules, its own remote-host assumptions, and eventually its own definition of success.

The actual problem had three parts. The declared dependency graph had to describe the real runtime relationships. The planner had to turn that graph into a complete shutdown order. The executor had to continue collecting results when one stop failed, then verify what really happened instead of trusting an exit code.

A dependency is directional. If a client consumes a proxy and the proxy consumes a provider, startup moves from provider to consumer. Shutdown moves in the other direction. During startup, every prerequisite in a wave has to become ready before the next wave begins. During best-effort shutdown, every stop attempt in the current wave has to finish; the executor records any failures and then continues through the remaining inverse-dependency waves.

Startup moves from provider through proxy to clients, while shutdown moves from clients through proxy to provider
Open full-size diagram

The graph is the same in both directions. The safe operation order is not.

This sounds obvious when the graph has three boxes. It gets less obvious when a stack spans process managers, execution identities, and more than one computer. It also gets dangerous when a missing edge makes an invalid plan look neat. A correct planner working from incorrect topology will faithfully produce the wrong answer.

Some edges can be inferred from endpoint wiring, but inference is not a substitute for review. The configured graph remains an operational contract. When a service begins depending on another service, that relationship has to be recorded as deliberately as a port or executable path.

Restart One Thing, or Admit the Blast Radius

The failure also forced me to make several operations less ambiguous. A request to restart one component should restart that component, not turn into a ceremonial reboot of everything nearby. Starting one component may require starting missing prerequisites. Stopping one component is different because active consumers may still need it. In that case, the operator has to force the stop or request a cascade with the larger impact made visible first.

Operator intent Expected operation
Restart one component Restart only the target by default
Start one component Start missing prerequisites before the target
Stop one component Refuse while active dependents exist unless force or cascade is explicit
Cascade restart Stop affected consumers, change the target, then restore the affected path in dependency order
Stop the stack Attempt every managed component in inverse dependency waves and report every failure

This is where a thin skill earns its keep. It can recognize that a request has dependent impact, ask the canonical planner for that impact, explain it in ordinary language, and put the exact reviewed operation in front of me. It should not recursively walk the graph itself. The moment it does, I have two planners, and they will eventually disagree.

Partial Failure Is Still State

One failed stop does not erase the successful stops around it. That sounds like bookkeeping, and it is. It is also the difference between a useful control plane and a confident liar.

The lifecycle path now continues independent shutdown work after a failure, aggregates the failures, and records each component that did stop. A stop is checked against observed lifecycle state, so a zero exit status does not become success when the process remains alive or can no longer be observed. Startup compensation is bounded too: when a start fails, it stops only the prerequisites started by that invocation and leaves services that were already running alone.

I use the word compensation deliberately. This is not a transaction that returns a distributed system to some magically identical earlier instant. It is a constrained attempt to undo the lifecycle work performed by one request. The operation record still has to show what succeeded, what failed, what remains running, and what could not be observed.

That record is exactly the sort of evidence an agent can summarize. The agent does not need permission to rewrite the facts. It needs a contract that prevents it from smoothing an uncomfortable partial result into a tidy sentence.

Installed Is Only the First Gate

The memory incident exposed the other half of the problem. The required packages were present in the application’s actual Python environment. The configuration named the intended provider. Files and settings looked correct. The running agent still did not expose the expected memory behavior.

The missing step was ownership. A long-lived process had already constructed the agent after an earlier import failure. Repairing the environment did not travel backward in time and reconstruct that object. The process that owned agent construction had to restart, after which provider registration, tool exposure, prompt injection, and functional routing could be checked together.

That gave me a capability-activation ladder I now use well beyond memory systems.

Six evidence gates from installed artifact through configuration, provider initialization, object reconstruction, capability exposure, and end-to-end routing
Open full-size diagram

Each step proves something different. Skipping a gate leaves a very specific kind of uncertainty behind.

A package query proves that an artifact exists in some environment. It does not prove that the live process uses that environment. A configuration dump proves that a value was written somewhere. It does not prove that the owning process loaded that authority. A port check proves that something accepted a connection. It does not prove that the agent constructed the right provider or placed the expected material in the prompt.

For a capability to be operational, I want six separate facts. The artifact is installed in the runtime actually used by the process. Configuration came from the expected authority. The provider or adapter initialized without falling back. The long-lived object that owns the capability was reconstructed. The expected tools, prompt fragments, or endpoints appeared. Finally, a bounded end-to-end route demonstrated the behavior.

This is a much stronger definition of installed. It also gives an operational skill somewhere useful to stand. The skill can gather evidence for each gate, distinguish a missing artifact from stale process state, and stop when the evidence becomes ambiguous.

What the Skill Owns

I think of an operational skill as a small, versioned packet of judgment. It says when the procedure applies, which evidence is required, which fields are safe to show, what conditions make the result incomplete, and where approval is required. It may select a reviewed command or API operation and explain the impact. It may render a decision packet that a human can inspect without reading raw logs.

That is already plenty of responsibility. The skill does not need to own the machinery beneath it.

Layer Responsibility
Agent skill Intent, evidence request, explanation, approval boundary, result summary
Canonical operation API or CLI Validation, topology, plan, policy, authority, and stable machine-readable output
Typed adapters and transports Process manager, remote execution, probes, logs, and observed lifecycle state

Dependency traversal, lifecycle locks, process discovery, remote command construction, secret retrieval, authority hashes, readiness checks, stop verification, and compensation stay in the tested operation library and its adapters. If I encode those rules again in skill prose, they will drift. Worse, the duplicate path may bypass a safety check that the normal CLI and TUI both enforce.

Invented captured evidence feeds an agent skill, while only a reviewed live request crosses the launcher boundary into the canonical operation model, typed adapters, and observed target state
Open full-size diagram

The skill packages the procedure. The operation model remains the authority for what will happen and what actually happened.

There is a subtle launcher problem here too. Read-only intent, read-only command semantics, and a read-only invocation path are not the same thing. A skill may select a status command that never mutates component state, yet the installed launcher may check an approved release manifest and update its own runtime before dispatch. Calling the selected subcommand read-only does not make the entire invocation mutation-free.

That is why the Hands-On companion uses bundled, invented captures and executes no subprocess at all. It makes the evidence contract runnable and shows equivalent fixed argument arrays without invoking an installed control plane. A future live collector would need its own qualification: pinned runtime identity, explicit update policy, current schema verification, bounded transport, redaction, and separate tests.

Portable Logic, Private Mapping

A publishable skill should contain symbolic component references and schema-defined inputs. The names of my computers, login accounts, internal addresses, filesystem layout, ports, service identities, credentials, memory banks, conversations, and customer material do not belong in it.

That separation is more than cosmetic anonymization. Portable logic says, “inspect the target and its active dependents.” Private configuration says which target, on which host, under which execution identity. The skill can carry a secret reference if the operation contract allows one. It must not carry the secret value. Sanitization needs to happen before evidence becomes model-visible or leaves the operator-controlled environment.

The same principle applies to prompt and memory evidence. My memory policy requires authorization before retrieval and treats ambiguous imports as a reason to stop for review. An operations skill should inherit that posture. It should not rummage through raw conversations to prove that memory works when a synthetic canary or content-free status field can answer the question.

Version the Contract, Not Just the Prompt

Prompts are easy to edit and difficult to audit after the fact. An installable operational skill needs more than well-written instructions. I want a versioned manifest, declared compatibility, closed input schemas, deterministic rendering, tests, an artifact checksum, and clear install and uninstall behavior. I also want negative tests because happy-path examples are where accidental orchestrators hide.

Those tests should reject unknown fields, incomplete evidence, stale authority, unsupported schema versions, unobserved targets, active dependents that were not disclosed, and any request that crosses the read-only boundary without an explicit approval step. If the skill discovers that it cannot explain the impact, stopping is correct behavior.

The existing lifecycle template follows the same shape for tools more generally: state the expected gain, document the data path, pin and verify installation, begin with no-op or observation settings, verify a controlled request, preserve a bypass, and define rollback before adoption. Packaging the procedure as a skill does not remove any of those obligations. It makes them easier to repeat.

Hands-On 12A: Package the Triage, Not the Orchestrator

The companion lab turns this boundary into a small installable skill. It loads invented status and restart-plan captures, validates a closed schema, rejects recognizable prohibited material, and renders a deterministic approval packet. One fixture contains incomplete observation so the reader can see the skill refuse to manufacture certainty.

The default lab will not call llmops, a shell, SSH, a package manager, or a lifecycle operation. It will not start, stop, restart, update, roll back, edit configuration, fetch secrets, or inspect a live topology. The point is to make the decision boundary runnable before connecting it to a real system.

Later, that same skill shape could sit in front of a separately qualified live evidence adapter. The important word is separately. A fixture-backed teaching artifact and a live operations integration are different releases with different failure modes. Keeping them separate gives readers something useful to run without pretending that arbitrary local installations share my private operating assumptions.

Current State

LLM-Ops-Kit currently implements the operation model this pattern needs: dependency-safe lifecycle waves, target-only restart by default, prerequisite-aware start, dependent-aware stop, explicit cascade, observed stop verification, partial-progress persistence, bounded startup compensation, and explicit direct control of externally owned components without pulling them into incidental stack-wide operations. The relevant behavior is covered in the current source tests and change record.

An agent-neutral operational skill remains deferred work. The historical memory restoration supports the activation ladder, but it is not evidence about the health of my current deployment. I have deliberately avoided turning old host, version, endpoint, and process details into present-tense claims.

Next Work

Hands-On 12A now packages the read-only component-triage workflow against invented captured evidence. It proves the bounded teaching claim that the skill can explain a plan, preserve incomplete state, reject recognizable private material without echoing it, and stop before mutation. The execution-surface audit makes that boundary inspectable rather than leaving it as a promise in the prose. A live collector remains separate work and would have to make launcher behavior and update policy explicit.

Part 13 will take the same ownership question into multi-agent operation on a local network, where process identity, workspace isolation, and comparative testing become harder to wave away.