ask-session 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +26 -183
- data/lib/ask/session/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 431ac519e177e903d981fd5b8909e0f61d0f3f87701e0da24404b7eff44ba3fc
|
|
4
|
+
data.tar.gz: 80c72aebcc96b0fd52e183e703732b8f0a804583728c4ffc28f5badb361fdccd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 216739303b1cc6ee78a9a6801ae4afcd715dc1c365383b42088870c2c025436106c5c43da4bab23e1c7a19269af658f50962a4fa1f50edfd6aa0f1f1a9b413f8
|
|
7
|
+
data.tar.gz: 76300dd1e7e90a752a1acad1ba53af2a06655890182a5469daecb2016aa011413a32c395b2209bd6ce8eca4df2843eae5144b170c27c0b03fcb6862580f6393a
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Task guides in `docs/` — in-memory sessions, durable SQLite sessions via the optional ask-state-providers integration, subscriptions and replay, tool lifecycle through `Host#sink`, and export/import — with a `docs/index.md` hub.
|
|
13
|
+
- README restructured to an overview, an in-memory quick start, and links to all guides.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Clarify that `Host#sink` records provider-neutral runtime lifecycle events, while protocol-facing tool events are translated by integration adapters; avoid attaching both producers to the same host for one execution.
|
|
18
|
+
|
|
10
19
|
## [0.1.0] - 2026-09-22
|
|
11
20
|
|
|
12
21
|
### Added
|
data/README.md
CHANGED
|
@@ -2,210 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
Event-sourced session state for the [ask-rb](https://github.com/ask-rb) ecosystem.
|
|
4
4
|
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
Requires Ruby 3.2+.
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
gem "ask-session"
|
|
11
|
-
```
|
|
12
|
-
|
|
13
5
|
## Overview
|
|
14
6
|
|
|
15
|
-
Ask::Session provides the foundational value objects and stores for event-sourced
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
Ask::Session provides the foundational value objects and stores for event-sourced
|
|
8
|
+
session management in ask-rb: immutable `Record` and `Event` envelopes, an
|
|
9
|
+
in-memory `Store` with concurrency guardrails, a durable `ProviderStore` for
|
|
10
|
+
restart-safe sessions, a replayable `Host` with subscriptions, a
|
|
11
|
+
`State` reducer, and a `Sink` that records ask-runtime tool lifecycle through
|
|
12
|
+
`Host#sink`. It ships with zero runtime dependencies — durability through
|
|
13
|
+
[ask-state-providers](https://github.com/ask-rb/ask-state-providers) is an
|
|
14
|
+
optional integration.
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```ruby
|
|
27
|
-
# Hash round-trip
|
|
28
|
-
record = Ask::Session::Record.create(id: "s1", status: :active, metadata: { key: "val" })
|
|
29
|
-
hash = record.to_h # => { id: "s1", status: :active, ..., created_at: <ISO8601>, version: 0 }
|
|
30
|
-
restored = Ask::Session::Record.from_h(hash)
|
|
31
|
-
|
|
32
|
-
# JSON via Codec
|
|
33
|
-
json = Ask::Session::Codec.dump_record(record)
|
|
34
|
-
record = Ask::Session::Codec.load_record(json)
|
|
35
|
-
|
|
36
|
-
# Events work the same way
|
|
37
|
-
event = Ask::Session::Event.create(session_id: "s1", seq: 1, type: "session.created")
|
|
38
|
-
json = Ask::Session::Codec.dump_event(event)
|
|
39
|
-
event = Ask::Session::Codec.load_event(json)
|
|
40
|
-
```
|
|
16
|
+
Task guides live in [docs/](https://github.com/ask-rb/ask-session/blob/master/docs/index.md).
|
|
41
17
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
### Store
|
|
18
|
+
## Installation
|
|
45
19
|
|
|
46
|
-
|
|
20
|
+
Requires Ruby 3.2+.
|
|
47
21
|
|
|
48
22
|
```ruby
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# Create a session
|
|
52
|
-
store.create(id: "sess_001")
|
|
53
|
-
|
|
54
|
-
# Append events with expected sequence
|
|
55
|
-
event = Ask::Session::Event.new(
|
|
56
|
-
session_id: "sess_001", seq: 1, type: "session.created",
|
|
57
|
-
payload: { status: :active }, created_at: Time.now
|
|
58
|
-
)
|
|
59
|
-
store.append_event(event, expected_sequence: 0)
|
|
60
|
-
|
|
61
|
-
# Load session events
|
|
62
|
-
events = store.events_after("sess_001", after_seq: 0)
|
|
63
|
-
|
|
64
|
-
# Get all events for a session (frozen)
|
|
65
|
-
events = store.events("sess_001")
|
|
66
|
-
|
|
67
|
-
# Export/import for portability (all sessions)
|
|
68
|
-
data = store.export
|
|
69
|
-
new_store = Ask::Session::Store.new
|
|
70
|
-
new_store.import(data)
|
|
71
|
-
|
|
72
|
-
# Export a single session into a fresh store
|
|
73
|
-
single = store.export("sess_001")
|
|
74
|
-
fresh = Ask::Session::Store.new
|
|
75
|
-
fresh.import(single)
|
|
76
|
-
|
|
77
|
-
# Rebuild session state from events
|
|
78
|
-
record = store.state("sess_001")
|
|
79
|
-
record.status # => :active
|
|
80
|
-
record.version # => 1
|
|
23
|
+
gem "ask-session"
|
|
81
24
|
```
|
|
82
25
|
|
|
83
|
-
|
|
26
|
+
## Quick start
|
|
84
27
|
|
|
85
|
-
|
|
28
|
+
In-memory, single process:
|
|
86
29
|
|
|
87
30
|
```ruby
|
|
88
|
-
require "ask-
|
|
31
|
+
require "ask-session"
|
|
89
32
|
|
|
90
|
-
|
|
91
|
-
host = Ask::Session::Host.new(store: Ask::Session::ProviderStore.new(adapter: adapter))
|
|
33
|
+
host = Ask::Session::Host.new
|
|
92
34
|
|
|
93
35
|
host.create(id: "s1", metadata: { user: "alice" })
|
|
94
36
|
host.send_message("s1", content: "hello")
|
|
37
|
+
host.session("s1").version # => 2
|
|
95
38
|
host.close("s1", reason: "done")
|
|
96
|
-
adapter.close
|
|
97
|
-
|
|
98
|
-
# Later — same file, new process:
|
|
99
|
-
adapter = Ask::State::Providers::SQLite.new(path: "sessions.db")
|
|
100
|
-
host = Ask::Session::Host.new(store: Ask::Session::ProviderStore.new(adapter: adapter))
|
|
101
|
-
host.session("s1") # => reduced Record with status: :closed, version: 3
|
|
102
|
-
host.events("s1") # full history survives the restart
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Each session's record and event list persist under namespaced keys (`ask.session:record:*`, `ask.session:events:*`) alongside a session index, using JSON with symbol-safe encoding so statuses and payload symbols round-trip exactly.
|
|
106
|
-
|
|
107
|
-
When the adapter exposes the provider lock API (`acquire_lock` / `release_lock`), every operation also runs under a cross-process store lock so read-modify-write sequences stay atomic across processes; exhausting the lock wait budget raises `Ask::Session::ConcurrencyError`. Adapters without lock methods fall back to an in-process mutex. Stale `expected_sequence` writers still fail with `ConcurrencyError` — locks serialize, they do not merge.
|
|
108
|
-
|
|
109
|
-
ask-session keeps zero runtime dependencies: `ProviderStore` duck-types the adapter, so `ask-state-providers` is an optional integration, not a dependency of this gem.
|
|
110
|
-
|
|
111
|
-
### State Reducer
|
|
112
|
-
|
|
113
|
-
Rebuild session state from event history:
|
|
114
|
-
|
|
115
|
-
```ruby
|
|
116
|
-
events = [
|
|
117
|
-
Ask::Session::Event.new(session_id: "s1", seq: 1, type: "session.created",
|
|
118
|
-
payload: { status: :active }, created_at: Time.now),
|
|
119
|
-
Ask::Session::Event.new(session_id: "s1", seq: 2, type: "message.added",
|
|
120
|
-
payload: { role: :user, content: "hello" }, created_at: Time.now)
|
|
121
|
-
]
|
|
122
|
-
|
|
123
|
-
record = Ask::Session::State.reduce("s1", events)
|
|
124
|
-
record.status # => :active
|
|
125
|
-
record.version # => 2
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Host
|
|
129
|
-
|
|
130
|
-
Replayable session host with publish-subscribe:
|
|
131
|
-
|
|
132
|
-
```ruby
|
|
133
|
-
store = Ask::Session::Store.new
|
|
134
|
-
host = Ask::Session::Host.new(store: store)
|
|
135
|
-
|
|
136
|
-
# Create a session
|
|
137
|
-
record = host.create(id: "s1", metadata: { user: "alice" })
|
|
138
|
-
record.status # => :active
|
|
139
|
-
|
|
140
|
-
# Send messages
|
|
141
|
-
event = host.send_message("s1", content: "hello")
|
|
142
|
-
event.type # => "message.added"
|
|
143
|
-
|
|
144
|
-
# Query
|
|
145
|
-
host.session("s1") # => reduced Record
|
|
146
|
-
host.list # => [Record, ...]
|
|
147
|
-
host.events("s1") # => [Event, ...]
|
|
148
|
-
|
|
149
|
-
# Subscribe with replay
|
|
150
|
-
sub = host.subscribe("s1")
|
|
151
|
-
event = sub.next(timeout: 1.0) # returns event or nil on timeout
|
|
152
|
-
sub.each { |e| puts e.type } # yields until closed
|
|
153
|
-
sub.close
|
|
154
|
-
|
|
155
|
-
# Close or abort
|
|
156
|
-
host.close("s1", reason: "done")
|
|
157
|
-
host.abort("s1", reason: "error")
|
|
158
39
|
```
|
|
159
40
|
|
|
160
|
-
Invalid transitions (
|
|
161
|
-
|
|
162
|
-
#### Generic event append
|
|
41
|
+
Invalid transitions (sending to a closed session, closing twice) raise
|
|
42
|
+
`Ask::Session::InvalidTransitionError`.
|
|
163
43
|
|
|
164
|
-
|
|
44
|
+
## Guides
|
|
165
45
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
type: "vendor.webhook.received",
|
|
173
|
-
payload: { raw: body },
|
|
174
|
-
trace_id: "trace_abc",
|
|
175
|
-
causation_id: originating_event.trace_id
|
|
176
|
-
)
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Every appended event advances the session's reduced `version` and `updated_at`, regardless of event type. Appends to closed or aborted sessions raise `InvalidTransitionError`. Adapters use `append` instead of `send_message` when the event type is not `message.added` — this keeps ask-session free of protocol and agent dependencies.
|
|
180
|
-
|
|
181
|
-
#### Runtime tool-lifecycle sink
|
|
182
|
-
|
|
183
|
-
`Ask::Session::Sink` is the boundary to ask-runtime's event-sink contract. Executors in ask-agent, ask-mcp, and ask-sandbox-providers report tool lifecycle through `ExecutionContext#event_sink` by calling `emit(event_type, event:)` — point that sink at a session host and tool history becomes part of the event-sourced session:
|
|
184
|
-
|
|
185
|
-
```ruby
|
|
186
|
-
host = Ask::Session::Host.new
|
|
187
|
-
host.create(id: "s1")
|
|
188
|
-
|
|
189
|
-
sink = host.sink("s1", trace_id: "trace_abc")
|
|
190
|
-
# pass `sink` as ExecutionContext's event_sink; runtime emits then record:
|
|
191
|
-
# :tool_started -> tool.started
|
|
192
|
-
# :tool_completed -> tool.completed
|
|
193
|
-
# :tool_failed -> tool.failed
|
|
194
|
-
# :tool_cancelled -> tool.cancelled
|
|
195
|
-
# :tool_timed_out -> tool.timed_out
|
|
196
|
-
|
|
197
|
-
host.events("s1").last.payload
|
|
198
|
-
# => { tool_name: "bash", tool_call_id: "tc_1", input: { "cmd" => "ls" }, turn: 3 }
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Terminal events carry `outcome` (`:completed`/`:failed`/`:cancelled`/`:timed_out`), `duration` in seconds, `error` when present, and `output` when the runtime result exposes one. The sink duck-types the runtime events' public readers, so ask-session keeps zero runtime dependencies.
|
|
202
|
-
|
|
203
|
-
Guards: an event correlated to a different session raises `Ask::Session::SessionMismatchError`; appends to closed/aborted sessions are dropped (terminal sessions stop recording without failing an in-flight tool run); missing sessions raise `NotFoundError`; unknown event types are ignored.
|
|
204
|
-
|
|
205
|
-
```ruby
|
|
206
|
-
sink = Ask::Session::Sink.new(host: host, session_id: "s1", causation_id: originating.trace_id)
|
|
207
|
-
sink.listening?(:tool_started) # => true
|
|
208
|
-
```
|
|
46
|
+
- [Guides index](https://github.com/ask-rb/ask-session/blob/master/docs/index.md)
|
|
47
|
+
- [In-memory sessions](https://github.com/ask-rb/ask-session/blob/master/docs/in_memory_sessions.md)
|
|
48
|
+
- [Durable sessions with SQLite (optional ask-state-providers)](https://github.com/ask-rb/ask-session/blob/master/docs/durable_sessions.md)
|
|
49
|
+
- [Subscriptions and replay](https://github.com/ask-rb/ask-session/blob/master/docs/subscriptions_and_replay.md)
|
|
50
|
+
- [Tool lifecycle through Host#sink](https://github.com/ask-rb/ask-session/blob/master/docs/tool_lifecycle.md)
|
|
51
|
+
- [Export and import](https://github.com/ask-rb/ask-session/blob/master/docs/export_import.md)
|
|
209
52
|
|
|
210
53
|
## Contributing
|
|
211
54
|
|
data/lib/ask/session/version.rb
CHANGED