@duetso/agent 0.1.20

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 (148) hide show
  1. package/LICENSE +189 -0
  2. package/README.md +315 -0
  3. package/dist/package.json +84 -0
  4. package/dist/src/cli.d.ts +23 -0
  5. package/dist/src/cli.d.ts.map +1 -0
  6. package/dist/src/cli.js +754 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/core/serializer.d.ts +3 -0
  9. package/dist/src/core/serializer.d.ts.map +1 -0
  10. package/dist/src/core/serializer.js +22 -0
  11. package/dist/src/core/serializer.js.map +1 -0
  12. package/dist/src/core/structured-output.d.ts +13 -0
  13. package/dist/src/core/structured-output.d.ts.map +1 -0
  14. package/dist/src/core/structured-output.js +41 -0
  15. package/dist/src/core/structured-output.js.map +1 -0
  16. package/dist/src/guardrails/firewall.d.ts +7 -0
  17. package/dist/src/guardrails/firewall.d.ts.map +1 -0
  18. package/dist/src/guardrails/firewall.js +31 -0
  19. package/dist/src/guardrails/firewall.js.map +1 -0
  20. package/dist/src/guardrails/pattern.d.ts +13 -0
  21. package/dist/src/guardrails/pattern.d.ts.map +1 -0
  22. package/dist/src/guardrails/pattern.js +70 -0
  23. package/dist/src/guardrails/pattern.js.map +1 -0
  24. package/dist/src/guardrails/semantic.d.ts +14 -0
  25. package/dist/src/guardrails/semantic.d.ts.map +1 -0
  26. package/dist/src/guardrails/semantic.js +47 -0
  27. package/dist/src/guardrails/semantic.js.map +1 -0
  28. package/dist/src/index.d.ts +20 -0
  29. package/dist/src/index.d.ts.map +1 -0
  30. package/dist/src/index.js +20 -0
  31. package/dist/src/index.js.map +1 -0
  32. package/dist/src/lib/compact-json.d.ts +11 -0
  33. package/dist/src/lib/compact-json.d.ts.map +1 -0
  34. package/dist/src/lib/compact-json.js +36 -0
  35. package/dist/src/lib/compact-json.js.map +1 -0
  36. package/dist/src/lib/xml.d.ts +3 -0
  37. package/dist/src/lib/xml.d.ts.map +1 -0
  38. package/dist/src/lib/xml.js +9 -0
  39. package/dist/src/lib/xml.js.map +1 -0
  40. package/dist/src/memory/observation-groups.d.ts +15 -0
  41. package/dist/src/memory/observation-groups.d.ts.map +1 -0
  42. package/dist/src/memory/observation-groups.js +159 -0
  43. package/dist/src/memory/observation-groups.js.map +1 -0
  44. package/dist/src/memory/observational-prompts.d.ts +27 -0
  45. package/dist/src/memory/observational-prompts.d.ts.map +1 -0
  46. package/dist/src/memory/observational-prompts.js +237 -0
  47. package/dist/src/memory/observational-prompts.js.map +1 -0
  48. package/dist/src/memory/observational.d.ts +63 -0
  49. package/dist/src/memory/observational.d.ts.map +1 -0
  50. package/dist/src/memory/observational.js +605 -0
  51. package/dist/src/memory/observational.js.map +1 -0
  52. package/dist/src/memory/storage.d.ts +3 -0
  53. package/dist/src/memory/storage.d.ts.map +1 -0
  54. package/dist/src/memory/storage.js +127 -0
  55. package/dist/src/memory/storage.js.map +1 -0
  56. package/dist/src/memory/store.d.ts +13 -0
  57. package/dist/src/memory/store.d.ts.map +1 -0
  58. package/dist/src/memory/store.js +106 -0
  59. package/dist/src/memory/store.js.map +1 -0
  60. package/dist/src/model-resolution/duet-gateway.d.ts +35 -0
  61. package/dist/src/model-resolution/duet-gateway.d.ts.map +1 -0
  62. package/dist/src/model-resolution/duet-gateway.js +56 -0
  63. package/dist/src/model-resolution/duet-gateway.js.map +1 -0
  64. package/dist/src/model-resolution/index.d.ts +31 -0
  65. package/dist/src/model-resolution/index.d.ts.map +1 -0
  66. package/dist/src/model-resolution/index.js +129 -0
  67. package/dist/src/model-resolution/index.js.map +1 -0
  68. package/dist/src/session/session-manager.d.ts +45 -0
  69. package/dist/src/session/session-manager.d.ts.map +1 -0
  70. package/dist/src/session/session-manager.js +94 -0
  71. package/dist/src/session/session-manager.js.map +1 -0
  72. package/dist/src/session/session.d.ts +113 -0
  73. package/dist/src/session/session.d.ts.map +1 -0
  74. package/dist/src/session/session.js +308 -0
  75. package/dist/src/session/session.js.map +1 -0
  76. package/dist/src/tui/app.d.ts +60 -0
  77. package/dist/src/tui/app.d.ts.map +1 -0
  78. package/dist/src/tui/app.js +742 -0
  79. package/dist/src/tui/app.js.map +1 -0
  80. package/dist/src/turn-runner/agent-events.d.ts +5 -0
  81. package/dist/src/turn-runner/agent-events.d.ts.map +1 -0
  82. package/dist/src/turn-runner/agent-events.js +59 -0
  83. package/dist/src/turn-runner/agent-events.js.map +1 -0
  84. package/dist/src/turn-runner/prompts.d.ts +13 -0
  85. package/dist/src/turn-runner/prompts.d.ts.map +1 -0
  86. package/dist/src/turn-runner/prompts.js +79 -0
  87. package/dist/src/turn-runner/prompts.js.map +1 -0
  88. package/dist/src/turn-runner/shell-state-handle.d.ts +32 -0
  89. package/dist/src/turn-runner/shell-state-handle.d.ts.map +1 -0
  90. package/dist/src/turn-runner/shell-state-handle.js +168 -0
  91. package/dist/src/turn-runner/shell-state-handle.js.map +1 -0
  92. package/dist/src/turn-runner/skill-context.d.ts +26 -0
  93. package/dist/src/turn-runner/skill-context.d.ts.map +1 -0
  94. package/dist/src/turn-runner/skill-context.js +110 -0
  95. package/dist/src/turn-runner/skill-context.js.map +1 -0
  96. package/dist/src/turn-runner/skills.d.ts +35 -0
  97. package/dist/src/turn-runner/skills.d.ts.map +1 -0
  98. package/dist/src/turn-runner/skills.js +130 -0
  99. package/dist/src/turn-runner/skills.js.map +1 -0
  100. package/dist/src/turn-runner/state-machine-controller.d.ts +90 -0
  101. package/dist/src/turn-runner/state-machine-controller.d.ts.map +1 -0
  102. package/dist/src/turn-runner/state-machine-controller.js +289 -0
  103. package/dist/src/turn-runner/state-machine-controller.js.map +1 -0
  104. package/dist/src/turn-runner/state-machine-session.d.ts +27 -0
  105. package/dist/src/turn-runner/state-machine-session.d.ts.map +1 -0
  106. package/dist/src/turn-runner/state-machine-session.js +189 -0
  107. package/dist/src/turn-runner/state-machine-session.js.map +1 -0
  108. package/dist/src/turn-runner/tools.d.ts +193 -0
  109. package/dist/src/turn-runner/tools.d.ts.map +1 -0
  110. package/dist/src/turn-runner/tools.js +509 -0
  111. package/dist/src/turn-runner/tools.js.map +1 -0
  112. package/dist/src/turn-runner/turn-runner.d.ts +160 -0
  113. package/dist/src/turn-runner/turn-runner.d.ts.map +1 -0
  114. package/dist/src/turn-runner/turn-runner.js +907 -0
  115. package/dist/src/turn-runner/turn-runner.js.map +1 -0
  116. package/dist/src/turn-runner/turn-state.d.ts +6 -0
  117. package/dist/src/turn-runner/turn-state.d.ts.map +1 -0
  118. package/dist/src/turn-runner/turn-state.js +32 -0
  119. package/dist/src/turn-runner/turn-state.js.map +1 -0
  120. package/dist/src/turn-runner/usage-accounting.d.ts +7 -0
  121. package/dist/src/turn-runner/usage-accounting.d.ts.map +1 -0
  122. package/dist/src/turn-runner/usage-accounting.js +49 -0
  123. package/dist/src/turn-runner/usage-accounting.js.map +1 -0
  124. package/dist/src/types/agent.d.ts +15 -0
  125. package/dist/src/types/agent.d.ts.map +1 -0
  126. package/dist/src/types/agent.js +2 -0
  127. package/dist/src/types/agent.js.map +1 -0
  128. package/dist/src/types/config.d.ts +37 -0
  129. package/dist/src/types/config.d.ts.map +1 -0
  130. package/dist/src/types/config.js +2 -0
  131. package/dist/src/types/config.js.map +1 -0
  132. package/dist/src/types/guardrails.d.ts +34 -0
  133. package/dist/src/types/guardrails.d.ts.map +1 -0
  134. package/dist/src/types/guardrails.js +2 -0
  135. package/dist/src/types/guardrails.js.map +1 -0
  136. package/dist/src/types/memory.d.ts +151 -0
  137. package/dist/src/types/memory.d.ts.map +1 -0
  138. package/dist/src/types/memory.js +2 -0
  139. package/dist/src/types/memory.js.map +1 -0
  140. package/dist/src/types/protocol.d.ts +426 -0
  141. package/dist/src/types/protocol.d.ts.map +1 -0
  142. package/dist/src/types/protocol.js +2 -0
  143. package/dist/src/types/protocol.js.map +1 -0
  144. package/dist/src/types/state-machine.d.ts +344 -0
  145. package/dist/src/types/state-machine.d.ts.map +1 -0
  146. package/dist/src/types/state-machine.js +2 -0
  147. package/dist/src/types/state-machine.js.map +1 -0
  148. package/package.json +84 -0
