@amalgm/agents 0.2.1 → 0.2.2
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/PURPOSE.md +25 -24
- package/README.md +21 -47
- package/dist/agents.d.ts +4 -26
- package/dist/agents.js +4 -87
- package/dist/bin/mcp.js +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +2 -13
- package/dist/cli/open.d.ts +1 -1
- package/dist/cli/open.js +1 -5
- package/dist/cli/run.js +3 -8
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +0 -2
- package/dist/http/server.js +1 -7
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/mcp/agent-tools.js +1 -1
- package/dist/mcp/tools.js +1 -2
- package/dist/rows.d.ts +1 -4
- package/dist/rows.js +0 -39
- package/dist/schema.js +53 -101
- package/dist/types.d.ts +0 -106
- package/docs/ARCHITECTURE.md +21 -33
- package/docs/CLI.md +3 -43
- package/docs/DATA_MODEL.md +7 -46
- package/docs/DEFINITIONS.md +4 -4
- package/docs/ENGINE_INTEGRATION.md +9 -71
- package/docs/MCP.md +3 -19
- package/docs/REST.md +14 -97
- package/docs/SDK.md +12 -61
- package/docs/SECURITY.md +6 -35
- package/docs/SHELL_INTEGRATION.md +29 -0
- package/examples/basic.ts +8 -22
- package/package.json +2 -2
- package/skills/amalgm-agents/SKILL.md +231 -20
- package/skills/amalgm-agents/agents/openai.yaml +2 -2
- package/dist/cli/session-commands.d.ts +0 -4
- package/dist/cli/session-commands.js +0 -54
- package/dist/drivers.d.ts +0 -4
- package/dist/drivers.js +0 -25
- package/dist/event-store.d.ts +0 -13
- package/dist/event-store.js +0 -50
- package/dist/http/session-routes.d.ts +0 -2
- package/dist/http/session-routes.js +0 -67
- package/dist/http/stream.d.ts +0 -3
- package/dist/http/stream.js +0 -30
- package/dist/mcp/session-tools.d.ts +0 -3
- package/dist/mcp/session-tools.js +0 -81
- package/dist/messages.d.ts +0 -3
- package/dist/messages.js +0 -56
- package/dist/runtime.d.ts +0 -29
- package/dist/runtime.js +0 -176
- package/dist/session-store.d.ts +0 -15
- package/dist/session-store.js +0 -83
- package/dist/turn-store.d.ts +0 -31
- package/dist/turn-store.js +0 -164
- package/docs/DRIVERS.md +0 -72
package/docs/REST.md
CHANGED
|
@@ -1,103 +1,20 @@
|
|
|
1
1
|
# REST API
|
|
2
2
|
|
|
3
|
-
Create an embeddable server with `createRestServer` from
|
|
4
|
-
|
|
3
|
+
Create an embeddable server with `createRestServer` from `@amalgm/agents/http`,
|
|
4
|
+
or run `amalgm-agents-rest`. The CLI binds `127.0.0.1:4317` by default.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
token receives the kernel's canonical 401 from `@amalgm/core/transport`,
|
|
10
|
-
compared in constant time. `/healthz` is a minimal unauthenticated liveness
|
|
11
|
-
response. JSON bodies default to a 512 KB limit.
|
|
12
|
-
|
|
13
|
-
## Agent configuration
|
|
14
|
-
|
|
15
|
-
| Method | Path | Result |
|
|
16
|
-
|---|---|---|
|
|
17
|
-
| `GET` | `/agent-config` | List normalized configs from live agent revisions |
|
|
18
|
-
| `POST` | `/agent-config/get` | Read `{ agent_id }` |
|
|
19
|
-
| `POST` | `/agent-config/update` | Atomically apply `{ agent_id, config }` |
|
|
20
|
-
| `POST` | `/agent-config/import-native` | Import safe native fields for `{ agent_id, replace? }` |
|
|
21
|
-
|
|
22
|
-
Set `nativeHomeDir` when embedding the server or `--native-home`/
|
|
23
|
-
`AMALGM_NATIVE_HOME` with the REST binary. Native import intentionally ignores
|
|
24
|
-
auth files, secrets, and MCP server configuration.
|
|
25
|
-
|
|
26
|
-
## Agent bundles
|
|
27
|
-
|
|
28
|
-
| Method | Path | Result |
|
|
29
|
-
|---|---|---|
|
|
30
|
-
| `GET` | `/agent-bundles` | Read bundle-v2 capability and available agent heads |
|
|
31
|
-
| `POST` | `/agent-bundles/preview` | Export requested agent/app/automation/tool heads |
|
|
32
|
-
| `POST` | `/agent-bundles/install` | Validate and install `{ bundle, auth_ref? }` |
|
|
33
|
-
|
|
34
|
-
Embedding hosts inject `bundlePort` for resources owned by Apps, Automations,
|
|
35
|
-
and Toolbox. Agent-only bundles need no port. Bundles with external resources
|
|
36
|
-
fail explicitly when the owner port is absent.
|
|
37
|
-
|
|
38
|
-
## Agents
|
|
6
|
+
When configured, authentication uses `Authorization: Bearer <token>` or the
|
|
7
|
+
`x-amalgm-runtime-token` header. `/healthz` is unauthenticated. JSON bodies
|
|
8
|
+
default to a 512 KB limit.
|
|
39
9
|
|
|
40
10
|
| Method | Path | Result |
|
|
41
11
|
|---|---|---|
|
|
42
|
-
| `GET` | `/v1/agents` | List
|
|
43
|
-
| `POST` | `/v1/agents` | Create from complete
|
|
44
|
-
| `GET` | `/v1/agents/:id` | Read current definition |
|
|
45
|
-
| `PATCH` | `/v1/agents/:id` |
|
|
46
|
-
| `DELETE` | `/v1/agents/:id` | Tombstone
|
|
47
|
-
|
|
48
|
-
Use `?include_deleted=true` on list or get to include tombstones.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
| Method | Path | Result |
|
|
53
|
-
|---|---|---|
|
|
54
|
-
| `GET` | `/v1/sessions` | List sessions |
|
|
55
|
-
| `POST` | `/v1/sessions` | Start `{ agentId, revisionId?, sessionId?, metadata? }` |
|
|
56
|
-
| `GET` | `/v1/sessions/:id` | Read session |
|
|
57
|
-
| `DELETE` | `/v1/sessions/:id` | Archive inactive session |
|
|
58
|
-
| `GET` | `/v1/sessions/:id/turns` | List turns |
|
|
59
|
-
| `GET` | `/v1/sessions/:id/events` | List ordered events |
|
|
60
|
-
| `POST` | `/v1/sessions/:id/messages` | Send a turn |
|
|
61
|
-
| `POST` | `/v1/sessions/:id/cancel` | Cancel active turn |
|
|
62
|
-
|
|
63
|
-
List filters are `agent_id` and `include_archived=true`. Event pagination uses
|
|
64
|
-
`after` and `limit` (maximum 1000).
|
|
65
|
-
|
|
66
|
-
Send a message:
|
|
67
|
-
|
|
68
|
-
```json
|
|
69
|
-
{
|
|
70
|
-
"message": "Review the current diff.",
|
|
71
|
-
"idempotencyKey": "review-001",
|
|
72
|
-
"wait": true
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
`message` may also be a structured `AgentMessage`. `wait: false` returns `202`
|
|
77
|
-
after durable acceptance; otherwise the response waits for a terminal turn.
|
|
78
|
-
|
|
79
|
-
## Server-sent events
|
|
80
|
-
|
|
81
|
-
Connect to:
|
|
82
|
-
|
|
83
|
-
```text
|
|
84
|
-
GET /v1/sessions/:id/events/stream?after=<last-sequence>
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
The stream sends committed backlog first and then live events. SSE `id` equals
|
|
88
|
-
the session sequence. Reconnect with the last seen id as `after`. Heartbeat
|
|
89
|
-
comments are sent every 15 seconds.
|
|
90
|
-
|
|
91
|
-
## Errors
|
|
92
|
-
|
|
93
|
-
Errors use the HTTP status associated with `AgentError`:
|
|
94
|
-
|
|
95
|
-
```json
|
|
96
|
-
{
|
|
97
|
-
"error": {
|
|
98
|
-
"code": "conflict",
|
|
99
|
-
"message": "Session already has an active turn.",
|
|
100
|
-
"details": {}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
```
|
|
12
|
+
| `GET` | `/v1/agents` | List available agents |
|
|
13
|
+
| `POST` | `/v1/agents` | Create from a complete definition |
|
|
14
|
+
| `GET` | `/v1/agents/:id` | Read the current definition |
|
|
15
|
+
| `PATCH` | `/v1/agents/:id` | Apply a definition patch |
|
|
16
|
+
| `DELETE` | `/v1/agents/:id` | Tombstone an identity |
|
|
17
|
+
|
|
18
|
+
Use `?include_deleted=true` on list or get to include tombstones. The same
|
|
19
|
+
server also exposes agent configuration, installed skills, and bundle routes
|
|
20
|
+
documented by their SDK modules. It exposes no conversation routes.
|
package/docs/SDK.md
CHANGED
|
@@ -1,72 +1,23 @@
|
|
|
1
1
|
# SDK reference
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
durable before
|
|
5
|
-
|
|
6
|
-
## Construction
|
|
3
|
+
`Agents` is the public behavior boundary for the local agent registry. Every
|
|
4
|
+
mutation is durable before it returns.
|
|
7
5
|
|
|
8
6
|
```ts
|
|
9
|
-
const agents = new Agents({
|
|
10
|
-
stateDir: './state',
|
|
11
|
-
drivers: [codexDriver],
|
|
12
|
-
maxInputBytes: 256_000,
|
|
13
|
-
maxEventBytes: 256_000,
|
|
14
|
-
turnTimeoutMs: 600_000,
|
|
15
|
-
});
|
|
7
|
+
const agents = new Agents({ stateDir: './state' });
|
|
16
8
|
```
|
|
17
9
|
|
|
18
|
-
`databasePath` overrides the database location directly. `
|
|
19
|
-
|
|
20
|
-
for them to settle, and close SQLite.
|
|
21
|
-
|
|
22
|
-
## Agent methods
|
|
10
|
+
`databasePath` overrides the database location directly. Call `close()` when
|
|
11
|
+
the embedding process releases the registry.
|
|
23
12
|
|
|
24
13
|
| Method | Behavior |
|
|
25
14
|
|---|---|
|
|
26
|
-
| `createAgent(definition)` | Create identity and revision 1
|
|
27
|
-
| `updateAgent(id, patch)` |
|
|
28
|
-
| `deleteAgent(id)` | Tombstone the identity while retaining
|
|
15
|
+
| `createAgent(definition)` | Create an identity and immutable revision 1 |
|
|
16
|
+
| `updateAgent(id, patch)` | Apply a patch, appending a revision when content changes |
|
|
17
|
+
| `deleteAgent(id)` | Tombstone the identity while retaining its revisions |
|
|
29
18
|
| `getAgent(id, includeDeleted?)` | Read the current definition |
|
|
30
|
-
| `listAgents(includeDeleted?)` | List
|
|
31
|
-
|
|
32
|
-
## Session methods
|
|
33
|
-
|
|
34
|
-
| Method | Behavior |
|
|
35
|
-
|---|---|
|
|
36
|
-
| `startSession(input)` | Pin a new session to the current or named revision |
|
|
37
|
-
| `talk(agentId, input)` | Start or continue a session, then enqueue a turn |
|
|
38
|
-
| `enqueue(sessionId, input)` | Return immediately with the turn and completion promise |
|
|
39
|
-
| `send(sessionId, input)` | Wait for the turn's terminal state |
|
|
40
|
-
| `cancelSession(id)` | Persist cancellation intent and signal its active driver |
|
|
41
|
-
| `archiveSession(id)` | Archive an inactive session |
|
|
42
|
-
| `listSessions(agentId?, includeArchived?)` | Discover durable sessions |
|
|
43
|
-
| `listTurns(sessionId)` | Read accepted inputs and outcomes |
|
|
44
|
-
| `listEvents(sessionId, after?, limit?)` | Read ordered events |
|
|
45
|
-
|
|
46
|
-
`enqueue` returns `{ turn, completion, duplicate }`. If an idempotency key was
|
|
47
|
-
already accepted, `duplicate` is true and the original turn is returned.
|
|
48
|
-
|
|
49
|
-
## Event subscription
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
const unsubscribe = agents.subscribe(session.id, (event) => {
|
|
53
|
-
console.log(event.sequence, event.type, event.data);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
await agents.send(session.id, { message: 'Hello' });
|
|
57
|
-
unsubscribe();
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Listeners receive events only after commit. To build a reconnectable consumer,
|
|
61
|
-
read `listEvents(after)` before subscribing and de-duplicate by sequence, as
|
|
62
|
-
the packaged SSE adapter does.
|
|
63
|
-
|
|
64
|
-
## Errors
|
|
65
|
-
|
|
66
|
-
Expected failures use `AgentError` with a stable code:
|
|
67
|
-
|
|
68
|
-
`invalid_input`, `not_found`, `conflict`, `deleted`, `driver_unavailable`,
|
|
69
|
-
`too_large`, or `cancelled`.
|
|
19
|
+
| `listAgents(includeDeleted?)` | List available agents by default |
|
|
20
|
+
| `getAgentRevision(agentId, revisionId?)` | Resolve the current or exact immutable revision |
|
|
70
21
|
|
|
71
|
-
|
|
72
|
-
|
|
22
|
+
Expected failures use `AgentError` with stable codes: `invalid_input`,
|
|
23
|
+
`not_found`, `conflict`, `deleted`, and `too_large`.
|
package/docs/SECURITY.md
CHANGED
|
@@ -1,38 +1,9 @@
|
|
|
1
1
|
# Security model
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Agent definitions contain `authRef`, never credentials. Toolbox ids, skill ids,
|
|
4
|
+
file references, and subagent ids are requests, not authority; Shell resolves
|
|
5
|
+
and authorizes them while preparing a Chat execution.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
reference at execution time through the host's credential boundary. Do not put
|
|
10
|
-
tokens, cookies, private keys, or passwords in `driver.config`, `metadata`,
|
|
11
|
-
messages, or events.
|
|
12
|
-
|
|
13
|
-
## Capability references
|
|
14
|
-
|
|
15
|
-
Toolbox ids and subagent ids are requests, not authority. The composing host
|
|
16
|
-
must resolve them against the caller's allowed Toolbox view and agent policy.
|
|
17
|
-
Agents never infers permission merely because an id appears in a definition.
|
|
18
|
-
|
|
19
|
-
## REST exposure
|
|
20
|
-
|
|
21
|
-
The REST CLI binds `127.0.0.1` by default. Configure a bearer token before
|
|
22
|
-
binding to a LAN, container bridge, tunnel, or public interface. The packaged
|
|
23
|
-
server intentionally does not implement browser sessions, CORS, TLS, user
|
|
24
|
-
accounts, or cloud authorization; Engine supplies those outer boundaries.
|
|
25
|
-
|
|
26
|
-
## Driver trust
|
|
27
|
-
|
|
28
|
-
Drivers execute inside the hosting process and are trusted code. A driver can
|
|
29
|
-
read its process environment and access the filesystem with that process's
|
|
30
|
-
permissions. Load only reviewed modules. Native process drivers should avoid a
|
|
31
|
-
shell, pass explicit arguments, bound output, and kill their child on abort.
|
|
32
|
-
|
|
33
|
-
## Stored data
|
|
34
|
-
|
|
35
|
-
SQLite files are created beneath a mode-`0700` state directory. Application
|
|
36
|
-
packaging and backups must preserve appropriate filesystem permissions.
|
|
37
|
-
Messages and driver events may contain sensitive user content; retention and
|
|
38
|
-
export policies belong to the embedding product.
|
|
7
|
+
The REST CLI binds loopback by default. Configure a bearer token before
|
|
8
|
+
binding it to a shared interface. SQLite state is created beneath a private
|
|
9
|
+
state directory and should be backed up with equivalent file permissions.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Shell integration
|
|
2
|
+
|
|
3
|
+
Shell is the composition root. Agents supplies the canonical registry and Chat
|
|
4
|
+
supplies the canonical conversation runtime.
|
|
5
|
+
|
|
6
|
+
## Resolver boundary
|
|
7
|
+
|
|
8
|
+
Shell gives Chat a resolver with one job: map an installed agent id and optional
|
|
9
|
+
revision id to `agents.getAgentRevision(agentId, revisionId)`, then project that
|
|
10
|
+
immutable definition into Chat's execution contract. The resolver never writes
|
|
11
|
+
either product's store.
|
|
12
|
+
|
|
13
|
+
Chat owns the conversation, accepted prompt, provider session, ordered durable
|
|
14
|
+
updates, transcript, usage, reconnect, and interrupt lifecycle. Agents owns the
|
|
15
|
+
identity and revision selected by that execution. Neither product imports the
|
|
16
|
+
other's database.
|
|
17
|
+
|
|
18
|
+
Tool and action ids are resolved through `@amalgm/tools`; `authRef` is resolved
|
|
19
|
+
through the credential boundary. The resulting concrete values are frozen by
|
|
20
|
+
Chat before it accepts a turn.
|
|
21
|
+
|
|
22
|
+
## Integration checks
|
|
23
|
+
|
|
24
|
+
- create, update, list, delete, and exact revision resolution use `Agents`;
|
|
25
|
+
- editing an agent never changes a Chat execution that already names a revision;
|
|
26
|
+
- Toolbox and credential references are authorized during preparation;
|
|
27
|
+
- provider restart, reconnect, and interrupt are handled only by Chat; and
|
|
28
|
+
- agent-to-agent interaction creates or joins a Chat conversation through the
|
|
29
|
+
same prepared-execution protocol as the UI.
|
package/examples/basic.ts
CHANGED
|
@@ -1,31 +1,17 @@
|
|
|
1
|
-
import { Agents
|
|
1
|
+
import { Agents } from '@amalgm/agents';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
id: 'echo',
|
|
5
|
-
async run(request) {
|
|
6
|
-
return {
|
|
7
|
-
driverSessionId: request.driverSessionId || `echo-${request.session.id}`,
|
|
8
|
-
message: {
|
|
9
|
-
role: 'assistant',
|
|
10
|
-
parts: [{ type: 'text', text: `Echo: ${messageText(request.input)}` }],
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
},
|
|
14
|
-
});
|
|
3
|
+
const agents = new Agents({ stateDir: './example-state' });
|
|
15
4
|
|
|
16
|
-
|
|
17
|
-
const agents = new Agents({ stateDir: './example-state', drivers: [echo] });
|
|
5
|
+
try {
|
|
18
6
|
agents.createAgent({
|
|
19
7
|
id: 'reviewer',
|
|
20
8
|
name: 'Reviewer',
|
|
21
|
-
driver: '
|
|
9
|
+
driver: 'codex',
|
|
22
10
|
instructions: 'Review carefully.',
|
|
23
11
|
});
|
|
24
12
|
|
|
25
|
-
const
|
|
26
|
-
console.log(
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
const revision = agents.getAgentRevision('reviewer');
|
|
14
|
+
console.log(revision.id, revision.definition);
|
|
15
|
+
} finally {
|
|
16
|
+
agents.close();
|
|
29
17
|
}
|
|
30
|
-
|
|
31
|
-
void main();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amalgm/agents",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Local-first agent
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Local-first agent identities, installations, and immutable revisions.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
7
7
|
"repository": {
|
|
@@ -1,30 +1,241 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: amalgm-agents
|
|
3
|
-
description:
|
|
3
|
+
description: Manage persistent Amalgm agent identities and their immutable revisions with the @amalgm/agents SDK, CLI, REST API, or MCP tools. Use when you need to define an agent (its harness, model, instructions, tools, skills, and workspace), list or inspect existing agents, edit or delete one, or resolve the exact revision to run through Chat.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Agents
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
`amalgm-agents` CLI command.
|
|
8
|
+
Agents is the registry for durable agent definitions on the Amalgm platform. It stores three things: **identities** (a stable id like `reviewer` that survives renames and edits), **installations** (the agent identities available on the local machine), and **immutable revisions** (append-only snapshots of every definition an identity has ever had). You define an agent once — its harness, model, instructions, tools, skills, and workspace — and every surface on the machine (SDK, CLI, REST, MCP, and the Amalgm apps) reads and writes the same local SQLite registry. Agents does not run conversations: it resolves an exact revision, and Chat executes it.
|
|
10
9
|
|
|
11
|
-
##
|
|
10
|
+
## Installation
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
2. Create from a complete definition.
|
|
15
|
-
3. Update only the fields the user requested.
|
|
16
|
-
4. Delete only when the user explicitly asks.
|
|
17
|
-
5. Keep tool, skill, file, credential, and subagent values as references; do
|
|
18
|
-
not copy those products into the agent definition.
|
|
12
|
+
Install the package from npm. It requires Node 20 or later.
|
|
19
13
|
|
|
20
|
-
|
|
14
|
+
```bash
|
|
15
|
+
npm install @amalgm/agents
|
|
16
|
+
```
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
ambiguous.
|
|
24
|
-
2. Call `talk_to_agent` without a session ID to start a session.
|
|
25
|
-
3. Preserve and return the resulting session ID.
|
|
26
|
-
4. Pass that ID to continue the same session.
|
|
27
|
-
5. Use conversation/session inspection to recover durable results or history.
|
|
28
|
-
6. Use background execution for work likely to outlive one MCP request.
|
|
18
|
+
The install also provides three executables: `amalgm-agents` (CLI), `amalgm-agents-mcp` (stdio MCP server), and `amalgm-agents-rest` (loopback REST server). Run them with `npx` or add the package's `.bin` directory to your `PATH`.
|
|
29
19
|
|
|
30
|
-
|
|
20
|
+
```bash
|
|
21
|
+
npx amalgm-agents --help
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
Create an agent, list what exists, and resolve the revision you would hand to Chat. The `Agents` class is synchronous and local-first — every mutation is written to disk before the call returns.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Agents } from '@amalgm/agents';
|
|
30
|
+
|
|
31
|
+
const agents = new Agents({ stateDir: './state' });
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Define and create an agent. `id`, `name`, and `driver` are required;
|
|
35
|
+
// everything else has a sensible empty default.
|
|
36
|
+
agents.createAgent({
|
|
37
|
+
id: 'reviewer',
|
|
38
|
+
name: 'Reviewer',
|
|
39
|
+
description: 'Reviews implementation work',
|
|
40
|
+
driver: 'codex',
|
|
41
|
+
instructions: 'Find correctness risks before style issues.',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// List every available (non-deleted) agent.
|
|
45
|
+
for (const agent of agents.listAgents()) {
|
|
46
|
+
console.log(agent.id, agent.definition.name, `rev ${agent.currentRevision}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Inspect one agent's current definition.
|
|
50
|
+
const record = agents.getAgent('reviewer');
|
|
51
|
+
console.log(record?.definition.instructions);
|
|
52
|
+
|
|
53
|
+
// Resolve the exact immutable revision a host such as Chat would execute.
|
|
54
|
+
const revision = agents.getAgentRevision('reviewer');
|
|
55
|
+
console.log(revision.id, revision.number, revision.hash);
|
|
56
|
+
} finally {
|
|
57
|
+
agents.close();
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
By default the registry lives at `~/.amalgm/users/local/agents/agents.db`. Pass `stateDir` to keep state somewhere else (useful for tests and embedded hosts), or `databasePath` to point at a database file directly. Setting the `AMALGAM_AGENTS_DIR` environment variable overrides the default location without code changes.
|
|
62
|
+
|
|
63
|
+
## Core concepts
|
|
64
|
+
|
|
65
|
+
Agents separates three ideas that most agent frameworks blur together. Keeping them distinct is what makes agent executions reproducible.
|
|
66
|
+
|
|
67
|
+
### Identity
|
|
68
|
+
|
|
69
|
+
An identity is the stable machine name of an agent: 1–128 lowercase letters, numbers, dots, underscores, or hyphens. Renaming the human-readable `name` never changes the `id`, and other products reference agents by this id. Deleting an agent tombstones the identity rather than erasing it — the id stays reserved so that recorded history can never silently point at a different, later agent.
|
|
70
|
+
|
|
71
|
+
### Revision
|
|
72
|
+
|
|
73
|
+
Every content change to a definition appends an immutable revision: a numbered, hashed snapshot of the complete normalized definition. Revisions are immutable for one reason — Chat records the exact revision used by each execution, so a conversation transcript always names precisely the instructions, model, and tool references that produced it. Editing an agent today can never rewrite what an execution meant yesterday.
|
|
74
|
+
|
|
75
|
+
Two consequences follow from the append-only design:
|
|
76
|
+
|
|
77
|
+
- **Idempotent edits.** Submitting a patch that produces identical content keeps the current revision; nothing is appended.
|
|
78
|
+
- **No revision reuse.** Reverting to content an earlier revision had still creates a *new* revision number, so the timeline of what the definition was, and when, stays complete.
|
|
79
|
+
|
|
80
|
+
### Installation
|
|
81
|
+
|
|
82
|
+
An installation is one agent identity available on the local machine, together with its home descriptor. The registry is per-machine and local-first: `listAgents()` answers "which agents can this machine run right now". Portable bundles let you move an agent (with its subagent, skill, and tool graph) between machines; installing a bundle creates fresh local identities rather than overwriting existing ones.
|
|
83
|
+
|
|
84
|
+
## Agent definitions
|
|
85
|
+
|
|
86
|
+
A definition is complete, portable execution configuration. It holds *references* to things owned by other products — tools, skills, files, credentials, subagents — never copies of them and never secrets.
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"id": "reviewer",
|
|
91
|
+
"name": "Reviewer",
|
|
92
|
+
"description": "Reviews implementation work",
|
|
93
|
+
"instructions": "Find correctness risks before style issues.",
|
|
94
|
+
"driver": { "id": "codex", "config": { "sandbox": "workspace-write" } },
|
|
95
|
+
"model": { "id": "openai/gpt-5.5", "settings": { "effort": "high" } },
|
|
96
|
+
"authRef": "openai-primary",
|
|
97
|
+
"resources": {
|
|
98
|
+
"files": ["AGENTS.md"],
|
|
99
|
+
"skills": ["code-review"],
|
|
100
|
+
"subagents": ["security-reviewer"]
|
|
101
|
+
},
|
|
102
|
+
"toolbox": {
|
|
103
|
+
"toolIds": ["git"],
|
|
104
|
+
"actionIds": ["github.pull_request_get"]
|
|
105
|
+
},
|
|
106
|
+
"workspace": { "cwd": "/projects/product" },
|
|
107
|
+
"metadata": { "icon": "search" }
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Field notes:
|
|
112
|
+
|
|
113
|
+
- `driver` names the harness that will run the agent. As shorthand you may pass a plain string (`"driver": "codex"` becomes `{ "id": "codex", "config": {} }`). `driver.config` is non-secret harness configuration interpreted at execution time.
|
|
114
|
+
- `model` and `authRef` are nullable. `authRef` names a credential that the host resolves when the agent runs; the secret itself never enters the definition.
|
|
115
|
+
- `resources` and `toolbox` are lists of opaque ids resolved by the host. Arrays are de-duplicated and order-normalized.
|
|
116
|
+
- `metadata` is free-form JSON for your own annotations (icons, tags, owners).
|
|
117
|
+
|
|
118
|
+
## SDK reference
|
|
119
|
+
|
|
120
|
+
`Agents` is the single behavior boundary — the CLI, REST, and MCP surfaces are thin adapters over these same methods, so behavior is identical everywhere.
|
|
121
|
+
|
|
122
|
+
| Method | Behavior |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `createAgent(definition)` | Create an identity and immutable revision 1 |
|
|
125
|
+
| `updateAgent(id, patch)` | Merge a patch; append a revision only when content changes |
|
|
126
|
+
| `deleteAgent(id)` | Tombstone the identity, retaining all revisions |
|
|
127
|
+
| `getAgent(id, includeDeleted?)` | Read the current record, or `null` if absent |
|
|
128
|
+
| `listAgents(includeDeleted?)` | List available agents (tombstones excluded by default) |
|
|
129
|
+
| `getAgentRevision(agentId, revisionId?)` | Resolve the current or one exact immutable revision |
|
|
130
|
+
| `health()` | Liveness summary for the open registry |
|
|
131
|
+
| `close()` | Release the underlying database |
|
|
132
|
+
|
|
133
|
+
### Patching
|
|
134
|
+
|
|
135
|
+
Patches are partial definitions with predictable merge rules: top-level fields replace their prior values, while the known nested objects — `driver`, `resources`, `toolbox`, and `workspace` — merge field by field. That means a patch touching only `resources.skills` preserves `resources.files` and `resources.subagents`. Set a nullable field to `null` to clear it.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const updated = agents.updateAgent('reviewer', {
|
|
139
|
+
instructions: 'Review for correctness first, style second.',
|
|
140
|
+
resources: { skills: ['code-review', 'security-review'] },
|
|
141
|
+
model: null, // clear the pinned model; the host default applies
|
|
142
|
+
});
|
|
143
|
+
console.log(updated.currentRevision); // bumped, because content changed
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Errors
|
|
147
|
+
|
|
148
|
+
Expected failures throw `AgentError` with a stable `code` — `invalid_input`, `not_found`, `conflict`, `deleted`, or `too_large` — plus a matching HTTP `status` used by the REST surface. Catch and branch on the code rather than the message.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { Agents, AgentError } from '@amalgm/agents';
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
agents.updateAgent('missing-id', { name: 'Nope' });
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error instanceof AgentError && error.code === 'not_found') {
|
|
157
|
+
console.log('No such agent.');
|
|
158
|
+
} else {
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## CLI
|
|
165
|
+
|
|
166
|
+
The `amalgm-agents` CLI shares the SDK database — there is no separate CLI registry. It prints JSON to stdout and errors to stderr, so it composes cleanly with `jq` and scripts.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
amalgm-agents agent list [--include-deleted]
|
|
170
|
+
amalgm-agents agent show <id> [--include-deleted]
|
|
171
|
+
amalgm-agents agent create <definition.json>
|
|
172
|
+
amalgm-agents agent update <id> <patch.json>
|
|
173
|
+
amalgm-agents agent delete <id>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`create` and `update` read a JSON file — a complete definition and a partial patch respectively, using exactly the shapes shown above. Global options are `--state-dir <path>` (choose the registry location) and `--help`.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Create from a file, then confirm the revision advanced after an edit.
|
|
180
|
+
amalgm-agents agent create reviewer.json
|
|
181
|
+
echo '{"instructions": "Be terse."}' > patch.json
|
|
182
|
+
amalgm-agents agent update reviewer patch.json
|
|
183
|
+
amalgm-agents agent show reviewer | jq .currentRevision
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## REST API
|
|
187
|
+
|
|
188
|
+
For non-Node hosts, run the loopback REST server or embed it with `createRestServer` from `@amalgm/agents/http`.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
amalgm-agents-rest --host 127.0.0.1 --port 4317 --token "$TOKEN"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
When a token is configured, authenticate with `Authorization: Bearer <token>`; `/healthz` stays unauthenticated. JSON bodies are limited to 512 KB by default.
|
|
195
|
+
|
|
196
|
+
| Method | Path | Result |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `GET` | `/v1/agents` | List available agents |
|
|
199
|
+
| `POST` | `/v1/agents` | Create from a complete definition |
|
|
200
|
+
| `GET` | `/v1/agents/:id` | Read the current definition |
|
|
201
|
+
| `PATCH` | `/v1/agents/:id` | Apply a definition patch |
|
|
202
|
+
| `DELETE` | `/v1/agents/:id` | Tombstone an identity |
|
|
203
|
+
|
|
204
|
+
Add `?include_deleted=true` to list or get requests to include tombstones. There are no conversation routes — execution belongs to Chat.
|
|
205
|
+
|
|
206
|
+
## MCP tools
|
|
207
|
+
|
|
208
|
+
`amalgm-agents-mcp` runs the registry as a stdio MCP server, so any MCP-capable assistant can manage agents. `createMcpServer` from `@amalgm/agents/mcp` embeds the same server in your own host.
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
amalgm-agents-mcp --state-dir ./state
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
It exposes five tools, each a direct projection of an SDK method:
|
|
215
|
+
|
|
216
|
+
| Tool | Arguments | Behavior |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `agents_list` | `include_deleted?` | List persistent agent definitions |
|
|
219
|
+
| `agents_get` | `agent_id`, `include_deleted?` | Read one agent and its current revision |
|
|
220
|
+
| `agents_create` | `definition` | Create from a complete definition |
|
|
221
|
+
| `agents_update` | `agent_id`, `patch` | Patch; content changes append a revision |
|
|
222
|
+
| `agents_delete` | `agent_id` | Tombstone an identity, retaining revisions |
|
|
223
|
+
|
|
224
|
+
When working through these tools, list or inspect before changing an existing agent, patch only the fields being changed, and keep tool, skill, file, credential, and subagent values as references rather than inlining their content.
|
|
225
|
+
|
|
226
|
+
## Running agents with Chat
|
|
227
|
+
|
|
228
|
+
Agents deliberately contains no conversation, execution, transcript, interrupt, or reconnect operation. To run an agent, resolve its identity here and hand the result to Chat:
|
|
229
|
+
|
|
230
|
+
1. Select an installed agent identity (from `listAgents` or a known id).
|
|
231
|
+
2. Call `getAgentRevision(agentId, revisionId?)` — you get the current revision, or one exact historical revision if you pass its id.
|
|
232
|
+
3. Project that immutable definition into Chat's execution contract. Chat resolves the `toolbox`, `resources`, and `authRef` references into concrete values, freezes them, and then accepts the turn.
|
|
233
|
+
4. Chat records which revision it executed, so the conversation remains reproducible even after the agent is edited or deleted.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const revision = agents.getAgentRevision('reviewer');
|
|
237
|
+
// revision.definition is the frozen, normalized definition:
|
|
238
|
+
// pass it to Chat to start or continue a conversation as this agent.
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The division of labor is strict by design: Agents answers "what is this agent, exactly?", and Chat owns everything about running it — the conversation, turns, streams, usage, interrupts, and reconnects. Editing an agent never changes a Chat execution that already names a revision.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Amalgm Agents"
|
|
3
|
-
short_description: "Create
|
|
4
|
-
default_prompt: "Use $amalgm-agents to create or
|
|
3
|
+
short_description: "Create and manage Amalgm agents"
|
|
4
|
+
default_prompt: "Use $amalgm-agents to create or manage an agent."
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { Agents } from '../agents.js';
|
|
2
|
-
import type { ParsedArgs } from './args.js';
|
|
3
|
-
export declare function runSessionCommand(agents: Agents, args: ParsedArgs): Promise<unknown>;
|
|
4
|
-
export declare function runTalkCommand(agents: Agents, args: ParsedArgs): Promise<unknown>;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { AgentError } from '../errors.js';
|
|
2
|
-
import { enabled, flag } from './args.js';
|
|
3
|
-
export async function runSessionCommand(agents, args) {
|
|
4
|
-
const [, action, first, second] = args.words;
|
|
5
|
-
if (action === 'list') {
|
|
6
|
-
return { sessions: agents.listSessions(flag(args, 'agent'), enabled(args, 'include-archived')) };
|
|
7
|
-
}
|
|
8
|
-
if (action === 'start' && first) {
|
|
9
|
-
const sessionId = flag(args, 'id');
|
|
10
|
-
const revisionId = flag(args, 'revision');
|
|
11
|
-
return { session: agents.startSession({
|
|
12
|
-
agentId: first,
|
|
13
|
-
...(sessionId ? { sessionId } : {}),
|
|
14
|
-
...(revisionId ? { revisionId } : {}),
|
|
15
|
-
}) };
|
|
16
|
-
}
|
|
17
|
-
if (action === 'show' && first) {
|
|
18
|
-
const session = agents.getSession(first);
|
|
19
|
-
if (!session)
|
|
20
|
-
throw new AgentError('not_found', `Session not found: ${first}`);
|
|
21
|
-
return { session, turns: agents.listTurns(first), events: agents.listEvents(first) };
|
|
22
|
-
}
|
|
23
|
-
if (action === 'events' && first) {
|
|
24
|
-
return { events: agents.listEvents(first, Number(flag(args, 'after') || 0)) };
|
|
25
|
-
}
|
|
26
|
-
if (action === 'send' && first && second) {
|
|
27
|
-
const idempotencyKey = flag(args, 'key');
|
|
28
|
-
return { turn: await agents.send(first, {
|
|
29
|
-
message: second,
|
|
30
|
-
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
31
|
-
}) };
|
|
32
|
-
}
|
|
33
|
-
if (action === 'cancel' && first)
|
|
34
|
-
return { turn: agents.cancelSession(first) };
|
|
35
|
-
if (action === 'archive' && first)
|
|
36
|
-
return { session: agents.archiveSession(first) };
|
|
37
|
-
throw new AgentError('invalid_input', 'Unknown or incomplete session command. Run with --help.');
|
|
38
|
-
}
|
|
39
|
-
export async function runTalkCommand(agents, args) {
|
|
40
|
-
const [, agentId, message] = args.words;
|
|
41
|
-
if (!agentId || !message)
|
|
42
|
-
throw new AgentError('invalid_input', 'talk requires agent id and message.');
|
|
43
|
-
const sessionId = flag(args, 'session');
|
|
44
|
-
const idempotencyKey = flag(args, 'key');
|
|
45
|
-
const call = agents.talk(agentId, {
|
|
46
|
-
message,
|
|
47
|
-
...(sessionId ? { sessionId } : {}),
|
|
48
|
-
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
49
|
-
});
|
|
50
|
-
if (enabled(args, 'background')) {
|
|
51
|
-
return { session: call.session, turn: call.turn, duplicate: call.duplicate };
|
|
52
|
-
}
|
|
53
|
-
return { session: call.session, turn: await call.completion, duplicate: call.duplicate };
|
|
54
|
-
}
|