@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.
Files changed (56) hide show
  1. package/PURPOSE.md +25 -24
  2. package/README.md +21 -47
  3. package/dist/agents.d.ts +4 -26
  4. package/dist/agents.js +4 -87
  5. package/dist/bin/mcp.js +1 -1
  6. package/dist/cli/help.d.ts +1 -1
  7. package/dist/cli/help.js +2 -13
  8. package/dist/cli/open.d.ts +1 -1
  9. package/dist/cli/open.js +1 -5
  10. package/dist/cli/run.js +3 -8
  11. package/dist/errors.d.ts +1 -1
  12. package/dist/errors.js +0 -2
  13. package/dist/http/server.js +1 -7
  14. package/dist/index.d.ts +0 -2
  15. package/dist/index.js +0 -2
  16. package/dist/mcp/agent-tools.js +1 -1
  17. package/dist/mcp/tools.js +1 -2
  18. package/dist/rows.d.ts +1 -4
  19. package/dist/rows.js +0 -39
  20. package/dist/schema.js +53 -101
  21. package/dist/types.d.ts +0 -106
  22. package/docs/ARCHITECTURE.md +21 -33
  23. package/docs/CLI.md +3 -43
  24. package/docs/DATA_MODEL.md +7 -46
  25. package/docs/DEFINITIONS.md +4 -4
  26. package/docs/ENGINE_INTEGRATION.md +9 -71
  27. package/docs/MCP.md +3 -19
  28. package/docs/REST.md +14 -97
  29. package/docs/SDK.md +12 -61
  30. package/docs/SECURITY.md +6 -35
  31. package/docs/SHELL_INTEGRATION.md +29 -0
  32. package/examples/basic.ts +8 -22
  33. package/package.json +2 -2
  34. package/skills/amalgm-agents/SKILL.md +231 -20
  35. package/skills/amalgm-agents/agents/openai.yaml +2 -2
  36. package/dist/cli/session-commands.d.ts +0 -4
  37. package/dist/cli/session-commands.js +0 -54
  38. package/dist/drivers.d.ts +0 -4
  39. package/dist/drivers.js +0 -25
  40. package/dist/event-store.d.ts +0 -13
  41. package/dist/event-store.js +0 -50
  42. package/dist/http/session-routes.d.ts +0 -2
  43. package/dist/http/session-routes.js +0 -67
  44. package/dist/http/stream.d.ts +0 -3
  45. package/dist/http/stream.js +0 -30
  46. package/dist/mcp/session-tools.d.ts +0 -3
  47. package/dist/mcp/session-tools.js +0 -81
  48. package/dist/messages.d.ts +0 -3
  49. package/dist/messages.js +0 -56
  50. package/dist/runtime.d.ts +0 -29
  51. package/dist/runtime.js +0 -176
  52. package/dist/session-store.d.ts +0 -15
  53. package/dist/session-store.js +0 -83
  54. package/dist/turn-store.d.ts +0 -31
  55. package/dist/turn-store.js +0 -164
  56. 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
- `@amalgm/agents/http`, or run `amalgm-agents-rest`.
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
- The CLI default is `127.0.0.1:4317`. If a token is configured, all `/v1`
7
- requests require the token via `Authorization: Bearer <token>` (scheme
8
- case-insensitive) or the `x-amalgm-runtime-token` header. A missing or wrong
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 live agents |
43
- | `POST` | `/v1/agents` | Create from complete body |
44
- | `GET` | `/v1/agents/:id` | Read current definition |
45
- | `PATCH` | `/v1/agents/:id` | Merge a definition patch |
46
- | `DELETE` | `/v1/agents/:id` | Tombstone agent |
47
-
48
- Use `?include_deleted=true` on list or get to include tombstones.
49
-
50
- ## Sessions
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
- The `Agents` class is the sole public behavior boundary. All mutations are
4
- durable before a method returns.
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. `turnTimeoutMs: 0`
19
- disables the service timeout. Call `close()` to abort owned executions, wait
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; conflicts on any reused id |
27
- | `updateAgent(id, patch)` | Merge a patch and apply the resulting definition |
28
- | `deleteAgent(id)` | Tombstone the identity while retaining history |
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 live agents by default |
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
- Driver failures become durable failed turns rather than escaping as transient
72
- transport errors. Inspect `turn.error` and the session event ledger.
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
- Agents stores executable configuration and conversation history, so its state
4
- directory should be treated as user-private data.
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
- ## Secrets
7
-
8
- Agent definitions contain `authRef`, not credentials. Drivers resolve that
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, defineDriver, messageText } from '@amalgm/agents';
1
+ import { Agents } from '@amalgm/agents';
2
2
 
3
- const echo = defineDriver({
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
- async function main(): Promise<void> {
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: 'echo',
9
+ driver: 'codex',
22
10
  instructions: 'Review carefully.',
23
11
  });
24
12
 
25
- const call = agents.talk('reviewer', { message: 'Check this design.' });
26
- console.log(await call.completion);
27
- console.log(agents.listEvents(call.session.id));
28
- await agents.close();
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.1",
4
- "description": "Local-first agent definitions, immutable revisions, and durable sessions.",
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: Create, inspect, edit, delete, and talk to persistent Amalgm agents and their existing sessions. Use when a user wants to manage an agent definition, discover available agents, delegate work to an agent, continue an agent session, or inspect its transcript. Agents are separate from Chat conversations.
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
- # Amalgm Agents
6
+ # Agents
7
7
 
8
- Use the available Agents MCP tools. If MCP is unavailable, use the equivalent
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
- ## Manage agents
10
+ ## Installation
12
11
 
13
- 1. List or inspect before changing an existing agent.
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
- ## Talk to an agent
14
+ ```bash
15
+ npm install @amalgm/agents
16
+ ```
21
17
 
22
- 1. Resolve the agent with `agents_list` or `agents_get` when its identity is
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
- Never create a Chat conversation merely to represent an Agents session.
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, manage, and talk to Amalgm agents"
4
- default_prompt: "Use $amalgm-agents to create or talk to an agent."
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
- }