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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/README.md +198 -5
  4. data/UPGRADE.md +57 -0
  5. data/lib/generators/turnkit/install/templates/create_turnkit_tables.rb +30 -0
  6. data/lib/generators/turnkit/install/templates/delivery.rb +7 -0
  7. data/lib/generators/turnkit/install/templates/initializer.rb +5 -0
  8. data/lib/generators/turnkit/install/templates/wait.rb +7 -0
  9. data/lib/generators/turnkit/install_generator.rb +2 -0
  10. data/lib/generators/turnkit/upgrade/templates/add_turnkit_durable_orchestration.rb +36 -0
  11. data/lib/generators/turnkit/upgrade_generator.rb +34 -0
  12. data/lib/turnkit/active_record_store.rb +124 -14
  13. data/lib/turnkit/adapters/ruby_llm.rb +107 -21
  14. data/lib/turnkit/agent.rb +26 -20
  15. data/lib/turnkit/authorization.rb +17 -0
  16. data/lib/turnkit/background.rb +281 -0
  17. data/lib/turnkit/budget.rb +4 -3
  18. data/lib/turnkit/client.rb +3 -1
  19. data/lib/turnkit/conversation.rb +54 -1
  20. data/lib/turnkit/coordination_tools.rb +67 -0
  21. data/lib/turnkit/cost.rb +7 -7
  22. data/lib/turnkit/error.rb +3 -0
  23. data/lib/turnkit/execution_store.rb +30 -0
  24. data/lib/turnkit/id.rb +1 -0
  25. data/lib/turnkit/image_tool.rb +10 -0
  26. data/lib/turnkit/job.rb +21 -0
  27. data/lib/turnkit/memory_store.rb +113 -20
  28. data/lib/turnkit/message_projection.rb +4 -1
  29. data/lib/turnkit/reconciliation.rb +13 -10
  30. data/lib/turnkit/record.rb +36 -3
  31. data/lib/turnkit/run.rb +28 -0
  32. data/lib/turnkit/skill.rb +5 -4
  33. data/lib/turnkit/specialists.rb +254 -0
  34. data/lib/turnkit/store.rb +38 -9
  35. data/lib/turnkit/sub_agent_tool.rb +23 -7
  36. data/lib/turnkit/system_prompt.rb +7 -7
  37. data/lib/turnkit/tool.rb +11 -0
  38. data/lib/turnkit/tool_runner.rb +109 -45
  39. data/lib/turnkit/turn.rb +238 -61
  40. data/lib/turnkit/turn_controls.rb +136 -0
  41. data/lib/turnkit/version.rb +1 -1
  42. data/lib/turnkit.rb +31 -0
  43. metadata +16 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3db08f0653a468f22daa74070c7b1a290f02b44beaf6d35c950f3d36d86dc224
4
- data.tar.gz: 18d3b2df593865ba53e0863d0e2b061f8744bdbbf78409b40d525c3ba47b5070
3
+ metadata.gz: 2a276982d34a5c7685f9512e29f1843942166c67bdb59c94ebc38b727213ffe3
4
+ data.tar.gz: 8ad4edfdc514c24a918441734c4c10294e7d1c45b08b7594f19db7e3294edd61
5
5
  SHA512:
6
- metadata.gz: dd2216c9e2275dd737d6e1cf7dadd97053ca5afb6992939791669f29e047f3e44eb45f492a09b4494222117428baf07ffd9aa60352f9363b0a8b3eedb9c6ab54
7
- data.tar.gz: 3b8f1a45bc98506dc09a9a6703aacf1f614fb7649c3c64942c98e902aff1db9232f986aad33c077be51fec1623d82abf4506d32698b34e3079ff775b9a1eb38b
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
- Reconciliation atomically marks pending and running turns whose last heartbeat
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 does not cancel or reassign the underlying process. If the
803
- original worker is still alive (its heartbeats were merely late), it continues
804
- over the synthetic interrupted result and later replaces `stale` with its
805
- actual `completed` or `failed` outcome treat `turn.stale` as provisional.
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|
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Turnkit
4
+ class Delivery < ApplicationRecord
5
+ self.table_name = "<%= table_prefix %>_deliveries"
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Turnkit
4
+ class Wait < ApplicationRecord
5
+ self.table_name = "<%= table_prefix %>_waits"
6
+ end
7
+ end
@@ -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