deepclause-sdk 0.1.3 → 0.2.1
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/README.md +45 -6
- package/dist/cli/config.d.ts +2 -2
- package/dist/cli/repl.d.ts +36 -0
- package/dist/cli/repl.d.ts.map +1 -1
- package/dist/cli/repl.js +414 -14
- package/dist/cli/repl.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/prolog-src/deepclause_mi.pl +516 -2
- package/dist/repl/backend.d.ts +9 -0
- package/dist/repl/backend.d.ts.map +1 -1
- package/dist/repl/backend.js +44 -0
- package/dist/repl/backend.js.map +1 -1
- package/dist/repl/harness-tools.d.ts +36 -0
- package/dist/repl/harness-tools.d.ts.map +1 -1
- package/dist/repl/harness-tools.js +173 -0
- package/dist/repl/harness-tools.js.map +1 -1
- package/dist/repl/history.d.ts +29 -0
- package/dist/repl/history.d.ts.map +1 -1
- package/dist/repl/history.js +71 -0
- package/dist/repl/history.js.map +1 -1
- package/dist/repl/index.d.ts +6 -0
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +9 -1
- package/dist/repl/index.js.map +1 -1
- package/dist/repl/picker.d.ts +3 -0
- package/dist/repl/picker.d.ts.map +1 -1
- package/dist/repl/picker.js +132 -0
- package/dist/repl/picker.js.map +1 -1
- package/dist/repl/process-backend.d.ts +31 -0
- package/dist/repl/process-backend.d.ts.map +1 -0
- package/dist/repl/process-backend.js +10 -0
- package/dist/repl/process-backend.js.map +1 -0
- package/dist/repl/process-executor.d.ts +67 -0
- package/dist/repl/process-executor.d.ts.map +1 -0
- package/dist/repl/process-executor.js +371 -0
- package/dist/repl/process-executor.js.map +1 -0
- package/dist/repl/process-worker.d.ts +10 -0
- package/dist/repl/process-worker.d.ts.map +1 -0
- package/dist/repl/process-worker.js +210 -0
- package/dist/repl/process-worker.js.map +1 -0
- package/dist/repl/processes.d.ts +282 -0
- package/dist/repl/processes.d.ts.map +1 -0
- package/dist/repl/processes.js +557 -0
- package/dist/repl/processes.js.map +1 -0
- package/dist/repl/render.d.ts.map +1 -1
- package/dist/repl/render.js +19 -5
- package/dist/repl/render.js.map +1 -1
- package/dist/repl/session.d.ts +135 -3
- package/dist/repl/session.d.ts.map +1 -1
- package/dist/repl/session.js +413 -4
- package/dist/repl/session.js.map +1 -1
- package/dist/repl/tui/autocomplete.d.ts.map +1 -1
- package/dist/repl/tui/autocomplete.js +7 -0
- package/dist/repl/tui/autocomplete.js.map +1 -1
- package/dist/repl/tui/harness-view.d.ts +102 -0
- package/dist/repl/tui/harness-view.d.ts.map +1 -0
- package/dist/repl/tui/harness-view.js +323 -0
- package/dist/repl/tui/harness-view.js.map +1 -0
- package/dist/repl/tui/renderer.d.ts.map +1 -1
- package/dist/repl/tui/renderer.js +6 -1
- package/dist/repl/tui/renderer.js.map +1 -1
- package/dist/repl/tui/services-panel.d.ts +35 -0
- package/dist/repl/tui/services-panel.d.ts.map +1 -0
- package/dist/repl/tui/services-panel.js +108 -0
- package/dist/repl/tui/services-panel.js.map +1 -0
- package/dist/repl/tui/status-bar.d.ts +2 -0
- package/dist/repl/tui/status-bar.d.ts.map +1 -1
- package/dist/repl/tui/status-bar.js +2 -0
- package/dist/repl/tui/status-bar.js.map +1 -1
- package/dist/repl/tui/tool-pane.d.ts.map +1 -1
- package/dist/repl/tui/tool-pane.js +6 -2
- package/dist/repl/tui/tool-pane.js.map +1 -1
- package/dist/repl/worker-host.d.ts +80 -0
- package/dist/repl/worker-host.d.ts.map +1 -0
- package/dist/repl/worker-host.js +305 -0
- package/dist/repl/worker-host.js.map +1 -0
- package/dist/repl/worker-protocol.d.ts +123 -0
- package/dist/repl/worker-protocol.d.ts.map +1 -0
- package/dist/repl/worker-protocol.js +9 -0
- package/dist/repl/worker-protocol.js.map +1 -0
- package/dist/runner.d.ts +24 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +47 -0
- package/dist/runner.js.map +1 -1
- package/dist/system/assets/docs/AGENT_PROMPT.md +193 -0
- package/dist/system/assets/docs/DML_AUTHORING.md +21 -0
- package/dist/system/assets/docs/DML_REFERENCE.md +145 -0
- package/package.json +2 -1
- package/src/prolog-src/deepclause_mi.pl +516 -2
|
@@ -189,6 +189,54 @@ than performing it as an edit.
|
|
|
189
189
|
Other tools: `reference(topic)` reads the DeepClause docs (`language`,
|
|
190
190
|
`authoring`, `patterns`, `examples`, `snippets`, `tools`).
|
|
191
191
|
|
|
192
|
+
# Concurrency and subagents
|
|
193
|
+
|
|
194
|
+
A **subagent** is a session-scoped DML process that keeps running after this
|
|
195
|
+
turn. Reach for one when work is long-running, must wait for messages, or can
|
|
196
|
+
run in parallel — not when a single `task/N` or `execute` call does the job.
|
|
197
|
+
|
|
198
|
+
| The work is… | Use |
|
|
199
|
+
|---|---|
|
|
200
|
+
| one model call, or a quick goal | `task/N` / `execute` |
|
|
201
|
+
| the same goal over a list, in parallel | `swarm/3` / `pmap/3` |
|
|
202
|
+
| long, and the user can wait | `spawn(Goal)` — report the id and carry on |
|
|
203
|
+
| a long-lived message handler | `serve(Name, Loop)` + `service_call`/`service_cast` |
|
|
204
|
+
| a decision from the user mid-run | `input/2` inside the subagent |
|
|
205
|
+
| several specialised roles collaborating | `serve` each role; a coordinator wires them |
|
|
206
|
+
|
|
207
|
+
A subagent is isolated and asynchronous:
|
|
208
|
+
|
|
209
|
+
- **Private snapshot.** Its `assert`/`retract` do not touch the harness, and it
|
|
210
|
+
does not see commits made after it started.
|
|
211
|
+
- **Value semantics.** `spawn` and `send` copy terms. A child cannot bind a
|
|
212
|
+
variable in the parent, so a result comes back by `send/2` or as the child's
|
|
213
|
+
`answer/1` — never by an output argument. Write `send(Parent, result(...))` in
|
|
214
|
+
the child and `receive` it in the parent (pass `self(Me)` into the child).
|
|
215
|
+
- **It outlives the turn.** Do not busy-wait; report the id and use `ps` later.
|
|
216
|
+
|
|
217
|
+
Patterns:
|
|
218
|
+
|
|
219
|
+
- **Fan out over a list** with `swarm(Worker, Items, Results)` — ordered, one
|
|
220
|
+
process per item (at most 4 at once), `ok(Value)`/`failed` per item — or
|
|
221
|
+
`pmap/3` (bare values, fails on any failure). Bound the list: each item is a
|
|
222
|
+
process and usually a model call, and the session has a process limit.
|
|
223
|
+
- **Background a long goal** with `spawn(Goal)`. If you need the result, have the
|
|
224
|
+
goal `send(Parent, result(...))` and `receive` it, or make it a service. A
|
|
225
|
+
one-shot subagent's terminal result can also be read with `wait(Pid, ok(Text))`.
|
|
226
|
+
- **A service** is `serve(Name, Loop)` where `Loop` handles
|
|
227
|
+
`request(From, Ref, Request)` by `send(From, reply(Ref, Reply))`, then recurses.
|
|
228
|
+
Call it with `service_call(Name, Request, Reply)`.
|
|
229
|
+
- **Recursive delegation** is a subagent that spawns subagents; bound the depth.
|
|
230
|
+
|
|
231
|
+
Subagent tools: `spawn(goal, name?)`, `ps`, `send(to, message)`,
|
|
232
|
+
`call(to, message)`, `stop(target)`, `swarm(worker, items)`, `wait(pid)`. Users
|
|
233
|
+
also have `/spawn`, `/send`, `/call`, `/stop`, and `/ps --watch --logs` (a live
|
|
234
|
+
follow of every subagent's streamed output, reasoning, and tool calls).
|
|
235
|
+
|
|
236
|
+
Do not spawn a subagent for a single model call, and do not fan out an unbounded
|
|
237
|
+
list. If the work needs the harness's shared state, or the user's answer now,
|
|
238
|
+
keep it in the foreground.
|
|
239
|
+
|
|
192
240
|
End your turn by replying with text and no tool call. To ask the human a
|
|
193
241
|
question, write it in that final message.
|
|
194
242
|
|
|
@@ -754,6 +802,7 @@ More patterns: `reference("patterns")`; shapes: `reference("examples")`.
|
|
|
754
802
|
- [system/1, user/1](#memory-predicates)
|
|
755
803
|
- [output/1, yield/1, log/1](#output-predicates)
|
|
756
804
|
- [answer/1](#answer1)
|
|
805
|
+
- [Processes and concurrency](#processes-and-concurrency)
|
|
757
806
|
- [Control Flow](#control-flow)
|
|
758
807
|
- [String Interpolation](#string-interpolation)
|
|
759
808
|
- [Backtracking](#backtracking)
|
|
@@ -1624,6 +1673,150 @@ agent_main :-
|
|
|
1624
1673
|
|
|
1625
1674
|
---
|
|
1626
1675
|
|
|
1676
|
+
## Processes and concurrency
|
|
1677
|
+
|
|
1678
|
+
A **process** (a "subagent") is an isolated DML execution with its own private
|
|
1679
|
+
memory, a private snapshot of this module, and a mailbox. Processes outlive the
|
|
1680
|
+
turn that started them, so a harness can background long work, wait for
|
|
1681
|
+
messages, or run many things at once.
|
|
1682
|
+
|
|
1683
|
+
Process primitives work anywhere a goal runs, including inside a `tool/2` body:
|
|
1684
|
+
a tool executes in its own engine, but `self/1` is the process the tool runs in,
|
|
1685
|
+
so a tool can route work to other processes.
|
|
1686
|
+
|
|
1687
|
+
A process runs the harness **as of its spawn**: later edits to a predicate do not
|
|
1688
|
+
reach a process already running, while a process spawned afterwards sees them.
|
|
1689
|
+
Inspect the code a process runs with `ps(code: "<pid or name>")`, and the
|
|
1690
|
+
session's current harness version with `ps(code: "")`.
|
|
1691
|
+
|
|
1692
|
+
### `spawn(+Goal, -Pid)` / `spawn_opt(+Goal, -Pid, +Options)`
|
|
1693
|
+
|
|
1694
|
+
Start `Goal` in a new process and return its pid immediately. `Goal` is **copied**:
|
|
1695
|
+
variables in the child are fresh, so a child can never bind a variable in the
|
|
1696
|
+
parent. A result comes back by message (`send/2`) or as the child's `answer/1`.
|
|
1697
|
+
|
|
1698
|
+
```prolog
|
|
1699
|
+
worker(Topic, Parent) :-
|
|
1700
|
+
task("Summarise {Topic} in one sentence.", string(Summary)),
|
|
1701
|
+
send(Parent, done(Topic, Summary)),
|
|
1702
|
+
answer(Summary).
|
|
1703
|
+
|
|
1704
|
+
fan_out(Topics, Results) :-
|
|
1705
|
+
self(Me),
|
|
1706
|
+
maplist(spawn_worker(Me), Topics, _Pids),
|
|
1707
|
+
collect(Topics, Results).
|
|
1708
|
+
|
|
1709
|
+
spawn_worker(Me, Topic, Pid) :- spawn(worker(Topic, Me), Pid).
|
|
1710
|
+
|
|
1711
|
+
collect([], []).
|
|
1712
|
+
collect([Topic|Topics], [Topic-Summary|Rest]) :-
|
|
1713
|
+
receive(done(Topic, Summary), 300000),
|
|
1714
|
+
collect(Topics, Rest).
|
|
1715
|
+
```
|
|
1716
|
+
|
|
1717
|
+
`spawn_opt/3` takes a dict of options: `name`, `priority`
|
|
1718
|
+
(`foreground`/`interactive`/`background`), `timeout` (ms), and `code`.
|
|
1719
|
+
|
|
1720
|
+
The goal's shape decides the lifecycle:
|
|
1721
|
+
|
|
1722
|
+
- **one-shot** — calls `output/1`/`answer/1` and returns.
|
|
1723
|
+
- **message-waiting** — loops on `receive/1,2`, handling terms from other
|
|
1724
|
+
processes or from the user.
|
|
1725
|
+
- **interactive** — calls `input/2` and blocks until the user answers.
|
|
1726
|
+
|
|
1727
|
+
### `send(+Target, +Message)` / `receive(?Message[, +Timeout])`
|
|
1728
|
+
|
|
1729
|
+
`send/2` copies a term into another process's mailbox; `Target` is a pid, a
|
|
1730
|
+
registered name, or a process label. `receive/2` pops the oldest message and
|
|
1731
|
+
fails on timeout. `receive_match/3` pops the oldest message that unifies with a
|
|
1732
|
+
template, requeues the rest, and fails on timeout.
|
|
1733
|
+
|
|
1734
|
+
```prolog
|
|
1735
|
+
receive(request(From, Ref, Query), 30000),
|
|
1736
|
+
handle(Query, Answer),
|
|
1737
|
+
send(From, reply(Ref, Answer)).
|
|
1738
|
+
```
|
|
1739
|
+
|
|
1740
|
+
### `self/1`, `register/2`, `whereis/2`
|
|
1741
|
+
|
|
1742
|
+
`self(Pid)` is this process's pid. `register(Name, Pid)` names a process in the
|
|
1743
|
+
session; `whereis(Name, Pid)` resolves it. A name is released when the process
|
|
1744
|
+
exits. A process's default label is its goal's functor, so `send(keeper, stop)`
|
|
1745
|
+
reaches an unregistered `keeper/…` process.
|
|
1746
|
+
|
|
1747
|
+
### `serve(+Name, +Goal)` / `service_call(+Target, +Request, -Reply[, +Timeout])` / `service_cast(+Target, +Message)`
|
|
1748
|
+
|
|
1749
|
+
`serve/2` is `spawn/2` + `register/2`. `service_call` sends
|
|
1750
|
+
`request(Self, Ref, Request)` and waits for `reply(Ref, Reply)`, ignoring other
|
|
1751
|
+
messages; `service_cast` sends without waiting. A service is an ordinary
|
|
1752
|
+
`receive` loop:
|
|
1753
|
+
|
|
1754
|
+
```prolog
|
|
1755
|
+
counter_service(Count) :-
|
|
1756
|
+
receive(Msg),
|
|
1757
|
+
( Msg = request(From, Ref, read) -> send(From, reply(Ref, Count)), counter_service(Count)
|
|
1758
|
+
; Msg = request(From, Ref, bump) -> Next is Count + 1, send(From, reply(Ref, Next)), counter_service(Next)
|
|
1759
|
+
; Msg = stop -> true
|
|
1760
|
+
; counter_service(Count)
|
|
1761
|
+
).
|
|
1762
|
+
```
|
|
1763
|
+
|
|
1764
|
+
### `swarm(+Worker, +Items, -Results[, +Concurrency])` / `pmap(+Worker, +Items, -Values)`
|
|
1765
|
+
|
|
1766
|
+
Run `call(Worker, Item, Value)` once per item, each in its own process, and
|
|
1767
|
+
collect the results **in input order**. At most `Concurrency` items run at once
|
|
1768
|
+
(default 4), so a large list does not fire every model call simultaneously.
|
|
1769
|
+
`swarm` returns `ok(Value)`/`failed` per item; `pmap` returns bare values and
|
|
1770
|
+
fails if any item failed.
|
|
1771
|
+
|
|
1772
|
+
```prolog
|
|
1773
|
+
swarm(summarise_file, Files, Results, 4),
|
|
1774
|
+
findall(S, member(ok(S), Results), Summaries).
|
|
1775
|
+
```
|
|
1776
|
+
|
|
1777
|
+
### `wait(+Pid[, -Outcome])`
|
|
1778
|
+
|
|
1779
|
+
Block until a process terminates, then report what happened. `Outcome` is
|
|
1780
|
+
`ok(Result)`, `failed(Error)`, or `killed(Reason)`; `Result` is the process's
|
|
1781
|
+
terminal text as a Prolog string. `wait/1` is semidet — it succeeds only on a
|
|
1782
|
+
successful finish.
|
|
1783
|
+
|
|
1784
|
+
```prolog
|
|
1785
|
+
spawn(research("qec", Me), Pid),
|
|
1786
|
+
wait(Pid, ok(Text)), % Text is the subagent's terminal text
|
|
1787
|
+
```
|
|
1788
|
+
|
|
1789
|
+
For a structured result, prefer the message form: have the child
|
|
1790
|
+
`send(Parent, result(Term))` and `receive` it in the parent.
|
|
1791
|
+
|
|
1792
|
+
### `exit/1,2` and stopping
|
|
1793
|
+
|
|
1794
|
+
`exit(Reason)` terminates the current process. `exit(Pid, Reason)` terminates
|
|
1795
|
+
another (a registered name or a process label works too); there is no `stop/1`
|
|
1796
|
+
predicate, so `exit/2` is how DML cancels a peer. (The `/stop` command and the
|
|
1797
|
+
`stop` tool are the interactive equivalents.) `process_info(Pid, Info)` returns a
|
|
1798
|
+
dict (`pid`, `name`, `state`, `priority`, `mailbox`, `usage`, …). `sleep(Ms)`
|
|
1799
|
+
yields; `now_ms(T)` and `make_ref(Ref)` are available.
|
|
1800
|
+
|
|
1801
|
+
### Rules
|
|
1802
|
+
|
|
1803
|
+
- **Isolation.** A process runs against a private module snapshot: its
|
|
1804
|
+
`assert`/`retract` do not affect the harness, and it does not see commits made
|
|
1805
|
+
after it started.
|
|
1806
|
+
- **Value semantics.** `spawn` and `send` copy terms; never rely on a shared
|
|
1807
|
+
variable with a child.
|
|
1808
|
+
- **Determinism.** A process that calls `task/N`, `exec/2`, `read_media/2`, or
|
|
1809
|
+
writes a file should yield one solution (`once/1`, `->`, `!`).
|
|
1810
|
+
- **Bound the work.** Every `swarm` item is a process and usually a model call;
|
|
1811
|
+
the session has a **live-process limit** (default 16 concurrent — finished
|
|
1812
|
+
processes do not count, so a long session can fan out again) and the provider
|
|
1813
|
+
has rate limits. A `swarm` item whose process cannot be spawned is reported as
|
|
1814
|
+
`failed(...)`; `pmap` then fails.
|
|
1815
|
+
- **CPU-bound Prolog** blocks the in-process host (L1). Long compute belongs in
|
|
1816
|
+
the REPL's `--execution worker` (L2), not a `swarm` on the default host.
|
|
1817
|
+
|
|
1818
|
+
---
|
|
1819
|
+
|
|
1627
1820
|
## Control Flow
|
|
1628
1821
|
|
|
1629
1822
|
DML supports standard Prolog control flow:
|
|
@@ -272,3 +272,24 @@ text-only — never attach media to them.
|
|
|
272
272
|
`/dc-run` skill.
|
|
273
273
|
- Media: reference images with `media(Path, Mime)` (`read_media/2` +
|
|
274
274
|
`attach_media/1`); never inline bytes.
|
|
275
|
+
|
|
276
|
+
## 7. Concurrency and subagents
|
|
277
|
+
|
|
278
|
+
Most skills are a short sequence of `task/N` calls in the foreground. Reach for a
|
|
279
|
+
**subagent** only when the work must outlive the turn, wait for a message, or run
|
|
280
|
+
in parallel.
|
|
281
|
+
|
|
282
|
+
- **Fan out over a list** (`swarm/3`, `pmap/3`): the same worker over many items,
|
|
283
|
+
each in its own process, results in input order. Bound the list.
|
|
284
|
+
- **Background a long goal** (`spawn/2`): report the id and carry on; the user
|
|
285
|
+
watches it with `/ps --watch --logs`.
|
|
286
|
+
- **A service** (`serve/2` + `service_call/3`): a long-lived `receive` loop that
|
|
287
|
+
answers requests; also good for a single-writer or rate-limited step.
|
|
288
|
+
|
|
289
|
+
A subagent is isolated (private module snapshot) and asynchronous (value
|
|
290
|
+
semantics): its `assert`/`retract` do not touch the harness, and a result comes
|
|
291
|
+
back by `send/2` or the child's `answer/1`, never by an output argument. Prefer
|
|
292
|
+
`swarm` over hand-rolled `spawn`+`receive` unless you need a custom protocol.
|
|
293
|
+
|
|
294
|
+
Do not fan out an unbounded list (each item is a process and usually a model
|
|
295
|
+
call), and do not spawn for a single model call.
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- [system/1, user/1](#memory-predicates)
|
|
16
16
|
- [output/1, yield/1, log/1](#output-predicates)
|
|
17
17
|
- [answer/1](#answer1)
|
|
18
|
+
- [Processes and concurrency](#processes-and-concurrency)
|
|
18
19
|
- [Control Flow](#control-flow)
|
|
19
20
|
- [String Interpolation](#string-interpolation)
|
|
20
21
|
- [Backtracking](#backtracking)
|
|
@@ -885,6 +886,150 @@ agent_main :-
|
|
|
885
886
|
|
|
886
887
|
---
|
|
887
888
|
|
|
889
|
+
## Processes and concurrency
|
|
890
|
+
|
|
891
|
+
A **process** (a "subagent") is an isolated DML execution with its own private
|
|
892
|
+
memory, a private snapshot of this module, and a mailbox. Processes outlive the
|
|
893
|
+
turn that started them, so a harness can background long work, wait for
|
|
894
|
+
messages, or run many things at once.
|
|
895
|
+
|
|
896
|
+
Process primitives work anywhere a goal runs, including inside a `tool/2` body:
|
|
897
|
+
a tool executes in its own engine, but `self/1` is the process the tool runs in,
|
|
898
|
+
so a tool can route work to other processes.
|
|
899
|
+
|
|
900
|
+
A process runs the harness **as of its spawn**: later edits to a predicate do not
|
|
901
|
+
reach a process already running, while a process spawned afterwards sees them.
|
|
902
|
+
Inspect the code a process runs with `ps(code: "<pid or name>")`, and the
|
|
903
|
+
session's current harness version with `ps(code: "")`.
|
|
904
|
+
|
|
905
|
+
### `spawn(+Goal, -Pid)` / `spawn_opt(+Goal, -Pid, +Options)`
|
|
906
|
+
|
|
907
|
+
Start `Goal` in a new process and return its pid immediately. `Goal` is **copied**:
|
|
908
|
+
variables in the child are fresh, so a child can never bind a variable in the
|
|
909
|
+
parent. A result comes back by message (`send/2`) or as the child's `answer/1`.
|
|
910
|
+
|
|
911
|
+
```prolog
|
|
912
|
+
worker(Topic, Parent) :-
|
|
913
|
+
task("Summarise {Topic} in one sentence.", string(Summary)),
|
|
914
|
+
send(Parent, done(Topic, Summary)),
|
|
915
|
+
answer(Summary).
|
|
916
|
+
|
|
917
|
+
fan_out(Topics, Results) :-
|
|
918
|
+
self(Me),
|
|
919
|
+
maplist(spawn_worker(Me), Topics, _Pids),
|
|
920
|
+
collect(Topics, Results).
|
|
921
|
+
|
|
922
|
+
spawn_worker(Me, Topic, Pid) :- spawn(worker(Topic, Me), Pid).
|
|
923
|
+
|
|
924
|
+
collect([], []).
|
|
925
|
+
collect([Topic|Topics], [Topic-Summary|Rest]) :-
|
|
926
|
+
receive(done(Topic, Summary), 300000),
|
|
927
|
+
collect(Topics, Rest).
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
`spawn_opt/3` takes a dict of options: `name`, `priority`
|
|
931
|
+
(`foreground`/`interactive`/`background`), `timeout` (ms), and `code`.
|
|
932
|
+
|
|
933
|
+
The goal's shape decides the lifecycle:
|
|
934
|
+
|
|
935
|
+
- **one-shot** — calls `output/1`/`answer/1` and returns.
|
|
936
|
+
- **message-waiting** — loops on `receive/1,2`, handling terms from other
|
|
937
|
+
processes or from the user.
|
|
938
|
+
- **interactive** — calls `input/2` and blocks until the user answers.
|
|
939
|
+
|
|
940
|
+
### `send(+Target, +Message)` / `receive(?Message[, +Timeout])`
|
|
941
|
+
|
|
942
|
+
`send/2` copies a term into another process's mailbox; `Target` is a pid, a
|
|
943
|
+
registered name, or a process label. `receive/2` pops the oldest message and
|
|
944
|
+
fails on timeout. `receive_match/3` pops the oldest message that unifies with a
|
|
945
|
+
template, requeues the rest, and fails on timeout.
|
|
946
|
+
|
|
947
|
+
```prolog
|
|
948
|
+
receive(request(From, Ref, Query), 30000),
|
|
949
|
+
handle(Query, Answer),
|
|
950
|
+
send(From, reply(Ref, Answer)).
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
### `self/1`, `register/2`, `whereis/2`
|
|
954
|
+
|
|
955
|
+
`self(Pid)` is this process's pid. `register(Name, Pid)` names a process in the
|
|
956
|
+
session; `whereis(Name, Pid)` resolves it. A name is released when the process
|
|
957
|
+
exits. A process's default label is its goal's functor, so `send(keeper, stop)`
|
|
958
|
+
reaches an unregistered `keeper/…` process.
|
|
959
|
+
|
|
960
|
+
### `serve(+Name, +Goal)` / `service_call(+Target, +Request, -Reply[, +Timeout])` / `service_cast(+Target, +Message)`
|
|
961
|
+
|
|
962
|
+
`serve/2` is `spawn/2` + `register/2`. `service_call` sends
|
|
963
|
+
`request(Self, Ref, Request)` and waits for `reply(Ref, Reply)`, ignoring other
|
|
964
|
+
messages; `service_cast` sends without waiting. A service is an ordinary
|
|
965
|
+
`receive` loop:
|
|
966
|
+
|
|
967
|
+
```prolog
|
|
968
|
+
counter_service(Count) :-
|
|
969
|
+
receive(Msg),
|
|
970
|
+
( Msg = request(From, Ref, read) -> send(From, reply(Ref, Count)), counter_service(Count)
|
|
971
|
+
; Msg = request(From, Ref, bump) -> Next is Count + 1, send(From, reply(Ref, Next)), counter_service(Next)
|
|
972
|
+
; Msg = stop -> true
|
|
973
|
+
; counter_service(Count)
|
|
974
|
+
).
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
### `swarm(+Worker, +Items, -Results[, +Concurrency])` / `pmap(+Worker, +Items, -Values)`
|
|
978
|
+
|
|
979
|
+
Run `call(Worker, Item, Value)` once per item, each in its own process, and
|
|
980
|
+
collect the results **in input order**. At most `Concurrency` items run at once
|
|
981
|
+
(default 4), so a large list does not fire every model call simultaneously.
|
|
982
|
+
`swarm` returns `ok(Value)`/`failed` per item; `pmap` returns bare values and
|
|
983
|
+
fails if any item failed.
|
|
984
|
+
|
|
985
|
+
```prolog
|
|
986
|
+
swarm(summarise_file, Files, Results, 4),
|
|
987
|
+
findall(S, member(ok(S), Results), Summaries).
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
### `wait(+Pid[, -Outcome])`
|
|
991
|
+
|
|
992
|
+
Block until a process terminates, then report what happened. `Outcome` is
|
|
993
|
+
`ok(Result)`, `failed(Error)`, or `killed(Reason)`; `Result` is the process's
|
|
994
|
+
terminal text as a Prolog string. `wait/1` is semidet — it succeeds only on a
|
|
995
|
+
successful finish.
|
|
996
|
+
|
|
997
|
+
```prolog
|
|
998
|
+
spawn(research("qec", Me), Pid),
|
|
999
|
+
wait(Pid, ok(Text)), % Text is the subagent's terminal text
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
For a structured result, prefer the message form: have the child
|
|
1003
|
+
`send(Parent, result(Term))` and `receive` it in the parent.
|
|
1004
|
+
|
|
1005
|
+
### `exit/1,2` and stopping
|
|
1006
|
+
|
|
1007
|
+
`exit(Reason)` terminates the current process. `exit(Pid, Reason)` terminates
|
|
1008
|
+
another (a registered name or a process label works too); there is no `stop/1`
|
|
1009
|
+
predicate, so `exit/2` is how DML cancels a peer. (The `/stop` command and the
|
|
1010
|
+
`stop` tool are the interactive equivalents.) `process_info(Pid, Info)` returns a
|
|
1011
|
+
dict (`pid`, `name`, `state`, `priority`, `mailbox`, `usage`, …). `sleep(Ms)`
|
|
1012
|
+
yields; `now_ms(T)` and `make_ref(Ref)` are available.
|
|
1013
|
+
|
|
1014
|
+
### Rules
|
|
1015
|
+
|
|
1016
|
+
- **Isolation.** A process runs against a private module snapshot: its
|
|
1017
|
+
`assert`/`retract` do not affect the harness, and it does not see commits made
|
|
1018
|
+
after it started.
|
|
1019
|
+
- **Value semantics.** `spawn` and `send` copy terms; never rely on a shared
|
|
1020
|
+
variable with a child.
|
|
1021
|
+
- **Determinism.** A process that calls `task/N`, `exec/2`, `read_media/2`, or
|
|
1022
|
+
writes a file should yield one solution (`once/1`, `->`, `!`).
|
|
1023
|
+
- **Bound the work.** Every `swarm` item is a process and usually a model call;
|
|
1024
|
+
the session has a **live-process limit** (default 16 concurrent — finished
|
|
1025
|
+
processes do not count, so a long session can fan out again) and the provider
|
|
1026
|
+
has rate limits. A `swarm` item whose process cannot be spawned is reported as
|
|
1027
|
+
`failed(...)`; `pmap` then fails.
|
|
1028
|
+
- **CPU-bound Prolog** blocks the in-process host (L1). Long compute belongs in
|
|
1029
|
+
the REPL's `--execution worker` (L2), not a `swarm` on the default host.
|
|
1030
|
+
|
|
1031
|
+
---
|
|
1032
|
+
|
|
888
1033
|
## Control Flow
|
|
889
1034
|
|
|
890
1035
|
DML supports standard Prolog control flow:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepclause-sdk",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Neuro-symbolic runtime: a self-modifying DML/Prolog REPL, one-shot runner, and TypeScript SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"examples": "tsx sdk-examples/basic-usage.ts",
|
|
39
39
|
"examples:smoke": "tsx sdk-examples/no-api-key.ts && npm run example && npm run examples && tsx sdk-examples/neurosymbolic.ts 1 && tsx sdk-examples/deep-research.ts \"test topic\"",
|
|
40
40
|
"cli": "tsx src/cli/index.ts",
|
|
41
|
+
"repl": "tsx src/cli/index.ts repl",
|
|
41
42
|
"sync:agent-prompt": "node scripts/sync-agent-prompt.mjs",
|
|
42
43
|
"check:agent-prompt": "node scripts/sync-agent-prompt.mjs --check"
|
|
43
44
|
},
|