Releases: AstroQore/agent-session-kit
Release list
agent-session-kit 0.8.0
The Rust lane can now delete a whole session, fenced exactly as the Swift
SessionDeleter — the piece a second client needs before it may offer
deletion at all.
Added
- Rust:
deletionmodule.agent-session-corecan now remove a whole
session's own log files — the Rust counterpart of the Swift
SessionDeleter, fenced the same way: every target must resolve strictly
below one of the provider's roots under the given home, a target that is
itself a symlink is refused, the session file is re-parsed immediately
before removal so a stale summary cannot delete a different session, and
only Codex and Claude sessions (supports_deletion) are ever candidates.
Claude removes the sidecar directory of the same stem when it exists; a
directory is walked without following links. Hosts call it only at the
person's explicit request.
agent-session-kit 0.7.0
Swift and Rust become peer implementation lanes. The repository now says out
loud what it had already become when the Rust crate landed: one set of
session-reading semantics with two implementations, neither generated from
the other, and a contracts/ directory holding the facts both must honour.
Changed
- Layout:
implementations/swift/andimplementations/rust/. The Swift
sources and tests moved out of the repository root, and the Rust crates
moved out ofcrates/. Both manifests stay at the root and reach in with
explicit paths, so no consumer changes anything: the git URL, the
exact:pin,import AgentSessionKit, and both product names are
unaffected.crates/at the root becameimplementations/rust/crates/,
which does move the Cargo path for anyone depending on the crate by path
rather than by git. ci.ymlis nowci-swift.yml, and both lane workflows are scoped to their
own paths pluscontracts/.
Added
contracts/storage/session-index-v5.sql— the canonical index DDL and
user_version, extracted from the Swift writer that had been its only
definition. Both lanes are now tested against it:SessionIndexContractTests
checks that Swift creates exactly the objects the contract names, and
index::contract_testsbuilds a database from the file and opens it with the
Rust reader. A schema change in either lane alone now fails CI.
Fixed
AgentSessionKitInfoTestsresolved the repository root by walking three
directories up from#filePath, which the new layout broke. It is five now,
and the failure was real: the test could not findCHANGELOG.md.
Fixed
- Rust discovery/search/transcript parity. The first Rust session-core
slice now mirrors Swift's default title/user/assistant search scopes,
explicit system/tool opt-in, project include/exclude filtering, and title
metadata branch. Codex and Claude transcript shapes are flattened more
tolerantly, and discovery validates bounded head/tail JSON, regular files,
tail metadata, stable ordering, and normalized 80-character titles.
Intentional first-slice limitation: filesystem discovery still exposes
only filemodified_at; it does not yet expose parsedcreatedAtor
lastActiveAt, and must not be presented as full timestamp parity. Index
rows retain their storedcreated_at/last_active_atfields. - Rust session core hardening. Transcript pages now stream into a bounded
result, report an explicit truncated state instead of a partial total, and
cap scan bytes/messages, page size, and rendered message text. Index list,
search, and excerpt queries clamp untrusted page arguments before SQLite
binding; shortLIKEsearches use the Swift store's 200-row cap. - Rust session core parity and IPC safety. Index search now includes
title/session-id metadata and deduplicates body and metadata results.
Unknown providers are reported through an explicit compatibility status;
opaque row references can resolve a transcript without treating a raw path
supplied by a UI as authority. Direct readers reject symlink and
non-regular leaves (while documenting the remaining same-user TOCTOU
limitation). - Rust reproducibility. The workspace now commits
Cargo.lock, pins the
toolchain to Rust 1.98.0, and runs CI with--locked.
Added
crates/agent-session-core— the Rust implementation lane. A new
Cargo workspace rooted atCargo.tomlcarriesagent-session-core
(v0.1.0), a dependency-light Rust crate mirroring the session-reading
semantics cross-platform hosts need without the Swift runtime: read-only
session_index.sqlite3access (schema v5; any other version is refused,
never rebuilt), lightweight Codex/Claude Code filesystem discovery,
tolerant JSONL transcript paging, and a byte-compatible mirror of
SessionResumeCommandBuilder. The Swift package is untouched; the two
lanes are peer implementations and this repository is the shared source
of truth. First consumer:vibe-bar-desktop.
agent-session-kit 0.6.3
Fixed
- MCP sockets set
SO_NOSIGPIPEon the server's listening and accepted
file descriptors and on the stdio bridge's connect descriptor. A peer
vanishing between poll and write used to raise SIGPIPE, killing the
host process with no crash report — observed as a menu bar host's
recurring silent exits. Writes to a gone peer now fail withEPIPE
and run the ordinary teardown path. Hosts should still ignore
SIGPIPE process-wide; this is defense in depth.
agent-session-kit 0.6.2
An AntiGravity turn gets its clock back. agy CLI ~1.1.18 (2026-08) stopped
writing the per-turn wall time, and every cost scanner reading these stores
went dark from that day; the offset it writes instead is now resolved
against the trajectory's own start time, so a host that bumps its pin
recovers dated usage for the affected conversations with no API change.
Fixed
AntigravityGenMetadataReaderdates the relative-clock records agy
~1.1.18 writes. Those builds replace the wall clock at1.9.4with a
UInt64.maxsentinel at1.9.2and an elapsed-milliseconds offset at
1.9.10.1, measured from the trajectory's start. The reader now reads
that base from the store's owntrajectory_metadata_blobtable (path
2.1, exposed astrajectoryBaseDate(at:)) and resolves each turn as
base + offset —readGenMetadata(at:)callers get dated turns again
without code changes. A store with neither clock still yields nothing:
an undatable usage row cannot be billed to a day, anddecodeTurn(blob:)
keeps that contract (decodeTurn(blob:baseDate:)is the new
offset-aware entry point). The live session paths (metadataSummary,
transcript turn cards) resolve the same way, so first/last activity and
turn cards return for the affected conversations too.
agent-session-kit 0.6.1
A task notification is not an instruction, and Fable 5 has a million-token
window.
Fixed
HumanPromptTextstrips Claude Code's background-task envelopes. A
subagent's completion arrives as a user turn wrapped in<task-notification>
(<task-id>,<tool-use-id>,<output-file>,<status>,<summary>,
<result>) and a peer message in<cross-session-message>; a session whose
first "prompt" was one of those showed the XML as its assignment.ModelContextWindows.standardknowsclaude-fable-5andclaude-mythos-5
are 1M-token models without a[1m]marker — Claude Code's own/context
panel reports them so. A derived fill that read 425 % was this.
agent-session-kit 0.6.0
Every session can now say how full its context window is — measured where
the harness records it (Codex, Grok Build), derived from usage where it
does not (Claude Code) — plus Codex's rate limits as a quota, and a
compaction count. eventSchemaVersion is 3: hosts re-seed stored snapshots.
Added
- Per-session context-window usage — the
/contextgauge.
SessionSnapshot.contextUsageis aContextUsage:used,window,
cached, the timestamp it was read at, and whether the window was
measuredorderived. One definition ofusedacross every harness —
the tokens that were in the model's context when it was last called, cached
prefix included, the reply just generated excluded, because that lands in
the next call's input. Folded bySessionStateReducerfrom a new
AgentEventKind.contextUsage(used:window:cached:source:), which is a
level and never summed;usageremains the delta it always was. A reading
stamped before the one on record is dropped, so a gauge cannot flicker
backwards when a file-mtime-stamped reading is merged behind a log line. SessionSnapshot.compactions, folded fromAgentEventKind.compaction.
One compaction is normal; four in an afternoon is the shape of a session
that keeps forgetting what it was told. A compaction does not zero
contextUsage— the window really did empty, but by how much is not on
disk, and the next model call says so within a step.ModelContextWindows, the model-id → window lookup for the stores that
record the token counts but not the size of the window they filled. Prefixes,
a marker rule for the long-context variants (claude-…[1m]), a documented
default of 200,000 for the Claude family, andnil— not a plausible guess —
for a model nothing matches. Hosts pass their own with
ModelContextWindows.standard.overriding(…), accepted byClaudeLiveAdapter
andClaudeCoworkLiveAdapter.SessionSnapshot.quotaandAgentEventKind.quota(usedPercent:resetsAt:plan:),
from Codex'srate_limits. Read off the rollout; nothing here asks a network
what a limit is. Only theprimarywindow becomes an event — a row with two
percentages and no room to label them is worse than one showing the limit a
person is about to hit — andresets_in_seconds, which older rollouts write
instead ofresets_at, is resolved against the record's clock so a
replayed rollout does not claim its limits reset an hour from now.GrokSignalsReader, which turnssignals.json'scontextTokensUsedand
contextWindowTokensinto a measured reading. Not a tailer: the file is
rewritten in place, so there is no cursor to advance and no history to
replay. It stats on every poll and parses only when the stamp moved, and
rides along insideGrokSessionTailerrather than earning a watch of its
own —signals.jsonstays out ofmightBeSessionFileand out of the
source's auxiliary paths, because Grok rewrites it many times a minute and a
host waking on each rewrite is the storm that rule exists to prevent.CodexJSON.double, for the fractional percentages Codex writes.
Changed
AgentSessionLive.eventSchemaVersionis 3. Two cases were added to
AgentEventKindand three fields toSessionSnapshot, both of which are
encoded structurally, so a host that persisted rows re-seeds rather than
decoding a model it no longer speaks.
agent-session-kit 0.5.0
AntiGravity sessions arrive with a workspace and a model again, and a Codex
Auto Review names the thread it reviewed in its live identity.
Added
AntigravityGenMetadataReader.modelNames(blob:)— the model a
gen_metadatarecord names, without the rest of the turn having to decode.
decodeTurn(blob:)answersnilfor a record with no wall-clock timestamp,
which is right for a cost scanner: a usage row that cannot be placed in a day
cannot be billed to one. AntiGravity builds from 2026-08 write exactly that
record — field1.9carries no4— while1.19and1.20["model_enum"]
name the model as they always did, so every recent conversation on the corpus
this was found on answered "no model" to a question its store could answer.
ModelNames.displayorders the three names for a reader — label, router
alias, internal enum — rather than for a price list, which isTurn.model's
order and is unchanged.AntigravityConversationReader.recentModel(limit:)— the newest model a
conversation billed against, decoded from a bounded window at the end of
gen_metadata.stepsdoes not record it: a reply and a tool call look the
same whichever model served them.
Fixed
- An AntiGravity session arrives with the directory it was launched in.
conversation_summaries.workspace_urisis the conversation's own row and
still wins, but it is populated for a fraction of the databases on disk — the
store goes stale, and a conversation the CLI opened non-interactively never
reaches it — so a live board filed most of a day's AntiGravity work under no
project at all. Two side files the CLI writes answer for the rest:
history.jsonl, which records the directory a person typed a prompt in, and
log/cli-<stamp>.log, whose startup banner names the directories the server
was launched over and whose body names every conversation that run handled.
One server, one workspace. Both are read bounded and cached against their own
mtimes, so the run still being written is the only one re-read, a sweep that
changes nothing reads nothing, and a conversation whose row already had a
workspace costs neither. The conversation database is not a third source — it
records a trajectory, a cascade, and a project label, and nowhere in it is the
directory the CLI was launched in. - A live AntiGravity identity carries its model. Discovery seeds
SessionIdentity.modelfrom the conversation's owngen_metadata, cached
against the store and its WAL. Nothing else in the live layer could: the
tailer readssteps, and the summaries index records no model at all.
Changed
- The live layer names an Auto Review run the same way the index does.
CodexLiveAdapterseeds a guardian rollout'sSessionIdentity.variantwith
auto-review:<root session id>rather than its originator, so
CodexSessionAdapter.autoReviewParentSessionID(providerVariant:)is one
parse for a session whichever layer produced it. 0.4.0 added the encoding to
SessionSummary.providerVariantonly, which left a host tailing live
sessions unable to tell a review from an ordinary thread without reading the
rollout header itself. An ordinary thread still carries its originator, and
nothing consults the variant for a Codex resume.parent_thread_idis now
read as the root as well — but only for a rollout whose header already
identifies it as a guardian run, where it is the last thing pointing
anywhere; on an ordinary thread it can name an intermediate sub-agent and is
still not trusted.
agent-session-kit 0.4.2
Full-text session indexing now rebuilds large derived indexes with a bounded
working set while preserving the complete existing search surface.
Fixed
- Full-text session rebuilds now have a bounded working set. Schema v5
still indexes every eligible session and the same title, user, assistant,
system, tool, and file-operation excerpts, but commits them in transactions
capped at 4 MiB or 128 sessions instead of one FTS5 segment per session.
Each batch releases SQLite caches and applies malloc-zone pressure relief,
preventing a large first-run rebuild from retaining a multi-gigabyte heap.
SQLite temporary storage is file-backed and its page cache is capped at
32 MiB; search semantics, snippets, roles, and the existing 512 KiB
per-session body policy are unchanged.
agent-session-kit 0.4.1
Release validation no longer races an FSEvents stream rearm when a watched
session-store root appears for the first time.
Fixed
- Late-root FSEvents integration coverage is deterministic. The test now
waits until the watcher has narrowed from the nearest existing ancestor to
the newly created root before writing its probe file, matching the
production restart/rescan contract and removing the stream teardown/startup
gap from the assertion.
agent-session-kit 0.3.0
Sessions learn to say what they were asked for and how to get back into
them, Grok Bot goes live, discovery and liveness stop paying per-session for
answers that are per-machine, and the package learns how to cut its own
releases.
Added
SessionBriefon everySessionSnapshot. The state machine says what a
session is doing; it could not say what anybody asked it for. The brief
carries the assignment (firstPrompt, with thefirstPromptAtit was given
at), the latest instruction, the last thing the model said in prose, and when
a turn last closed — folded bySessionStateReducerfromuserPrompt,
assistantText, andturnEnded, the three events all eight live adapters
already emit. No adapter changed.- A filter for the things a person did not type. Half of what a harness
records as a "user message" is machinery: Claude Code spells a slash command
as<command-name>…</command-name>, injects context as<system-reminder>,
and spills hook output into<local-command-…>; Codex prepends
<environment_context>and<user_instructions>.
SessionBrief.instruction(_:)strips those blocks — including the ones a
200-character preview cut in half, from either end — and refuses what is left
when it is empty or a bare slash command. A rejected prompt moves nothing:
not the assignment, not the latest prompt, notlastPromptAt. Every field
guards its own clock, so a turn flushed out of order cannot overwrite a newer
one, and the brief survives an explicit restart. SessionResume— "how do I get back to this one?", answered from a
SessionIdentity.resumeCommand(for:)returns a cwd-aware
cd '<dir>' && <command>ornil;availability(for:)returns the same
answer plus the sentence to put in a disabled menu item, because Claude
Cowork, Cursor, and Grok Bot have no command-line entry point at all and a
menu item that quietly disappears explains nothing.Harness.sessionProvider
is the inverse ofSessionProvider.defaultHarness, which is not injective —
Codex and ChatGPT Work share one rollout tree.SourceAdapter.discover(home:activeSince:under:)— discovery narrowed to
one directory.nilmeans the whole store, and the default implementation
sweeps, so an adapter outside this package is unaffected and merely as
expensive as it always was. Every adapter here narrows: Grok reads a scope
positionally against~/.grok/sessions, Codex recognises a<yyyy>/<MM>/<dd>
directory by shape, Claude Code and Cowork resolve a project directory from
any path below it, Cursor resolves a workspace or an agent, and AntiGravity
resolves which of its two roots a change was in. Each of them refuses to
narrow what it cannot —~/.claude/sessions, Codex's lock directory,
AntiGravity's summaries store — and sweeps instead, because an entry
appearing in any of those can make a session far older than the cutoff worth
tailing.FSEventBatch.isDirectory(_:)— whether FSEvents said a delivered path is
itself a directory. Meaningful only underCreateFlags.fileEvents, which is
what makes FSEvents report per-item flags at all.IngestConfiguration.discoveryDebounce— how long a routed discovery
waits for the rest of its burst. Default 250 ms.GrokBotLiveAdapter— Grok Bot is now live, not just indexed. A
conversation is a JSON document the desktop client rewrites whole, so
GrokBotTranscriptTailerdiffs the file against itself rather than walking
it: a poll asks which entries are new and which of the ones already read have
stopped streaming, and the cursor is.blobHead(<id of the last entry consumed>)because entry ids survive a rewrite and a byte offset does not.
Twostatcalls short-circuit a poll with nothing to read — the client
rewrites the file on every step of a streaming reply. A streaming entry
producesthinkingand nothing else; the words come out of the read that
finds the flag cleared.send-messageis the bot answering the person and a
messagewithrole: "user"is inbound, matchingGrokBotSessionAdapter
exactly, and renames, automations, widgets, secret requests, and attachments
ride innoterather than pretending to be turns.- A needs-you signal for Grok Bot. The roster slice travels as an auxiliary
path and itsawaitingUserResponsebecomes
permissionRequested(id: "grokbot:<bot>", tool: nil), resolving when the flag
clears, stamped with the roster file's own mtime. It is the only field
anywhere in this store that says a person is needed, and a conversation
carrying it is discovered however old its timestamps are. - Liveness about the client, not the conversation. The conversation runs on
xAI's servers, so noGrok Botprocess and no fresh
~/.grokbot/local-exec-supervisor.jsonheartbeat ends every conversation at
once, a running client with a replica written in the last two minutes is
alive, and a running client with a quiet conversation answersunknown
however long the quiet has lasted — idle is not ended. That heartbeat is the
only file in~/.grokbotthis package opens, and the directory is
deliberately not watched: its neighbours hold a daemon token and a
credential. AgentSessionKitInfo— the version of the package, written down where
a statically linked host can read it. There is nothing else to ask: once
this code is compiled into somebody's binary there is no bundle, no
Info.plist, and no dylib left to interrogate.versionis pinned to the
top released section of this file byAgentSessionKitInfoTests, so the
constant and the changelog cannot drift apart without failing the suite.
repositoryURLandreleaseNotesURL(for:)are there so a host showing the
number has somewhere to send the reader; the tag normalizer tolerates the
vprefix this project does not use, because a caller reading a tag out of
the GitHub API should not have to know that.- A release workflow. Pushing a bare
X.Y.Ztag runs
.github/workflows/release.yml: it refuses the tag unless
AgentSessionKitInfo.versionand a## [X.Y.Z]changelog section both
agree with it, builds, tests, and then publishes a GitHub Release whose
notes are that changelog section. Every check fails loudly — a tag that
does not match its own source is not a release, and the workflow would
rather publish nothing than publish a lie. - RELEASING.md — the semver policy while this package is
0.x, the exact steps to cut a version, what a consumer has to do
afterwards, and the rule that a published tag never moves.
Changed
AgentSessionLive.eventSchemaVersionis 2. A field was added to
SessionSnapshot, which is encoded structurally, so a host that persisted
snapshots re-seeds rather than decoding rows from a model it no longer
speaks. That is the contract the constant already documented.- A file-system change is routed, not broadcast. An unrecognised path used
to restart discovery for every adapter over every store, throttled to once
per three seconds; with a transcript gaining a line a second, that is a sweep
of the whole machine every three seconds. A change now goes to the adapters
whose declaredwatchRootscontain it, about the one directory it happened
in. Nothing checked containment before, andmightBeSessionFileis a rule
about names: Codex's "any*.lockcould be a thread" claimed every writer
lock Grok rewrites and every presence file AntiGravity heartbeats, and
Cursor's "any*.jsonl" claimed every Claude Code transcript. Each of those
was a full sweep. FSEvents also announces every watch root as a directory
event the moment a stream arms, which woke every adapter once per launch for
news that was not news. IngestConfiguration.rediscoverEveryis 60 s, was 15 s. It is the safety
net now rather than the mechanism: a session that appears while the pipeline
runs is found by routing, in well under a second. It is also still the only
pass that drops a source, because "nobody discovered this" needs somebody to
have looked everywhere.rediscoverThrottlestill defaults to 3 s and is now
per adapter — Grok being rewritten is no reason to make Claude Code wait.- Discovery does not re-read what cannot have changed. Each adapter keeps
what it derived from a file until that file moves: Claude Code and Cowork key
a transcript's head on the inode, because an append-only file that grew by
a thousand lines has the same first three; Grok keys a source on its
summary.json; Cursor keys the conversation card on the store's own stamp;
AntiGravity keys the summaries index on the store and its WAL, since in WAL
mode that is where the writes land. - Grok's writer-lock probe is asked last, and remembered. It is a
readdir
plus anF_GETLKper lock file, Grok keeps one lock per mutable file, and it
ran for every session in the store on every pass — about seven hundred
syscalls, and the single most expensive thing the pipeline did at rest. It is
asked only after the mtimes and the registry have failed to answer, and only
when the answer can have changed: taking a lock creates a file, which moves
the directory's mtime, and a session doing anything moves one of its logs. A
held verdict additionally expires after thirty seconds, because a lock is
released without leaving a trace on disk. - Codex's second pass no longer re-walks history for the same unresolvable
ids. The whole-tree walk that finds a locked thread whose rollout predates
the cutoff kept running, every year of it, for lock files left behind by
threads whose rollouts are long gone.CodexRolloutIndexremembers where a
rollout was found and which ids have none; a rollout that appears later is
found by the first pass, on the notification that creates it. - **A liveness probe reads a machine-wide registry once per p...