jsonl

package
v0.0.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package jsonl is the default session store: one append-only JSONL file per session under a root directory, laid out as

<root>/<project-key>/<created-at>_<session-id>.jsonl

where the project key derives from the session's working directory. Each Append writes one line; a SyncPolicy decides when the file is fsynced. Opening a session whose last line was cut short by a crash drops that line so later appends produce a valid file.

A session read back gives members as they were written, member order inside opaque JSON such as a tool's parameter schema included. That is a property of this store, not of the format: a cas store gives the same members in canonical order. See agentsession.CanonicalRequest.

Index

Constants

This section is empty.

Variables

View Source
var ErrSessionLocked = agentsession.ErrSessionLocked

ErrSessionLocked is returned by Create, Open and Append when another process holds the session. The error's message names the holder; BreakLock removes a lock the caller has decided is stale.

It is agentsession.ErrSessionLocked, so a host that reads the store through the interface matches the same sentinel whichever store it was given.

Functions

func ProjectKey

func ProjectKey(cwd string) string

ProjectKey derives the directory name for a working directory: the leading separator stripped and path separators and colons replaced by "-", so "/home/u/proj" becomes "home-u-proj". An empty directory maps to "default".

Types

type LockInfo added in v0.0.3

type LockInfo struct {
	PID   int       `json:"pid"`
	Host  string    `json:"host,omitempty"`
	Since time.Time `json:"since"`
}

LockInfo describes the holder of a session lock.

type Option

type Option func(*Store)

Option configures a Store.

func WithFollowInterval added in v0.0.21

func WithFollowInterval(d time.Duration) Option

WithFollowInterval sets how often a follower looks at a session file no writer in this process rang for: the shortest wait, which it keeps while the file keeps growing and doubles while it does not, up to a second or the interval itself when that is longer. The default is 100 ms. A follower of a session this store appends to is woken by the append and does not wait for the interval.

func WithReadOnly added in v0.0.6

func WithReadOnly() Option

WithReadOnly opens the store for reading: Open takes no lock on a session, so a session another process is writing can be read while it writes, and Create, Append, Delete, Sync and BreakLock return agentsession.ErrReadOnly. Nothing is written, the store's root directory included, and a file whose last line was cut short is reported through Session.Truncated and left alone, since trimming it is a write. It is what show, verify and export want, and what a host wants when an operator asks about a session the agent holds.

An open session is cached as it is in a writing store, so a session read while another process appends to it shows what it held when it was opened; call Store.Release and open it again to see the rest. Store.Read, on any store, reads a session without a hold and caches nothing, so it reads what the store holds at each call.

func WithStaleLockReport added in v0.0.5

func WithStaleLockReport(report func(LockInfo)) Option

WithStaleLockReport sets a function the store calls when Create or Open takes over the lock of a process on this host that no longer runs, with the dead holder's details. A session file left by a crash is a valid prefix and cannot say by itself whether the last writer exited cleanly; the stale lock is the only durable sign that it did not, and the takeover would otherwise consume it silently. A host wires its crash recovery to this. The takeover itself is not changed and the function must not block on the store.

func WithSync

func WithSync(p SyncPolicy) Option

WithSync sets the sync policy.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is a file-backed agentsession.Store. It is safe for concurrent use within one process. A store opened with WithReadOnly takes no lock and refuses every write. Across processes each open session is guarded by an advisory lock file beside it, <file>.lock, so a second process that opens the same session gets ErrSessionLocked instead of interleaving lines with the first. The lock is released by Release, Delete and Close; a lock left by a process on this host that no longer runs is taken over on the next open, and BreakLock clears one from any other holder.

func Open

func Open(root string, opts ...Option) (*Store, error)

Open returns a store over root, creating the directory if needed. A read-only store creates nothing: a missing root is an empty listing and a session that is not there.

func (*Store) Append

func (s *Store) Append(ctx context.Context, sessionID string, e agentsession.Entry) (string, error)

Append implements agentsession.Store: the entry joins the in-memory tree, then its line is written and synced according to the policy. The first append to a session whose header names an earlier minor raises the header's format first.

func (*Store) BreakLock added in v0.0.3

func (s *Store) BreakLock(id string) error

BreakLock removes a session's lock whoever holds it. Use it when Open reports ErrSessionLocked and the caller has confirmed the holder is gone, for example a process on another host that crashed. Breaking the lock of a live writer lets two processes append to one file. A store opened with WithReadOnly refuses: it takes no lock, so it has no business dropping another process's.

func (*Store) Close

func (s *Store) Close() error

Close syncs and closes every open session file.

func (*Store) Create

Create implements agentsession.Store. The file is created with the header line, and for a header with a base the prefix after it, and synced before Create returns. The origin is read, not claimed: a fork can be made from a session another process is writing.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, id string) error

Delete implements agentsession.Store: the file is removed and the session forgotten.

func (*Store) Follow added in v0.0.21

