Documentation
¶
Overview ¶
Package state persists what the server knows across process lifetimes.
The task registry used to live only in RAM. That was fine as long as there was exactly one server process, but MCP clients do start a second instance — sometimes alongside the first rather than in place of it. The new process then came up blank: agent_list_tasks returned nothing and every task_id from before was unknown, while the workers those tasks owned kept running happily under the original process, still writing their results to disk. Nothing was broken except the server's ability to see its own work.
This package closes that gap from both ends. It writes each task to disk as it progresses, so a later instance can list and read what came before. And it keeps a PID lock, so an instance can tell that it is not the only one running instead of silently presenting an empty registry as the truth.
Index ¶
- func DefaultDir() string
- func ResolveDir(dir string) string
- type Follower
- type Owner
- type Store
- func (s *Store) Acquire() (previous *Owner, err error)
- func (s *Store) AppendLine(id, line string) error
- func (s *Store) CancelRequested(id string) bool
- func (s *Store) ClearCancel(id string) error
- func (s *Store) Close()
- func (s *Store) CountTasks() int
- func (s *Store) Dir() string
- func (s *Store) Follow(id string, lastN int) (*Follower, error)
- func (s *Store) Forget(id string)
- func (s *Store) LoadTask(id string) (json.RawMessage, error)
- func (s *Store) LoadTasks() ([]json.RawMessage, error)
- func (s *Store) Owner() *Owner
- func (s *Store) Prune(keep int)
- func (s *Store) ReadLines(id string) ([]string, error)
- func (s *Store) RequestCancel(id string) error
- func (s *Store) SaveTask(id string, v any) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultDir ¶
func DefaultDir() string
DefaultDir is where state goes when the operator names no directory: %AppData%\cli-agent-mcp on Windows, ~/.config/cli-agent-mcp elsewhere.
func ResolveDir ¶ added in v0.13.0
ResolveDir turns a configured directory into the absolute path actually used, applying the default for an empty value. Callers that need to read state without opening a store — the pairing check runs before anything else, and must not create directories on behalf of a launcher it has yet to authorize — go through this.
Types ¶
type Follower ¶
type Follower struct {
// contains filtered or unexported fields
}
Follower streams a task's transcript as it grows, returning only what is new since the last call. It is the primitive behind `cli-agent-mcp logs -f` and the local web viewer.
It re-opens the file on every poll rather than holding a handle. At this cadence that costs nothing, and it keeps a reader from being one more handle contending with the writer's own append handle on Windows.
type Owner ¶
type Owner struct {
PID int `json:"pid"`
Started time.Time `json:"started"`
Exe string `json:"exe"`
}
Owner identifies the process that holds the lock.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the on-disk home for task records and the instance lock. Its methods are safe for concurrent use.
func (*Store) Acquire ¶
Acquire records this process as the owner of the state directory and reports the previous owner when that process is still alive.
It deliberately does not refuse to start on a conflict. The client that just launched this process is talking to it and nothing else; failing here would leave the user with no server at all, which is worse than having two. The caller's job is to surface the conflict, not to prevent it.
func (*Store) AppendLine ¶
AppendLine adds one transcript line to the task's log. Lines are written as they arrive so another instance can read a run that is still in progress.
func (*Store) CancelRequested ¶ added in v0.11.0
CancelRequested reports whether a stop has been asked for.
func (*Store) ClearCancel ¶ added in v0.11.0
ClearCancel drops the request once it has been acted on, so a task id reused by a later run does not inherit it.
func (*Store) Close ¶
func (s *Store) Close()
Close releases the open log handles. It leaves the lock file in place: a stale lock is harmless because Acquire checks whether the recorded process is still alive, whereas deleting it on the way out would erase the evidence in exactly the case that matters — a process that was killed rather than shut down cleanly.
func (*Store) CountTasks ¶ added in v0.13.0
LoadTasks returns every stored record, oldest file first. Records that fail to parse are skipped rather than failing the whole load: one corrupt file must not cost the operator the rest of their history. CountTasks reports how many task records are on disk, without reading them.
It exists for the one line that has to say what is NOT being loaded: when another server instance is live, this one leaves its records alone (issue #21), and "isolated: 4 task record(s) belong to pid 1234" is the difference between a startup log that explains an empty listing and one that lets the user think their tasks vanished.
func (*Store) Follow ¶
Follow opens a follower over a task's transcript.
lastN >= 0 positions it so the first Next returns the final lastN lines already on disk (0 means "only what arrives from now on"); lastN < 0 starts at the beginning of the transcript. A task that has not written anything yet is not an error — the follower simply yields nothing until it does.
func (*Store) LoadTask ¶
func (s *Store) LoadTask(id string) (json.RawMessage, error)
LoadTask returns one stored record, or nil when there is none. It is how a task owned by another process gets re-read: that process keeps rewriting the record, so this is the only way to learn that it finished.
func (*Store) Owner ¶
Owner reports the process recorded in the lock file, when that process is still alive.
It exists because Acquire cannot be used for this: Acquire *writes* the lock, so a read-only viewer calling it would take ownership away from the running server and leave the next real instance believing it was alone. Reading is the whole contract here.
func (*Store) Prune ¶
Prune keeps the newest `keep` task records and deletes the rest. Without it the directory would grow for the life of the installation.
func (*Store) RequestCancel ¶ added in v0.11.0
RequestCancel records that someone wants a task stopped.
It exists because the process that can actually stop a worker is the one that spawned it, and that is not always the process being asked. Clients do start a second server instance alongside the first, and from there a task belonging to the other one is visible — its record and transcript are on disk and keep advancing — but untouchable.
Killing the worker by pid from outside was the obvious alternative and is not safe: pids are recycled, briskly on Windows, so a stale record would eventually name a process that has nothing to do with us. Leaving the request where the owner will find it costs a moment's delay and cannot kill the wrong thing.