durable_agent 0.1.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 (42) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +29 -0
  3. data/DESIGN.md +614 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +23 -0
  6. data/Rakefile +46 -0
  7. data/app/controllers/durable_agent/application_controller.rb +15 -0
  8. data/app/controllers/durable_agent/events_controller.rb +28 -0
  9. data/app/controllers/durable_agent/tools_controller.rb +33 -0
  10. data/app/models/concerns/durable_agent/harness_record.rb +11 -0
  11. data/app/models/durable_agent/application_record.rb +5 -0
  12. data/app/models/durable_agent/connection.rb +66 -0
  13. data/app/models/durable_agent/conversation.rb +107 -0
  14. data/app/models/durable_agent/document/revision.rb +16 -0
  15. data/app/models/durable_agent/document.rb +27 -0
  16. data/app/models/durable_agent/entry.rb +56 -0
  17. data/app/models/durable_agent/report.rb +14 -0
  18. data/app/models/durable_agent/submission.rb +57 -0
  19. data/app/models/durable_agent/task.rb +17 -0
  20. data/config/routes.rb +5 -0
  21. data/docs/guide.md +421 -0
  22. data/lib/durable_agent/account.rb +29 -0
  23. data/lib/durable_agent/client.rb +121 -0
  24. data/lib/durable_agent/configuration.rb +81 -0
  25. data/lib/durable_agent/current.rb +6 -0
  26. data/lib/durable_agent/engine.rb +7 -0
  27. data/lib/durable_agent/indexed_string.rb +14 -0
  28. data/lib/durable_agent/message_text.rb +23 -0
  29. data/lib/durable_agent/sidecar.rb +97 -0
  30. data/lib/durable_agent/test_helper.rb +110 -0
  31. data/lib/durable_agent/tool.rb +91 -0
  32. data/lib/durable_agent/version.rb +3 -0
  33. data/lib/durable_agent.rb +67 -0
  34. data/lib/generators/durable_agent/install/install_generator.rb +75 -0
  35. data/lib/generators/durable_agent/install/templates/create_durable_agent_tables.rb.tt +120 -0
  36. data/lib/generators/durable_agent/install/templates/durable_agent.rb.tt +36 -0
  37. data/lib/tasks/durable_agent_tasks.rake +6 -0
  38. data/sidecar/dist/sidecar.js +76417 -0
  39. data/sidecar/package.json +32 -0
  40. data/sidecar/sql/schema.postgres.sql +120 -0
  41. data/sidecar/sql/schema.sqlite.sql +120 -0
  42. metadata +115 -0
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "durable-agent-sidecar",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "durable_agent sidecar: the pi-durable runtime, storing its state in the Rails app's database (Postgres or SQLite).",
7
+ "engines": {
8
+ "node": ">=22.19.0"
9
+ },
10
+ "scripts": {
11
+ "dev": "node --watch --env-file-if-exists=.env src/main.ts",
12
+ "start": "node --env-file-if-exists=.env src/main.ts",
13
+ "build": "esbuild src/main.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/sidecar.js --legal-comments=none \"--banner:js=import { createRequire as __durableAgentCreateRequire } from 'node:module'; const require = __durableAgentCreateRequire(import.meta.url);\"",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "node --test --test-concurrency=1 test/",
16
+ "test:postgres": "DURABLE_AGENT_TEST_DATABASE_URL=${DURABLE_AGENT_TEST_DATABASE_URL:-postgres://localhost/durable_agent_sidecar_test} node --test --test-concurrency=1 test/"
17
+ },
18
+ "dependencies": {
19
+ "@earendil-works/chord": "1.3.0",
20
+ "@earendil-works/pi-ai": "1.3.0",
21
+ "@earendil-works/pi-durable": "1.3.0",
22
+ "@hono/node-server": "^1.14.0",
23
+ "hono": "^4.7.0",
24
+ "pg": "^8.13.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.15.0",
28
+ "@types/pg": "^8.11.0",
29
+ "esbuild": "0.28.2",
30
+ "typescript": "^5.8.0"
31
+ }
32
+ }
@@ -0,0 +1,120 @@
1
+ -- durable_agent schema v2 for Postgres: a hand rendering of the gem's install migration
2
+ -- (lib/generators/durable_agent/install/templates/create_durable_agent_tables.rb.tt) for the sidecar tests.
3
+ -- Rails owns the real schema; test/lib/durable_agent/sidecar_schema_test.rb runs both against fresh databases and
4
+ -- compares tables, column types, nullability and defaults, and index definitions.
5
+ -- The first nine tables are pi-durable 1.3.0's schema (version 1) under the durable_agent_ prefix, minus STRICT
6
+ -- and the json_valid checks. No rows: the sidecar seeds the singletons at boot.
7
+
8
+ CREATE TABLE IF NOT EXISTS durable_agent_durable_schema (
9
+ singleton bigserial PRIMARY KEY,
10
+ version integer NOT NULL
11
+ );
12
+
13
+ CREATE TABLE IF NOT EXISTS durable_agent_durable_metadata (
14
+ singleton bigserial PRIMARY KEY,
15
+ next_id text NOT NULL,
16
+ next_seq bigint NOT NULL
17
+ );
18
+
19
+ CREATE TABLE IF NOT EXISTS durable_agent_record_ids (
20
+ id bigserial PRIMARY KEY,
21
+ record_type text NOT NULL
22
+ );
23
+
24
+ CREATE TABLE IF NOT EXISTS durable_agent_conversations (
25
+ id bigserial PRIMARY KEY,
26
+ owner_conversation_id bigint,
27
+ owner_task_id bigint,
28
+ record text NOT NULL,
29
+ created_at timestamp(6) without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
30
+ );
31
+ CREATE INDEX IF NOT EXISTS durable_agent_conversations_by_owner_conversation ON durable_agent_conversations (owner_conversation_id, id);
32
+ CREATE INDEX IF NOT EXISTS durable_agent_conversations_by_owner_task ON durable_agent_conversations (owner_task_id, id);
33
+
34
+ CREATE TABLE IF NOT EXISTS durable_agent_entries (
35
+ id bigserial PRIMARY KEY,
36
+ conversation_id bigint NOT NULL,
37
+ head bigint,
38
+ commit_seq bigint NOT NULL,
39
+ record text NOT NULL,
40
+ created_at timestamp(6) without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
41
+ );
42
+ CREATE INDEX IF NOT EXISTS durable_agent_entries_by_conversation ON durable_agent_entries (conversation_id, id DESC);
43
+ CREATE INDEX IF NOT EXISTS durable_agent_entry_heads_by_conversation ON durable_agent_entries (conversation_id, id DESC) WHERE head IS NOT NULL;
44
+
45
+ CREATE TABLE IF NOT EXISTS durable_agent_tasks (
46
+ id bigserial PRIMARY KEY,
47
+ conversation_id bigint NOT NULL,
48
+ kind text NOT NULL,
49
+ status text NOT NULL,
50
+ abort_requested integer NOT NULL,
51
+ background integer NOT NULL,
52
+ record text NOT NULL
53
+ );
54
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_status ON durable_agent_tasks (status, id);
55
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_conversation ON durable_agent_tasks (conversation_id, id);
56
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_kind ON durable_agent_tasks (kind, id);
57
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_abort_requested ON durable_agent_tasks (abort_requested, id);
58
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_background ON durable_agent_tasks (background, id);
59
+
60
+ CREATE TABLE IF NOT EXISTS durable_agent_submissions (
61
+ id bigserial PRIMARY KEY,
62
+ conversation_id bigint NOT NULL,
63
+ request_id text,
64
+ status text NOT NULL,
65
+ record text NOT NULL,
66
+ created_at timestamp(6) without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
67
+ );
68
+ CREATE INDEX IF NOT EXISTS durable_agent_submissions_by_request ON durable_agent_submissions (conversation_id, request_id);
69
+ CREATE INDEX IF NOT EXISTS durable_agent_submissions_by_conversation ON durable_agent_submissions (conversation_id, id);
70
+ CREATE INDEX IF NOT EXISTS durable_agent_submissions_by_status ON durable_agent_submissions (status, id);
71
+
72
+ CREATE TABLE IF NOT EXISTS durable_agent_documents (
73
+ id bigserial PRIMARY KEY,
74
+ kind text NOT NULL,
75
+ family integer NOT NULL,
76
+ key_value text NOT NULL,
77
+ scope_kind text NOT NULL,
78
+ owner_id bigint NOT NULL,
79
+ created_at bigint NOT NULL,
80
+ retired_at bigint,
81
+ record text NOT NULL
82
+ );
83
+ CREATE INDEX IF NOT EXISTS durable_agent_documents_by_address ON durable_agent_documents (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at);
84
+ CREATE INDEX IF NOT EXISTS durable_agent_documents_by_scope ON durable_agent_documents (scope_kind, owner_id, id);
85
+ CREATE INDEX IF NOT EXISTS durable_agent_documents_by_scope_kind ON durable_agent_documents (scope_kind, owner_id, kind, id);
86
+
87
+ CREATE TABLE IF NOT EXISTS durable_agent_document_revisions (
88
+ document_id bigint NOT NULL,
89
+ seq bigint NOT NULL,
90
+ kind text NOT NULL,
91
+ version bigint NOT NULL,
92
+ content text NOT NULL,
93
+ PRIMARY KEY (document_id, seq)
94
+ );
95
+ CREATE INDEX IF NOT EXISTS durable_agent_document_revisions_by_kind ON durable_agent_document_revisions (document_id, kind, seq DESC);
96
+
97
+ CREATE TABLE IF NOT EXISTS durable_agent_reports (
98
+ id bigserial PRIMARY KEY,
99
+ type text NOT NULL,
100
+ conversation_id bigint,
101
+ task_id bigint,
102
+ task_kind text,
103
+ extension text,
104
+ message text NOT NULL,
105
+ info text NOT NULL,
106
+ created_at timestamp(6) without time zone NOT NULL
107
+ );
108
+ CREATE INDEX IF NOT EXISTS index_durable_agent_reports_on_conversation_id_and_id ON durable_agent_reports (conversation_id, id);
109
+
110
+ CREATE TABLE IF NOT EXISTS durable_agent_credentials (
111
+ provider_id text PRIMARY KEY,
112
+ credential text NOT NULL,
113
+ updated_at timestamp(6) without time zone NOT NULL
114
+ );
115
+
116
+ CREATE TABLE IF NOT EXISTS durable_agent_settings (
117
+ key text PRIMARY KEY,
118
+ value text NOT NULL,
119
+ updated_at timestamp(6) without time zone NOT NULL
120
+ );
@@ -0,0 +1,120 @@
1
+ -- durable_agent schema v2 for SQLite: a hand rendering of the gem's install migration
2
+ -- (lib/generators/durable_agent/install/templates/create_durable_agent_tables.rb.tt) for the sidecar tests.
3
+ -- Rails owns the real schema; test/lib/durable_agent/sidecar_schema_test.rb runs both against fresh databases and
4
+ -- compares tables, column types, nullability and defaults, and index definitions.
5
+ -- The first nine tables are pi-durable 1.3.0's schema (version 1) under the durable_agent_ prefix, minus STRICT
6
+ -- and the json_valid checks. No rows: the sidecar seeds the singletons at boot.
7
+
8
+ CREATE TABLE IF NOT EXISTS durable_agent_durable_schema (
9
+ singleton integer PRIMARY KEY AUTOINCREMENT NOT NULL,
10
+ version integer NOT NULL
11
+ );
12
+
13
+ CREATE TABLE IF NOT EXISTS durable_agent_durable_metadata (
14
+ singleton integer PRIMARY KEY AUTOINCREMENT NOT NULL,
15
+ next_id text NOT NULL,
16
+ next_seq bigint NOT NULL
17
+ );
18
+
19
+ CREATE TABLE IF NOT EXISTS durable_agent_record_ids (
20
+ id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
21
+ record_type text NOT NULL
22
+ );
23
+
24
+ CREATE TABLE IF NOT EXISTS durable_agent_conversations (
25
+ id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
26
+ owner_conversation_id bigint,
27
+ owner_task_id bigint,
28
+ record text NOT NULL,
29
+ created_at datetime(6) DEFAULT CURRENT_TIMESTAMP NOT NULL
30
+ );
31
+ CREATE INDEX IF NOT EXISTS durable_agent_conversations_by_owner_conversation ON durable_agent_conversations (owner_conversation_id, id);
32
+ CREATE INDEX IF NOT EXISTS durable_agent_conversations_by_owner_task ON durable_agent_conversations (owner_task_id, id);
33
+
34
+ CREATE TABLE IF NOT EXISTS durable_agent_entries (
35
+ id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
36
+ conversation_id bigint NOT NULL,
37
+ head bigint,
38
+ commit_seq bigint NOT NULL,
39
+ record text NOT NULL,
40
+ created_at datetime(6) DEFAULT CURRENT_TIMESTAMP NOT NULL
41
+ );
42
+ CREATE INDEX IF NOT EXISTS durable_agent_entries_by_conversation ON durable_agent_entries (conversation_id, id DESC);
43
+ CREATE INDEX IF NOT EXISTS durable_agent_entry_heads_by_conversation ON durable_agent_entries (conversation_id, id DESC) WHERE head IS NOT NULL;
44
+
45
+ CREATE TABLE IF NOT EXISTS durable_agent_tasks (
46
+ id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
47
+ conversation_id bigint NOT NULL,
48
+ kind text NOT NULL,
49
+ status text NOT NULL,
50
+ abort_requested integer NOT NULL,
51
+ background integer NOT NULL,
52
+ record text NOT NULL
53
+ );
54
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_status ON durable_agent_tasks (status, id);
55
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_conversation ON durable_agent_tasks (conversation_id, id);
56
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_kind ON durable_agent_tasks (kind, id);
57
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_abort_requested ON durable_agent_tasks (abort_requested, id);
58
+ CREATE INDEX IF NOT EXISTS durable_agent_tasks_by_background ON durable_agent_tasks (background, id);
59
+
60
+ CREATE TABLE IF NOT EXISTS durable_agent_submissions (
61
+ id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
62
+ conversation_id bigint NOT NULL,
63
+ request_id text,
64
+ status text NOT NULL,
65
+ record text NOT NULL,
66
+ created_at datetime(6) DEFAULT CURRENT_TIMESTAMP NOT NULL
67
+ );
68
+ CREATE INDEX IF NOT EXISTS durable_agent_submissions_by_request ON durable_agent_submissions (conversation_id, request_id);
69
+ CREATE INDEX IF NOT EXISTS durable_agent_submissions_by_conversation ON durable_agent_submissions (conversation_id, id);
70
+ CREATE INDEX IF NOT EXISTS durable_agent_submissions_by_status ON durable_agent_submissions (status, id);
71
+
72
+ CREATE TABLE IF NOT EXISTS durable_agent_documents (
73
+ id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
74
+ kind text NOT NULL,
75
+ family integer NOT NULL,
76
+ key_value text NOT NULL,
77
+ scope_kind text NOT NULL,
78
+ owner_id bigint NOT NULL,
79
+ created_at bigint NOT NULL,
80
+ retired_at bigint,
81
+ record text NOT NULL
82
+ );
83
+ CREATE INDEX IF NOT EXISTS durable_agent_documents_by_address ON durable_agent_documents (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at);
84
+ CREATE INDEX IF NOT EXISTS durable_agent_documents_by_scope ON durable_agent_documents (scope_kind, owner_id, id);
85
+ CREATE INDEX IF NOT EXISTS durable_agent_documents_by_scope_kind ON durable_agent_documents (scope_kind, owner_id, kind, id);
86
+
87
+ CREATE TABLE IF NOT EXISTS durable_agent_document_revisions (
88
+ document_id bigint NOT NULL,
89
+ seq bigint NOT NULL,
90
+ kind text NOT NULL,
91
+ version bigint NOT NULL,
92
+ content text NOT NULL,
93
+ PRIMARY KEY (document_id, seq)
94
+ );
95
+ CREATE INDEX IF NOT EXISTS durable_agent_document_revisions_by_kind ON durable_agent_document_revisions (document_id, kind, seq DESC);
96
+
97
+ CREATE TABLE IF NOT EXISTS durable_agent_reports (
98
+ id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
99
+ type text NOT NULL,
100
+ conversation_id bigint,
101
+ task_id bigint,
102
+ task_kind text,
103
+ extension text,
104
+ message text NOT NULL,
105
+ info text NOT NULL,
106
+ created_at datetime(6) NOT NULL
107
+ );
108
+ CREATE INDEX IF NOT EXISTS index_durable_agent_reports_on_conversation_id_and_id ON durable_agent_reports (conversation_id, id);
109
+
110
+ CREATE TABLE IF NOT EXISTS durable_agent_credentials (
111
+ provider_id text NOT NULL PRIMARY KEY,
112
+ credential text NOT NULL,
113
+ updated_at datetime(6) NOT NULL
114
+ );
115
+
116
+ CREATE TABLE IF NOT EXISTS durable_agent_settings (
117
+ key text NOT NULL PRIMARY KEY,
118
+ value text NOT NULL,
119
+ updated_at datetime(6) NOT NULL
120
+ );
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: durable_agent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mikkel Malmberg
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: turbo-rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ description: Crash-safe, resumable agent conversations for Rails. Ships the pi-durable
41
+ runtime as a sidecar process inside the gem that keeps its state in your app's database,
42
+ Postgres or SQLite, which Rails reads with Active Record. Broadcasts over Turbo,
43
+ and lets you write tools in Ruby.
44
+ email:
45
+ - mikkel@brnbw.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - CHANGELOG.md
51
+ - DESIGN.md
52
+ - MIT-LICENSE
53
+ - README.md
54
+ - Rakefile
55
+ - app/controllers/durable_agent/application_controller.rb
56
+ - app/controllers/durable_agent/events_controller.rb
57
+ - app/controllers/durable_agent/tools_controller.rb
58
+ - app/models/concerns/durable_agent/harness_record.rb
59
+ - app/models/durable_agent/application_record.rb
60
+ - app/models/durable_agent/connection.rb
61
+ - app/models/durable_agent/conversation.rb
62
+ - app/models/durable_agent/document.rb
63
+ - app/models/durable_agent/document/revision.rb
64
+ - app/models/durable_agent/entry.rb
65
+ - app/models/durable_agent/report.rb
66
+ - app/models/durable_agent/submission.rb
67
+ - app/models/durable_agent/task.rb
68
+ - config/routes.rb
69
+ - docs/guide.md
70
+ - lib/durable_agent.rb
71
+ - lib/durable_agent/account.rb
72
+ - lib/durable_agent/client.rb
73
+ - lib/durable_agent/configuration.rb
74
+ - lib/durable_agent/current.rb
75
+ - lib/durable_agent/engine.rb
76
+ - lib/durable_agent/indexed_string.rb
77
+ - lib/durable_agent/message_text.rb
78
+ - lib/durable_agent/sidecar.rb
79
+ - lib/durable_agent/test_helper.rb
80
+ - lib/durable_agent/tool.rb
81
+ - lib/durable_agent/version.rb
82
+ - lib/generators/durable_agent/install/install_generator.rb
83
+ - lib/generators/durable_agent/install/templates/create_durable_agent_tables.rb.tt
84
+ - lib/generators/durable_agent/install/templates/durable_agent.rb.tt
85
+ - lib/tasks/durable_agent_tasks.rake
86
+ - sidecar/dist/sidecar.js
87
+ - sidecar/package.json
88
+ - sidecar/sql/schema.postgres.sql
89
+ - sidecar/sql/schema.sqlite.sql
90
+ homepage: https://github.com/mikker/durable_agent
91
+ licenses:
92
+ - MIT
93
+ metadata:
94
+ homepage_uri: https://github.com/mikker/durable_agent
95
+ source_code_uri: https://github.com/mikker/durable_agent
96
+ changelog_uri: https://github.com/mikker/durable_agent/blob/main/CHANGELOG.md
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '3.3'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 4.0.20
112
+ specification_version: 4
113
+ summary: 'A durable AI agent runtime for Rails: pi-durable as a bundled sidecar, in
114
+ your own database.'
115
+ test_files: []