Follow implements agentsession.Follower by tailing the session's file: it reads the file up to its last newline for the snapshot, then reads from that offset each time the file grows, which a stat finds and an append through this store announces. It takes no lock, writes nothing and trims nothing, so a read-only store follows too.

A line still being written is not consumed until its newline lands. The file is replaced, and the follower yields a Reset, when the writer's first append to an older minor raises the header's format (the file is rewritten and renamed over), when the file shrinks, and when its inode changes. A file gone is agentsession.ErrNoSession, and so is one under the same name that is another session. The cursor names the byte offset after a line, the file's inode and its header.

func (*Store) List

List implements agentsession.Store by reading the header line of every session file under the root. Files that do not parse are reported as errors in the sequence and skipped.

func (*Store) ListRefs added in v0.0.21

func (s *Store) ListRefs(ctx context.Context, prefix string) iter.Seq2[agentsession.Ref, error]

ListRefs implements agentsession.RefStore.

func (*Store) LockHolder added in v0.0.3

func (s *Store) LockHolder(id string) (*LockInfo, error)

LockHolder reports who holds a session's lock, or nil when it is free. It reads the lock file and does not consult this store's open sessions, so a session this store holds is reported as locked by this process.

func (*Store) Open

func (s *Store) Open(ctx context.Context, id string) (*agentsession.Session, error)

Open implements agentsession.Store. A session already open in this store is returned as is; otherwise its lock is taken and its file is read. A final line left incomplete by a crash is reported through Session.Truncated and removed from the file so later appends continue a valid file. Open returns ErrSessionLocked when another process holds the session; a store opened with WithReadOnly claims nothing and so is never refused.

The lock is the store's, not the caller's: it is kept until Store.Release, Store.Delete or Store.Close, and a Release frees it whoever opened the session. A process that reads sessions it does not write uses Store.Read, or a second store opened with WithReadOnly, so that it neither keeps them from other processes nor frees one its own writer is using.

func (*Store) Path

func (s *Store) Path(id string) (string, error)

Path returns the file a session lives in, whether or not it is open.

func (*Store) Read added in v0.0.19

func (s *Store) Read(ctx context.Context, id string) (*agentsession.Session, error)

Read implements agentsession.Reader: it reads the session's file as a read-only store's Open does, taking no lock, trimming nothing and keeping nothing, so a session this store or another process is writing is read and stays the writer's. The session returned is the caller's own, read afresh from the file, even when this store holds the session open. A line an append is writing as the file is read is reported through Session.Truncated, as a crash's would be; an entry is in the file once the Append that wrote it has returned, and a leaf moved through Session.Branch is never in it. Under SyncOnResponse or SyncNever such an entry may not be durable yet, and a power loss can take back what Read showed; nothing here reads only what is durable, short of the writer calling Store.Sync first.

func (*Store) RefLog added in v0.0.21

func (s *Store) RefLog(ctx context.Context, name string) iter.Seq2[agentsession.RefUpdate, error]

RefLog implements agentsession.RefStore.

func (*Store) Release

func (s *Store) Release(id string) error

Release syncs and closes an open session's file and drops its lock without deleting it. The session can be opened again later, by this or another process. On a read-only store there is no lock and nothing to sync, and Release is how a reader drops a session it has cached so the next Open reads what has been appended since.

The store holds a session once, however many callers opened it, so Release lets it go for all of them: a writer still appending through this store has the session read again at its next append, or is refused with ErrSessionLocked if another process took it meanwhile, and the Session it was handed no longer follows the store.

func (*Store) ResolveRef added in v0.0.21

func (s *Store) ResolveRef(ctx context.Context, name string) (agentsession.RefTarget, error)

ResolveRef implements agentsession.RefStore.

func (*Store) Root

func (s *Store) Root() string

Root returns the store's directory.

func (*Store) Sync

func (s *Store) Sync(ctx context.Context, id string) error

Sync fsyncs an open session's file.

func (*Store) UpdateRef added in v0.0.21

func (s *Store) UpdateRef(ctx context.Context, name string, expected, next agentsession.RefTarget, reason string) error

UpdateRef implements agentsession.RefStore.

type SyncPolicy

type SyncPolicy int

SyncPolicy says when the store fsyncs a session file.

const (
	// SyncEveryAppend fsyncs after every line. It is the default.
	SyncEveryAppend SyncPolicy = iota
	// SyncOnResponse fsyncs after a response entry and a function call
	// output, which the format recommends as the minimum, after a
	// compaction or a branch summary, which are as expensive to lose,
	// and after any record entry whose type the header names in
	// records, which the format requires to be durable before the side
	// effect it precedes.
	SyncOnResponse
	// SyncNever leaves syncing to the operating system and to explicit
	// calls to Store.Sync, apart from a record entry whose type the
	// header names in records, which the format requires to be durable
	// before the side effect it precedes and which this policy too
	// fsyncs.
	SyncNever
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL