yuzu

services

What it doesSystem services — enumerate, query, and configure service startup types
Version0.2.0
KindAction · mutating · gathered (crossplatform.service.list, crossplatform.service.running)
PlatformsWindows ✅ · macOS ✅ · Linux ✅
Actionslist (definition crossplatform.service.list) · running (definition crossplatform.service.running) · set_start_mode (definition crossplatform.service.set_start_mode)
Securitylist: securable Infrastructure · operation Read · risk Low · dispatch ReadOnly · approval gate None; running: securable Infrastructure · operation Read · risk Low · dispatch ReadOnly · approval gate None; set_start_mode: securable Infrastructure · operation Write · risk High · dispatch Mutating · approval gate AdminOrApproval
Rolesexecute: list: endpoint-admin, endpoint-operator; running: endpoint-admin, endpoint-operator; set_start_mode: endpoint-admin · author: content-author

How it works

list and running read the host’s service registry and return one svc| row per service: the Service Control Manager on Windows (Win32 services only, not kernel drivers), systemctl list-units on Linux (--type=service units that systemd has loaded, which is not every unit file on disk and excludes timers, sockets and other unit types) and launchctl list on macOS. running is the same read narrowed to services that are up: Windows asks the Service Control Manager for every state except stopped, Linux keeps systemd’s running sub-state, and macOS keeps rows that have a PID (launchctl has no filter of its own, so the plugin filters after parsing). On macOS one extra call, launchctl print-disabled system, supplies each row’s startup type; a label that domain holds no override for reads unknown.

set_start_mode is the only mutation. It checks the service name and mode, then changes one service’s boot-time start setting and nothing else: it does not start, stop or restart the service. Windows sets the Service Control Manager start type. Linux runs systemctl enable for automatic, unmask then disable for manual, and mask for disabled. macOS runs launchctl enable or disable against system/<label> and refuses manual, because launchd only has enabled and disabled. Linux and macOS commands run as shell-free argv under a 20 second deadline, prefixed with sudo -n -- unless the agent is already root. The plugin is deliberately not a change history; the Echo service collector does that separately.

flowchart LR
  OP["Operator or workflow"] --> SRV["Server<br/>Infrastructure.Read for list and running<br/>Infrastructure.Write with AdminOrApproval for set_start_mode"]
  SRV -- "gRPC mTLS" --> HOST["Agent plugin host"] --> EX["services.execute"]
  EX --> WIN["Windows<br/>EnumServicesStatusExW, QueryServiceConfigW, ChangeServiceConfigW"]
  EX --> LIN["Linux<br/>systemctl list-units, sudo -n -- systemctl enable, disable, mask, unmask"]
  EX --> MAC["macOS<br/>launchctl list, print-disabled, sudo -n -- launchctl enable, disable"]
  WIN & LIN & MAC --> ROWS["pipe rows and result status"] --> RS[("ResponseStore")] --> API["REST /api/responses and MCP"]

What you can decide with it

QuestionActionWindowsmacOSLinux
Which services exist?listEvery Win32 service the Service Control Manager knows, running or notEvery launchd job launchctl list reports, up to 512 rowsEvery .service unit systemd has loaded (--type=service --all); a unit file that was never loaded does not appear
Is a service up right now?running or liststatus is the SCM state; running returns every service that is not stoppedThe PID column is the answer (- means not running); status is a last-exit code, not a statestatus is systemd’s sub-state (running, dead, exited, failed)
Will it start at boot?list (startup_type)automatic, boot, manual, disabled, system or unknown; delayed-start reads as automaticautomatic (explicit enable override), disabled, or unknown (no override recorded, which is the common case)Not answered: Linux rows have no startup field
Change how it startsset_start_modeExact: automatic, manual or disabledautomatic or disabled onlydisabled is a systemd mask, stronger than the other platforms’ disabled

What it cannot tell you: whether a service is healthy, why it stopped, what depends on it, or what changed since last time. An empty Windows result is not proof of zero services (caveat 2), and a Linux or macOS result with a non-OK status is a partial answer, not a clean one.

