turnkit 0.5.0 → 0.6.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 +23 -0
- data/README.md +187 -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 +14 -0
- 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/conversation.rb +23 -1
- data/lib/turnkit/coordination_tools.rb +61 -0
- 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 +106 -15
- data/lib/turnkit/reconciliation.rb +13 -10
- data/lib/turnkit/record.rb +36 -3
- data/lib/turnkit/run.rb +15 -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 +101 -45
- data/lib/turnkit/turn.rb +188 -57
- data/lib/turnkit/version.rb +1 -1
- data/lib/turnkit.rb +30 -0
- metadata +15 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8ca23035231ccf58df8389aa2e845bb3e8874b1a23d33a8e8ffb8b793c533fe
|
|
4
|
+
data.tar.gz: 1f1bafaf7eb330caa119504846b9960989f116780b5ea364fdbe4e7c47869db2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dbe5569a73d9118776e207567a515642d9fc59923de34b5084499623f36798e9c826832bf35d7f8105b9272252edb6da244faf896f9509e59e571d9db7f9022f
|
|
7
|
+
data.tar.gz: 31b2decb3c2336eafd7f943eaff23cf149b88c67067b75c785f9cd71960349a9f89a20da5dfd1d415fb1f7e083f9e061514f359568c4a5b92fc44426a8e05fcd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 - 2026-09-06
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Independent background submission through Active Job, registered agent reconstruction, durable messages/completion callbacks, inbox/outbox queries, and worker-free joins.
|
|
8
|
+
- Parallel background sub-agents with persisted parent/child lineage and ordered results; opt-in launch, send-message, and wait tools.
|
|
9
|
+
- Persisted execution phases, fenced claims, root-wide budget reservations, and recovery of missed enqueues and abandoned workers without replaying unknown external effects.
|
|
10
|
+
- PostgreSQL concurrency/process-death tests and a reversible `turnkit:upgrade` migration generator.
|
|
11
|
+
- Agent-scoped context and skills, extensible specialist factories, authorization hooks, and fenced cancellation with explicit descendant handling.
|
|
12
|
+
- Live recipe, deep-research and rare-earth policy examples, plus prepared orb dependencies and OIDC-based RubyGems releases.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Reject conflicting delivery-key reuse without disclosing another conversation's payload; authorize implicit joins and detect conversation-lane wait cycles.
|
|
17
|
+
- Bound inline reconciliation queries and preserve replay-safe tool keys and skill activation across recovery.
|
|
18
|
+
- Forward image references and normalize RubyLLM provider failures as terminal model errors instead of repeatedly retrying permanent failures.
|
|
19
|
+
|
|
20
|
+
### Breaking
|
|
21
|
+
|
|
22
|
+
- Existing ActiveRecord installations must run `turnkit:upgrade` and migrate; turn records gain `submitted_at` and `claim_token`, plus delivery and wait tables.
|
|
23
|
+
- Custom stores must implement the new transactional coordination contract. Reconciled inline workers lose write authority; `stale` is no longer provisional.
|
|
24
|
+
- Background execution requires Active Job 7.2+ and a persistent queue backend. `async: true` remains previewable pending work; call `perform_later` to submit.
|
|
25
|
+
|
|
3
26
|
## 0.5.0 - 2026-07-22
|
|
4
27
|
|
|
5
28
|
### Breaking
|
data/README.md
CHANGED
|
@@ -548,6 +548,21 @@ end
|
|
|
548
548
|
|
|
549
549
|
Require an image before completion with `TurnKit::OutputPolicy.require_image`.
|
|
550
550
|
|
|
551
|
+
For editing or style references, override `input_images(**arguments)` and
|
|
552
|
+
`mask(**arguments)` in your `ImageTool` subclass, just like `prompt` and
|
|
553
|
+
`metadata`. Both default to `nil` and are forwarded to the image provider.
|
|
554
|
+
Declare the corresponding tool parameters yourself; accepted sources, reference
|
|
555
|
+
limits, and mask support depend on the provider. Local paths must exist on the
|
|
556
|
+
executing worker. Generate only when the application/user requested it; use
|
|
557
|
+
`ViewMediaTool` for inspection, not image generation.
|
|
558
|
+
|
|
559
|
+
Image results retain URL or base64 bytes, including in tool results. Provider
|
|
560
|
+
URLs may expire: use the event hook above to copy images into application-owned
|
|
561
|
+
storage for durable user-facing links. TurnKit does not host attachments or
|
|
562
|
+
automatically make generated images visually available to the next text model
|
|
563
|
+
call. Base64 tool results also consume prompt space; for large images, prefer a
|
|
564
|
+
custom tool that stores the artifact and returns its application-owned URL.
|
|
565
|
+
|
|
551
566
|
### Media analysis
|
|
552
567
|
|
|
553
568
|
Analyze existing images, PDFs, audio, or video inside a durable turn with
|
|
@@ -727,6 +742,161 @@ puts turn.output_text
|
|
|
727
742
|
|
|
728
743
|
Use sub-agents for isolated child conversations.
|
|
729
744
|
|
|
745
|
+
#### Oracle- and Librarian-style specialists
|
|
746
|
+
|
|
747
|
+
Use ordinary agents, not a second specialist runtime. Give each specialist its
|
|
748
|
+
own model, instructions, description (when the parent should invoke it), and
|
|
749
|
+
explicit tool list, then include it in the parent's `sub_agents`:
|
|
750
|
+
|
|
751
|
+
- **Oracle:** instructions for a specific unresolved decision, the evidence
|
|
752
|
+
already checked, constraints, and a recommendation. Supply only scoped
|
|
753
|
+
read/search tools; the parent owns implementation and verification.
|
|
754
|
+
- **Librarian:** instructions for external repository understanding and a full
|
|
755
|
+
evidence-backed report. Supply authenticated repository read/search, history,
|
|
756
|
+
diff, and issue-reading tools appropriate to the application. A role name does
|
|
757
|
+
not provide GitHub access. Exclude repository and issue mutation tools.
|
|
758
|
+
- **Painter:** use `ImageTool` for an actual image-provider call rather than a
|
|
759
|
+
text agent pretending to generate an image.
|
|
760
|
+
|
|
761
|
+
Children receive the explicit `task`, not the parent's conversation history or
|
|
762
|
+
tool list. Their complete final text, structured `output_data`, status/error,
|
|
763
|
+
and conversation/turn IDs return through the parent's matching tool result;
|
|
764
|
+
intermediate child messages remain in the child conversation. Inline calls
|
|
765
|
+
block; background calls use the same durable fan-out/join machinery below.
|
|
766
|
+
|
|
767
|
+
This is a context boundary, not an OS or credential sandbox. Global context
|
|
768
|
+
contributors and custom clients can supply additional context/capabilities;
|
|
769
|
+
scope those deliberately. Enforce read-only access in the supplied tools and
|
|
770
|
+
credentials, not merely in prompts. Likewise, enforce user approval for image
|
|
771
|
+
generation in application tool exposure/authorization when it is a requirement.
|
|
772
|
+
TurnKit does not ship Amp's private tools, repository service, or attachment host.
|
|
773
|
+
|
|
774
|
+
### Background execution and agent messaging
|
|
775
|
+
|
|
776
|
+
Durable background execution uses Active Record/Active Job 7.2+, `ActiveRecordStore`, and a
|
|
777
|
+
persistent backend such as Solid Queue or Sidekiq. Configure the backend in your
|
|
778
|
+
application; TurnKit does not start worker processes. The database owns pending
|
|
779
|
+
work, while jobs are wake-up signals. `MemoryStore` is for inline use and tests.
|
|
780
|
+
|
|
781
|
+
Existing installations must run `bin/rails generate turnkit:upgrade` and migrate
|
|
782
|
+
before using this version. New installations use `turnkit:install` as usual.
|
|
783
|
+
|
|
784
|
+
Register agent definitions at application boot **in every web and worker
|
|
785
|
+
process**. Registration also registers configured sub-agents. Names must identify
|
|
786
|
+
the same agent configuration in every process; use Rails `to_prepare` when
|
|
787
|
+
definitions are reloadable. TurnKit persists IDs, model, options, and lineage,
|
|
788
|
+
not Ruby clients, tools, closures, or executable configuration.
|
|
789
|
+
Subject `to_prompt` data is snapshotted in conversation metadata for worker
|
|
790
|
+
reconstruction. Use live-context contributors with application-owned identifiers
|
|
791
|
+
when background turns need freshly loaded domain objects rather than that snapshot.
|
|
792
|
+
|
|
793
|
+
```ruby
|
|
794
|
+
require "turnkit/job"
|
|
795
|
+
|
|
796
|
+
writer = TurnKit::Agent.new(name: "writer", instructions: "Draft concise copy.")
|
|
797
|
+
editor = TurnKit.register(TurnKit::Agent.new(
|
|
798
|
+
name: "editor",
|
|
799
|
+
sub_agents: [writer],
|
|
800
|
+
tools: [TurnKit::LaunchAgentTool, TurnKit::SendMessageTool, TurnKit::WaitTool]
|
|
801
|
+
))
|
|
802
|
+
|
|
803
|
+
run = editor.run("Research and draft the announcement.", async: true)
|
|
804
|
+
run.perform_later
|
|
805
|
+
|
|
806
|
+
# Another process can reconstruct records using the registered definitions.
|
|
807
|
+
run = TurnKit::Run.new(TurnKit.load_turn(run.id))
|
|
808
|
+
run.reload.status
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
`async: true` still means **prepare pending work**, not enqueue it. This preserves
|
|
812
|
+
preview-only use. `perform_later` persists submission before enqueueing and
|
|
813
|
+
returns immediately. Background work uses the application-wide `TurnKit.store`.
|
|
814
|
+
Worker clients and tools must be safe for the concurrency configured in your job
|
|
815
|
+
backend. Do not launch unjoined Ruby threads from tools.
|
|
816
|
+
|
|
817
|
+
In a background turn, a group of consecutive sub-agent tool calls launches
|
|
818
|
+
independent child jobs. The parent becomes `waiting` and releases its worker;
|
|
819
|
+
once all children finish, it resumes with tool results in call order. A failed
|
|
820
|
+
child returns a structured failure, not an implicit parent failure. Ordinary
|
|
821
|
+
tools remain sequential, and terminal tools prevent later calls from running.
|
|
822
|
+
Inline turns use the same engine but execute sub-agents synchronously.
|
|
823
|
+
|
|
824
|
+
`LaunchAgentTool` starts a configured sub-agent without waiting. Its optional
|
|
825
|
+
`callback: true` requests a completion message in the launching conversation.
|
|
826
|
+
`WaitTool` joins submitted turns without holding a worker. These coordination
|
|
827
|
+
tools are opt-in; the application owns authorization and should expose only
|
|
828
|
+
the destinations/actions appropriate for that agent.
|
|
829
|
+
|
|
830
|
+
Application code can send messages and request completion callbacks too:
|
|
831
|
+
|
|
832
|
+
```ruby
|
|
833
|
+
parent = TurnKit.load_conversation(parent_conversation_id)
|
|
834
|
+
child = writer.run("Investigate the issue.", async: true)
|
|
835
|
+
child.perform_later(callback: parent)
|
|
836
|
+
|
|
837
|
+
parent.send_message(other_conversation_id, "Please check the latest results.",
|
|
838
|
+
key: "request-123:check-results")
|
|
839
|
+
parent.inbox
|
|
840
|
+
parent.outbox
|
|
841
|
+
|
|
842
|
+
# Attach dependencies before enqueueing, avoiding a race with an eager worker.
|
|
843
|
+
summary = editor.run("Summarize the joined results.", async: true)
|
|
844
|
+
summary.wait_for(child).perform_later
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
Delivery keys are globally unique idempotency keys. Retrying the same key returns
|
|
848
|
+
the original delivery; it does not replace its payload. Inbox, outbox, and
|
|
849
|
+
completion callbacks use the same persisted delivery rows. Delivery appends one
|
|
850
|
+
message and records the need for a continuation transactionally. Messages to a
|
|
851
|
+
running or waiting conversation do not change its current input snapshot: they
|
|
852
|
+
are consumed by a later turn. Background turns in one conversation execute
|
|
853
|
+
serially. Use `TurnKit.load_conversation` to address a conversation independently
|
|
854
|
+
of a worker's fenced execution context.
|
|
855
|
+
|
|
856
|
+
Application-level `wait_for` supplies joined results as turn-local input before
|
|
857
|
+
the first model call. Attach waits only to pending turns. Wait targets must be
|
|
858
|
+
submitted or finished, and must not form dependency cycles. Waiting for unfinished
|
|
859
|
+
work in the same conversation is rejected because execution there is serial.
|
|
860
|
+
|
|
861
|
+
#### Recovery and operational requirements
|
|
862
|
+
|
|
863
|
+
Schedule `TurnKit::ReconcileJob` using your backend's recurring-job facility
|
|
864
|
+
(for example every minute). This repairs missed enqueues, undelivered messages,
|
|
865
|
+
ready joins, and abandoned workers. `TurnKit.reconcile_stale!` also recovers
|
|
866
|
+
submitted work while retaining the inline stale-turn behavior described below.
|
|
867
|
+
|
|
868
|
+
- Claims are fenced: after revocation or completion, an old execution cannot
|
|
869
|
+
persist another model response, tool result, heartbeat, or completion.
|
|
870
|
+
- Recovery reuses committed model responses and tool results. An interrupted
|
|
871
|
+
model request may be reissued and charged again by the provider.
|
|
872
|
+
- Started external tool calls without results become `interrupted`, with unknown
|
|
873
|
+
outcomes. They are **not replayed**. The built-in durable coordination tools can
|
|
874
|
+
resume using their existing child IDs/delivery keys.
|
|
875
|
+
- Waiting is not a stale heartbeat. Root timeout still applies, including queue
|
|
876
|
+
and wait time from initial submission. It is a cooperative limit, not a hard
|
|
877
|
+
process kill. Use provider/tool timeouts for blocking external calls.
|
|
878
|
+
- Root iteration and tool-count limits are reserved under a database lock across
|
|
879
|
+
parallel children. Spend limits use observed cost; already-running requests can
|
|
880
|
+
overshoot them.
|
|
881
|
+
- `on_event` remains an inline observation hook, not a durable callback. Background
|
|
882
|
+
infrastructure exceptions reach the job backend rather than being converted
|
|
883
|
+
into success. Monitor failed jobs and run reconciliation.
|
|
884
|
+
- TurnKit fences its own persistence, not arbitrary external side effects. Tools
|
|
885
|
+
should use `context.turn.store` for execution-owned TurnKit writes. External
|
|
886
|
+
services need their own idempotency keys where appropriate.
|
|
887
|
+
|
|
888
|
+
The PostgreSQL integration suite exercises real row locks and worker-process
|
|
889
|
+
death. Run it against a dedicated test database:
|
|
890
|
+
|
|
891
|
+
```sh
|
|
892
|
+
TURNKIT_TEST_DATABASE_URL=postgresql:///turnkit_test bundle exec rake test
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
It creates/rebuilds only `turnkit_test_*` and `turnkit_upgrade_test_*` test tables.
|
|
896
|
+
Without the variable, database tests explicitly skip. Custom stores must implement
|
|
897
|
+
the transactional `Store` contract, including reentrant `atomic`, deliveries,
|
|
898
|
+
waits, and submitted-turn queries; a mutex alone is not cross-process durability.
|
|
899
|
+
|
|
730
900
|
### Context Compaction
|
|
731
901
|
|
|
732
902
|
Disable compaction:
|
|
@@ -791,7 +961,8 @@ process). Run this periodically:
|
|
|
791
961
|
TurnKit.reconcile_stale!
|
|
792
962
|
```
|
|
793
963
|
|
|
794
|
-
|
|
964
|
+
For unsubmitted inline work, reconciliation atomically marks pending and running
|
|
965
|
+
turns whose last heartbeat
|
|
795
966
|
is older than `TurnKit.timeout` as `stale`, so it never overwrites a turn that
|
|
796
967
|
was concurrently claimed, heartbeated, or completed. Each stale turn's
|
|
797
968
|
unfinished tool executions become `interrupted`, and a synthetic error tool
|
|
@@ -799,10 +970,10 @@ result is appended for any unresolved tool call so the conversation can be
|
|
|
799
970
|
continued. TurnKit never reruns an interrupted tool — whether its side effect
|
|
800
971
|
happened is unknown, so the continued model is told not to assume either way.
|
|
801
972
|
|
|
802
|
-
Reconciliation
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
973
|
+
Reconciliation revokes the original worker's commit authority. A late worker
|
|
974
|
+
cannot replace `stale` with its own outcome. It does not kill the underlying
|
|
975
|
+
process or undo external side effects. Continue an inline stale conversation
|
|
976
|
+
with a new turn; submitted background work is resumed automatically.
|
|
806
977
|
|
|
807
978
|
## Options
|
|
808
979
|
|
|
@@ -877,6 +1048,11 @@ agent = TurnKit::Agent.new(
|
|
|
877
1048
|
|
|
878
1049
|
## Upgrading
|
|
879
1050
|
|
|
1051
|
+
See [runtime hardening](docs/runtime-hardening.md) for scoped context, authorization,
|
|
1052
|
+
cancellation, cycle-safe waits, replay-safe effects and bounded maintenance, and
|
|
1053
|
+
[specialists and skills](docs/specialists.md) for the extensible Oracle, Librarian
|
|
1054
|
+
and Painter factories and skill-owned tools.
|
|
1055
|
+
|
|
880
1056
|
See the [0.4.2 Upgrade Guide](UPGRADE_TO_0_4_2.md) for the full API migration checklist.
|
|
881
1057
|
|
|
882
1058
|
Rails installs from older versions may need `output_data` for structured output,
|
|
@@ -906,6 +1082,12 @@ find lib test examples -type f -name '*.rb' -print0 | xargs -0 ruby -c
|
|
|
906
1082
|
|
|
907
1083
|
Open a pull request.
|
|
908
1084
|
|
|
1085
|
+
## Releasing
|
|
1086
|
+
|
|
1087
|
+
Maintainers: follow [the release guide](docs/releasing.md) for RubyGems trusted
|
|
1088
|
+
publishing setup, version and lockfile updates, and the explicit tag-push procedure.
|
|
1089
|
+
Pushing a `v*` tag triggers tests and publication through the `release` environment.
|
|
1090
|
+
|
|
909
1091
|
## License
|
|
910
1092
|
|
|
911
1093
|
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
|