Skip to content

Commit 7bea12f

Browse files
authored
Merge pull request #50 from ScriptKittyOS/slice/041-skill-self-management
feat(s041): complete slice 041 (skill self-management with approval) Signed-off-by: Ayla Croft <aylacroft@proton.me>
2 parents 0f46141 + 888b1a3 commit 7bea12f

40 files changed

Lines changed: 2687 additions & 19 deletions

‎ROADMAP.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ standards register names the rows that ask for them.
5050
| 033 | Project context: AGENTS.md | 3 Memory | S | 030, 022 | approved |
5151
| 034 | Export, import, restore | 3 Memory | S | 030, 031 | approved |
5252
| 040 | Skills registry + agentskills.io format + progressive disclosure | 4 Skills | M | 020 | approved |
53-
| 041 | Skill self-management with staged approval + scanner | 4 Skills | M | 040, 021 | planned |
53+
| 041 | Skill self-management with staged approval + scanner | 4 Skills | M | 040, 021 | done |
5454
| 050 | Scheduler: Oban cron agent tasks with delivery targets | 5 Automation | M | 012 | planned |
5555
| 059 | MCP capability gap against beam_mcp, and the server seam probe | 6 MCP | S/M | 020 | planned |
5656
| 060 | MCP client: Trinity's thin driver (2026-07-28 preferred, 2025-11-25 compat, MRTR, Tasks) | 6 MCP | L | 059, 021 | planned |

‎config/config.exs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ config :trinity, :tools,
7979
Trinity.Skills.Tools.List,
8080
Trinity.Skills.Tools.View,
8181
Trinity.Skills.Tools.File,
82+
# Slice 041: proposals and learning, staged for approval.
83+
Trinity.Skills.Tools.Manage,
84+
Trinity.Skills.Tools.Learn,
8285
# Slice 030: the always-on memory tiers.
8386
Trinity.Tools.Memory,
8487
Trinity.Tools.Shell.Run
@@ -90,7 +93,7 @@ config :trinity, :tools,
9093
# Slice 031: search over past conversations.
9194
memory: ["session_search", "recall", "memory"],
9295
# Slice 040: the skill tools.
93-
skills: ["skills_list", "skill_view", "skill_file"]
96+
skills: ["skills_list", "skill_view", "skill_file", "skill_manage", "learn"]
9497
}
9598

9699
# Slice 040: the skill roots. `user_dir:` and `bundled_dir:` default to the data directory's

‎config/test.exs‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ config :trinity, :tools,
3333
Trinity.Skills.Tools.List,
3434
Trinity.Skills.Tools.View,
3535
Trinity.Skills.Tools.File,
36+
# Slice 041: proposals and learning, staged for approval.
37+
Trinity.Skills.Tools.Manage,
38+
Trinity.Skills.Tools.Learn,
3639
# Slice 030: the always-on memory tiers.
3740
Trinity.Tools.Memory,
3841
Trinity.Tools.Shell.Run
@@ -45,7 +48,7 @@ config :trinity, :tools,
4548
# Slice 031: search over past conversations.
4649
memory: ["session_search", "recall", "memory"],
4750
# Slice 040: the skill tools.
48-
skills: ["skills_list", "skill_view", "skill_file"]
51+
skills: ["skills_list", "skill_view", "skill_file", "skill_manage", "learn"]
4952
},
5053
timeout_ms: 2_000
5154

@@ -183,5 +186,8 @@ config :trinity, :memory, embedder: :fake, observer: false
183186
config :trinity, :skills,
184187
bundled_dir: Path.expand("../test/support/fixtures/skills/bundled", __DIR__),
185188
user_dir: Path.expand("../test/support/fixtures/skills/user", __DIR__),
189+
# Slice 041: staged changes go under a scratch pending root; a test that promotes points
190+
# user_dir at a temporary directory first.
191+
pending_dir: Path.join(System.tmp_dir!(), "trinity-test-pending-skills"),
186192
watch: true,
187193
index_tokens: 338

‎coverage.tsv‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ slice_id percent sha date
1717
034 78.83 3a5785f 2026-09-21
1818
032 78.99 193800e 2026-09-21
1919
040 79.86 dc9dc6b 2026-09-21
20+
041 80.55 ae200d7 2026-09-21

‎docs/01-architecture.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ without anything failing.
8888
| `Trinity.Authority` | Behaviour; `Local` implementation (the one caller of `execute/2` for effectful tools); selection at boot; `Staged` | Receipts, Repo |
8989
| `Trinity.Receipts` | Local chain (one supervised writer per scope, ADR-0013), the signer seam (Ed25519, P-384, ML-DSA-87), key custody and the registry, checkpoints, the verifier, the alarm | Repo (`Repo.Receipts`) |
9090
| `Trinity.Memory` | Always-on tiers with their budget and consolidator (030), search (031), semantic store and retrieval (032), compaction (023) | LLM (summaries/embeddings), Repo |
91-
| `Trinity.Skills` | SKILL.md parsing, registry, loader, manager, scanner (as built at 040: parser, sources, registry, index, the three tools) | Repo, Permissions, **Effects**, **Receipts**, Sandbox (as built at 040: Tools, for conditional activation and the tool behaviour, and Memory, for the token estimator; Tools never depends on Skills) |
91+
| `Trinity.Skills` | SKILL.md parsing, registry, loader, manager, scanner (as built at 040: parser, sources, registry, index, the three tools; at 041: staging, promotion, manager, scanner, diff, learn, `skill_manage` and `learn`) | Repo, Permissions, **Effects**, **Receipts**, Sandbox (as built at 041: Tools, Memory, Permissions, Receipts and LLM; Effects is not a dependency: the promotion is not a tool call, it writes its own effect receipt; Tools never depends on Skills) |
9292
| `Trinity.Scheduler` | Oban workers for agent tasks, delivery | Sessions, Gateways, **Repo** |
9393
| `Trinity.MCP` | Client manager, tool bridge, server | Tools, **Effects**, **Permissions**, Memory |
9494
| `Trinity.Gateways` | Adapter behaviour, router, allowlists, pairing | Sessions, **Permissions**, PubSub |

