Dependency-aware agent operations¶
Prompt¶
Before responding to questions or discussion points in this document,
explore the shakenfist codebase thoroughly. Read the cluster operation
dependency machinery (depends_on / runs_after on
BaseClusterOperation in shakenfist/operations/baseoperation.py,
the dependency schema model in
shakenfist/schema/operations/baseclusteroperation.py, and the
dequeue-time evaluation in shakenfist/daemons/queues/workitem.py),
the agent operation object and its queue
(shakenfist/operations/agentoperation.py,
Instance.agent_operation_next in shakenfist/instance.py), the
sidechannel dispatch loop
(shakenfist/daemons/sidechannel/main.py), and the deadlines plan
this plan is gated on
(docs/plans/PLAN-agent-operation-deadlines.md), whose design this
plan assumes has landed. The client-side surface lives in the sibling
client-python repository, and the first example application in the
sibling client-python-k3s repository. Ground your answers in what
the code actually does rather than guessing.
All planning documents go into docs/plans/.
This plan is a placeholder. It captures intent, the decisions
already made in discussion, and the known open questions, and is
intentionally light on implementation detail. Phase 0 will resolve
the open questions into a decisions section and re-cut the phase
table accordingly. It must not begin until
PLAN-agent-operation-deadlines has landed, because it builds
directly on that plan's expiry semantics, terminal-only queue pop,
and per-command capability registry.
When we get to detailed planning, I prefer a separate plan file per
detailed phase, named for the master plan with -phase-NN-descriptive
appended before the .md extension.
I prefer one commit per logical change, and at minimum one commit per phase. Do not batch unrelated changes into a single commit.
Situation¶
Agent operations execute linearly per instance with independent
outcomes — ordering, not dependency; shell ;, not && (see the
failure semantics section of PLAN-agent-operation-deadlines.md).
Callers who need dependency semantics today either submit-and-await
each operation serially, or rely on the intra-operation command list
(which aborts remaining commands on error but is not composable from
the public API).
Meanwhile cluster operations already carry a full dependency
vocabulary, evaluated at dequeue in daemons/queues/workitem.py:
depends_on— fate-sharing. A missing dependency errors the dependent operation; a dependency inERROR/DELETED/ABORTaborts it (the cascade); an in-flight dependency defers it.runs_after— ordering only. Wait for the named operation to finish; its outcome is irrelevant; a missing operation is a logged warning, not fatal.
That is exactly the dependency-versus-ordering distinction agent
operations lack, with the cascade following declared edges rather
than queue adjacency — which is what makes it correct in a queue
shared by unrelated callers. A fate-shared chain of depends_on
edges is a transaction in every sense that is meaningful against a
running operating system (rollback is not).
Separately: agent operations are a fairly unique idea and subtle to
use well — this has become clear from how much design discussion the
deadlines plan required. The documentation needs to be genuinely
good, with worked examples, and the client-python-k3s plugin (which
orchestrates k3s clusters over the sf-agent2 side channel, storing
cluster state in namespace metadata) is already a de-facto example
application. It should become the first of a curated suite of
example applications that adopt each new agent operation feature as
it lands, serving as both living documentation and functional
exercise of the feature set.
Decisions already made¶
Settled in discussion (2026-08-14, the same thread that produced the deadlines plan) and not open questions:
- Extend
depends_on/runs_afterto agent operations rather than introducing a lane or session identifier or a new operation type. Dependencies are declared per-operation on the existing verbs; operations that declare nothing keep today's contract unchanged (linear order, independent outcomes). The cascade follows declared edges only. - Cross-instance edges are in scope. Agent operation state lives
in MariaDB and is cluster-visible, so an operation on instance A
depending on an operation on instance B is a state read at
dispatch evaluation, the same read
workitem.pyperforms for cluster operations. This enables fire-and-await orchestration: a rolling update submitted as one dependency chain across a fleet, awaited as a unit by awaiting its final operation. - Settle delays are an attribute on the dependency edge — "satisfied N seconds after the dependency completed" — not a sleep operation, which would hold an instance's executor slot to do nothing and require a healthy agent to run a no-op.
- Deadlines are the cycle-breaker. A dependency-blocked
operation accrues queue time against its deadline (from
PLAN-agent-operation-deadlines), so user-created dependency cycles resolve by expiry rather than deadlocking. This is why the deadlines plan is a hard prerequisite. - Documentation with worked examples is a first-class
deliverable, not an afterthought phase: a developer-guide
treatment of the agent operation model (ordering, independence,
deadlines, dependencies, retry) plus a curated example-application
suite starting with
client-python-k3s, updated as features land.
Design sketch¶
Mechanically, the extension is: add depends_on / runs_after (and
per-edge settle seconds) to AgentOperation static values and the
creating API endpoints; admit ObjectType.AGENTOPERATION to the
dependency schema's permitted operation types; and evaluate edges
at dispatch time in the sidechannel dispatcher, before an operation
is handed to an executor. The evaluation semantics mirror
workitem.py: missing dependency errors, failed dependency cascades
(abort/skip), in-flight dependency waits. The sidechannel has no
defer machinery — a blocked operation simply stays queued and is
re-evaluated on later dispatch passes, with the deadline bounding how
long that can continue.
The awaited-transaction experience is the client's: submit the chain, await the final operation (which transitively awaits the rest), and report per-operation outcomes from the chain on failure.
Open questions (resolve in phase 0)¶
- Head-of-queue interplay. Does a dependency-blocked operation at the head of the instance's FIFO queue block the operations behind it (strict linearity preserved, but one blocked chain stalls unrelated callers until it expires), or is it passed by operations that are themselves dispatchable (better utilisation, but declared-dependency operations then sit outside the implicit FIFO contract)? The answer probably follows from deciding whether declaring dependencies opts an operation out of implicit queue ordering entirely.
- Mixed-type edges. Should an agent operation be able to depend
on a cluster operation (e.g. "run this script after the
artifact fetch completes"), or a cluster operation on an agent
operation?
node_aop_opalready wraps agent operations in cluster operations, so the type system is adjacent; the question is whether the use cases justify the evaluation complexity now. - Namespace boundaries. Cross-instance edges presumably must be restricted to operations the caller could see anyway (same namespace, or namespace-trust rules). Decide and enforce at submission time.
- Failure attribution. How a caller inspects why an operation
was cascade-aborted: the cluster-op machinery emits
"dependency is unsuitable" events; agent operations should surface
the failed dependency in
external_view()so the awaiting client can report the chain's first failure directly. - Cheap cycle rejection. Expiry breaks cycles eventually, but same-queue cycles (an operation depending on one behind it in its own instance queue) are detectable at submission time for a few database reads. Decide how much validation is worth doing synchronously in the API handler.
- Dispatch-pass cost. Edge evaluation adds database reads per
blocked operation per pass to a loop that is already careful about
per-instance read cost (see the throttling comments in
_dispatch_loop). Decide the evaluation cadence and caching. - Example suite shape. Where the suite lives (a docs section
indexing sibling repositories, starting with
client-python-k3s), what the second example is, and whether examples get CI that exercises them against a real cluster.
Non-goals¶
- Rollback. Compensating actions against a running operating system are the caller's domain; the transaction concept here is ordering plus fate-sharing, nothing more.
- Concurrent execution of independent agent operations on one instance. Dependency edges express ordering constraints; they do not change the one-executor-per-instance execution model. If parallel lanes are ever wanted, that is the separate re-engineering recorded in the deadlines plan's non-goals.
- Agent-side cancellation of in-flight work (tracked from the deadlines plan; would sharpen cascade behaviour but is not required by it).
Phases (to be re-cut by phase 0)¶
| Phase | Content |
|---|---|
| 0 | Resolve open questions; record decisions; re-cut this table |
| 1 | Schema and object: dependency fields on AgentOperation, AGENTOPERATION admitted to the dependency model, API parameters |
| 2 | Dispatch evaluation: same-instance edges, cascade, settle attribute |
| 3 | Cross-instance edges and namespace enforcement |
| 4 | client-python: chain submission helpers, await-the-chain UX, failure reporting |
| 5 | Documentation: developer-guide agent operation model, worked examples; client-python-k3s adopts dependencies (rolling update via one submitted chain) as the first example application |