nexo_ai 0.10.0 → 0.12.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/.rdoc_options +5 -0
- data/CHANGELOG.md +48 -0
- data/README.md +2 -1
- data/Rakefile +5 -0
- data/docs/durable-workflows.md +2 -2
- data/docs/getting-started.md +1 -1
- data/docs/loops.md +2 -1
- data/docs/permissions.md +17 -2
- data/docs/rails.md +1 -1
- data/docs/sandboxes.md +13 -4
- data/docs/tools.md +16 -6
- data/docs/workflows.md +2 -2
- data/lib/generators/nexo/artifacts/artifacts_generator.rb +9 -7
- data/lib/generators/nexo/artifacts/templates/add_artifacts_to_nexo_workflow_runs.rb +1 -1
- data/lib/generators/nexo/install/install_generator.rb +7 -5
- data/lib/generators/nexo/skill/skill_generator.rb +10 -8
- data/lib/generators/nexo/state/state_generator.rb +9 -7
- data/lib/generators/nexo/state/templates/add_state_to_nexo_workflow_runs.rb +1 -1
- data/lib/generators/nexo/workflows/templates/create_nexo_workflow_runs.rb +3 -3
- data/lib/generators/nexo/workflows/workflows_generator.rb +7 -5
- data/lib/nexo/agent.rb +163 -141
- data/lib/nexo/concurrent.rb +17 -15
- data/lib/nexo/configuration.rb +21 -21
- data/lib/nexo/engine.rb +1 -1
- data/lib/nexo/loop.rb +6 -6
- data/lib/nexo/loops/agent_sdk.rb +11 -11
- data/lib/nexo/loops/ruby_llm.rb +10 -10
- data/lib/nexo/mcp/gated_tool.rb +16 -16
- data/lib/nexo/mcp.rb +37 -37
- data/lib/nexo/output_truncator.rb +9 -9
- data/lib/nexo/permissions.rb +64 -41
- data/lib/nexo/read_tracker.rb +4 -4
- data/lib/nexo/run_store.rb +30 -28
- data/lib/nexo/sandbox.rb +29 -27
- data/lib/nexo/sandboxes/container.rb +75 -75
- data/lib/nexo/sandboxes/local.rb +15 -15
- data/lib/nexo/sandboxes/remote.rb +25 -23
- data/lib/nexo/sandboxes/virtual.rb +2 -2
- data/lib/nexo/sandboxes.rb +9 -9
- data/lib/nexo/session.rb +31 -29
- data/lib/nexo/skills.rb +30 -28
- data/lib/nexo/tools/fetch.rb +20 -20
- data/lib/nexo/tools/glob.rb +5 -5
- data/lib/nexo/tools/read_file.rb +7 -7
- data/lib/nexo/tools/shell.rb +6 -6
- data/lib/nexo/tools/web_search.rb +15 -15
- data/lib/nexo/tools/write_file.rb +9 -9
- data/lib/nexo/turbo_broadcaster.rb +1 -1
- data/lib/nexo/version.rb +2 -2
- data/lib/nexo/workflow.rb +197 -188
- data/lib/nexo/workflow_job.rb +6 -6
- data/lib/nexo/workflow_run.rb +10 -10
- data/lib/nexo.rb +26 -22
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6739e4be2c75c08ffb553133324b96b29d13e0212e21cc23c4cd6287316b3249
|
|
4
|
+
data.tar.gz: d051ce3a0916cd5fb9328df9b893e5aad15b6f60c8d5c23bb9be8b3126da7285
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 94c1df2b9d56ab1c31aacd79a52473bf6ed3da9ba43d2e7403658fdee0e52bc31bded23181dbe7f60e82b4fd9b0f5e64c5d933de80cf99f546ca0982b883d878
|
|
7
|
+
data.tar.gz: ca8829f2003854bd1c24e2e77739b0b4c62bd173ee0bd31297171ba017f2c1a02dd31f6fb9bb7ef938fc1c67a93229c407f5fbae06b29c4aee305552f6eba242
|
data/.rdoc_options
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.12.0] - 2026-09-12
|
|
4
|
+
|
|
5
|
+
The gem now says exactly which ruby_llm it supports, and its API docs are written in Markdown.
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **`ruby_llm` is now pinned to the 1.16 line (`~> 1.16`, was `>= 1.16`).** The supported
|
|
10
|
+
version is 1.16.0 — every composed API was verified against it. The upcoming ruby_llm 2.0
|
|
11
|
+
is not supported yet, and the pessimistic constraint keeps it from being resolved in.
|
|
12
|
+
- **API documentation is written in Markdown.** RDoc now runs with `markup: markdown`
|
|
13
|
+
(`.rdoc_options` + the `rake doc` task), and every doc comment under `lib/` was converted
|
|
14
|
+
from RDoc markup — backtick code spans, `##` headings, fenced code samples. Guides link the
|
|
15
|
+
`examples/` files by GitHub URL so the links work on rubydoc.info as well as on GitHub.
|
|
16
|
+
|
|
17
|
+
## [0.11.0] - 2026-08-20
|
|
18
|
+
|
|
19
|
+
An agent's tool schema now tells the truth about what it may do.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **An agent no longer advertises a tool its permission mode can never authorize.**
|
|
24
|
+
Tool attach was gated on the sandbox (`Sandbox#supports?`) but not on the gate, so a
|
|
25
|
+
`:read_only` agent — the default — put `WriteFile` and `Shell` in its schema on every
|
|
26
|
+
turn even though `#authorize!` was guaranteed to deny them. The same held for `Fetch`
|
|
27
|
+
and `WebSearch`, which were gated on `fetch_allow` / `search_backend` being declared
|
|
28
|
+
but not on the capability being permitted. Models do try these tools, and each attempt
|
|
29
|
+
costs a full round trip.
|
|
30
|
+
|
|
31
|
+
`Agent#chat`, `#apply_fetch` and `#apply_search` now also consult
|
|
32
|
+
`Permissions#never_allows?`. Only `:read_only` is decidable ahead of time; `:auto`,
|
|
33
|
+
`:ask` and `:approve` decide per call and still attach — `:approve` in particular must
|
|
34
|
+
reach the gate so it can raise `ApprovalRequired` and suspend the run.
|
|
35
|
+
|
|
36
|
+
This is a cost and description-accuracy measure, **not** a security change:
|
|
37
|
+
`#authorize!` remains the boundary and still denies at call time.
|
|
38
|
+
|
|
39
|
+
**Upgrading:** an agent that is supposed to write, shell out, fetch or search needs the
|
|
40
|
+
capability in `allow:` (or a non-`:read_only` mode). If it does not have it today the
|
|
41
|
+
tool was already failing on every call — the tool now disappears from the schema instead
|
|
42
|
+
of erroring. Note that `fetch_allow` scopes hosts and `search_backend` names a backend;
|
|
43
|
+
neither is a capability grant.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- `Nexo::Permissions#never_allows?(capability)` — true when a capability can never be
|
|
48
|
+
authorized for this gate, for any call. Derived from the new `Permissions::PRIVILEGED`
|
|
49
|
+
constant, which `#authorize!` also reads, so the predicate cannot drift from the gate.
|
|
50
|
+
|
|
3
51
|
## [0.10.0] - 2026-08-20
|
|
4
52
|
|
|
5
53
|
Durable workflows without a database.
|
data/README.md
CHANGED
|
@@ -94,7 +94,8 @@ access until you explicitly opt in.
|
|
|
94
94
|
## Requirements
|
|
95
95
|
|
|
96
96
|
- Ruby 3.3+
|
|
97
|
-
- [ruby_llm](https://github.com/crmne/ruby_llm)
|
|
97
|
+
- [ruby_llm](https://github.com/crmne/ruby_llm) 1.16.x (`~> 1.16`) — the supported version is
|
|
98
|
+
**1.16.0**; the upcoming ruby_llm 2.0 is not supported yet
|
|
98
99
|
- [ruby_llm-skills](https://github.com/kieranklaassen/ruby_llm-skills) — optional, only
|
|
99
100
|
when you use the `skills` macro
|
|
100
101
|
- [ruby_llm-mcp](https://github.com/patvice/ruby_llm-mcp) — optional, only when you attach
|
data/Rakefile
CHANGED
|
@@ -31,7 +31,12 @@ RDoc::Task.new(:doc) do |rd|
|
|
|
31
31
|
# are not part of the gem's Ruby API, so they belong in neither the generated
|
|
32
32
|
# API site nor the coverage denominator.
|
|
33
33
|
rd.rdoc_files.exclude("lib/generators/**/templates/*.rb")
|
|
34
|
+
# Local planning notes (gitignored) that happen to live under docs/.
|
|
35
|
+
rd.rdoc_files.exclude("docs/SITE_DOCS_PLAN.md")
|
|
34
36
|
rd.rdoc_dir = "doc"
|
|
37
|
+
# Every comment in lib/ (and the docs/ guides) is Markdown, not RDoc markup.
|
|
38
|
+
# Mirrors `.rdoc_options`, which `gem rdoc`/rubydoc.info read instead of this task.
|
|
39
|
+
rd.markup = "markdown"
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
# NOTE: `default` intentionally stays test + standard — `rake doc`/`doc:coverage`
|
data/docs/durable-workflows.md
CHANGED
|
@@ -61,7 +61,7 @@ run:
|
|
|
61
61
|
DocumentApproval.resume_later(run.id, { approved: true }, queue: :nexo)
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
See [`examples/approval_workflow.rb`](
|
|
64
|
+
See [`examples/approval_workflow.rb`](https://github.com/maquina-app/nexo/blob/main/examples/approval_workflow.rb) for the full
|
|
65
65
|
offline flow (`ruby -Ilib examples/approval_workflow.rb`).
|
|
66
66
|
|
|
67
67
|
## Parallel checkpoints — `checkpoint_all`
|
|
@@ -187,7 +187,7 @@ resumed.status # => "done" (the gate allowed the
|
|
|
187
187
|
`ruby_llm` swallows tool exceptions, tool-triggered approval would be constrained — a
|
|
188
188
|
genuine upstream dependency, stated plainly.
|
|
189
189
|
|
|
190
|
-
See [`examples/approval_agent.rb`](
|
|
190
|
+
See [`examples/approval_agent.rb`](https://github.com/maquina-app/nexo/blob/main/examples/approval_agent.rb) for the live flow
|
|
191
191
|
(`NEXO_LIVE=1 NEXO_MODEL=… ruby -Ilib examples/approval_agent.rb`).
|
|
192
192
|
|
|
193
193
|
The `state` column ships with fresh installs. Apps installed before this feature
|
data/docs/getting-started.md
CHANGED
|
@@ -97,7 +97,7 @@ Both are class macros with the same reader/writer convention as `model`. `provid
|
|
|
97
97
|
is passed straight through to `RubyLLM.chat`; `assume_model_exists` defaults to
|
|
98
98
|
`false` (registry validation on). Setting `assume_model_exists` **without** a
|
|
99
99
|
`provider` raises `Nexo::ConfigurationError` — `ruby_llm` can't infer a provider once
|
|
100
|
-
the lookup is skipped. See [`examples/code_reviewer.rb`](
|
|
100
|
+
the lookup is skipped. See [`examples/code_reviewer.rb`](https://github.com/maquina-app/nexo/blob/main/examples/code_reviewer.rb)
|
|
101
101
|
for a runnable Ollama example.
|
|
102
102
|
|
|
103
103
|
← Back to the [README](../README.md)
|
data/docs/loops.md
CHANGED
|
@@ -63,7 +63,8 @@ proven.
|
|
|
63
63
|
|
|
64
64
|
## Verified vs assumed
|
|
65
65
|
|
|
66
|
-
Built against **`ruby_llm` 1.16**
|
|
66
|
+
Built against **`ruby_llm` 1.16.0** (the supported line — the gemspec pins `~> 1.16`, and the
|
|
67
|
+
upcoming `ruby_llm` 2.0 is not supported yet) and **`ruby_llm-test` 0.2**. The tool body method is
|
|
67
68
|
`#execute`, tools attach with `chat.with_tools(*instances)`, and instructions set with
|
|
68
69
|
`chat.with_instructions`. `Open3.capture3` has no `timeout:` keyword on the target Ruby, so
|
|
69
70
|
`Local#shell` bounds the command with `Timeout.timeout`. These may differ on other
|
data/docs/permissions.md
CHANGED
|
@@ -8,7 +8,7 @@ and the agent loop continues — it does not raise. A path that escapes the work
|
|
|
8
8
|
|
|
9
9
|
| | `:read` | `:glob` | `:write` | `:shell` | `:fetch` | `:search` |
|
|
10
10
|
| ------------------------ | ------- | ------- | ------------------- | --------------------------------- | ---------- | ---------- |
|
|
11
|
-
| `:read_only` (default) | ✅ | ✅ | ❌
|
|
11
|
+
| `:read_only` (default) | ✅ | ✅ | ❌ not attached ‡ | ❌ not attached ‡ | ❌ not attached ‡ | ❌ not attached ‡ |
|
|
12
12
|
| `:auto` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
13
13
|
| `:ask` | ✅ | ✅ | per `on_ask` | per `on_ask` | per `on_ask` | per `on_ask` |
|
|
14
14
|
| `:approve` | ✅ | ✅ | per `decision` | per `decision` | per `decision` | per `decision` |
|
|
@@ -18,7 +18,22 @@ and the agent loop continues — it does not raise. A path that escapes the work
|
|
|
18
18
|
|
|
19
19
|
`:read`/`:glob` are auto-allowed under **every** mode (they sit in the default
|
|
20
20
|
`allow` list), so `:ask`/`:approve` never prompt for them — only
|
|
21
|
-
`:write`/`:shell`/`:fetch`/`:search` reach the gate.
|
|
21
|
+
`:write`/`:shell`/`:fetch`/`:search` reach the gate.
|
|
22
|
+
|
|
23
|
+
**‡** Under `:read_only` these four capabilities can *never* be authorized, so their tools are
|
|
24
|
+
not attached at all — the model never sees `WriteFile`, `Shell`, `Fetch` or `WebSearch` in its
|
|
25
|
+
schema. Anything named in `allow:` is exempt and attaches normally, and `:auto`/`:ask`/`:approve`
|
|
26
|
+
always attach because they decide per call. Leaving a guaranteed failure in the schema is not
|
|
27
|
+
free: a model that reads the schema tries the tool, and each attempt costs a full round trip.
|
|
28
|
+
`Permissions#never_allows?` is the predicate, and it is derived from the same `PRIVILEGED` list
|
|
29
|
+
`#authorize!` uses so the two cannot disagree. This is a cost and description-accuracy measure,
|
|
30
|
+
**not** a security boundary — `#authorize!` is still the gate and still denies at call time.
|
|
31
|
+
|
|
32
|
+
Note that declaring `fetch_allow` or a `search_backend` is *not* a capability grant: `fetch_allow`
|
|
33
|
+
scopes which hosts are reachable, and both still need `:fetch` / `:search` permitted before the
|
|
34
|
+
tool is attached.
|
|
35
|
+
|
|
36
|
+
**†** `:fetch` and `:search`
|
|
22
37
|
run in the **host process** (stdlib `net/http` / a host-injected backend), so **no
|
|
23
38
|
sandbox constrains them** — not even a `--network none` container. They are bounded
|
|
24
39
|
only by the capability gate above plus `fetch_allow` / the injected backend.
|
data/docs/rails.md
CHANGED
|
@@ -156,7 +156,7 @@ run.artifact("digest.md") # => {"name" =>, "content" =>, "at" =>} or ni
|
|
|
156
156
|
run.artifact_content("digest.md") # => "…the body…" or nil
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
See [`examples/rails_usage.md`](
|
|
159
|
+
See [`examples/rails_usage.md`](https://github.com/maquina-app/nexo/blob/main/examples/rails_usage.md) for a controller +
|
|
160
160
|
Turbo-page walkthrough.
|
|
161
161
|
|
|
162
162
|
← Back to the [README](../README.md)
|
data/docs/sandboxes.md
CHANGED
|
@@ -54,10 +54,19 @@ scope; none widens authority silently.
|
|
|
54
54
|
instructions → sandbox instructions → skill instructions**. Provider-neutral, injected through
|
|
55
55
|
the existing `with_instructions` path.
|
|
56
56
|
|
|
57
|
-
- **
|
|
58
|
-
a
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
- **Gated tool attach, on two axes.** An agent does not advertise a tool it could never
|
|
58
|
+
successfully call, because a guaranteed failure in the schema costs a round trip every time a
|
|
59
|
+
model tries it. `Agent#chat` attaches a tool only when **both** hold:
|
|
60
|
+
|
|
61
|
+
1. *The sandbox supports the capability* (`Sandbox#supports?`). `Local`/`Container` support all
|
|
62
|
+
four; `Virtual` supports everything but `:shell`, so a `:virtual` agent has no `Shell`.
|
|
63
|
+
2. *The permission gate does not deny it statically* (`Permissions#never_allows?`). Under
|
|
64
|
+
`:read_only`, `:write` and `:shell` can never be authorized unless listed in `allow:`, so
|
|
65
|
+
neither `WriteFile` nor `Shell` is attached. `:auto`, `:ask` and `:approve` decide per call
|
|
66
|
+
and always attach — `:approve` in particular *must* reach the gate so it can suspend the run.
|
|
67
|
+
|
|
68
|
+
`ReadFile` and `Glob` are always attached. This is a cost and description-accuracy measure, not
|
|
69
|
+
a security boundary: `Permissions#authorize!` remains the gate and still denies at call time.
|
|
61
70
|
|
|
62
71
|
- **Shell output truncation (`Nexo::OutputTruncator`).** Unbounded command output (`npm install`,
|
|
63
72
|
`git log`) is truncated before it reaches the model, so a single command can't blow a small
|
data/docs/tools.md
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# Tools
|
|
2
2
|
Nexo attaches four sandbox-backed tools — `ReadFile`, `WriteFile`, `Shell`, and `Glob` — each gated by the [sandbox](sandboxes.md) and [permission](permissions.md) seams.
|
|
3
3
|
|
|
4
|
-
Which tools attach depends on what the sandbox supports
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Which tools attach depends on what the sandbox supports *and* on what the permission mode could
|
|
5
|
+
ever allow:
|
|
6
|
+
|
|
7
|
+
- **Gated tool attach, on two axes.** An agent does not advertise a tool it could never
|
|
8
|
+
successfully call, because a guaranteed failure in the schema costs a round trip every time a
|
|
9
|
+
model tries it. `Agent#chat` attaches a tool only when **both** hold:
|
|
10
|
+
|
|
11
|
+
1. *The sandbox supports the capability* (`Sandbox#supports?`). `Local`/`Container` support all
|
|
12
|
+
four; `Virtual` supports everything but `:shell`, so a `:virtual` agent has no `Shell`.
|
|
13
|
+
2. *The permission gate does not deny it statically* (`Permissions#never_allows?`). Under
|
|
14
|
+
`:read_only`, `:write` and `:shell` can never be authorized unless listed in `allow:`, so
|
|
15
|
+
neither `WriteFile` nor `Shell` is attached. `:auto`, `:ask` and `:approve` decide per call
|
|
16
|
+
and always attach — `:approve` in particular *must* reach the gate so it can suspend the run.
|
|
17
|
+
|
|
18
|
+
`ReadFile` and `Glob` are always attached. This is a cost and description-accuracy measure, not
|
|
19
|
+
a security boundary: `Permissions#authorize!` remains the gate and still denies at call time.
|
|
10
20
|
|
|
11
21
|
`Shell` truncates unbounded command output before it reaches the model:
|
|
12
22
|
|
data/docs/workflows.md
CHANGED
|
@@ -176,7 +176,7 @@ artifact("digest.md", from: "app/templates/digest.md.erb",
|
|
|
176
176
|
> or uploaded template is remote code execution. If a body is untrusted, pass it
|
|
177
177
|
> as `content:` (inert data), not as a `from:` template.
|
|
178
178
|
|
|
179
|
-
See [`examples/artifact_from_template.rb`](
|
|
179
|
+
See [`examples/artifact_from_template.rb`](https://github.com/maquina-app/nexo/blob/main/examples/artifact_from_template.rb) for
|
|
180
180
|
the full offline flow (`ruby -Ilib examples/artifact_from_template.rb`).
|
|
181
181
|
### Agent output — `produces`
|
|
182
182
|
|
|
@@ -307,7 +307,7 @@ end
|
|
|
307
307
|
> *how* (skills/instructions). Driving an agent never widens its authority — its
|
|
308
308
|
> safe default (`:read_only`) is untouched.
|
|
309
309
|
|
|
310
|
-
See [`examples/inbox_digest_task.rb`](
|
|
310
|
+
See [`examples/inbox_digest_task.rb`](https://github.com/maquina-app/nexo/blob/main/examples/inbox_digest_task.rb) for a live
|
|
311
311
|
example that wraps the MCP-backed `InboxTriage` agent in a workflow and captures
|
|
312
312
|
the digest as an artifact.
|
|
313
313
|
|
|
@@ -5,15 +5,17 @@ require "rails/generators/migration"
|
|
|
5
5
|
|
|
6
6
|
module Nexo
|
|
7
7
|
module Generators
|
|
8
|
-
# Adds the
|
|
8
|
+
# Adds the `artifacts` column to an already-installed `nexo_workflow_runs`
|
|
9
9
|
# table (Spec 7):
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# ```sh
|
|
12
|
+
# rails g nexo:artifacts
|
|
13
|
+
# ```
|
|
12
14
|
#
|
|
13
|
-
# copies a timestamped, additive migration adding a
|
|
14
|
-
# (default
|
|
15
|
+
# copies a timestamped, additive migration adding a `json` `artifacts` column
|
|
16
|
+
# (default `[]`), after which `rails db:migrate` lets Nexo::Workflow runs
|
|
15
17
|
# record named artifacts. Fresh installs get the column from
|
|
16
|
-
#
|
|
18
|
+
# `nexo:workflows` directly; this generator is for apps installed before
|
|
17
19
|
# Spec 7. Modeled on WorkflowsGenerator.
|
|
18
20
|
class ArtifactsGenerator < Rails::Generators::Base
|
|
19
21
|
include Rails::Generators::Migration
|
|
@@ -27,8 +29,8 @@ module Nexo
|
|
|
27
29
|
::ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
28
30
|
end
|
|
29
31
|
|
|
30
|
-
# Generation step: copy the timestamped
|
|
31
|
-
#
|
|
32
|
+
# Generation step: copy the timestamped `artifacts`-column migration into
|
|
33
|
+
# `db/migrate`.
|
|
32
34
|
def create_migration_file
|
|
33
35
|
migration_template "add_artifacts_to_nexo_workflow_runs.rb", "db/migrate/add_artifacts_to_nexo_workflow_runs.rb"
|
|
34
36
|
end
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# column from create_nexo_workflow_runs directly). Portable json column, default
|
|
5
5
|
# [], matching the events column shape.
|
|
6
6
|
class AddArtifactsToNexoWorkflowRuns < ActiveRecord::Migration[8.0]
|
|
7
|
-
# Adds the
|
|
7
|
+
# Adds the `artifacts` json column (default `[]`) to `nexo_workflow_runs`.
|
|
8
8
|
def change
|
|
9
9
|
add_column :nexo_workflow_runs, :artifacts, :json, null: false, default: []
|
|
10
10
|
end
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
require "rails/generators"
|
|
4
4
|
|
|
5
5
|
module Nexo
|
|
6
|
-
# Namespace for Nexo's Rails generators (
|
|
7
|
-
#
|
|
6
|
+
# Namespace for Nexo's Rails generators (`rails g nexo:install`,
|
|
7
|
+
# `nexo:workflows`, `nexo:artifacts`, `nexo:state`, `nexo:skill`). Each is
|
|
8
8
|
# Rails-coupled and never autoloaded by the plain-Ruby core.
|
|
9
9
|
module Generators
|
|
10
10
|
# Sets up the conventional Nexo layout in a host Rails app:
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# ```sh
|
|
13
|
+
# rails g nexo:install
|
|
14
|
+
# ```
|
|
13
15
|
#
|
|
14
16
|
# creates the app/agents, app/workflows and app/skills directories (each
|
|
15
17
|
# with a committable .keep) and a provider-neutral config/initializers/nexo.rb.
|
|
@@ -17,7 +19,7 @@ module Nexo
|
|
|
17
19
|
source_root File.expand_path("templates", __dir__)
|
|
18
20
|
|
|
19
21
|
# Generation step: create the app/agents, app/workflows, and app/skills
|
|
20
|
-
# directories, each with a committable
|
|
22
|
+
# directories, each with a committable `.keep`.
|
|
21
23
|
def create_directories
|
|
22
24
|
%w[app/agents app/workflows app/skills].each do |dir|
|
|
23
25
|
empty_directory dir
|
|
@@ -26,7 +28,7 @@ module Nexo
|
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
# Generation step: copy the provider-neutral
|
|
29
|
-
#
|
|
31
|
+
# `config/initializers/nexo.rb` into the host app.
|
|
30
32
|
def copy_initializer
|
|
31
33
|
copy_file "nexo.rb", "config/initializers/nexo.rb"
|
|
32
34
|
end
|
|
@@ -7,21 +7,23 @@ module Nexo
|
|
|
7
7
|
module Generators
|
|
8
8
|
# Scaffolds an Agent Skills package in a host Rails app:
|
|
9
9
|
#
|
|
10
|
-
#
|
|
10
|
+
# ```sh
|
|
11
|
+
# rails g nexo:skill triage
|
|
12
|
+
# ```
|
|
11
13
|
#
|
|
12
|
-
# creates
|
|
14
|
+
# creates `app/skills/triage/SKILL.md` (valid frontmatter + placeholder
|
|
13
15
|
# process steps, per agentskills.io/specification) and a kept
|
|
14
|
-
#
|
|
15
|
-
# cite. Reference it from an agent with the
|
|
16
|
+
# `app/skills/triage/references/` directory for supporting docs the skill can
|
|
17
|
+
# cite. Reference it from an agent with the `skills :triage` macro.
|
|
16
18
|
#
|
|
17
|
-
# Rails-coupled, like Nexo's other generators: it requires
|
|
19
|
+
# Rails-coupled, like Nexo's other generators: it requires `rails/generators`
|
|
18
20
|
# at load time and is never autoloaded by the plain-Ruby core, so
|
|
19
|
-
#
|
|
21
|
+
# `require "nexo"` with no Rails present neither defines nor fails on it.
|
|
20
22
|
class SkillGenerator < Rails::Generators::NamedBase
|
|
21
23
|
source_root File.expand_path("templates", __dir__)
|
|
22
24
|
|
|
23
|
-
# Generation step: scaffold
|
|
24
|
-
# directory and a valid
|
|
25
|
+
# Generation step: scaffold `app/skills/<name>/` with a kept `references/`
|
|
26
|
+
# directory and a valid `SKILL.md` rendered from the template.
|
|
25
27
|
def create_skill_package
|
|
26
28
|
empty_directory File.join(skill_root, "references")
|
|
27
29
|
# An empty references/ would not survive git; .keep keeps it tracked.
|
|
@@ -5,15 +5,17 @@ require "rails/generators/migration"
|
|
|
5
5
|
|
|
6
6
|
module Nexo
|
|
7
7
|
module Generators
|
|
8
|
-
# Adds the
|
|
8
|
+
# Adds the `state` column to an already-installed `nexo_workflow_runs` table
|
|
9
9
|
# (Spec 13):
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# ```sh
|
|
12
|
+
# rails g nexo:state
|
|
13
|
+
# ```
|
|
12
14
|
#
|
|
13
|
-
# copies a timestamped, additive migration adding a
|
|
14
|
-
# (default
|
|
15
|
+
# copies a timestamped, additive migration adding a `json` `state` column
|
|
16
|
+
# (default `{}`), after which `rails db:migrate` lets Nexo::Workflow runs
|
|
15
17
|
# store checkpoint results and suspend metadata (durable suspend/resume).
|
|
16
|
-
# Fresh installs get the column from
|
|
18
|
+
# Fresh installs get the column from `nexo:workflows` directly; this generator
|
|
17
19
|
# is for apps installed before Spec 13. Modeled on ArtifactsGenerator.
|
|
18
20
|
class StateGenerator < Rails::Generators::Base
|
|
19
21
|
include Rails::Generators::Migration
|
|
@@ -27,8 +29,8 @@ module Nexo
|
|
|
27
29
|
::ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
28
30
|
end
|
|
29
31
|
|
|
30
|
-
# Generation step: copy the timestamped
|
|
31
|
-
#
|
|
32
|
+
# Generation step: copy the timestamped `state`-column migration into
|
|
33
|
+
# `db/migrate`.
|
|
32
34
|
def create_migration_file
|
|
33
35
|
migration_template "add_state_to_nexo_workflow_runs.rb", "db/migrate/add_state_to_nexo_workflow_runs.rb"
|
|
34
36
|
end
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# "__suspend__" suspend metadata. The migration version is resolved from the host
|
|
7
7
|
# app's ActiveRecord rather than hardcoded, so it tracks whatever Rails is installed.
|
|
8
8
|
class AddStateToNexoWorkflowRuns < ActiveRecord::Migration[ActiveRecord::Migration.current_version]
|
|
9
|
-
# Adds the
|
|
9
|
+
# Adds the `state` json object column (default `{}`) to `nexo_workflow_runs`.
|
|
10
10
|
def change
|
|
11
11
|
add_column :nexo_workflow_runs, :state, :json, null: false, default: {}
|
|
12
12
|
end
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
# SQLite and PostgreSQL without an adapter-aware path. The primary key is the
|
|
5
5
|
# UUID string id assigned by Nexo::WorkflowRun before_create.
|
|
6
6
|
class CreateNexoWorkflowRuns < ActiveRecord::Migration[8.0]
|
|
7
|
-
# Creates the
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# Creates the `nexo_workflow_runs` table (UUID string primary key, portable
|
|
8
|
+
# `json` columns for payload/result/events/artifacts/state) plus indexes on
|
|
9
|
+
# `workflow_class` and `status`.
|
|
10
10
|
def change
|
|
11
11
|
create_table :nexo_workflow_runs, id: false do |t|
|
|
12
12
|
t.string :id, null: false, primary_key: true
|
|
@@ -7,10 +7,12 @@ module Nexo
|
|
|
7
7
|
module Generators
|
|
8
8
|
# Installs the WorkflowRun persistence schema into a host Rails app:
|
|
9
9
|
#
|
|
10
|
-
#
|
|
10
|
+
# ```sh
|
|
11
|
+
# rails g nexo:workflows
|
|
12
|
+
# ```
|
|
11
13
|
#
|
|
12
|
-
# copies a timestamped migration that creates the
|
|
13
|
-
# table, after which
|
|
14
|
+
# copies a timestamped migration that creates the `nexo_workflow_runs`
|
|
15
|
+
# table, after which `rails db:migrate` makes Nexo::Workflow runs persist
|
|
14
16
|
# to ActiveRecord instead of the in-memory store.
|
|
15
17
|
class WorkflowsGenerator < Rails::Generators::Base
|
|
16
18
|
include Rails::Generators::Migration
|
|
@@ -24,8 +26,8 @@ module Nexo
|
|
|
24
26
|
::ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
# Generation step: copy the timestamped
|
|
28
|
-
# migration into
|
|
29
|
+
# Generation step: copy the timestamped `create_nexo_workflow_runs`
|
|
30
|
+
# migration into `db/migrate`.
|
|
29
31
|
def create_migration_file
|
|
30
32
|
migration_template "create_nexo_workflow_runs.rb", "db/migrate/create_nexo_workflow_runs.rb"
|
|
31
33
|
end
|