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.
Files changed (92) hide show
  1. package/README.md +45 -6
  2. package/dist/cli/config.d.ts +2 -2
  3. package/dist/cli/repl.d.ts +36 -0
  4. package/dist/cli/repl.d.ts.map +1 -1
  5. package/dist/cli/repl.js +414 -14
  6. package/dist/cli/repl.js.map +1 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +3 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/prolog-src/deepclause_mi.pl +516 -2
  12. package/dist/repl/backend.d.ts +9 -0
  13. package/dist/repl/backend.d.ts.map +1 -1
  14. package/dist/repl/backend.js +44 -0
  15. package/dist/repl/backend.js.map +1 -1
  16. package/dist/repl/harness-tools.d.ts +36 -0
  17. package/dist/repl/harness-tools.d.ts.map +1 -1
  18. package/dist/repl/harness-tools.js +173 -0
  19. package/dist/repl/harness-tools.js.map +1 -1
  20. package/dist/repl/history.d.ts +29 -0
  21. package/dist/repl/history.d.ts.map +1 -1
  22. package/dist/repl/history.js +71 -0
  23. package/dist/repl/history.js.map +1 -1
  24. package/dist/repl/index.d.ts +6 -0
  25. package/dist/repl/index.d.ts.map +1 -1
  26. package/dist/repl/index.js +9 -1
  27. package/dist/repl/index.js.map +1 -1
  28. package/dist/repl/picker.d.ts +3 -0
  29. package/dist/repl/picker.d.ts.map +1 -1
  30. package/dist/repl/picker.js +132 -0
  31. package/dist/repl/picker.js.map +1 -1
  32. package/dist/repl/process-backend.d.ts +31 -0
  33. package/dist/repl/process-backend.d.ts.map +1 -0
  34. package/dist/repl/process-backend.js +10 -0
  35. package/dist/repl/process-backend.js.map +1 -0
  36. package/dist/repl/process-executor.d.ts +67 -0
  37. package/dist/repl/process-executor.d.ts.map +1 -0
  38. package/dist/repl/process-executor.js +371 -0
  39. package/dist/repl/process-executor.js.map +1 -0
  40. package/dist/repl/process-worker.d.ts +10 -0
  41. package/dist/repl/process-worker.d.ts.map +1 -0
  42. package/dist/repl/process-worker.js +210 -0
  43. package/dist/repl/process-worker.js.map +1 -0
  44. package/dist/repl/processes.d.ts +282 -0
  45. package/dist/repl/processes.d.ts.map +1 -0
  46. package/dist/repl/processes.js +557 -0
  47. package/dist/repl/processes.js.map +1 -0
  48. package/dist/repl/render.d.ts.map +1 -1
  49. package/dist/repl/render.js +19 -5
  50. package/dist/repl/render.js.map +1 -1
  51. package/dist/repl/session.d.ts +135 -3
  52. package/dist/repl/session.d.ts.map +1 -1
  53. package/dist/repl/session.js +413 -4
  54. package/dist/repl/session.js.map +1 -1
  55. package/dist/repl/tui/autocomplete.d.ts.map +1 -1
  56. package/dist/repl/tui/autocomplete.js +7 -0
  57. package/dist/repl/tui/autocomplete.js.map +1 -1
  58. package/dist/repl/tui/harness-view.d.ts +102 -0
  59. package/dist/repl/tui/harness-view.d.ts.map +1 -0
  60. package/dist/repl/tui/harness-view.js +323 -0
  61. package/dist/repl/tui/harness-view.js.map +1 -0
  62. package/dist/repl/tui/renderer.d.ts.map +1 -1
  63. package/dist/repl/tui/renderer.js +6 -1
  64. package/dist/repl/tui/renderer.js.map +1 -1
  65. package/dist/repl/tui/services-panel.d.ts +35 -0
  66. package/dist/repl/tui/services-panel.d.ts.map +1 -0
  67. package/dist/repl/tui/services-panel.js +108 -0
  68. package/dist/repl/tui/services-panel.js.map +1 -0
  69. package/dist/repl/tui/status-bar.d.ts +2 -0
  70. package/dist/repl/tui/status-bar.d.ts.map +1 -1
  71. package/dist/repl/tui/status-bar.js +2 -0
  72. package/dist/repl/tui/status-bar.js.map +1 -1
  73. package/dist/repl/tui/tool-pane.d.ts.map +1 -1
  74. package/dist/repl/tui/tool-pane.js +6 -2
  75. package/dist/repl/tui/tool-pane.js.map +1 -1
  76. package/dist/repl/worker-host.d.ts +80 -0
  77. package/dist/repl/worker-host.d.ts.map +1 -0
  78. package/dist/repl/worker-host.js +305 -0
  79. package/dist/repl/worker-host.js.map +1 -0
  80. package/dist/repl/worker-protocol.d.ts +123 -0
  81. package/dist/repl/worker-protocol.d.ts.map +1 -0
  82. package/dist/repl/worker-protocol.js +9 -0
  83. package/dist/repl/worker-protocol.js.map +1 -0
  84. package/dist/runner.d.ts +24 -0
  85. package/dist/runner.d.ts.map +1 -1
  86. package/dist/runner.js +47 -0
  87. package/dist/runner.js.map +1 -1
  88. package/dist/system/assets/docs/AGENT_PROMPT.md +193 -0
  89. package/dist/system/assets/docs/DML_AUTHORING.md +21 -0
  90. package/dist/system/assets/docs/DML_REFERENCE.md +145 -0
  91. package/package.json +2 -1
  92. 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",
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
  },