ADR-012: memcastle status answers for a stopped daemon too, and its exit code says which state it found¶
Status¶
Accepted, amended by ADR-015 (--json on db start and db status),
ADR-016 (a command may choose its form by stream),
ADR-038 (status follows the stream, and --json is global)
and ADR-017 (the daemon is started with memcastle daemon start)
Context¶
memcastle status printed the daemon's status counts as JSON, and failed with memcastle::client::not_running
when nothing answered.
It could not answer most of what someone opening a terminal wants to know:
whether a daemon is running, where it listens, which palace it serves, whether its datastore is healthy and migrated,
and what to type next.
The address it had dialed was not shown, the palace path was known only to the CLI's own configuration,
and "the daemon is up but its database is not" looked the same as "no daemon".
GET /api/health is deliberately static: it proves the process answers, and touches nothing else.
Orchestrators poll it, and a database hiccup must not make one restart a daemon that would recover on its own.
Discovery already exists: a live registry file, then the configured address (see ADR-011). The registry file is a hint and never the source of truth for "is a daemon running".
Decision¶
statusis a report, not just a probe. It prints whether a daemon answered, the endpoint it dialed and whether that came from the registry file or the configuration, the MCP URL, the palace name and path, the datastore's backend, health and migration state, the counts, and the next command to run.--jsonprints the same report for scripts. The flag lives on the commands that have a human rendering,statushere anddb startanddb statussince ADR-015, so a global flag would promise one to the commands that have none.- A stopped daemon is an answer.
With nothing listening,
statusprints the configured endpoint, the palace path and the registry file's state (absent,live, orstalewhen the recorded PID is gone) and how to start the daemon. Nothing new is discovered: it is the same registry-then-configuration resolution every command uses. - The exit code carries the state.
0 is a running daemon with a healthy datastore, 1 is a running daemon that is degraded (datastore unreachable or
migrations pending) or any error, 3 is no daemon.
Three follows
systemctl status, and keeps "stopped" distinguishable from "broken" in a script. A timeout or a 5xx from something that does answer stays an error, because it is not evidence of absence. - Datastore health is part of
/api/status, which stays HTTP 200.AppServices::statuspings the store, reads the migration watermark and reports both underdatastore, with the backend kind and a location that never carries a password. A failing ping is reported asdatastore.ok = falsewith the error, and zeroed counts, rather than as a 500: the daemon is up, and a 500 would make it look absent exactly when the difference matters. /api/healthstays a cheap liveness check.- The report's new fields are additive and defaulted.
A newer CLI reads an older daemon's report and the reverse.
The MCP
memcastle_statustool serves the same report.
Alternatives rejected¶
- Keep failing with
not_runningwhen nothing answers. Correct, but a stopped daemon is the case wherestatusis most useful, and a diagnostic on stderr is not something a script can read. - Make
/api/healthcheck the datastore and return 503. Better for a load balancer that should stop routing to a broken node, worse for a supervisor that restarts on failure. Nothing here needs that yet, and it can be added as a separate readiness endpoint without changingstatus. - A global
--jsonor--format. Every command already prints JSON, so it would change nothing for them and promise a human form that does not exist. - A new discovery mechanism, such as a socket or a PID file next to the registry file. The registry file and the configured address already answer the question, and a second source is a second thing to go stale.
- Return 1 for "not running".
The simplest contract, but
memcastle status || memcastle servethen also starts a daemon over a broken one.
Consequences¶
- The CLI's
statusJSON changed shape: the daemon's report is now underdaemon, next torunning,endpoint,endpoint_source,mcp_url,palace_pathandregistry. The REST and MCP reports only gained fields. These field names are a scripting contract from here on. - A
statusrequest now runs a ping and reads the migration watermark on top of the counts it already read. - On platforms without a process-liveness probe, a stale registry file is reported as
live, as discovery already treats it.
Amendment (2026-10-02)¶
ADR-015 added memcastle db start and memcastle db status, which also print a
readable report and accept --json.
status is therefore no longer the only command with a human rendering.
The decision stands: the flag is still per command rather than global, because most commands print JSON and have no
human form to switch to.
Amendment (2026-10-03)¶
ADR-016 gave jobs list a table for a terminal.
It has no --json flag: JSON is what it prints whenever standard output is not a terminal.
The decision stands for the commands that already had the flag, and for rejecting a global one.