package/LICENSE ADDED
@@ -0,0 +1,189 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work.
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other modifications
42
+ represent, as a whole, an original work of authorship. For the purposes
43
+ of this License, Derivative Works shall not include works that remain
44
+ separable from, or merely link (or bind by name) to the interfaces of,
45
+ the Work and Derivative Works thereof.
46
+
47
+ "Contribution" shall mean any work of authorship, including
48
+ the original version of the Work and any modifications or additions
49
+ to that Work or Derivative Works thereof, that is intentionally
50
+ submitted to the Licensor for inclusion in the Work by the copyright owner
51
+ or by an individual or Legal Entity authorized to submit on behalf of
52
+ the copyright owner. For the purposes of this definition, "submitted"
53
+ means any form of electronic, verbal, or written communication sent
54
+ to the Licensor or its representatives, including but not limited to
55
+ communication on electronic mailing lists, source code control systems,
56
+ and issue tracking systems that are managed by, or on behalf of, the
57
+ Licensor for the purpose of discussing and improving the Work, but
58
+ excluding communication that is conspicuously marked or otherwise
59
+ designated in writing by the copyright owner as "Not a Contribution."
60
+
61
+ "Contributor" shall mean Licensor and any individual or Legal Entity
62
+ on behalf of whom a Contribution has been received by the Licensor and
63
+ subsequently incorporated within the Work.
64
+
65
+ 2. Grant of Copyright License. Subject to the terms and conditions of
66
+ this License, each Contributor hereby grants to You a perpetual,
67
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
68
+ copyright license to reproduce, prepare Derivative Works of,
69
+ publicly display, publicly perform, sublicense, and distribute the
70
+ Work and such Derivative Works in Source or Object form.
71
+
72
+ 3. Grant of Patent License. Subject to the terms and conditions of
73
+ this License, each Contributor hereby grants to You a perpetual,
74
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
75
+ (except as stated in this section) patent license to make, have made,
76
+ use, offer to sell, sell, import, and otherwise transfer the Work,
77
+ where such license applies only to those patent claims licensable
78
+ by such Contributor that are necessarily infringed by their
79
+ Contribution(s) alone or by combination of their Contribution(s)
80
+ with the Work to which such Contribution(s) was submitted. If You
81
+ institute patent litigation against any entity (including a
82
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
83
+ or a Contribution incorporated within the Work constitutes direct
84
+ or contributory patent infringement, then any patent licenses
85
+ granted to You under this License for that Work shall terminate
86
+ as of the date such litigation is filed.
87
+
88
+ 4. Redistribution. You may reproduce and distribute copies of the
89
+ Work or Derivative Works thereof in any medium, with or without
90
+ modifications, and in Source or Object form, provided that You
91
+ meet the following conditions:
92
+
93
+ (a) You must give any other recipients of the Work or
94
+ Derivative Works a copy of this License; and
95
+
96
+ (b) You must cause any modified files to carry prominent notices
97
+ stating that You changed the files; and
98
+
99
+ (c) You must retain, in the Source form of any Derivative Works
100
+ that You distribute, all copyright, patent, trademark, and
101
+ attribution notices from the Source form of the Work,
102
+ excluding those notices that do not pertain to any part of
103
+ the Derivative Works; and
104
+
105
+ (d) If the Work includes a "NOTICE" text file as part of its
106
+ distribution, then any Derivative Works that You distribute must
107
+ include a readable copy of the attribution notices contained
108
+ within such NOTICE file, excluding any notices that do not
109
+ pertain to any part of the Derivative Works, in at least one
110
+ of the following places: within a NOTICE text file distributed
111
+ as part of the Derivative Works; within the Source form or
112
+ documentation, if provided along with the Derivative Works; or,
113
+ within a display generated by the Derivative Works, if and
114
+ wherever such third-party notices normally appear. The contents
115
+ of the NOTICE file are for informational purposes only and
116
+ do not modify the License. You may add Your own attribution
117
+ notices within Derivative Works that You distribute, alongside
118
+ or as an addendum to the NOTICE text from the Work, provided
119
+ that such additional attribution notices cannot be construed
120
+ as modifying the License.
121
+
122
+ You may add Your own copyright statement to Your modifications and
123
+ may provide additional or different license terms and conditions
124
+ for use, reproduction, or distribution of Your modifications, or
125
+ for any such Derivative Works as a whole, provided Your use,
126
+ reproduction, and distribution of the Work otherwise complies with
127
+ the conditions stated in this License.
128
+
129
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
130
+ any Contribution intentionally submitted for inclusion in the Work
131
+ by You to the Licensor shall be under the terms and conditions of
132
+ this License, without any additional terms or conditions.
133
+ Notwithstanding the above, nothing herein shall supersede or modify
134
+ the terms of any separate license agreement you may have executed
135
+ with Licensor regarding such Contributions.
136
+
137
+ 6. Trademarks. This License does not grant permission to use the trade
138
+ names, trademarks, service marks, or product names of the Licensor,
139
+ except as required for reasonable and customary use in describing the
140
+ origin of the Work and reproducing the content of the NOTICE file.
141
+
142
+ 7. Disclaimer of Warranty. Unless required by applicable law or
143
+ agreed to in writing, Licensor provides the Work (and each
144
+ Contributor provides its Contributions) on an "AS IS" BASIS,
145
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
146
+ implied, including, without limitation, any warranties or conditions
147
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
148
+ PARTICULAR PURPOSE. You are solely responsible for determining the
149
+ appropriateness of using or redistributing the Work and assume any
150
+ risks associated with Your exercise of permissions under this License.
151
+
152
+ 8. Limitation of Liability. In no event and under no legal theory,
153
+ whether in tort (including negligence), contract, or otherwise,
154
+ unless required by applicable law (such as deliberate and grossly
155
+ negligent acts) or agreed to in writing, shall any Contributor be
156
+ liable to You for damages, including any direct, indirect, special,
157
+ incidental, or consequential damages of any character arising as a
158
+ result of this License or out of the use or inability to use the
159
+ Work (including but not limited to damages for loss of goodwill,
160
+ work stoppage, computer failure or malfunction, or any and all
161
+ other commercial damages or losses), even if such Contributor
162
+ has been advised of the possibility of such damages.
163
+
164
+ 9. Accepting Warranty or Additional Liability. While redistributing
165
+ the Work or Derivative Works thereof, You may choose to offer,
166
+ and charge a fee for, acceptance of support, warranty, indemnity,
167
+ or other liability obligations and/or rights consistent with this
168
+ License. However, in accepting such obligations, You may act only
169
+ on Your own behalf and on Your sole responsibility, not on behalf
170
+ of any other Contributor, and only if You agree to indemnify,
171
+ defend, and hold each Contributor harmless for any liability
172
+ incurred by, or claims asserted against, such Contributor by reason
173
+ of your accepting any such warranty or additional liability.
174
+
175
+ END OF TERMS AND CONDITIONS
176
+
177
+ Copyright 2026 Duet
178
+
179
+ Licensed under the Apache License, Version 2.0 (the "License");
180
+ you may not use this file except in compliance with the License.
181
+ You may obtain a copy of the License at
182
+
183
+ http://www.apache.org/licenses/LICENSE-2.0
184
+
185
+ Unless required by applicable law or agreed to in writing, software
186
+ distributed under the License is distributed on an "AS IS" BASIS,
187
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
188
+ See the License for the specific language governing permissions and
189
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,315 @@
1
+ # duet-agent
2
+
3
+ An opinionated, full-stack agent turn runner. Native memories. Native interrupts. Multi-agent by default.
4
+
5
+ **No MCP. Everything is files and CLI.**
6
+
7
+ ## Why another agent framework?
8
+
9
+ Existing agent turn runners treat tools and memories as pluggable modules. This makes them flexible but fundamentally disconnected — memory is an afterthought.
10
+
11
+ duet-agent takes the opposite approach: **memory is woven into the core architecture.** An agent without memory is stateless. Interrupts are handled by the underlying pi agent runtime, so the turn runner does not need its own interrupt bus.
12
+
13
+ ## Architecture
14
+
15
+ The diagram below walks through a realistic agent-routed state machine: outbound conference
16
+ outreach. The user prompt enters the `TurnRunner`, the runner agent picks the next state based on
17
+ prompt, history, and available state definitions, and the state machine drives the business process
18
+ until it hits a terminal state. The same definition can start in the middle — for example, the
19
+ runner can skip straight to `wait_for_reply` if the user says “I already emailed them”.
20
+
21
+ ```mermaid
22
+ stateDiagram-v2
23
+ direction TB
24
+
25
+ [*] --> Classify : user prompt
26
+ Classify --> AgentMode : one-off task
27
+ Classify --> Outreach : matches a state machine
28
+
29
+ AgentMode --> [*] : answer / edits
30
+
31
+ state Outreach {
32
+ direction TB
33
+ [*] --> research_prospect
34
+
35
+ research_prospect : research_prospect (agent)\nweb + notes lookup
36
+ draft_email : draft_email (agent)\nwrite first-touch email
37
+ send_email : send_email (script)\nbash: gmail send
38
+ wait_for_reply : wait_for_reply (poll)\nevery 6h: check inbox
39
+ schedule_meeting : schedule_meeting (script)\nbash: calendly create
40
+ meeting_booked : meeting_booked (terminal: completed)
41
+ not_interested : not_interested (terminal: completed)
42
+ no_response : no_response (terminal: cancelled)
43
+
44
+ research_prospect --> draft_email : enough signal
45
+ research_prospect --> not_interested : disqualified
46
+ draft_email --> send_email
47
+ send_email --> wait_for_reply
48
+ wait_for_reply --> schedule_meeting : positive reply
49
+ wait_for_reply --> not_interested : declined
50
+ wait_for_reply --> no_response : 14d timeout
51
+ schedule_meeting --> meeting_booked
52
+ }
53
+
54
+ Outreach --> [*]
55
+ ```
56
+
57
+ Each state is one of the four kinds the runner understands:
58
+
59
+ - **agent** states run a sub-agent with a prompt, optional system prompt, and optional skill allowlist.
60
+ - **script** states shell out (`bash`, `curl`, CLIs) for anything with an API.
61
+ - **poll** states wait on an external signal by running one script check per interval, or by using a timer poll for pure delays.
62
+ - **terminal** states record a business outcome (`completed`, `cancelled`, `failed`).
63
+
64
+ Observational memory, pi coding tools, and guardrails sit underneath every state transition; they
65
+ are not states themselves.
66
+
67
+ ## Key Differentiators
68
+
69
+ ### Native Memory
70
+
71
+ Memory is first-class. The default `MemoryStore` is in-memory and emits observation events; optional PGlite storage hydrates and persists durable observations outside the turn runner session.
72
+
73
+ The memory model follows observational memory: turn runner session messages are observed into durable text observations, and a reflector condenses observations when they grow too large. Observations are scoped as `session` or `resource`.
74
+
75
+ ### Pi Coding Tools
76
+
77
+ Sub-agents use the default tools from `@earendil-works/pi-coding-agent`: read, bash, edit, and write. The turn runner supplies a working directory and can restrict which skills are injected into a state-machine agent state; it does not wrap those tools in a second sandbox abstraction.
78
+
79
+ ### Native Interrupts
80
+
81
+ Interrupt behavior comes from the underlying pi agent runtime. A user can send a message while a pi session is running, and the runtime can handle it as an interruption or as a follow-up. duet-agent does not add a second interrupt bus on top.
82
+
83
+ ### Multi-Agent by Default
84
+
85
+ The turn runner can delegate durable process steps into agent states. Agent states are not pre-built classes; they are state-machine states with prompts, optional system prompts, and optional skill allowlists.
86
+
87
+ ### Three Execution Modes
88
+
89
+ The turn runner has three top-level modes:
90
+
91
+ - `agent`: handle the prompt as a normal agent session. This is for one-off tasks, coding requests, reviews, research, and anything that can complete in the current session.
92
+ - `state_machine`: route the prompt into an agent-routed state machine. This is for long-running business processes with durable state, waits, and terminal business outcomes.
93
+ - `auto`: let the turn runner classify the prompt and choose either `agent` or `state_machine`.
94
+
95
+ Normal agent mode handles immediate work; state-machine mode handles business processes that may pause, resume, wait on external systems, or start in the middle based on the user's prompt. In `auto`, the runner classifies the prompt and routes to whichever fits.
96
+
97
+ ### Agent-Routed State Machines
98
+
99
+ duet-agent is exploring long-running agent-routed state machines for business processes like outbound sales, conference outreach, and development loops. The design goal is **not** to become a workflow engine like Temporal, Airflow, or GitHub Actions.
100
+
101
+ Instead, state machines are agent-routed. A state-machine definition describes the available business states: agent states, shell-script states, poll states, and terminal states. The runner keeps the state-machine system prompt cache-friendly by including only stable routing instructions plus the original prompt and available state definitions. Current state and history stay in the parent agent conversation, where state transitions, script results, poll results, and user follow-ups are already recorded.
102
+
103
+ The state machine is higher level than task execution. It tracks one current business state at a time. If a state needs fan-out, parallelism, or a task-level workflow, that belongs inside an agent or script state. The agent can execute a complex workflow internally; the state machine only records the business transition before and after that state.
104
+
105
+ This keeps state machines flexible enough to start in the middle. For example, a user can say: "prospect person X, I've already sent email, just wait for response." The same outreach state machine can skip research and email sending, then choose the wait-for-response state because the runner agent understands the existing context.
106
+
107
+ External integrations stay simple: anything with an API or CLI is a script state or script poll. Timer polls cover pure delays such as "wait before retry." Email, GitHub, Calendly, CRM systems, and webhooks do not need first-class engine concepts. If the state machine can tolerate a few minutes of polling delay, a bash script is enough.
108
+
109
+ What this is not:
110
+
111
+ - Not a deterministic DAG scheduler.
112
+ - Not a low-level durable execution runtime.
113
+ - Not a workflow service with queues, workers, locks, and retries as the main abstraction.
114
+ - Not a replacement for infrastructure workflow engines when exact-once execution or strict SLAs matter.
115
+
116
+ The turn runner should provide enough structure for an agent to make good process decisions, while leaving hard operational guarantees to external systems.
117
+
118
+ ### Optional Guardrails
119
+
120
+ Pattern-based (fast, regex) and semantic (LLM-evaluated) guardrails compose into a firewall. Every bash command and file write can be checked before execution.
121
+
122
+ ## CLI Install
123
+
124
+ The CLI runs on Bun because OpenTUI is Bun-native. Install Bun first if it is not already available:
125
+
126
+ ```bash
127
+ curl -fsSL https://bun.sh/install | bash
128
+ ```
129
+
130
+ Install the CLI globally to make the `duet` command available on your PATH:
131
+
132
+ ```bash
133
+ bun add --global @duetso/agent
134
+ ```
135
+
136
+ You can also install it globally with another package manager:
137
+
138
+ ```bash
139
+ npm install --global @duetso/agent
140
+ pnpm add --global @duetso/agent
141
+ yarn global add @duetso/agent
142
+ ```
143
+
144
+ Upgrade an existing global installation:
145
+
146
+ ```bash
147
+ duet upgrade
148
+ ```
149
+
150
+ ## SDK Install
151
+
152
+ Install the package as a dependency when you want to use the turn runner from TypeScript or JavaScript:
153
+
154
+ ```bash
155
+ npm install @duetso/agent
156
+ ```
157
+
158
+ ## Development
159
+
160
+ This repo uses Bun for package management, Husky for pre-commit checks, and Docker for functional tests.
161
+
162
+ ```bash
163
+ bun install
164
+ bun run setup # install/start Docker on macOS or Linux if needed
165
+ bun run check-types
166
+ bun run lint
167
+ bun run eval # runs live evals inside Docker
168
+ bun run test # runs the test suite inside Docker
169
+ ```
170
+
171
+ Use `bun run test` and `bun run eval`, not raw `bun test`, as the source of truth. File-writing tests and evals run in Docker so focused host runs cannot create `.duet`, PGlite databases, or home-directory skill fixtures in the checkout.
172
+
173
+ The pre-commit hook runs `format`, `check-types`, and `lint`.
174
+
175
+ ## CLI Quick Start
176
+
177
+ Set a provider API key in the environment or in `<workdir>/.env`, then run `duet` from any project directory. When `--model` is omitted, the CLI infers a default from the configured provider: Anthropic, AI Gateway, and OpenRouter use Opus 4.7; OpenAI uses GPT-5.5.
178
+
179
+ ```bash
180
+ export ANTHROPIC_API_KEY=sk-...
181
+
182
+ # Start a session
183
+ duet "build a REST API with Express"
184
+
185
+ # Open an interactive session without an initial prompt
186
+ duet
187
+
188
+ # With options
189
+ duet -m anthropic:claude-opus-4-7 --workdir ./my-project "refactor the auth module"
190
+
191
+ # With a custom observational memory model
192
+ duet --memory-model anthropic:claude-sonnet-4-6 "summarize this repo"
193
+
194
+ # With additional system instructions
195
+ duet --system-prompt "Prefer concise answers." "review this repo"
196
+
197
+ # Override the default AGENTS.md system prompt file
198
+ duet --system-prompt-file TEAM.md "review this repo"
199
+
200
+ # Disable system prompt file loading
201
+ duet --no-system-prompt-files "review this repo"
202
+
203
+ # Resume a saved session
204
+ duet --resume session_abc123 --workdir ./my-project
205
+
206
+ # Through Vercel AI Gateway
207
+ export AI_GATEWAY_API_KEY=...
208
+ duet -m vercel-ai-gateway:anthropic/claude-opus-4.7 "review this repo"
209
+ ```
210
+
211
+ For local development from a checkout, use the package script:
212
+
213
+ ```bash
214
+ bun run cli -- "build a REST API with Express"
215
+ ```
216
+
217
+ ## SDK Quick Start
218
+
219
+ ```typescript
220
+ import { TurnRunner } from "@duetso/agent";
221
+
222
+ const turnRunner = new TurnRunner({
223
+ model: "anthropic:claude-opus-4-7",
224
+ cwd: process.cwd(),
225
+ mode: "auto",
226
+ });
227
+
228
+ const terminal = await turnRunner.turn({
229
+ type: "start",
230
+ prompt: "Build a todo app with React and TypeScript",
231
+ });
232
+ ```
233
+
234
+ `TurnRunner.turn()` is the concurrency boundary. Callers may call it repeatedly
235
+ while work is active; the runner folds active `prompt` and `answer` commands
236
+ back into the active pi agent as `steer` or `follow_up`, queues wakes and other
237
+ work it cannot absorb immediately, and emits one terminal event when the whole
238
+ active work chain is done. The parent runner transcript stays linear: state
239
+ machine continuations, script results, poll results, and user follow-ups rejoin
240
+ the parent agent rather than creating separate conversation branches.
241
+
242
+ ## Memory And Persistence
243
+
244
+ duet-agent owns a concrete event-emitting `MemoryStore` internally. It is the runtime state container, not a database adapter.
245
+
246
+ `SessionManager` stores session snapshots under `~/.duet/sessions` by default and enables durable observational memory at `~/.duet/memory.db`. Pass `memoryDbPath: false` to keep observational memory in process only, or provide `memoryDbPath` for a custom database location.
247
+
248
+ ```typescript
249
+ import { SessionManager } from "@duetso/agent";
250
+
251
+ const manager = new SessionManager({
252
+ model: "anthropic:claude-opus-4-7",
253
+ });
254
+ ```
255
+
256
+ The memory module hydrates durable observations from an embedded Postgres database powered by PGlite before the first turn and writes observation updates back as memory changes. Raw conversation messages stay in `TurnState.agent.messages`; memory persistence stores only derived observations/reflections.
257
+
258
+ You can also resume directly from saved state:
259
+
260
+ ```typescript
261
+ const terminal = await turnRunner.turn({
262
+ type: "prompt",
263
+ state: savedState,
264
+ message: "Continue the previous goal",
265
+ behavior: "follow_up",
266
+ });
267
+ ```
268
+
269
+ Resume continues turn runner session state, not an in-flight model/tool call. Any `in_progress` todo is retried from `pending`.
270
+
271
+ Observational memory is enabled by default with thresholds tuned for modern 200k-token model windows:
272
+
273
+ - Raw messages are observed around `150_000` tokens so exact transcript context and prompt caching are used before compaction.
274
+ - Observation logs are reflected around `90_000` tokens, targeting about `65_000` tokens after reflection.
275
+ - Raw-tail retention keeps about `40_000` exact message tokens after observation activation.
276
+ - Observation context is injected as reminder messages; replacing raw context with observations/reflections is the compaction path.
277
+
278
+ ## Skills
279
+
280
+ Skills are loaded from `<cwd>/.duet/skills`, `<cwd>/.agents/skills`, `~/.duet/skills`, and `~/.agents/skills` by default, using `@earendil-works/pi-coding-agent`'s skill loader. The turn runner injects every loaded skill's description and instructions into the agent system prompt. `getSkills()` returns the discovered skills, including YAML frontmatter descriptions such as block scalars.
281
+
282
+ ## Guardrails
283
+
284
+ The turn runner installs its default safety checks internally. Add extra guardrail config objects when a deployment needs stricter local policy.
285
+
286
+ ```typescript
287
+ const turnRunner = new TurnRunner({
288
+ // ...
289
+ guardrails: [
290
+ {
291
+ kind: "pattern",
292
+ rules: [
293
+ { pattern: /production-db/i, action: "warn", reason: "Production database mentioned" },
294
+ ],
295
+ },
296
+ {
297
+ kind: "semantic",
298
+ model: getModel("anthropic", "claude-haiku-4-5"),
299
+ policy: "Never delete production data. Never expose secrets in output.",
300
+ },
301
+ ],
302
+ });
303
+ ```
304
+
305
+ ## Design Principles
306
+
307
+ 1. **Files and CLI over protocols.** No MCP, no custom APIs. If you can't do it with bash, you can't do it.
308
+ 2. **Runtime state over persistence.** The turn runner owns in-memory state and emits events. Persistence lives in external modules or initial-state hydration.
309
+ 3. **Agent-routed state machines over workflow engines.** Long-running state machines describe available business states; a runner agent decides what to do next from prompt, state, and history. Task-level workflows belong inside agent or script states.
310
+ 4. **Dynamic over static.** Agent states are defined by state machines at runtime, not pre-built classes.
311
+ 5. **Simple over flexible.** Default pi coding tools. One default memory store. Constraints breed creativity.
312
+
313
+ ## License
314
+
315
+ Apache-2.0
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@duetso/agent",
3
+ "version": "0.1.20",
4
+ "description": "An opinionated full-stack agent turn runner with native memories, interrupts, and multi-agent orchestration",
5
+ "keywords": [
6
+ "agent",
7
+ "ai",
8
+ "llm",
9
+ "memory",
10
+ "multi-agent",
11
+ "session-manager"
12
+ ],
13
+ "license": "Apache-2.0",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/dzhng/duet-agent"
17
+ },
18
+ "bin": {
19
+ "duet": "dist/src/cli.js"
20
+ },
21
+ "files": [
22
+ "dist/package.json",
23
+ "dist/src",
24
+ "LICENSE",
25
+ "README.md"
26
+ ],
27
+ "type": "module",
28
+ "main": "dist/src/index.js",
29
+ "types": "dist/src/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/src/index.d.ts",
33
+ "import": "./dist/src/index.js"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "setup": "bash scripts/setup-docker.sh",
42
+ "build": "tsc",
43
+ "check-types": "tsc --noEmit",
44
+ "cli": "bun src/cli.ts",
45
+ "dev": "tsc --watch",
46
+ "eval": "docker run --rm -v \"$PWD:/src:ro\" -w /work -e HOME=/tmp/home -e DUET_TEST_IN_DOCKER=1 oven/bun:1.3.11 sh -lc 'cp -R /src/. /work && bun install --frozen-lockfile && bun test ./evals/*.eval.ts'",
47
+ "format": "oxfmt --write --no-error-on-unmatched-pattern",
48
+ "format:check": "oxfmt --check --no-error-on-unmatched-pattern",
49
+ "lint": "oxlint src/",
50
+ "prepack": "bun run build",
51
+ "test": "docker run --rm -v \"$PWD:/src:ro\" -w /work -e HOME=/tmp/home -e DUET_TEST_IN_DOCKER=1 oven/bun:1.3.11 sh -lc 'cp -R /src/. /work && bun install --frozen-lockfile && bun test ./test/*.test.ts'",
52
+ "example": "bun examples/basic.ts",
53
+ "example:state-machine": "bun examples/state-machine.ts",
54
+ "prepare": "husky",
55
+ "build:compile": "bun build src/cli.ts --compile --outfile dist/duet"
56
+ },
57
+ "dependencies": {
58
+ "@earendil-works/pi-agent-core": "^0.74.0",
59
+ "@earendil-works/pi-ai": "^0.74.0",
60
+ "@earendil-works/pi-coding-agent": "^0.74.0",
61
+ "@electric-sql/pglite": "^0.4.5",
62
+ "@opentui/core": "^0.2.2",
63
+ "ajv": "^8.20.0",
64
+ "dedent": "^1.7.2",
65
+ "dotenv": "^17.4.2",
66
+ "eventemitter3": "^5.0.0",
67
+ "jstoxml": "^7.1.0",
68
+ "nanoid": "^5.0.0",
69
+ "typebox": "^1.1.24"
70
+ },
71
+ "devDependencies": {
72
+ "@types/bun": "^1.3.13",
73
+ "@types/node": "^22.0.0",
74
+ "husky": "^9.1.7",
75
+ "oxfmt": "^0.47.0",
76
+ "oxlint": "^0.16.0",
77
+ "tsx": "^4.19.0",
78
+ "typescript": "^5.7.0"
79
+ },
80
+ "engines": {
81
+ "node": ">=20"
82
+ },
83
+ "packageManager": "bun@1.3.11"
84
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * duet CLI
4
+ *
5
+ * Usage:
6
+ * duet "build a todo app in React"
7
+ * duet --model claude-opus-4-7 "refactor auth system"
8
+ * echo "fix the bug in server.ts" | duet
9
+ */
10
+ declare const PACKAGE_MANAGERS: readonly ["npm", "bun", "pnpm", "yarn"];
11
+ type PackageManager = (typeof PACKAGE_MANAGERS)[number];
12
+ type PackageManagerDetectionContext = {
13
+ userAgent?: string;
14
+ runtimeExecutable?: string;
15
+ cliFilePath?: string;
16
+ scriptPath?: string;
17
+ };
18
+ export declare function parseResumeHistoryLines(value: string, optionName?: string): number;
19
+ export declare function formatNewVersionNotice(packageName: string, currentVersion: string, latestVersion: string): string;
20
+ export declare function compareSemverVersions(left: string, right: string): number;
21
+ export declare function detectPackageManagerFromContext(context: PackageManagerDetectionContext): PackageManager;
22
+ export {};
23
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;GAOG;AAwBH,QAAA,MAAM,gBAAgB,yCAA0C,CAAC;AAEjE,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,KAAK,8BAA8B,GAAG;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAqZF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,UAAU,SAA2B,GACpC,MAAM,CAKR;AAeD,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,GACpB,MAAM,CAER;AAmBD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAWzE;AA+FD,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,8BAA8B,GACtC,cAAc,CAiBhB"}