@securecoms/agent-runtime 0.1.0 → 0.2.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.
- package/PROVENANCE.md +9 -0
- package/README.md +163 -15
- package/dist/bin.js +1000 -58
- package/package.json +10 -1
- package/persona/AGENTS.md +17 -0
- package/persona/IDENTITY.md +13 -0
- package/persona/SOUL.md +15 -0
- package/skills/current_time/SKILL.md +11 -0
- package/skills/fetch_url/SKILL.md +13 -0
- package/skills/http_request/SKILL.md +21 -0
- package/skills/remember/SKILL.md +13 -0
- package/skills/sql_query/SKILL.md +21 -0
- package/skills/web_search/SKILL.md +15 -0
package/PROVENANCE.md
CHANGED
|
@@ -26,3 +26,12 @@ own transport, so no row is seeded for it here.
|
|
|
26
26
|
|
|
27
27
|
This table is complete as of 2026-09-08 and should be re-reviewed if the
|
|
28
28
|
source repo's files move or are re-cut.
|
|
29
|
+
|
|
30
|
+
## SKILL.md format (2026-09-09)
|
|
31
|
+
|
|
32
|
+
The `SKILL.md` + YAML-frontmatter skill layout follows the convention used by
|
|
33
|
+
Hermes Agent, OpenClaw and Claude Code, so a skill written for one of those is
|
|
34
|
+
recognisable here. **No code was cloned from any of them.** The frontmatter
|
|
35
|
+
parser in `src/skill-md.ts` is original and deliberately accepts only the
|
|
36
|
+
documented subset; the built-ins are written against this package's own
|
|
37
|
+
`BuiltinContext`.
|
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ connection this process serves.
|
|
|
20
20
|
```
|
|
21
21
|
securecoms-agent install --identity <identity_id> [--llm-endpoint <url>] \
|
|
22
22
|
[--llm-model <model>] [--llm-key-file <path>] [--skills-dir <dir>] \
|
|
23
|
-
[--data-dir <dir>] [--
|
|
23
|
+
[--data-dir <dir>] [--brave-key-file <path>] [--tavily-key-file <path>] \
|
|
24
|
+
[--skills-config <path>] [--exec-path <node>]
|
|
24
25
|
securecoms-agent install --identity <identity_id> --dry-run
|
|
25
26
|
securecoms-agent uninstall --identity <identity_id>
|
|
26
27
|
```
|
|
@@ -28,13 +29,15 @@ securecoms-agent uninstall --identity <identity_id>
|
|
|
28
29
|
Every install flag also reads its `SECURECOMS_*` variable from the environment
|
|
29
30
|
when the flag is absent (`SECURECOMS_IDENTITY`, `SECURECOMS_LLM_ENDPOINT`,
|
|
30
31
|
`SECURECOMS_LLM_MODEL`, `SECURECOMS_LLM_API_KEY_FILE`, `SECURECOMS_SKILLS_DIR`,
|
|
31
|
-
`SECURECOMS_DATA_DIR`
|
|
32
|
+
`SECURECOMS_DATA_DIR`, `SECURECOMS_BRAVE_KEY_FILE`, `SECURECOMS_TAVILY_KEY_FILE`,
|
|
33
|
+
`SECURECOMS_SKILLS_CONFIG`), so an operator who already exports the run-mode
|
|
32
34
|
environment can just run `securecoms-agent install`. The flag always wins.
|
|
33
35
|
`--exec-path` is the one exception: it is never taken from the environment.
|
|
34
36
|
`--dry-run` prints the resolved Node path, the service file path, the log
|
|
35
|
-
path, the key file, and the
|
|
36
|
-
|
|
37
|
-
it
|
|
37
|
+
path, the key file, the brave/tavily key files and the skills config path —
|
|
38
|
+
each as a PATH only, never a key value — and the rendered plist/unit
|
|
39
|
+
**without writing anything or calling `launchctl`/`systemctl`** — use it to
|
|
40
|
+
inspect an install before making it.
|
|
38
41
|
|
|
39
42
|
`install` writes a per-user service that starts the agent at login and
|
|
40
43
|
restarts it on crash — a launchd LaunchAgent at
|
|
@@ -88,10 +91,13 @@ All configuration is via `SECURECOMS_*` environment variables (see
|
|
|
88
91
|
| `SECURECOMS_LLM_MODEL` | `llama3` | Model name to request |
|
|
89
92
|
| `SECURECOMS_LLM_API_KEY` | unset | API key for the LLM endpoint, if it requires one |
|
|
90
93
|
| `SECURECOMS_LLM_API_KEY_FILE` | unset (or `--llm-key-file <path>`) | File holding the API key (trimmed on read). Preferred: it wins over `SECURECOMS_LLM_API_KEY`, and it is the only key form the service installer accepts. An unreadable or empty file exits 2 |
|
|
94
|
+
| `SECURECOMS_BRAVE_KEY_FILE` | unset (or `--brave-key-file <path>`) | File holding a Brave Search API key. A path, never a value — required for `web_search` |
|
|
95
|
+
| `SECURECOMS_TAVILY_KEY_FILE` | unset (or `--tavily-key-file <path>`) | File holding a Tavily API key. Used only when the Brave key file is absent |
|
|
91
96
|
| `SECURECOMS_SKILLS_DIR` | this package's own `skills/` | Directory of skill manifests to load |
|
|
92
97
|
| `SECURECOMS_DATA_DIR` | `~/.securecoms/agent-runtime` | Root directory for per-channel state |
|
|
98
|
+
| `SECURECOMS_SKILLS_CONFIG` | `<dataDir>/skills.json` (or `--skills-config <path>`) | Path to `skills.json` — named endpoints, databases and flags |
|
|
93
99
|
| `SECURECOMS_LOG_LEVEL` | `info` | One of `debug`, `info`, `warn`, `error` |
|
|
94
|
-
| `SECURECOMS_TOOL_ALLOW` | `echo` | Comma-separated tool allow-list |
|
|
100
|
+
| `SECURECOMS_TOOL_ALLOW` | `echo`, `web_search`, `fetch_url`, `http_request`, `sql_query`, `current_time`, `remember` | Comma-separated tool allow-list |
|
|
95
101
|
| `SECURECOMS_TOOL_DENY` | unset | Comma-separated tool deny-list (wins over allow) |
|
|
96
102
|
| `SECURECOMS_TOOL_DEFAULT` | `deny` | Fallback policy for a tool matched by neither list |
|
|
97
103
|
|
|
@@ -103,21 +109,163 @@ history and captured memories never leak into another channel's turn.
|
|
|
103
109
|
|
|
104
110
|
## Adding a skill
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
manifest needs `name`, `description`, a JSON-Schema `parameters` object, and
|
|
109
|
-
a `handler`:
|
|
112
|
+
A skill is either a JSON manifest or a `SKILL.md` file, dropped under
|
|
113
|
+
`SECURECOMS_SKILLS_DIR` in one of three layouts:
|
|
110
114
|
|
|
111
|
-
- `
|
|
112
|
-
|
|
115
|
+
- flat `<skillsDir>/<name>.json`
|
|
116
|
+
- `<skillsDir>/<dir>/manifest.json`
|
|
117
|
+
- `<skillsDir>/<dir>/SKILL.md`
|
|
118
|
+
|
|
119
|
+
All three keep working side by side. The loader reads them in that order —
|
|
120
|
+
flat `.json`, then `manifest.json`, then `SKILL.md` — sorted within each
|
|
121
|
+
bucket for a deterministic result; if two files declare the same skill name,
|
|
122
|
+
the one loaded later (later bucket, or later alphabetically) wins, with a
|
|
123
|
+
warning naming both paths.
|
|
124
|
+
|
|
125
|
+
A `SKILL.md` is YAML frontmatter (between two `---` lines) followed by
|
|
126
|
+
free-text guidance appended to the model-facing description (capped at 1 kB).
|
|
127
|
+
The frontmatter accepts a deliberately small subset of YAML — anything else
|
|
128
|
+
throws with the file and line, rather than silently misreading it:
|
|
129
|
+
|
|
130
|
+
- scalars (`key: value`, optionally quoted)
|
|
131
|
+
- flow lists (`key: [a, b, c]`)
|
|
132
|
+
- flow maps (`key: { a: 1, b: 2 }`)
|
|
133
|
+
- nested mappings (an indented block under a bare `key:`), two-space indents
|
|
134
|
+
only, no tabs
|
|
135
|
+
- comments (`#`) and blank lines
|
|
136
|
+
- block scalars (`|`, `>`), anchors, aliases and other YAML constructs are
|
|
137
|
+
**not** supported — use a JSON manifest for anything that needs them
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
---
|
|
143
|
+
name: web_search
|
|
144
|
+
description: Search the web for recent information
|
|
145
|
+
parameters:
|
|
146
|
+
type: object
|
|
147
|
+
properties:
|
|
148
|
+
query: { type: string }
|
|
149
|
+
count: { type: number }
|
|
150
|
+
required: [query]
|
|
151
|
+
handler: builtin:web_search
|
|
152
|
+
requires: [search_provider]
|
|
153
|
+
---
|
|
154
|
+
Use this when the user asks about something time-sensitive or outside your
|
|
155
|
+
training data. Prefer a specific query over a broad one.
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`name`, `description`, `parameters` (a nested mapping) and `handler` are
|
|
159
|
+
required. `handler` is one of:
|
|
160
|
+
|
|
161
|
+
- `builtin:<name>` — one of the runtime's built-in handlers (see "Curated
|
|
162
|
+
skills" below).
|
|
113
163
|
- `script:<path>` — an executable file, path relative to `skillsDir` and
|
|
114
164
|
contained within it (symlinks included — a link that resolves outside
|
|
115
165
|
`skillsDir` is refused). It runs with its arguments as JSON on stdin, cwd
|
|
116
166
|
set to `skillsDir`, and a minimal environment (no inherited secrets).
|
|
117
167
|
|
|
118
|
-
A manifest with any other handler shape
|
|
119
|
-
|
|
120
|
-
rather than dropping the turn.
|
|
168
|
+
A manifest or `SKILL.md` with any other handler shape, or a missing required
|
|
169
|
+
field, is skipped with a warning, and a skill invocation that throws is
|
|
170
|
+
turned into a `tool_failed` tool message rather than dropping the turn.
|
|
171
|
+
|
|
172
|
+
### Gating a skill on configuration
|
|
173
|
+
|
|
174
|
+
A manifest or `SKILL.md` may carry `requires: [...]`, and the skill is offered
|
|
175
|
+
to the model only when every entry is configured: `endpoint:<name>` and
|
|
176
|
+
`database:<name>` name entries in `skills.json`, `search_provider` means a
|
|
177
|
+
readable, non-empty search key file, `any_endpoint` / `any_database` mean at
|
|
178
|
+
least one endpoint or database is configured, and anything else is matched
|
|
179
|
+
against `skills.json`'s `flags`.
|
|
180
|
+
|
|
181
|
+
The shipped `http_request` and `sql_query` skills declare `requires:
|
|
182
|
+
[any_endpoint]` and `requires: [any_database]`: the endpoint and database names
|
|
183
|
+
belong to the customer, so the shipped copies cannot name one, but neither is
|
|
184
|
+
offered to the model until `skills.json` gives it something to reach. With no
|
|
185
|
+
endpoints configured, startup prints `unavailable: http_request (missing
|
|
186
|
+
any_endpoint)`. To narrow one to a particular system, copy the folder and change
|
|
187
|
+
the line — for example `requires: [endpoint:crm]` in
|
|
188
|
+
`skills/http_request/SKILL.md`.
|
|
189
|
+
|
|
190
|
+
## Curated skills
|
|
191
|
+
|
|
192
|
+
Seven built-in handlers ship with the runtime. All network built-ins refuse
|
|
193
|
+
`localhost` and any private, loopback or link-local resolved address, and
|
|
194
|
+
every tool result is capped at 20 kB (truncated, not an error). A redirect to a
|
|
195
|
+
different origin is followed without the caller's headers and without the
|
|
196
|
+
request body, so a configured header value or a search key can never be handed
|
|
197
|
+
to a host the operator did not name.
|
|
198
|
+
|
|
199
|
+
| Skill | Arguments | Notes |
|
|
200
|
+
| --- | --- | --- |
|
|
201
|
+
| `echo` | `text` | Returns the text unchanged. No `requires`. |
|
|
202
|
+
| `current_time` | `timezone` (an IANA name such as `America/New_York`; defaults to UTC) | The server clock, ISO 8601. No `requires`. |
|
|
203
|
+
| `fetch_url` | `url` | Fetches a page and returns extracted text (HTML is stripped to text). Up to 3 redirects, 10 s timeout, 2 MB response cap. |
|
|
204
|
+
| `web_search` | `query`, `count` (1–10, default 5) | `requires: [search_provider]` — needs a readable, non-empty `SECURECOMS_BRAVE_KEY_FILE` or `SECURECOMS_TAVILY_KEY_FILE`; Brave is used first when both are set. |
|
|
205
|
+
| `http_request` | `name` (a configured `endpoint:<name>`), `path` (a query string is kept, a `#fragment` is dropped), `method` (GET/POST/PUT/DELETE), `body` | `requires: [any_endpoint]`. The model names an endpoint and a path, never a host; a path that would leave the endpoint's `baseUrl` origin is refused. |
|
|
206
|
+
| `sql_query` | `name` (a configured `database:<name>`), `sql` (read-only `SELECT` only; a `;` or a write keyword anywhere in the text — inside a string literal or a comment included — is refused) | `requires: [any_database]`. SQLite or Postgres (`pg` required separately for Postgres). Results capped at 200 rows. |
|
|
207
|
+
| `remember` | `fact` (≤300 chars) | Appends a dated line to the agent-wide `MEMORY.md`. |
|
|
208
|
+
|
|
209
|
+
## skills.json
|
|
210
|
+
|
|
211
|
+
`skills.json` names the endpoints and databases the curated skills are
|
|
212
|
+
allowed to reach, plus arbitrary boolean `flags` for gating. It carries paths,
|
|
213
|
+
never secrets — a header value or a Postgres connection string is read from
|
|
214
|
+
the file it names, at call time, and never logged. Relative paths (a
|
|
215
|
+
`sqlite`, `postgresUrlFile` or `headersFile` value) resolve against the data
|
|
216
|
+
dir, not the current working directory.
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"endpoints": {
|
|
221
|
+
"crm": { "baseUrl": "https://crm.example.com/api", "headersFile": "crm-headers.json" }
|
|
222
|
+
},
|
|
223
|
+
"databases": {
|
|
224
|
+
"reports": { "sqlite": "reports.db" },
|
|
225
|
+
"warehouse": { "postgresUrlFile": "warehouse-url.txt" }
|
|
226
|
+
},
|
|
227
|
+
"flags": ["beta_tools"]
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
A `headersFile` is a flat JSON object of header name to string value, read
|
|
232
|
+
fresh on every `http_request` call — never cached, never logged. A Postgres
|
|
233
|
+
database needs the optional `pg` package installed separately
|
|
234
|
+
(`npm i -g pg`); SQLite needs nothing extra (`node:sqlite`).
|
|
235
|
+
|
|
236
|
+
By default `skills.json` lives at `<dataDir>/skills.json`; point elsewhere
|
|
237
|
+
with `SECURECOMS_SKILLS_CONFIG` or `--skills-config <path>`. A missing file
|
|
238
|
+
means no endpoints, no databases and no flags — not an error.
|
|
239
|
+
|
|
240
|
+
## Persona and memory
|
|
241
|
+
|
|
242
|
+
Three optional files in the data dir make up an agent's persona, read fresh
|
|
243
|
+
on **every turn** — no restart needed after an edit:
|
|
244
|
+
|
|
245
|
+
| File | Heading |
|
|
246
|
+
| --- | --- |
|
|
247
|
+
| `IDENTITY.md` | `## Identity` |
|
|
248
|
+
| `SOUL.md` | `## Soul` |
|
|
249
|
+
| `AGENTS.md` | `## Operating rules` |
|
|
250
|
+
|
|
251
|
+
Whichever of the three exist are joined, in that fixed order, under their
|
|
252
|
+
fixed headings; each is capped at 8 kB (truncated, with a warning, if
|
|
253
|
+
larger). If none exist, a built-in default prompt is used instead. Either
|
|
254
|
+
way, a fixed, non-removable safety preamble is appended last, so it is the
|
|
255
|
+
part of the prompt read most recently.
|
|
256
|
+
|
|
257
|
+
`securecoms-agent persona init [--identity <id>] [--data-dir <dir>]` copies
|
|
258
|
+
the shipped default persona files into the data dir — it never overwrites a
|
|
259
|
+
file that already exists, so a customized `IDENTITY.md` is safe to keep
|
|
260
|
+
across a re-init. `securecoms-agent persona show` prints the assembled system
|
|
261
|
+
prompt exactly as a turn would see it, useful for checking an edit landed.
|
|
262
|
+
|
|
263
|
+
Separately, `MEMORY.md` in the data dir is **agent-wide** (not per-persona,
|
|
264
|
+
not per-channel): the `remember` skill appends a dated, one-line fact to it,
|
|
265
|
+
and its contents are injected as a system message on every turn across every
|
|
266
|
+
channel. It is capped at 8 kB; once it overflows, the **oldest** lines are
|
|
267
|
+
dropped first, on the theory that the newest remembered facts are the ones
|
|
268
|
+
still likely to be true.
|
|
121
269
|
|
|
122
270
|
## Provenance
|
|
123
271
|
|