‎docs/05-data-model.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ on the next scan.
129129
### skill_changes (Slice 041)
130130
Staged proposals by the agent: `skill_id`, `diff`, `rationale`, `status`, `decided_by`, `decided_at`.
131131

132+
As built at slice 041: `skill_name` (not an id: a create names a skill that has no row yet), `action`
133+
(`create | patch | write_file | remove_file | delete`), `source` (the target root, `user`), `change_dir` (the
134+
staged tree under the pending root), `diff` (unified, per changed text file; a non-text or oversized file is
135+
"replaced, N bytes"), `rationale`, `destructive` (a whole-file replace or a delete), `digest` (SHA-256 over the
136+
staged tree's paths and bytes; the promotion recomputes it), `status` (`pending | approved | rejected | applied
137+
| failed`), `severity` (`none | low | medium | high`), `findings` (the scanner's, with file, line, rule,
138+
severity and the matched text), `proposed_by` (session id, nullable), `approval_id`, `decided_by`, `decided_at`,
139+
`comment`, `receipt_hash` (the promotion's effect receipt) and `applied_version`. `approvals.session_id` is
140+
nullable since this slice (a change approved from the page has no session).
141+
132142
### tool_permissions (Slice 021, as built)
133143
| column | type | notes |
134144
|---|---|---|

‎docs/07-security-model.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,24 @@ is not public (loopback, private, link-local) to `:ask`.
114114
- Scanner flags: shell commands, network calls, credential-looking strings, instructions to disable safety, external URLs.
115115
- Hub-installed skills are scanned and default to `disabled` until the human enables.
116116

117+
As built at slice 041: the agent's `skill_manage` (and the `learn` flow) never writes a skill root. Every
118+
change is staged by `Trinity.Skills.Staging` as the whole target tree under `<data dir>/pending/skills/<name>/<change id>/`,
119+
outside every root the registry scans, with a unified diff, the scanner's findings (`Trinity.Skills.Scanner`:
120+
shell pipes into a shell, destructive commands, credential shapes and instructions to ignore or disable safety
121+
are `high`; plain shell commands, network calls, external URLs and base64 blobs `medium`; a file it cannot read
122+
as text is a `low` finding naming it) and a `skill_changes` row. The one path that moves a staged tree into a
123+
root is `Trinity.Skills.Promotion.swap/4`, and it requires an allowed `skill_apply` approval whose arguments
124+
name the change's id and digest (021's fingerprint binds them), recomputes the staged tree's digest, archives
125+
the previous version under `.history/`, renames the tree into place and writes an `effect` receipt on the
126+
`skills` chain scope carrying the digest and the approval id. The census (`test/trinity/skills/census_test.exs`)
127+
holds the tree to one caller of `swap` and two filesystem writers under `lib/trinity/skills/`, with a plant.
128+
Auto-approval is the persona's (`settings.skills.auto_approve`, off by default, `"low"` applies `none` and
129+
`low`); `medium` and `high` are never auto-approved, whatever a rule says. Proposing is itself a `:write` tool
130+
call under the default policy (an approval to propose); "always allow" on `skill_manage` makes proposing free
131+
while the promotion stays gated. An approval may have no session (a change approved from the page): its topic
132+
is `approvals:none` and `approvals:all`. Hub installation is not built; a skill dropped by hand into the user
133+
root loads as any other and is not scanned (a follow-up in the slice's NOTES).
134+
117135
## Sandbox (Slice 110)
118136

119137
- Luerl with reduction limits, no `os`/`io`/`require`, no filesystem; explicit host functions only.

‎lib/trinity.ex‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ defmodule Trinity do
5555
Skills.Skill,
5656
Skills.Registry,
5757
Skills.Sources,
58+
Skills.Change,
59+
Skills.Staging,
60+
Skills.Promotion,
61+
Skills.Manager,
62+
Skills.Learn,
5863
CorePolicy,
5964
Receipts,
6065
Receipts.Receipt,

‎lib/trinity/permissions.ex‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ defmodule Trinity.Permissions do
157157
@doc "The PubSub topic of a session's approvals, or of all of them."
158158
@spec topic(String.t() | :all) :: String.t()
159159
def topic(:all), do: "approvals:all"
160+
def topic(nil), do: "approvals:none"
160161
def topic(session_id), do: "approvals:" <> session_id
161162

162163
@doc "Subscribes the caller to `{:approval, :requested | :decided, %Approval{}}` for a session or all."

‎lib/trinity/permissions/approval.ex‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ defmodule Trinity.Permissions.Approval do
4545
def request_changeset(approval, attrs) do
4646
approval
4747
|> cast(attrs, [:session_id, :tool, :args, :risk, :fingerprint, :expires_at])
48-
|> validate_required([:session_id, :tool, :risk, :fingerprint, :expires_at])
48+
# Slice 041: a request may have no session (a staged skill change approved from the
49+
# page); its topic is `approvals:none` and `approvals:all`, its scope `session:none`.
50+
|> validate_required([:tool, :risk, :fingerprint, :expires_at])
4951
|> foreign_key_constraint(:session_id)
5052
end
5153

0 commit comments

Comments
 (0)