Skip to content

Releases: AstroQore/agent-session-kit

agent-session-kit 0.8.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 11:42
9e836f2

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: deletion module. agent-session-core can 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

Choose a tag to compare

@github-actions github-actions released this 31 Aug 07:50
443ed10

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/ and implementations/rust/. The Swift
    sources and tests moved out of the repository root, and the Rust crates
    moved out of crates/. 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 became implementations/rust/crates/,
    which does move the Cargo path for anyone depending on the crate by path
    rather than by git.
  • ci.yml is now ci-swift.yml, and both lane workflows are scoped to their
    own paths plus contracts/.

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_tests builds a database from the file and opens it with the
    Rust reader. A schema change in either lane alone now fails CI.

Fixed

  • AgentSessionKitInfoTests resolved 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 find CHANGELOG.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 file modified_at; it does not yet expose parsed createdAt or
    lastActiveAt, and must not be presented as full timestamp parity. Index
    rows retain their stored created_at / last_active_at fields.
  • 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; short LIKE searches 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 at Cargo.toml carries agent-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.sqlite3 access (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

Choose a tag to compare

@github-actions github-actions released this 30 Aug 09:19
1c58e03

Fixed

  • MCP sockets set SO_NOSIGPIPE on 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 with EPIPE
    and run the ordinary teardown path. Hosts should still ignore
    SIGPIPE process-wide; this is defense in depth.

agent-session-kit 0.6.2

Choose a tag to compare

@github-actions github-actions released this 28 Aug 14:45
38bf06d

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

  • AntigravityGenMetadataReader dates the relative-clock records agy
    ~1.1.18 writes.
    Those builds replace the wall clock at 1.9.4 with a
    UInt64.max sentinel at 1.9.2 and 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 own trajectory_metadata_blob table (path
    2.1, exposed as trajectoryBaseDate(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, and decodeTurn(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

Choose a tag to compare

@github-actions github-actions released this 21 Aug 12:20

A task notification is not an instruction, and Fable 5 has a million-token
window.

Fixed

  • HumanPromptText strips 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.standard knows claude-fable-5 and claude-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

Choose a tag to compare

@github-actions github-actions released this 21 Aug 11:15

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 /context gauge.
    SessionSnapshot.contextUsage is a ContextUsage: used, window,
    cached, the timestamp it was read at, and whether the window was
    measured or derived. One definition of used across 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 by SessionStateReducer from a new
    AgentEventKind.contextUsage(used:window:cached:source:), which is a
    level and never summed; usage remains 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 from AgentEventKind.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, and nil — not a plausible guess —
    for a model nothing matches. Hosts pass their own with
    ModelContextWindows.standard.overriding(…), accepted by ClaudeLiveAdapter
    and ClaudeCoworkLiveAdapter.
  • SessionSnapshot.quota and AgentEventKind.quota(usedPercent:resetsAt:plan:),
    from Codex's rate_limits. Read off the rollout; nothing here asks a network
    what a limit is. Only the primary window 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 — and resets_in_seconds, which older rollouts write
    instead of resets_at, is resolved against the record's clock so a
    replayed rollout does not claim its limits reset an hour from now.
  • GrokSignalsReader, which turns signals.json's contextTokensUsed and
    contextWindowTokens into 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 inside GrokSessionTailer rather than earning a watch of its
    own — signals.json stays out of mightBeSessionFile and 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.eventSchemaVersion is 3. Two cases were added to
    AgentEventKind and three fields to SessionSnapshot, 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

Choose a tag to compare

@github-actions github-actions released this 21 Aug 10:15

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_metadata record names, without the rest of the turn having to decode.
    decodeTurn(blob:) answers nil for 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 — field 1.9 carries no 4 — while 1.19 and 1.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.display orders the three names for a reader — label, router
    alias, internal enum — rather than for a price list, which is Turn.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. steps does 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_uris is 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.model from the conversation's own gen_metadata, cached
    against the store and its WAL. Nothing else in the live layer could: the
    tailer reads steps, and the summaries index records no model at all.

Changed

  • The live layer names an Auto Review run the same way the index does.
    CodexLiveAdapter seeds a guardian rollout's SessionIdentity.variant with
    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.providerVariant only, 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_id is 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

Choose a tag to compare

@github-actions github-actions released this 20 Aug 06:04
fd07393

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

Choose a tag to compare

@github-actions github-actions released this 19 Aug 20:31
64dd681

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

Choose a tag to compare

@github-actions github-actions released this 19 Aug 10:09
84f0746

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

  • SessionBrief on every SessionSnapshot. 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 the firstPromptAt it was given
    at), the latest instruction, the last thing the model said in prose, and when
    a turn last closed — folded by SessionStateReducer from userPrompt,
    assistantText, and turnEnded, 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, not lastPromptAt. 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> or nil; 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 of SessionProvider.defaultHarness, which is not injective —
    Codex and ChatGPT Work share one rollout tree.
  • SourceAdapter.discover(home:activeSince:under:) — discovery narrowed to
    one directory. nil means 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 under CreateFlags.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
    GrokBotTranscriptTailer diffs 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.
    Two stat calls short-circuit a poll with nothing to read — the client
    rewrites the file on every step of a streaming reply. A streaming entry
    produces thinking and nothing else; the words come out of the read that
    finds the flag cleared. send-message is the bot answering the person and a
    message with role: "user" is inbound, matching GrokBotSessionAdapter
    exactly, and renames, automations, widgets, secret requests, and attachments
    ride in note rather than pretending to be turns.
  • A needs-you signal for Grok Bot. The roster slice travels as an auxiliary
    path and its awaitingUserResponse becomes
    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 no Grok Bot process and no fresh
    ~/.grokbot/local-exec-supervisor.json heartbeat 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 answers unknown
    however long the quiet has lasted — idle is not ended. That heartbeat is the
    only file in ~/.grokbot this 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. version is pinned to the
    top released section of this file by AgentSessionKitInfoTests, so the
    constant and the changelog cannot drift apart without failing the suite.
    repositoryURL and releaseNotesURL(for:) are there so a host showing the
    number has somewhere to send the reader; the tag normalizer tolerates the
    v prefix 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.Z tag runs
    .github/workflows/release.yml: it refuses the tag unless
    AgentSessionKitInfo.version and 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.eventSchemaVersion is 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 declared watchRoots contain it, about the one directory it happened
    in. Nothing checked containment before, and mightBeSessionFile is a rule
    about names: Codex's "any *.lock could 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.rediscoverEvery is 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. rediscoverThrottle still 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 an F_GETLK per 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. CodexRolloutIndex remembers 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...
Read more