Safe workflow for set_start_mode:

  1. Run list and record the service’s current startup_type (Windows and macOS only; Linux gives no startup type to record).
  2. Run set_start_mode with the target name and mode. The change takes effect at the next boot or login; a running service keeps running.
  3. Run list again and confirm the new startup_type (Windows and macOS). On Linux this plugin has no read-back, so confirm with systemctl is-enabled by other means.
  4. To undo, call set_start_mode with the value recorded in step 1. Windows boot and system start types cannot be restored, because the action only accepts automatic, manual and disabled.

Target restrictions: name is 1 to 256 characters from letters, digits, ., _, @ and -, so a Windows service name containing a space, or a systemd instance name with escaped characters, cannot be targeted. The action needs the endpoint-admin role and passes the AdminOrApproval gate.

Diagrams

sequenceDiagram
  participant S as Server
  participant P as services plugin
  participant O as OS service manager
  S->>P: set_start_mode(name, mode)
  P->>P: reject unsafe name or unknown mode
  alt Windows
    P->>O: open SCM (connect), open service (change config)
    P->>O: ChangeServiceConfigW(start type only)
  else Linux
    P->>O: sudo -n -- systemctl enable, or unmask then disable, or mask
  else macOS
    P->>P: refuse manual
    P->>O: sudo -n -- launchctl enable or disable system/label
  end
  O-->>P: success or failure
  P-->>S: status ok, service, mode  or  one error line

The plugin validates input first, then makes exactly one start-setting change per call (Linux manual adds a best-effort unmask beforehand). Nothing starts or stops the service, and a failure produces a single error| line instead of the three success lines.

flowchart TD
  A["list or running"] --> B{"Windows?"}
  B -- "yes" --> C["Enumerate via SCM"]
  C --> D{"Open or enumerate failed?"}
  D -- "yes" --> E["Zero rows, no status"]
  D -- "no" --> R["Rows, status not set"]
  B -- "no" --> F["Run systemctl or launchctl (20 s)"]
  F --> G{"Runner failed, truncated or malformed?"}
  G -- "yes" --> H["Typed UNAVAILABLE or CONSTRAINED, rc 1"]
  G -- "no" --> R

This shows why the two families of legs fail differently: Windows has no way to say it could not read, while Linux and macOS attach a typed status to any degraded run, so a status other than the default means the rows are incomplete.

OS capability

ActionWindowsmacOSLinux
list✅ supported · rung 1 · win32_service_api✅ supported · rung 2 · runner argv ‘launchctl list’✅ supported · rung 2 · runner argv ‘systemctl list-units’
running✅ supported · rung 1 · win32_service_api✅ supported · rung 2 · runner argv ‘launchctl list’✅ supported · rung 2 · runner argv ‘systemctl list-units’
set_start_mode✅ supported · rung 1 · win32_service_api✅ supported · rung 2 · runner argv ‘sudo -n — launchctl enable|disable’✅ supported · rung 2 · runner argv ‘sudo -n — systemctl enable|disable|mask|unmask’

Privileges and prerequisites

