turnkit 0.5.0 → 0.7.0
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 +46 -0
- data/README.md +198 -5
- data/UPGRADE.md +57 -0
- data/lib/generators/turnkit/install/templates/create_turnkit_tables.rb +30 -0
- data/lib/generators/turnkit/install/templates/delivery.rb +7 -0
- data/lib/generators/turnkit/install/templates/initializer.rb +5 -0
- data/lib/generators/turnkit/install/templates/wait.rb +7 -0
- data/lib/generators/turnkit/install_generator.rb +2 -0
- data/lib/generators/turnkit/upgrade/templates/add_turnkit_durable_orchestration.rb +36 -0
- data/lib/generators/turnkit/upgrade_generator.rb +34 -0
- data/lib/turnkit/active_record_store.rb +124 -14
- data/lib/turnkit/adapters/ruby_llm.rb +107 -21
- data/lib/turnkit/agent.rb +26 -20
- data/lib/turnkit/authorization.rb +17 -0
- data/lib/turnkit/background.rb +281 -0
- data/lib/turnkit/budget.rb +4 -3
- data/lib/turnkit/client.rb +3 -1
- data/lib/turnkit/conversation.rb +54 -1
- data/lib/turnkit/coordination_tools.rb +67 -0
- data/lib/turnkit/cost.rb +7 -7
- data/lib/turnkit/error.rb +3 -0
- data/lib/turnkit/execution_store.rb +30 -0
- data/lib/turnkit/id.rb +1 -0
- data/lib/turnkit/image_tool.rb +10 -0
- data/lib/turnkit/job.rb +21 -0
- data/lib/turnkit/memory_store.rb +113 -20
- data/lib/turnkit/message_projection.rb +4 -1
- data/lib/turnkit/reconciliation.rb +13 -10
- data/lib/turnkit/record.rb +36 -3
- data/lib/turnkit/run.rb +28 -0
- data/lib/turnkit/skill.rb +5 -4
- data/lib/turnkit/specialists.rb +254 -0
- data/lib/turnkit/store.rb +38 -9
- data/lib/turnkit/sub_agent_tool.rb +23 -7
- data/lib/turnkit/system_prompt.rb +7 -7
- data/lib/turnkit/tool.rb +11 -0
- data/lib/turnkit/tool_runner.rb +109 -45
- data/lib/turnkit/turn.rb +238 -61
- data/lib/turnkit/turn_controls.rb +136 -0
- data/lib/turnkit/version.rb +1 -1
- data/lib/turnkit.rb +31 -0
- metadata +16 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a276982d34a5c7685f9512e29f1843942166c67bdb59c94ebc38b727213ffe3
|
|
4
|
+
data.tar.gz: 8ad4edfdc514c24a918441734c4c10294e7d1c45b08b7594f19db7e3294edd61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bff210317826da7bd796b0271006b76c2ed301dbd9070248529efd8c14e3b2d43414258373f6c709a1496f91004764227ff2453073be0040ac0d5bde4eb3d77
|
|
7
|
+
data.tar.gz: ed8c88ae93a81983f2284e3ed69c018a1e05abd4b2ae081db2160a937c1e4e42fdece546baac10ca8c976e94da54354275f15d80d5dc4cc60cb55e919a78e8d1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0 - 2026-09-10
|
|
4
|
+
|
|
5
|
+
- Add destination-oriented `Conversation#post`, durable input/request receipts,
|
|
6
|
+
authorized transcript cursor reads, and cooperative `pause!`, `resume!`, and
|
|
7
|
+
`steer!` controls on turns/runs, including parent-linked subtree controls.
|
|
8
|
+
- Preserve in-flight results while skipping stale tool proposals before steering;
|
|
9
|
+
keep paused conversations closed to automatic wake and retain dependency waits.
|
|
10
|
+
- Enqueue newly eligible joined turns in the same maintenance pass, and serialize
|
|
11
|
+
MemoryStore message insertion with sequence allocation for cursor catchup.
|
|
12
|
+
- Project busy-time deliveries at their receiving turn's context boundary so
|
|
13
|
+
earlier steering cannot override next-turn input or split prior tool exchanges.
|
|
14
|
+
- Support optional RubyLLM 2.0.0.rc2 with `protocol: :responses`, opaque provider
|
|
15
|
+
replay, single-response `generate`, and updated usage/media APIs. Keep RubyLLM
|
|
16
|
+
1.16 compatibility and the stable development dependency; no separate HTTP
|
|
17
|
+
adapter or tool executor. Add live Astra/xhigh Rails 8.1/Sidekiq validation.
|
|
18
|
+
- Preserve complete native Anthropic/Gemini thinking/tool blocks across durable
|
|
19
|
+
reload, steering and recovery, without exposing opaque state in UI/activity.
|
|
20
|
+
Avoid duplicate raw Gemini calls and double-counted native thinking on 1.16;
|
|
21
|
+
validate Claude Opus 4.8/high and Gemini 3.1 Pro/high on both SDK versions.
|
|
22
|
+
- No migration on the 0.6.0 schema. Add `paused` to application/custom-store status
|
|
23
|
+
handling and upgrade workers together before enabling controls. Existing
|
|
24
|
+
delivery retry payloads remain compatible. See `docs/interactive-research.md`.
|
|
25
|
+
|
|
26
|
+
## 0.6.0 - 2026-09-06
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Independent background submission through Active Job, registered agent reconstruction, durable messages/completion callbacks, inbox/outbox queries, and worker-free joins.
|
|
31
|
+
- Parallel background sub-agents with persisted parent/child lineage and ordered results; opt-in launch, send-message, and wait tools.
|
|
32
|
+
- Persisted execution phases, fenced claims, root-wide budget reservations, and recovery of missed enqueues and abandoned workers without replaying unknown external effects.
|
|
33
|
+
- PostgreSQL concurrency/process-death tests and a reversible `turnkit:upgrade` migration generator.
|
|
34
|
+
- Agent-scoped context and skills, extensible specialist factories, authorization hooks, and fenced cancellation with explicit descendant handling.
|
|
35
|
+
- Live recipe, deep-research and rare-earth policy examples, plus prepared orb dependencies and OIDC-based RubyGems releases.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- Reject conflicting delivery-key reuse without disclosing another conversation's payload; authorize implicit joins and detect conversation-lane wait cycles.
|
|
40
|
+
- Bound inline reconciliation queries and preserve replay-safe tool keys and skill activation across recovery.
|
|
41
|
+
- Forward image references and normalize RubyLLM provider failures as terminal model errors instead of repeatedly retrying permanent failures.
|
|
42
|
+
|
|
43
|
+
### Breaking
|
|
44
|
+
|
|
45
|
+
- Existing ActiveRecord installations must run `turnkit:upgrade` and migrate; turn records gain `submitted_at` and `claim_token`, plus delivery and wait tables.
|
|
46
|
+
- Custom stores must implement the new transactional coordination contract. Reconciled inline workers lose write authority; `stale` is no longer provisional.
|
|
47
|
+
- Background execution requires Active Job 7.2+ and a persistent queue backend. `async: true` remains previewable pending work; call `perform_later` to submit.
|
|
48
|
+
|
|
3
49
|
## 0.5.0 - 2026-07-22
|
|
4
50
|
|
|
5
51
|
### Breaking
|
data/README.md
CHANGED
|
@@ -7,6 +7,17 @@
|
|
|
7
7
|
Build durable Ruby and Rails agents with conversations, runs, orchestrator agents,
|
|
8
8
|
tools, skills, output audits, sub-agents, and persistence.
|
|
9
9
|
|
|
10
|
+
For interactive long-running work, use `conversation.post(text, key:, principal:)`
|
|
11
|
+
for next-turn input and `turn.steer!(text, key:, principal:)` to revise the active
|
|
12
|
+
plan. `turn.pause!`/`resume!` preserve progress and release background workers;
|
|
13
|
+
use `descendants: :cascade` for a research subtree. See
|
|
14
|
+
[interactive research](docs/interactive-research.md) for exact interruption
|
|
15
|
+
boundaries, durable receipts, approval gates, and Rails integration.
|
|
16
|
+
For GPT-6 Astra tools, opt into the pinned RubyLLM 2 release candidate and
|
|
17
|
+
[Responses protocol](docs/interactive-research.md#gpt-6-astra-and-provider-continuation-state).
|
|
18
|
+
The [live validation app](examples/interactive_validation/README.md) exercises
|
|
19
|
+
these controls with Rails 8.1, PostgreSQL, Sidekiq, and actual Astra/xhigh requests.
|
|
20
|
+
|
|
10
21
|
## Installation
|
|
11
22
|
|
|
12
23
|
Add this line to your application's **Gemfile**:
|
|
@@ -548,6 +559,21 @@ end
|
|
|
548
559
|
|
|
549
560
|
Require an image before completion with `TurnKit::OutputPolicy.require_image`.
|
|
550
561
|
|
|
562
|
+
For editing or style references, override `input_images(**arguments)` and
|
|
563
|
+
`mask(**arguments)` in your `ImageTool` subclass, just like `prompt` and
|
|
564
|
+
`metadata`. Both default to `nil` and are forwarded to the image provider.
|
|
565
|
+
Declare the corresponding tool parameters yourself; accepted sources, reference
|
|
566
|
+
limits, and mask support depend on the provider. Local paths must exist on the
|
|
567
|
+
executing worker. Generate only when the application/user requested it; use
|
|
568
|
+
`ViewMediaTool` for inspection, not image generation.
|
|
569
|
+
|
|
570
|
+
Image results retain URL or base64 bytes, including in tool results. Provider
|
|
571
|
+
URLs may expire: use the event hook above to copy images into application-owned
|
|
572
|
+
storage for durable user-facing links. TurnKit does not host attachments or
|
|
573
|
+
automatically make generated images visually available to the next text model
|
|
574
|
+
call. Base64 tool results also consume prompt space; for large images, prefer a
|
|
575
|
+
custom tool that stores the artifact and returns its application-owned URL.
|
|
576
|
+
|
|
551
577
|
### Media analysis
|
|
552
578
|
|
|
553
579
|
Analyze existing images, PDFs, audio, or video inside a durable turn with
|
|
@@ -727,6 +753,161 @@ puts turn.output_text
|
|
|
727
753
|
|
|
728
754
|
Use sub-agents for isolated child conversations.
|
|
729
755
|
|
|
756
|
+
#### Oracle- and Librarian-style specialists
|
|
757
|
+
|
|
758
|
+
Use ordinary agents, not a second specialist runtime. Give each specialist its
|
|
759
|
+
own model, instructions, description (when the parent should invoke it), and
|
|
760
|
+
explicit tool list, then include it in the parent's `sub_agents`:
|
|
761
|
+
|
|
762
|
+
- **Oracle:** instructions for a specific unresolved decision, the evidence
|
|
763
|
+
already checked, constraints, and a recommendation. Supply only scoped
|
|
764
|
+
read/search tools; the parent owns implementation and verification.
|
|
765
|
+
- **Librarian:** instructions for external repository understanding and a full
|
|
766
|
+
evidence-backed report. Supply authenticated repository read/search, history,
|
|
767
|
+
diff, and issue-reading tools appropriate to the application. A role name does
|
|
768
|
+
not provide GitHub access. Exclude repository and issue mutation tools.
|
|
769
|
+
- **Painter:** use `ImageTool` for an actual image-provider call rather than a
|
|
770
|
+
text agent pretending to generate an image.
|
|
771
|
+
|
|
772
|
+
Children receive the explicit `task`, not the parent's conversation history or
|
|
773
|
+
tool list. Their complete final text, structured `output_data`, status/error,
|
|
774
|
+
and conversation/turn IDs return through the parent's matching tool result;
|
|
775
|
+
intermediate child messages remain in the child conversation. Inline calls
|
|
776
|
+
block; background calls use the same durable fan-out/join machinery below.
|
|
777
|
+
|
|
778
|
+
This is a context boundary, not an OS or credential sandbox. Global context
|
|
779
|
+
contributors and custom clients can supply additional context/capabilities;
|
|
780
|
+
scope those deliberately. Enforce read-only access in the supplied tools and
|
|
781
|
+
credentials, not merely in prompts. Likewise, enforce user approval for image
|
|
782
|
+
generation in application tool exposure/authorization when it is a requirement.
|
|
783
|
+
TurnKit does not ship Amp's private tools, repository service, or attachment host.
|
|
784
|
+
|
|
785
|
+
### Background execution and agent messaging
|
|
786
|
+
|
|
787
|
+
Durable background execution uses Active Record/Active Job 7.2+, `ActiveRecordStore`, and a
|
|
788
|
+
persistent backend such as Solid Queue or Sidekiq. Configure the backend in your
|
|
789
|
+
application; TurnKit does not start worker processes. The database owns pending
|
|
790
|
+
work, while jobs are wake-up signals. `MemoryStore` is for inline use and tests.
|
|
791
|
+
|
|
792
|
+
Existing installations must run `bin/rails generate turnkit:upgrade` and migrate
|
|
793
|
+
before using this version. New installations use `turnkit:install` as usual.
|
|
794
|
+
|
|
795
|
+
Register agent definitions at application boot **in every web and worker
|
|
796
|
+
process**. Registration also registers configured sub-agents. Names must identify
|
|
797
|
+
the same agent configuration in every process; use Rails `to_prepare` when
|
|
798
|
+
definitions are reloadable. TurnKit persists IDs, model, options, and lineage,
|
|
799
|
+
not Ruby clients, tools, closures, or executable configuration.
|
|
800
|
+
Subject `to_prompt` data is snapshotted in conversation metadata for worker
|
|
801
|
+
reconstruction. Use live-context contributors with application-owned identifiers
|
|
802
|
+
when background turns need freshly loaded domain objects rather than that snapshot.
|
|
803
|
+
|
|
804
|
+
```ruby
|
|
805
|
+
require "turnkit/job"
|
|
806
|
+
|
|
807
|
+
writer = TurnKit::Agent.new(name: "writer", instructions: "Draft concise copy.")
|
|
808
|
+
editor = TurnKit.register(TurnKit::Agent.new(
|
|
809
|
+
name: "editor",
|
|
810
|
+
sub_agents: [writer],
|
|
811
|
+
tools: [TurnKit::LaunchAgentTool, TurnKit::SendMessageTool, TurnKit::WaitTool]
|
|
812
|
+
))
|
|
813
|
+
|
|
814
|
+
run = editor.run("Research and draft the announcement.", async: true)
|
|
815
|
+
run.perform_later
|
|
816
|
+
|
|
817
|
+
# Another process can reconstruct records using the registered definitions.
|
|
818
|
+
run = TurnKit::Run.new(TurnKit.load_turn(run.id))
|
|
819
|
+
run.reload.status
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
`async: true` still means **prepare pending work**, not enqueue it. This preserves
|
|
823
|
+
preview-only use. `perform_later` persists submission before enqueueing and
|
|
824
|
+
returns immediately. Background work uses the application-wide `TurnKit.store`.
|
|
825
|
+
Worker clients and tools must be safe for the concurrency configured in your job
|
|
826
|
+
backend. Do not launch unjoined Ruby threads from tools.
|
|
827
|
+
|
|
828
|
+
In a background turn, a group of consecutive sub-agent tool calls launches
|
|
829
|
+
independent child jobs. The parent becomes `waiting` and releases its worker;
|
|
830
|
+
once all children finish, it resumes with tool results in call order. A failed
|
|
831
|
+
child returns a structured failure, not an implicit parent failure. Ordinary
|
|
832
|
+
tools remain sequential, and terminal tools prevent later calls from running.
|
|
833
|
+
Inline turns use the same engine but execute sub-agents synchronously.
|
|
834
|
+
|
|
835
|
+
`LaunchAgentTool` starts a configured sub-agent without waiting. Its optional
|
|
836
|
+
`callback: true` requests a completion message in the launching conversation.
|
|
837
|
+
`WaitTool` joins submitted turns without holding a worker. These coordination
|
|
838
|
+
tools are opt-in; the application owns authorization and should expose only
|
|
839
|
+
the destinations/actions appropriate for that agent.
|
|
840
|
+
|
|
841
|
+
Application code can send messages and request completion callbacks too:
|
|
842
|
+
|
|
843
|
+
```ruby
|
|
844
|
+
parent = TurnKit.load_conversation(parent_conversation_id)
|
|
845
|
+
child = writer.run("Investigate the issue.", async: true)
|
|
846
|
+
child.perform_later(callback: parent)
|
|
847
|
+
|
|
848
|
+
parent.send_message(other_conversation_id, "Please check the latest results.",
|
|
849
|
+
key: "request-123:check-results")
|
|
850
|
+
parent.inbox
|
|
851
|
+
parent.outbox
|
|
852
|
+
|
|
853
|
+
# Attach dependencies before enqueueing, avoiding a race with an eager worker.
|
|
854
|
+
summary = editor.run("Summarize the joined results.", async: true)
|
|
855
|
+
summary.wait_for(child).perform_later
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
Delivery keys are globally unique idempotency keys. Retrying the same key returns
|
|
859
|
+
the original delivery; it does not replace its payload. Inbox, outbox, and
|
|
860
|
+
completion callbacks use the same persisted delivery rows. Delivery appends one
|
|
861
|
+
message and records the need for a continuation transactionally. Messages to a
|
|
862
|
+
running or waiting conversation do not change its current input snapshot: they
|
|
863
|
+
are consumed by a later turn. Background turns in one conversation execute
|
|
864
|
+
serially. Use `TurnKit.load_conversation` to address a conversation independently
|
|
865
|
+
of a worker's fenced execution context.
|
|
866
|
+
|
|
867
|
+
Application-level `wait_for` supplies joined results as turn-local input before
|
|
868
|
+
the first model call. Attach waits only to pending turns. Wait targets must be
|
|
869
|
+
submitted or finished, and must not form dependency cycles. Waiting for unfinished
|
|
870
|
+
work in the same conversation is rejected because execution there is serial.
|
|
871
|
+
|
|
872
|
+
#### Recovery and operational requirements
|
|
873
|
+
|
|
874
|
+
Schedule `TurnKit::ReconcileJob` using your backend's recurring-job facility
|
|
875
|
+
(for example every minute). This repairs missed enqueues, undelivered messages,
|
|
876
|
+
ready joins, and abandoned workers. `TurnKit.reconcile_stale!` also recovers
|
|
877
|
+
submitted work while retaining the inline stale-turn behavior described below.
|
|
878
|
+
|
|
879
|
+
- Claims are fenced: after revocation or completion, an old execution cannot
|
|
880
|
+
persist another model response, tool result, heartbeat, or completion.
|
|
881
|
+
- Recovery reuses committed model responses and tool results. An interrupted
|
|
882
|
+
model request may be reissued and charged again by the provider.
|
|
883
|
+
- Started external tool calls without results become `interrupted`, with unknown
|
|
884
|
+
outcomes. They are **not replayed**. The built-in durable coordination tools can
|
|
885
|
+
resume using their existing child IDs/delivery keys.
|
|
886
|
+
- Waiting is not a stale heartbeat. Root timeout still applies, including queue
|
|
887
|
+
and wait time from initial submission. It is a cooperative limit, not a hard
|
|
888
|
+
process kill. Use provider/tool timeouts for blocking external calls.
|
|
889
|
+
- Root iteration and tool-count limits are reserved under a database lock across
|
|
890
|
+
parallel children. Spend limits use observed cost; already-running requests can
|
|
891
|
+
overshoot them.
|
|
892
|
+
- `on_event` remains an inline observation hook, not a durable callback. Background
|
|
893
|
+
infrastructure exceptions reach the job backend rather than being converted
|
|
894
|
+
into success. Monitor failed jobs and run reconciliation.
|
|
895
|
+
- TurnKit fences its own persistence, not arbitrary external side effects. Tools
|
|
896
|
+
should use `context.turn.store` for execution-owned TurnKit writes. External
|
|
897
|
+
services need their own idempotency keys where appropriate.
|
|
898
|
+
|
|
899
|
+
The PostgreSQL integration suite exercises real row locks and worker-process
|
|
900
|
+
death. Run it against a dedicated test database:
|
|
901
|
+
|
|
902
|
+
```sh
|
|
903
|
+
TURNKIT_TEST_DATABASE_URL=postgresql:///turnkit_test bundle exec rake test
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
It creates/rebuilds only `turnkit_test_*` and `turnkit_upgrade_test_*` test tables.
|
|
907
|
+
Without the variable, database tests explicitly skip. Custom stores must implement
|
|
908
|
+
the transactional `Store` contract, including reentrant `atomic`, deliveries,
|
|
909
|
+
waits, and submitted-turn queries; a mutex alone is not cross-process durability.
|
|
910
|
+
|
|
730
911
|
### Context Compaction
|
|
731
912
|
|
|
732
913
|
Disable compaction:
|
|
@@ -791,7 +972,8 @@ process). Run this periodically:
|
|
|
791
972
|
TurnKit.reconcile_stale!
|
|
792
973
|
```
|
|
793
974
|
|
|
794
|
-
|
|
975
|
+
For unsubmitted inline work, reconciliation atomically marks pending and running
|
|
976
|
+
turns whose last heartbeat
|
|
795
977
|
is older than `TurnKit.timeout` as `stale`, so it never overwrites a turn that
|
|
796
978
|
was concurrently claimed, heartbeated, or completed. Each stale turn's
|
|
797
979
|
unfinished tool executions become `interrupted`, and a synthetic error tool
|
|
@@ -799,10 +981,10 @@ result is appended for any unresolved tool call so the conversation can be
|
|
|
799
981
|
continued. TurnKit never reruns an interrupted tool — whether its side effect
|
|
800
982
|
happened is unknown, so the continued model is told not to assume either way.
|
|
801
983
|
|
|
802
|
-
Reconciliation
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
984
|
+
Reconciliation revokes the original worker's commit authority. A late worker
|
|
985
|
+
cannot replace `stale` with its own outcome. It does not kill the underlying
|
|
986
|
+
process or undo external side effects. Continue an inline stale conversation
|
|
987
|
+
with a new turn; submitted background work is resumed automatically.
|
|
806
988
|
|
|
807
989
|
## Options
|
|
808
990
|
|
|
@@ -877,6 +1059,11 @@ agent = TurnKit::Agent.new(
|
|
|
877
1059
|
|
|
878
1060
|
## Upgrading
|
|
879
1061
|
|
|
1062
|
+
See [runtime hardening](docs/runtime-hardening.md) for scoped context, authorization,
|
|
1063
|
+
cancellation, cycle-safe waits, replay-safe effects and bounded maintenance, and
|
|
1064
|
+
[specialists and skills](docs/specialists.md) for the extensible Oracle, Librarian
|
|
1065
|
+
and Painter factories and skill-owned tools.
|
|
1066
|
+
|
|
880
1067
|
See the [0.4.2 Upgrade Guide](UPGRADE_TO_0_4_2.md) for the full API migration checklist.
|
|
881
1068
|
|
|
882
1069
|
Rails installs from older versions may need `output_data` for structured output,
|
|
@@ -906,6 +1093,12 @@ find lib test examples -type f -name '*.rb' -print0 | xargs -0 ruby -c
|
|
|
906
1093
|
|
|
907
1094
|
Open a pull request.
|
|
908
1095
|
|
|
1096
|
+
## Releasing
|
|
1097
|
+
|
|
1098
|
+
Maintainers: follow [the release guide](docs/releasing.md) for RubyGems trusted
|
|
1099
|
+
publishing setup, version and lockfile updates, and the explicit tag-push procedure.
|
|
1100
|
+
Pushing a `v*` tag triggers tests and publication through the `release` environment.
|
|
1101
|
+
|
|
909
1102
|
## License
|
|
910
1103
|
|
|
911
1104
|
Use this gem under the MIT License.
|
data/UPGRADE.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Upgrade Guide
|
|
2
2
|
|
|
3
|
+
## 0.6.0: Runtime hardening
|
|
4
|
+
|
|
5
|
+
Configure global context/skills at boot, before constructing agents; defaults are now
|
|
6
|
+
snapshotted. Use agent-scoped `context_contributors:` and run-scoped `context:` for requests.
|
|
7
|
+
Specialist factories disable global inheritance and validate read-only skill tools.
|
|
8
|
+
Skill-owned tools activate only after successful loading in the current turn.
|
|
9
|
+
|
|
10
|
+
Configure `TurnKit.authorization_policy` and pass authenticated `principal:` values for
|
|
11
|
+
multi-user applications. The default remains trusted application code. Submitted cancellation
|
|
12
|
+
is terminal and fenced, with explicit `descendants: :retain` or `:cascade` semantics.
|
|
13
|
+
|
|
14
|
+
PostgreSQL is required for ActiveRecord wait-graph locking. Custom stores must implement
|
|
15
|
+
atomic graph operations and bounded actionable queries. Earlier durable-schema adopters
|
|
16
|
+
need the two indexes in [runtime hardening](docs/runtime-hardening.md); new install/upgrade
|
|
17
|
+
migrations already contain them. That guide also documents stable tool idempotency keys,
|
|
18
|
+
opt-in replay safety and the limits of external-effect recovery.
|
|
19
|
+
|
|
20
|
+
Delivery key reuse with different routing or payload now raises `ToolError` instead of
|
|
21
|
+
returning the original message. Implicit subagent joins honor `:wait` policy. Cycle
|
|
22
|
+
checks conservatively include all unfinished turns in a conversation's serial lane.
|
|
23
|
+
Custom stores also need `list_stale_inline_turns(before:, limit:)`; public inline
|
|
24
|
+
reconciliation now drains bounded batches rather than all history in one call.
|
|
25
|
+
|
|
26
|
+
## 0.6.0: Durable background execution
|
|
27
|
+
|
|
28
|
+
Stop existing workers before upgrading: older workers do not respect claim
|
|
29
|
+
fencing. Generate and apply the additive migration, then restart the application
|
|
30
|
+
and workers together:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
bin/rails generate turnkit:upgrade
|
|
34
|
+
bin/rails db:migrate
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use `--table-prefix your_prefix` if your installation uses custom table names.
|
|
38
|
+
The generator adds delivery/wait models and a reversible migration; it does not
|
|
39
|
+
replace existing models or delete conversation history. For custom model names,
|
|
40
|
+
pass `delivery_class:` and `wait_class:` to `ActiveRecordStore` alongside the
|
|
41
|
+
existing model-class options.
|
|
42
|
+
|
|
43
|
+
Background execution requires Active Job 7.2+ with a persistent queue adapter,
|
|
44
|
+
agent registration at boot in every worker, and a recurring
|
|
45
|
+
`TurnKit::ReconcileJob`. See [background execution](README.md#background-execution-and-agent-messaging)
|
|
46
|
+
for examples and operational semantics. `async: true` does not enqueue work.
|
|
47
|
+
|
|
48
|
+
Custom stores need reentrant, rollback-capable `atomic(conversation_id)` with
|
|
49
|
+
cross-process locking for durable use, submitted-turn queries, delivery CRUD
|
|
50
|
+
with a unique idempotency key, and idempotent wait relations. Turn statuses now
|
|
51
|
+
include `waiting`; turn records carry `submitted_at` and `claim_token`.
|
|
52
|
+
The base store implements inline stale reconciliation using these primitives.
|
|
53
|
+
|
|
54
|
+
Do not rely on a late worker overwriting a reconciled stale turn. Its claim is
|
|
55
|
+
now revoked. Execution-owned writes must go through `context.turn.store`, not a
|
|
56
|
+
global/raw store. Background limits are rooted in persisted run configuration;
|
|
57
|
+
timeout includes queue and wait time after submission. Inline use needs no job
|
|
58
|
+
backend, but ActiveRecord schemas and custom store contracts still need updating.
|
|
59
|
+
|
|
3
60
|
## 0.4.2
|
|
4
61
|
|
|
5
62
|
TurnKit 0.4.2 is a pre-1.0 surface cleanup. Update these call sites before upgrading:
|
|
@@ -31,6 +31,8 @@ class CreateTurnkitTables < ActiveRecord::Migration[7.1]
|
|
|
31
31
|
t.json :error
|
|
32
32
|
t.text :output_text
|
|
33
33
|
t.json :output_data
|
|
34
|
+
t.datetime :submitted_at
|
|
35
|
+
t.string :claim_token
|
|
34
36
|
t.datetime :started_at
|
|
35
37
|
t.datetime :heartbeat_at
|
|
36
38
|
t.datetime :completed_at
|
|
@@ -40,6 +42,34 @@ class CreateTurnkitTables < ActiveRecord::Migration[7.1]
|
|
|
40
42
|
t.index :conversation_uid
|
|
41
43
|
t.index :root_turn_uid
|
|
42
44
|
t.index [ :status, :heartbeat_at ]
|
|
45
|
+
t.index [ :status, :submitted_at, :updated_at ], name: "index_<%= table_prefix %>_turns_on_maintenance"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
create_table :<%= table_prefix %>_deliveries do |t|
|
|
49
|
+
t.string :uid, null: false
|
|
50
|
+
t.string :source_conversation_uid, null: false
|
|
51
|
+
t.string :destination_conversation_uid, null: false
|
|
52
|
+
t.string :source_turn_uid
|
|
53
|
+
t.string :key, null: false
|
|
54
|
+
t.json :payload, null: false, default: {}
|
|
55
|
+
t.string :message_uid
|
|
56
|
+
t.datetime :delivered_at
|
|
57
|
+
t.timestamps
|
|
58
|
+
|
|
59
|
+
t.index :uid, unique: true
|
|
60
|
+
t.index :key, unique: true
|
|
61
|
+
t.index [ :source_conversation_uid, :created_at ]
|
|
62
|
+
t.index [ :destination_conversation_uid, :delivered_at ]
|
|
63
|
+
t.index [ :delivered_at, :created_at ], name: "index_<%= table_prefix %>_deliveries_on_pending"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
create_table :<%= table_prefix %>_waits do |t|
|
|
67
|
+
t.string :turn_uid, null: false
|
|
68
|
+
t.string :target_turn_uid, null: false
|
|
69
|
+
t.timestamps
|
|
70
|
+
|
|
71
|
+
t.index [ :turn_uid, :target_turn_uid ], unique: true
|
|
72
|
+
t.index :target_turn_uid
|
|
43
73
|
end
|
|
44
74
|
|
|
45
75
|
create_table :<%= table_prefix %>_messages do |t|
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
# TurnKit::ActiveRecordStore.new(conversation_class: "My::Conversation", ...)
|
|
9
9
|
TurnKit.store = TurnKit::ActiveRecordStore.new
|
|
10
10
|
|
|
11
|
+
# Background work: require "turnkit/job", configure a persistent Active Job
|
|
12
|
+
# backend, register agents with TurnKit.register at boot in every process,
|
|
13
|
+
# and schedule TurnKit::ReconcileJob in your recurring-job facility.
|
|
14
|
+
# async: true only prepares a run; perform_later submits it.
|
|
15
|
+
|
|
11
16
|
# TurnKit.default_model = "claude-sonnet-4-5"
|
|
12
17
|
# TurnKit.max_iterations = 25
|
|
13
18
|
# TurnKit.timeout = 300
|
|
@@ -23,6 +23,8 @@ module TurnKit
|
|
|
23
23
|
template "turn.rb", "app/models/turnkit/turn.rb"
|
|
24
24
|
template "message.rb", "app/models/turnkit/message.rb"
|
|
25
25
|
template "tool_execution.rb", "app/models/turnkit/tool_execution.rb"
|
|
26
|
+
template "delivery.rb", "app/models/turnkit/delivery.rb"
|
|
27
|
+
template "wait.rb", "app/models/turnkit/wait.rb"
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def create_ai_directories
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddTurnkitDurableOrchestration < ActiveRecord::Migration[7.1]
|
|
4
|
+
def change
|
|
5
|
+
add_column :<%= table_prefix %>_turns, :submitted_at, :datetime
|
|
6
|
+
add_column :<%= table_prefix %>_turns, :claim_token, :string
|
|
7
|
+
add_index :<%= table_prefix %>_turns, [ :status, :submitted_at, :updated_at ], name: "index_<%= table_prefix %>_turns_on_maintenance"
|
|
8
|
+
|
|
9
|
+
create_table :<%= table_prefix %>_deliveries do |t|
|
|
10
|
+
t.string :uid, null: false
|
|
11
|
+
t.string :source_conversation_uid, null: false
|
|
12
|
+
t.string :destination_conversation_uid, null: false
|
|
13
|
+
t.string :source_turn_uid
|
|
14
|
+
t.string :key, null: false
|
|
15
|
+
t.json :payload, null: false, default: {}
|
|
16
|
+
t.string :message_uid
|
|
17
|
+
t.datetime :delivered_at
|
|
18
|
+
t.timestamps
|
|
19
|
+
|
|
20
|
+
t.index :uid, unique: true
|
|
21
|
+
t.index :key, unique: true
|
|
22
|
+
t.index [ :source_conversation_uid, :created_at ]
|
|
23
|
+
t.index [ :destination_conversation_uid, :delivered_at ]
|
|
24
|
+
t.index [ :delivered_at, :created_at ], name: "index_<%= table_prefix %>_deliveries_on_pending"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
create_table :<%= table_prefix %>_waits do |t|
|
|
28
|
+
t.string :turn_uid, null: false
|
|
29
|
+
t.string :target_turn_uid, null: false
|
|
30
|
+
t.timestamps
|
|
31
|
+
|
|
32
|
+
t.index [ :turn_uid, :target_turn_uid ], unique: true
|
|
33
|
+
t.index :target_turn_uid
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/migration"
|
|
5
|
+
|
|
6
|
+
module TurnKit
|
|
7
|
+
module Generators
|
|
8
|
+
class UpgradeGenerator < Rails::Generators::Base
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
|
|
11
|
+
namespace "turnkit:upgrade"
|
|
12
|
+
source_root File.expand_path("upgrade/templates", __dir__)
|
|
13
|
+
source_paths << File.expand_path("install/templates", __dir__)
|
|
14
|
+
|
|
15
|
+
class_option :table_prefix, type: :string, default: "turnkit", desc: "Database table prefix."
|
|
16
|
+
|
|
17
|
+
def copy_models
|
|
18
|
+
template "delivery.rb", "app/models/turnkit/delivery.rb"
|
|
19
|
+
template "wait.rb", "app/models/turnkit/wait.rb"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def copy_migration
|
|
23
|
+
migration_template "add_turnkit_durable_orchestration.rb", "db/migrate/add_turnkit_durable_orchestration.rb"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.next_migration_number(dirname)
|
|
27
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
def table_prefix = options[:table_prefix]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|