OSRuns asExtra grant neededMeasuredIf the read is refused
Windowsagent service account, LocalSystem today (#1442)list/running: none beyond the default account. set_start_mode: the plugin asks the Service Control Manager for SC_MANAGER_CONNECT and the target service for SERVICE_CHANGE_CONFIG, so the right is decided by that service’s access-control list. The plugin enables or checks no privilege itself; the SeAssignPrimaryTokenPrivilege entry in docs/agent-privilege-model.md is not exercised by this code.2026-09-07, bare-metal, captured as SYSTEM; list and running only, set_start_mode stopped at parameter validationlist/running: a failed OpenSCManagerW or EnumServicesStatusExW returns an empty list with no error and no result status, so a refusal reads like “zero services”. A single service whose configuration cannot be opened reports startup_type unknown. set_start_mode: failures are explicit error|... rows (access denied changing service '<name>', service '<name>' not found, or the Win32 error code).
macOSagent daemon, root (the shipped LaunchDaemon has no UserName key)None as root: the sudo -n -- prefix is skipped when the agent’s effective uid is 0. A non-root agent needs the sudoers grants /bin/launchctl enable system/* and /bin/launchctl disable system/* (the installed grant also covers bootstrap/bootout, which this plugin does not use).2026-09-07, bare-metal, captured as an unprivileged interactive user, not the root identity the daemon runs underlist/running: a failed launchctl list or launchctl print-disabled sets a typed UNAVAILABLE or CONSTRAINED status through the shared runner seam. set_start_mode: a sudo -n denial is captured (stderr merged into the output) and appended to the error|launchctl command failed for '<name>' (exit=<n>) line.
Linuxagent daemon, dedicated unprivileged account by design (docs/agent-privilege-model.md)list/running: none. set_start_mode: the sudoers grant systemctl enable, disable, mask and unmask (/bin/systemctl and /usr/bin/systemctl); the sudo -n -- prefix is skipped when the agent is root. Under the packaged unit’s NoNewPrivileges=true, sudo cannot escalate, so this grant takes effect only on a unit without that setting.2026-09-06, container, captured as root (euid 0), not the deployed unprivileged identitylist/running: a typed UNAVAILABLE or CONSTRAINED status; the captured sample shows subprocess_runner:spawn_error on both because systemctl could not be resolved in the capture container, not because of a permission denial. set_start_mode: a missing grant surfaces as error|systemctl command failed for '<name>' (exit=1): sudo: a password is required.

Binaries and network: Linux runs systemctl (list-units for reads; enable, disable, mask, unmask under sudo -n -- for the mutation). macOS runs launchctl (list and print-disabled for reads; enable, disable under sudo -n -- for the mutation). Both go through the bounded, shell-free runner with a 20 second deadline. Windows uses the Service Control Manager API only, with no subprocess. No network access on any OS.

Data contract

Inputs

DefinitionParameterTypeRequiredDefaultConstraintsDescription
crossplatform.service.set_start_modenamestringyes-pattern: ^[a-zA-Z0-9._@-]+$ · minLength 1 · maxLength 256The service identifier. On Windows, the short service name (e.g. “Spooler”). On Linux, the systemd unit name (e.g. “nginx.service”). On macOS, the launchd label (e.g. “com.apple.metadata.mds”).
crossplatform.service.set_start_modemodestringyes-enum: automatic, manual, disabledTarget startup type. “automatic” starts the service at boot. “manual” prevents auto-start but allows on-demand start. “disabled” prevents the service from starting entirely (Linux: systemctl mask; macOS: launchctl disable).

Outputs

Rows are pipe-delimited, prefixed svc|, one per service. The row shape differs by OS, not just the values: Windows and macOS emit the four fields the schema names (name|display_name|status|startup_type, where macOS puts the PID in the second slot), but Linux emits only three (name|status|description), because that leg never derives a startup type. A consumer must branch on OS before indexing a row (the server’s own device-page parser does). set_start_mode never writes a svc| row: on success it writes three lines, status|ok, service|<name>, mode|<mode>; on any failure it writes one error|<message> line and none of the three, with return code 1. There is no placeholder-row convention for the reads. On Windows an enumeration failure returns zero rows with no status; on Linux and macOS a degraded run sets a typed UNAVAILABLE or CONSTRAINED status before whatever rows were parsed and returns code 1. On macOS the row cap (512) ends the list with a svc|__truncated__|-|<total>|- sentinel row.

crossplatform.service.list — name|display_name|status|startup_type

FieldTypeValuesAvailableExampleDescription
namestring-Windows, Linux, macOSADPSvcThe service’s short identifier: a Win32 service name, a systemd unit name, or a launchd label. Values: free text.
display_namestring-Windows, macOSAggregated Data Platform Service (windows) · 1111 (darwin)Column 2, whose meaning is OS-specific. On Windows it is the Service Control Manager’s human-readable display name. On macOS it holds the launchd job’s numeric PID as a string, or ”-” when not running (the platform has no per-service display name in launchctl list). It is NOT emitted on Linux at all — the Linux leg’s row carries only 3 pipe fields (name, status, description), so this column is absent from the wire, not merely empty. Values: free text (windows) · integer PID or ”-” (darwin) · field absent (linux).
statusstring-Windows, Linux, macOSstopped (windows) · 0 (darwin)Column 3, whose meaning is OS-specific. On Windows it is the service’s current SCM state. On Linux it is systemd’s SUB state from systemctl list-units. On macOS it is launchctl’s raw last-exit-status code for the job — an integer as a string, NOT a running/stopped word; use the display_name (PID) column to tell whether a macOS service is currently running. Values: stopped, start_pending, stop_pending, running, continue_pending, pause_pending, paused, unknown (windows) · free text systemd SUB state, e.g. running, dead, exited, failed (linux) · integer exit-status code as a string (darwin).
startup_typestring-Windows, macOSmanual (windows) · unknown (darwin)Column 4: the service’s configured startup type. On Windows this is the SCM’s 5-state taxonomy. On macOS it is an honest 3-value read derived from a bulk launchctl print-disabled join — a label with no explicit override reports “unknown” rather than a guessed default. It is NOT emitted on Linux at all (no 4th column on the wire), because the systemctl leg does not derive a startup type. Values: automatic, boot, manual, disabled, system, unknown (windows) · automatic, disabled, unknown (darwin) · field absent (linux).

crossplatform.service.running — name|display_name|status|startup_type

FieldTypeValuesAvailableExampleDescription
namestring-Windows, Linux, macOSAppXSvcThe service’s short identifier: a Win32 service name, a systemd unit name, or a launchd label. Values: free text.
display_namestring-Windows, macOSAppX Deployment Service (AppXSVC) (windows) · 1111 (darwin)Column 2, whose meaning is OS-specific. On Windows it is the Service Control Manager’s human-readable display name. On macOS it holds the launchd job’s numeric PID as a string, or ”-” when not running (the platform has no per-service display name in launchctl list). It is NOT emitted on Linux at all — the Linux leg’s row carries only 3 pipe fields (name, status, description), so this column is absent from the wire, not merely empty. Values: free text (windows) · integer PID or ”-” (darwin) · field absent (linux).
statusstring-Windows, Linux, macOSrunning (windows) · 0 (darwin)Column 3, whose meaning is OS-specific. On Windows it is the service’s current SCM state (always “running” for this action’s filtered result). On Linux it is systemd’s SUB state from systemctl list-units --state=running. On macOS it is launchctl’s raw last-exit-status code for the job — an integer as a string, NOT a running/stopped word; this action’s own running-filter is applied on the PID column, not this one. Values: running (windows, this action always filters to it) · free text systemd SUB state (linux) · integer exit-status code as a string (darwin).
startup_typestring-Windows, macOSautomatic (windows) · unknown (darwin)Column 4: the service’s configured startup type. On Windows this is the SCM’s 5-state taxonomy. On macOS it is an honest 3-value read derived from a bulk launchctl print-disabled join — a label with no explicit override reports “unknown” rather than a guessed default. It is NOT emitted on Linux at all (no 4th column on the wire), because the systemctl leg does not derive a startup type. Values: automatic, boot, manual, disabled, system, unknown (windows) · automatic, disabled, unknown (darwin) · field absent (linux).

crossplatform.service.set_start_mode — status|service|mode

FieldTypeValuesAvailableExampleDescription
statusstring-Windows, Linux, macOSokLiteral “ok” on a successful mode change. On any failure the plugin instead writes a single “error|” line and this row does not appear at all. Values: ok (the only value ever emitted).
servicestring-Windows, Linux, macOS-The name parameter echoed back unmodified, on a successful mode change only. Values: free text — echoes the name parameter.
modestring-Windows, Linux, macOS-The mode parameter echoed back unmodified, on a successful mode change only. Values: automatic, disabled (all OS) · manual additionally on windows/linux (darwin rejects it before this row is ever written).

Result status

StatusCompletenessProvenanceWhen
UNDECLARED (default)UNKNOWN(empty)Every Windows read (including a failed one), every Linux/macOS read that completed cleanly, a successful set_start_mode on any OS, and every set_start_mode validation or platform-decision error (missing name/mode, unsafe name, invalid mode, macOS manual, a command that ran and exited non-zero). The plugin sets no status on these paths.
UNAVAILABLEPARTIALsubprocess_runner:spawn_errorLinux/macOS list/running/set_start_mode: systemctl/launchctl (or sudo) could not be started or was not found; shown in docs/samples/linux.txt
CONSTRAINEDPARTIALsubprocess_runner:deadlineThe 20 second runner deadline elapsed and the child was killed
CONSTRAINEDPARTIALsubprocess_runner:cancelledThe systemctl/launchctl run was cancelled before it finished
CONSTRAINEDPARTIALsubprocess_runner:signaledThe child was killed by a signal rather than exiting cleanly
OKPARTIALsubprocess_runner:line_limitDefined by the shared runner for a capped line count. This plugin sets no line limit, so it does not occur here.
CONSTRAINEDPARTIALservices:output_truncatedLinux/macOS list/running: the captured output hit the runner’s byte cap but the tool itself exited cleanly
CONSTRAINEDPARTIALservices:malformed_launchctl_capturemacOS list/running: launchctl list exited cleanly but its output has no lines or no PID\tStatus\tLabel header. No svc| rows are emitted, so a failed read is distinguishable from a host with nothing running. Reported only when no runner failure has already set a status.

Where the data goes

Sample output

Windows — captured: windows Microsoft Windows NT 10.0.26200.0 x64 · bare-metal · 2026-09-07 · SYSTEM · leg-hash e724207e0989

== action=list
svc|ADPSvc|Aggregated Data Platform Service|stopped|manual
svc|ALG|Application Layer Gateway Service|stopped|manual
svc|AppIDSvc|Application Identity|stopped|manual
svc|Appinfo|Application Information|stopped|manual
svc|AppMgmt|Application Management|stopped|manual
svc|AppReadiness|App Readiness|stopped|manual
svc|AppVClient|Microsoft App-V Client|stopped|disabled
svc|AppXSvc|AppX Deployment Service (AppXSVC)|running|automatic
svc|ApxSvc|Windows Virtual Audio Device Proxy Service|stopped|manual
svc|AssignedAccessManagerSvc|AssignedAccessManager Service|stopped|manual
svc|AsusUpdateCheck|AsusUpdateCheck|stopped|automatic
svc|AudioEndpointBuilder|Windows Audio Endpoint Builder|running|automatic
… 12 of 289 rows shown
[result_status] UNDECLARED / UNKNOWN

== action=running
svc|AppXSvc|AppX Deployment Service (AppXSVC)|running|automatic
svc|AudioEndpointBuilder|Windows Audio Endpoint Builder|running|automatic
svc|Audiosrv|Windows Audio|running|automatic
svc|BFE|Base Filtering Engine|running|automatic
svc|BrokerInfrastructure|Background Tasks Infrastructure Service|running|automatic
svc|BTAGService|Bluetooth Audio Gateway Service|running|manual
svc|BthAvctpSvc|AVCTP service|running|manual
svc|bthserv|Bluetooth Support Service|running|manual
svc|camsvc|Capability Access Manager Service|running|automatic
svc|CCleaner7|CCleaner 7|running|automatic
svc|CDPSvc|Connected Devices Platform Service|running|automatic
svc|CertPropSvc|Certificate Propagation|running|manual
… 12 of 115 rows shown
[result_status] UNDECLARED / UNKNOWN

== action=set_start_mode
error|missing required parameter: name
[result_status] UNDECLARED / UNKNOWN
[rc] 1

macOS — captured: macos macOS 26.6.2 arm64 · bare-metal · 2026-09-07 · euid 501 (jsmith) · leg-hash e724207e0989

== action=list
svc|io.tailscale.ipn.macsys.login-item-helper|-|0|unknown
svc|com.apple.SafariHistoryServiceAgent|-|0|unknown
svc|com.apple.progressd|1111|0|unknown
svc|com.apple.enhancedloggingd|-|0|unknown
svc|com.apple.cloudphotod|1012|0|unknown
svc|com.apple.MENotificationService|1099|0|unknown
svc|com.apple.Finder|1186|0|unknown
svc|com.apple.homed|889|0|unknown
svc|com.apple.dataaccess.dataaccessd|1223|0|unknown
svc|com.apple.quicklook|-|0|unknown
svc|com.apple.parentalcontrols.check|-|0|unknown
svc|com.apple.mediaremoteagent|1057|0|unknown
… 12 of 513 rows shown
[result_status] UNDECLARED / UNKNOWN

== action=running
svc|com.apple.progressd|1111|0|unknown
svc|com.apple.cloudphotod|1012|0|unknown
svc|com.apple.MENotificationService|1099|0|unknown
svc|com.apple.Finder|1186|0|unknown
svc|com.apple.homed|889|0|unknown
svc|com.apple.dataaccess.dataaccessd|1223|0|unknown
svc|com.apple.mediaremoteagent|1057|0|unknown
svc|com.apple.FontWorker|1042|0|unknown
svc|com.apple.bird|941|0|unknown
svc|com.apple.knowledgeconstructiond|2105|0|unknown
svc|com.apple.inputanalyticsd|1559|0|unknown
svc|com.apple.GameController.gamecontrolleragentd|974|0|unknown
… 12 of 279 rows shown
[result_status] UNDECLARED / UNKNOWN

== action=set_start_mode
error|missing required parameter: name
[result_status] UNDECLARED / UNKNOWN
[rc] 1

Linux — captured: linux Debian GNU/Linux 13 (trixie) aarch64 · container · 2026-09-06 · euid 0 · leg-hash e724207e0989

== action=list
[result_status] UNAVAILABLE / PARTIAL / subprocess_runner:spawn_error
[rc] 1

== action=running
[result_status] UNAVAILABLE / PARTIAL / subprocess_runner:spawn_error
[rc] 1

== action=set_start_mode
error|missing required parameter: name
[result_status] UNDECLARED / UNKNOWN
[rc] 1

Caveats and known gaps

  1. Linux rows have three fields, not four. The Linux leg emits svc|name|status|description; Windows and macOS emit four. A consumer that indexes by the schema’s column names misreads Linux status and description as display_name and status, and has no startup type to read at all.
  2. A Windows enumeration failure looks like zero services. If OpenSCManagerW or EnumServicesStatusExW fails, enumerate_services_win returns an empty list and the plugin sets no result status, unlike Linux and macOS, which forward a typed status for the same class of failure. Treat an empty Windows list on a host that should have services as unreliable, not as proof.
  3. macOS status is an exit code, and some labels never appear. status is launchctl’s last-exit code (0 for nearly every row), so use the PID in the second field to tell whether a job is running. The list is capped at 512 rows with the __truncated__ sentinel, and a launchd label containing a character outside letters, digits, ., _, @ and - is dropped silently, without a status and without counting toward the sentinel’s total.
  4. The macOS startup type is rarely known. The join uses the system domain’s override list only, so any label with no recorded override reads unknown; every row in the captured sample is unknown. That capture ran as an unprivileged user against the user’s job list, so it does not show what the root daemon sees.
  5. set_start_mode has never been captured past validation. Every OS sample stops at error|missing required parameter: name, so the sudo-wrapped and Win32 changes are verified from source and unit tests only. The Linux list and running captures also failed with spawn_error, so the three-field Linux row shape is likewise verified from source, not from a captured row.

Source and tests