@jslee124/forge 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1189 -90
- package/package.json +2 -1
- package/resources/docs/en/ARCHITECTURE.md +519 -0
- package/resources/docs/en/AUTHENTICATION.md +224 -0
- package/resources/docs/en/CLI_UI.md +266 -0
- package/resources/docs/en/CONFIGURATION.md +263 -0
- package/resources/docs/en/CONTEXT_MANAGEMENT.md +692 -0
- package/resources/docs/en/GETTING_STARTED.md +241 -0
- package/resources/docs/en/PLUGINS.md +622 -0
- package/resources/docs/en/PRODUCT.md +157 -0
- package/resources/docs/en/PROJECT_CONTEXT.md +225 -0
- package/resources/docs/en/RELEASING.md +94 -0
- package/resources/docs/en/SECURITY.md +272 -0
- package/resources/docs/en/SESSIONS.md +134 -0
- package/resources/docs/en/TROUBLESHOOTING.md +256 -0
- package/resources/docs/index.json +24334 -0
- package/resources/docs/zh-CN/ARCHITECTURE.md +174 -0
- package/resources/docs/zh-CN/AUTHENTICATION.md +96 -0
- package/resources/docs/zh-CN/CLI_UI.md +112 -0
- package/resources/docs/zh-CN/CONFIGURATION.md +221 -0
- package/resources/docs/zh-CN/CONTEXT_MANAGEMENT.md +200 -0
- package/resources/docs/zh-CN/GETTING_STARTED.md +193 -0
- package/resources/docs/zh-CN/PLUGINS.md +286 -0
- package/resources/docs/zh-CN/PRODUCT.md +86 -0
- package/resources/docs/zh-CN/PROJECT_CONTEXT.md +130 -0
- package/resources/docs/zh-CN/RELEASING.md +86 -0
- package/resources/docs/zh-CN/SECURITY.md +92 -0
- package/resources/docs/zh-CN/SESSIONS.md +69 -0
- package/resources/docs/zh-CN/TROUBLESHOOTING.md +185 -0
- package/resources/skills/forge-plugin-creator/SKILL.md +70 -0
- package/resources/skills/forge-plugin-creator/references/plugin-api.md +36 -0
- package/resources/skills/forge-plugin-creator/templates/index.mjs +30 -0
- package/resources/skills/forge-plugin-creator/templates/plugin.json +8 -0
- package/resources/skills/forge-plugin-creator/templates/plugin.test-template.ts +14 -0
- package/resources/skills/forge-product-help/SKILL.md +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jslee124/forge",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A safe, observable, and evaluable coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"resources",
|
|
12
13
|
"README.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
简体中文 · Documentation index
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
This document describes the current architecture on the `dev` branch and the
|
|
8
|
+
rationale behind its package boundaries. Simplified interface blocks are
|
|
9
|
+
explanatory sketches, not a stable public SDK; checked-in TypeScript types and
|
|
10
|
+
tests remain authoritative.
|
|
11
|
+
|
|
12
|
+
## Implementation baseline
|
|
13
|
+
|
|
14
|
+
These choices define the current implementation baseline. They may be revisited
|
|
15
|
+
when measured behavior provides contrary evidence.
|
|
16
|
+
|
|
17
|
+
| Area | Initial decision | Reason |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| Runtime | Node.js 24 LTS | Use a supported LTS runtime and current platform APIs |
|
|
20
|
+
| Package manager | pnpm 11.18.0 | Fast, strict dependency layout with workspace support |
|
|
21
|
+
| Module format | ESM only | Avoid maintaining dual ESM/CommonJS output |
|
|
22
|
+
| Repository shape | pnpm monorepo | Make runtime boundaries visible without separate repositories |
|
|
23
|
+
| Build | TypeScript project references with `tsc -b` | Enforce package direction without an initial bundler |
|
|
24
|
+
| CLI parsing | Commander | Small, mature process command and help parser |
|
|
25
|
+
| Interactive UI | Ink + React | Component rendering and keyboard input without moving runtime logic into the UI |
|
|
26
|
+
| Validation | Zod | Share runtime validation between configuration and tool inputs |
|
|
27
|
+
| Formatting and linting | Biome | One fast tool with a small configuration surface |
|
|
28
|
+
| Testing | Vitest | Fast TypeScript tests and straightforward fakes |
|
|
29
|
+
| First provider | DeepSeek through `@ai-sdk/deepseek` | Prove one provider path before generalizing |
|
|
30
|
+
| Initial model | `deepseek-v4-flash` | Current fast DeepSeek model with tool and thinking support |
|
|
31
|
+
| Process execution | Node.js `spawn`, `shell: false` | Keep program and arguments structured and avoid implicit shell parsing |
|
|
32
|
+
|
|
33
|
+
The root `package.json` is private and pins pnpm through `packageManager`. Every
|
|
34
|
+
workspace package uses `"type": "module"`. Dependency versions are pinned by
|
|
35
|
+
the lockfile rather than copied into design documents, except for the runtime
|
|
36
|
+
and package-manager baseline above.
|
|
37
|
+
|
|
38
|
+
### Monorepo layout
|
|
39
|
+
|
|
40
|
+
Packages are created when their milestone begins, not as empty placeholders:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
apps/
|
|
44
|
+
`-- cli/ # @forge/cli: parsing, rendering, approval UI
|
|
45
|
+
packages/
|
|
46
|
+
|-- core/ # @forge/core: loop, events, policy contracts
|
|
47
|
+
|-- codex-app-server/ # Official Codex JSON-RPC transport and auth boundary
|
|
48
|
+
|-- model-deepseek/ # @forge/model-deepseek: AI SDK translation
|
|
49
|
+
|-- model-compat/ # Configured OpenAI-compatible route translation
|
|
50
|
+
|-- model-openai/ # @forge/model-openai: Responses API translation
|
|
51
|
+
|-- auth/ # provider-neutral API-key resolution
|
|
52
|
+
|-- persistence/ # session snapshots, JSONL traces, redaction
|
|
53
|
+
|-- plugin-api/ # executable plugin discovery, trust, host, and API v1
|
|
54
|
+
|-- resources/ # non-executable Skill catalog and safe lazy loading
|
|
55
|
+
|-- tools/ # @forge/tools: built-in tool implementations
|
|
56
|
+
`-- config/ # @forge/config: configuration and context loading
|
|
57
|
+
fixtures/ # Small repository tasks used by integration tests
|
|
58
|
+
`-- validation-bug/
|
|
59
|
+
evals/ # Task manifests, graders, trial runner, reports
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`evals/` is a private workspace package. Its live runner imports the real CLI
|
|
63
|
+
run boundary, copies one fixture into a fresh temporary workspace, applies a
|
|
64
|
+
narrow test approval channel, persists the normal run trace, and invokes the
|
|
65
|
+
external grader only after the Agent stops. Generated artifacts are ignored
|
|
66
|
+
until a reviewed report is selected for publication.
|
|
67
|
+
|
|
68
|
+
A generic `shared` package is intentionally avoided; each cross-cutting contract
|
|
69
|
+
belongs to the package that owns its behavior.
|
|
70
|
+
|
|
71
|
+
### CLI and process conventions
|
|
72
|
+
|
|
73
|
+
The initial CLI uses these exit codes:
|
|
74
|
+
|
|
75
|
+
| Code | Meaning |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `0` | Run completed and required verification succeeded |
|
|
78
|
+
| `1` | Unrecovered runtime, provider, or tool failure |
|
|
79
|
+
| `2` | Invalid CLI usage or configuration |
|
|
80
|
+
| `3` | Run stopped without success, including a configured limit |
|
|
81
|
+
| `4` | A required action was denied or no approval channel was available |
|
|
82
|
+
| `130` | User cancellation through Ctrl+C |
|
|
83
|
+
|
|
84
|
+
Tool failures may be returned to the model as observations and therefore do not
|
|
85
|
+
immediately determine the process exit code. Only the terminal run status does.
|
|
86
|
+
Ordinary user errors do not print stack traces unless debug output is enabled.
|
|
87
|
+
|
|
88
|
+
## System context
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
User
|
|
92
|
+
|
|
|
93
|
+
v
|
|
94
|
+
CLI
|
|
95
|
+
|
|
|
96
|
+
v
|
|
97
|
+
Agent Runtime
|
|
98
|
+
| | | | |
|
|
99
|
+
v v v v v
|
|
100
|
+
Model Adapter Context Loader Plugin Host Policy Kernel Run Events
|
|
101
|
+
| | | | |
|
|
102
|
+
v v v v v
|
|
103
|
+
Auth Manager Instructions Contributions Tool Executor Terminal + Trace
|
|
104
|
+
|
|
|
105
|
+
v
|
|
106
|
+
AI SDK -> Model Provider
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Responsibilities
|
|
110
|
+
|
|
111
|
+
### CLI
|
|
112
|
+
|
|
113
|
+
The CLI is responsible for:
|
|
114
|
+
|
|
115
|
+
- Parsing commands and configuration
|
|
116
|
+
- Managing the persistent interactive session, multi-line editor, slash-command
|
|
117
|
+
completion, and structured `@` file mentions
|
|
118
|
+
- Selecting the workspace
|
|
119
|
+
- Rendering streamed events and readable diffs
|
|
120
|
+
- Asking the user to approve sensitive actions
|
|
121
|
+
- Forwarding cancellation through an `AbortSignal`
|
|
122
|
+
- Choosing an appropriate process exit code
|
|
123
|
+
|
|
124
|
+
The CLI does not own the agent loop or tool implementation logic.
|
|
125
|
+
Commander owns process-level commands, while Ink owns only the interactive
|
|
126
|
+
terminal presentation. React and Ink remain dependencies of `apps/cli` and must
|
|
127
|
+
not cross into `@forge/core`. File mentions carry workspace-relative paths to
|
|
128
|
+
the model; they do not bypass `read_file`, workspace validation, policy, or
|
|
129
|
+
trace events by injecting file contents automatically.
|
|
130
|
+
|
|
131
|
+
Each interactive prompt starts a fresh bounded run and approval-policy instance.
|
|
132
|
+
Only completed user and assistant text is carried into the next prompt. That
|
|
133
|
+
conversation is persisted as a session and can be restored after restart, while
|
|
134
|
+
tool continuation metadata and approvals remain scoped to the run that produced
|
|
135
|
+
them. See Persistent Sessions and Run Traces.
|
|
136
|
+
|
|
137
|
+
### Agent runtime
|
|
138
|
+
|
|
139
|
+
The runtime owns:
|
|
140
|
+
|
|
141
|
+
- Run state and step count
|
|
142
|
+
- Conversation messages
|
|
143
|
+
- The model/tool execution loop
|
|
144
|
+
- Provider-supplied reasoning blocks
|
|
145
|
+
- Stop conditions
|
|
146
|
+
- Tool-call validation and dispatch
|
|
147
|
+
- Approval-policy checks
|
|
148
|
+
- Project-context assembly
|
|
149
|
+
- Controlled plugin-hook orchestration
|
|
150
|
+
- Event emission
|
|
151
|
+
- Final run status
|
|
152
|
+
|
|
153
|
+
The runtime depends on interfaces for model access, tools, approval, and trace
|
|
154
|
+
persistence. This keeps it independently testable.
|
|
155
|
+
|
|
156
|
+
### Model adapter
|
|
157
|
+
|
|
158
|
+
The initial adapter uses Vercel AI SDK and `@ai-sdk/deepseek` for streaming and
|
|
159
|
+
tool-call transport. It uses `deepseek-v4-flash` and explicitly enables thinking
|
|
160
|
+
mode so a provider default change cannot silently alter behavior.
|
|
161
|
+
|
|
162
|
+
The DeepSeek adapter selects the OpenAI-compatible Responses transport only for
|
|
163
|
+
`deepseek-v4-flash-vision-exp`. Core requests carry provider-neutral URL or
|
|
164
|
+
base64 image parts; the CLI owns local-file canonicalization, format validation,
|
|
165
|
+
size limits, and encoding. The transport maps those parts to `input_image`
|
|
166
|
+
content while keeping the runtime-owned tool loop and opaque continuation
|
|
167
|
+
contract unchanged. Other DeepSeek models reject attached images before a
|
|
168
|
+
provider call.
|
|
169
|
+
|
|
170
|
+
The model adapter performs exactly one provider turn and maps the AI SDK full
|
|
171
|
+
stream into Forge model events. Forge controls the multi-step loop and does not
|
|
172
|
+
delegate it to `ToolLoopAgent`, `stopWhen`, or another prebuilt agent
|
|
173
|
+
abstraction. AI SDK tool definitions sent to the model do not receive direct
|
|
174
|
+
`execute` callbacks; Forge validates and executes tool calls only after the
|
|
175
|
+
policy kernel records a decision.
|
|
176
|
+
|
|
177
|
+
DeepSeek thinking-mode tool calls require the provider-returned reasoning
|
|
178
|
+
content to be preserved in subsequent tool-result turns. The adapter therefore
|
|
179
|
+
returns an opaque continuation record alongside observable Forge events. The
|
|
180
|
+
core stores and returns that record to the same adapter, but it must not
|
|
181
|
+
reconstruct it from terminal text or discard provider metadata. An integration
|
|
182
|
+
test covers this round trip.
|
|
183
|
+
|
|
184
|
+
When a provider returns reasoning or thinking content, the adapter preserves it
|
|
185
|
+
as a typed response part. The runtime exposes that content to the terminal and
|
|
186
|
+
trace pipeline. It does not invent reasoning for providers that do not return
|
|
187
|
+
it.
|
|
188
|
+
|
|
189
|
+
### Authentication manager
|
|
190
|
+
|
|
191
|
+
Authentication is separate from model transport. The model adapter asks an
|
|
192
|
+
authentication manager for request credentials instead of reading environment
|
|
193
|
+
variables or token files directly.
|
|
194
|
+
|
|
195
|
+
The native Forge Engine resolves `DEEPSEEK_API_KEY` or `OPENAI_API_KEY` through
|
|
196
|
+
one provider-neutral manager and uses provider-specific AI SDK adapters. The
|
|
197
|
+
separate Codex Engine starts the official Codex App Server over stdio JSON-RPC.
|
|
198
|
+
Forge initiates managed ChatGPT browser or device-code login, but Codex owns the
|
|
199
|
+
OAuth client identity, callback, tokens, persistence, refresh, and logout.
|
|
200
|
+
|
|
201
|
+
Codex App Server is a complete agent runtime rather than a raw model endpoint.
|
|
202
|
+
It therefore remains a separate engine instead of implementing Forge's
|
|
203
|
+
`ModelAdapter`. Forge dynamically reads `model/list`, validates the selected
|
|
204
|
+
reasoning effort, and streams Codex turn events, while Codex owns its tools,
|
|
205
|
+
sandbox, approvals, and conversation state.
|
|
206
|
+
|
|
207
|
+
Forge must not copy client credentials from another application, depend on
|
|
208
|
+
undocumented endpoints as a stable contract, or silently read credentials from
|
|
209
|
+
`~/.codex/auth.json`. See Authentication.
|
|
210
|
+
|
|
211
|
+
### Project context loader
|
|
212
|
+
|
|
213
|
+
The project context loader first resolves `FORGE_HOME`, defaulting to the
|
|
214
|
+
operating system user's `~/.forge/`. It validates user configuration and then
|
|
215
|
+
resolves the canonical workspace and working directory. Ordinary configuration
|
|
216
|
+
merges from defaults, user configuration, project configuration, environment
|
|
217
|
+
variables, and explicit CLI flags, preserving provenance for every value. The
|
|
218
|
+
configuration schema marks user-only and strictness-only keys so project values
|
|
219
|
+
cannot pass through the ordinary override algorithm.
|
|
220
|
+
|
|
221
|
+
The loader reads optional user instructions from `~/.forge/AGENTS.md`, then
|
|
222
|
+
loads project `AGENTS.md` instructions from the repository root toward the
|
|
223
|
+
working directory, preferring `AGENTS.override.md` at each level. It preserves
|
|
224
|
+
all instruction paths in the run trace.
|
|
225
|
+
|
|
226
|
+
The separate resource boundary discovers bundled, user, and portable `.agents/`
|
|
227
|
+
Skills as bounded metadata, resolves precedence, and exposes registered content
|
|
228
|
+
only through `load_skill`. Discovery does not execute a resource. It also
|
|
229
|
+
discovers Forge-specific `.forge/` configuration. Project-local executable
|
|
230
|
+
plugins under `.forge/plugins/` are handed to the plugin host only after the
|
|
231
|
+
workspace has been explicitly trusted.
|
|
232
|
+
|
|
233
|
+
User configuration may choose a supported permission profile. Project context
|
|
234
|
+
can influence prompts and make policy stricter, but it cannot grant permissions
|
|
235
|
+
or weaken the policy kernel. Secrets are resolved by the authentication manager
|
|
236
|
+
and never from project configuration. See Project Context and Local
|
|
237
|
+
Customization.
|
|
238
|
+
|
|
239
|
+
### Plugin host
|
|
240
|
+
|
|
241
|
+
The plugin host is an extension boundary, not the security authority. Trusted
|
|
242
|
+
plugins may:
|
|
243
|
+
|
|
244
|
+
- Register custom tools
|
|
245
|
+
- Register user commands
|
|
246
|
+
- Declare bounded host-managed subagent roles
|
|
247
|
+
- Contribute prompt instructions
|
|
248
|
+
- Observe immutable run events
|
|
249
|
+
- Participate in selected lifecycle hooks
|
|
250
|
+
- Make policy decisions stricter
|
|
251
|
+
|
|
252
|
+
The manifest may also declare `network:access`. A registered tool whose risk is
|
|
253
|
+
`network` requires that declaration and is confirmed on every call under both
|
|
254
|
+
implemented permission profiles. This is an application-level review and
|
|
255
|
+
approval boundary, not network isolation; trusted plugin code can still use
|
|
256
|
+
Node.js directly.
|
|
257
|
+
|
|
258
|
+
All custom tool calls still pass through the policy kernel and tool executor.
|
|
259
|
+
Plugins cannot convert a core `deny` into `allow` or bypass an approval request.
|
|
260
|
+
|
|
261
|
+
A subagent declaration becomes a `model`-risk parent tool. The host—not the
|
|
262
|
+
plugin—creates the child adapter, isolated conversation, inherited policy and
|
|
263
|
+
approval channel, shared budgets, cancellation path, bounded result, and linked
|
|
264
|
+
trace. Child tool selection excludes every subagent tool, so delegation depth
|
|
265
|
+
is one. The active parent model is inherited; cross-model routing and resumable
|
|
266
|
+
child sessions are not part of this contract.
|
|
267
|
+
|
|
268
|
+
An in-process JavaScript plugin is trusted local code and can use Node.js APIs
|
|
269
|
+
directly. API-level capability declarations do not create real isolation. Strong
|
|
270
|
+
plugin isolation requires a separate process or operating-system sandbox and is
|
|
271
|
+
deferred.
|
|
272
|
+
|
|
273
|
+
### Tools
|
|
274
|
+
|
|
275
|
+
Every tool has:
|
|
276
|
+
|
|
277
|
+
- A unique name
|
|
278
|
+
- A concise model-facing description
|
|
279
|
+
- A Zod input schema
|
|
280
|
+
- An execution function
|
|
281
|
+
- A risk classification
|
|
282
|
+
- A structured result
|
|
283
|
+
|
|
284
|
+
The native tools and checked-in extension examples are:
|
|
285
|
+
|
|
286
|
+
| Tool | Responsibility | Initial risk |
|
|
287
|
+
| --- | --- | --- |
|
|
288
|
+
| `list_files` | List a bounded part of the workspace | Read-only |
|
|
289
|
+
| `read_file` | Read a workspace file with output limits | Read-only |
|
|
290
|
+
| `search` | Search text within the workspace | Read-only |
|
|
291
|
+
| `create_file` | Exclusively create a new UTF-8 workspace file | Write |
|
|
292
|
+
| `apply_patch` | Apply a structured file change | Write |
|
|
293
|
+
| `run_command` | Spawn a program with structured arguments and limits | Variable |
|
|
294
|
+
| `web_search` (example plugin) | Search through a configured or fallback public provider | Network |
|
|
295
|
+
| `web_fetch` (example plugin) | Fetch bounded readable public HTTP(S) text | Network |
|
|
296
|
+
| `delegate_code_review` (example plugin) | Run an isolated read-only review role | Model |
|
|
297
|
+
|
|
298
|
+
Tools receive an explicit execution context instead of reading global process
|
|
299
|
+
state. The context includes the workspace root, abort signal, limits, and event
|
|
300
|
+
emitter.
|
|
301
|
+
|
|
302
|
+
### Approval policy
|
|
303
|
+
|
|
304
|
+
The policy evaluates an action before execution and returns one of:
|
|
305
|
+
|
|
306
|
+
```text
|
|
307
|
+
allow Execute without user interaction
|
|
308
|
+
confirm Ask the user before execution
|
|
309
|
+
deny Do not execute
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The policy considers:
|
|
313
|
+
|
|
314
|
+
- The canonical target path and whether it remains inside the workspace
|
|
315
|
+
- Whether an operation changes files
|
|
316
|
+
- Whether a command is destructive or otherwise sensitive
|
|
317
|
+
- Whether configured time, output, or call limits have been reached
|
|
318
|
+
- Whether an approval UI is available
|
|
319
|
+
|
|
320
|
+
The default policy is:
|
|
321
|
+
|
|
322
|
+
| Action | Default decision |
|
|
323
|
+
| --- | --- |
|
|
324
|
+
| Read, list, or search inside the workspace | Allow |
|
|
325
|
+
| First write inside the workspace | Confirm |
|
|
326
|
+
| Later workspace writes in the approved run scope | Allow |
|
|
327
|
+
| Any process command | Confirm |
|
|
328
|
+
| Any registered network tool | Confirm |
|
|
329
|
+
| Built-in file operation outside the workspace | Deny in v0.1 |
|
|
330
|
+
| Approval-required action without an approval channel | Deny |
|
|
331
|
+
|
|
332
|
+
Symlinks must be resolved before the policy decision. A future release may add
|
|
333
|
+
narrow outside-workspace approvals, but v0.1 does not expose that capability.
|
|
334
|
+
|
|
335
|
+
`run_command` accepts a program and argument array and uses Node.js `spawn` with
|
|
336
|
+
`shell: false`; shell expressions such as pipelines and redirection are not a
|
|
337
|
+
v0.1 feature. Starting a process in the workspace still does not confine it to
|
|
338
|
+
that workspace. Until an OS-level sandbox exists, confirmation, timeout, output
|
|
339
|
+
limits, and trace records are safety controls but not filesystem or network
|
|
340
|
+
isolation.
|
|
341
|
+
|
|
342
|
+
This policy is an application safety boundary, not a replacement for a hardened
|
|
343
|
+
operating-system sandbox.
|
|
344
|
+
|
|
345
|
+
### Hooks, events, and traces
|
|
346
|
+
|
|
347
|
+
Forge separates behavior-changing hooks from immutable observation events:
|
|
348
|
+
|
|
349
|
+
- Lifecycle hooks have specific, typed return values.
|
|
350
|
+
- Policy contributions may change `allow` to `confirm` or `deny`, but never make
|
|
351
|
+
a mandatory decision less strict.
|
|
352
|
+
- `RunEvent` values are immutable observations used by renderers, traces, and
|
|
353
|
+
metrics.
|
|
354
|
+
|
|
355
|
+
Runtime behavior is represented as structured events. Candidate event types
|
|
356
|
+
include:
|
|
357
|
+
|
|
358
|
+
```text
|
|
359
|
+
run.started
|
|
360
|
+
model.started
|
|
361
|
+
model.reasoning
|
|
362
|
+
model.completed
|
|
363
|
+
tool.proposed
|
|
364
|
+
tool.approved
|
|
365
|
+
tool.denied
|
|
366
|
+
tool.started
|
|
367
|
+
tool.completed
|
|
368
|
+
tool.failed
|
|
369
|
+
file.changed
|
|
370
|
+
run.completed
|
|
371
|
+
run.failed
|
|
372
|
+
run.cancelled
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Terminal rendering and JSONL persistence consume the same event stream. This
|
|
376
|
+
prevents the user-visible activity and stored trace from becoming two unrelated
|
|
377
|
+
systems.
|
|
378
|
+
|
|
379
|
+
Trace files must not store API keys or other known secrets.
|
|
380
|
+
|
|
381
|
+
### Sessions and runs
|
|
382
|
+
|
|
383
|
+
A session is a persistent user conversation. A run is one bounded invocation of
|
|
384
|
+
the agent loop for one prompt. One session can therefore contain multiple runs,
|
|
385
|
+
and each run has its own event trace, limits, policy instance, and terminal
|
|
386
|
+
status.
|
|
387
|
+
|
|
388
|
+
The session store belongs at the application boundary rather than inside the
|
|
389
|
+
model adapter or tool packages. It saves completed user/assistant turns and
|
|
390
|
+
ordered run IDs. It does not serialize provider continuation objects, pending
|
|
391
|
+
approvals, an active child process, or an in-progress tool call.
|
|
392
|
+
|
|
393
|
+
On resume, the CLI validates the saved canonical workspace, reloads current
|
|
394
|
+
configuration and instructions, restores completed conversation messages, and
|
|
395
|
+
starts a new run. This makes recovery deterministic without treating stale
|
|
396
|
+
permission state as authority.
|
|
397
|
+
|
|
398
|
+
## Core interfaces
|
|
399
|
+
|
|
400
|
+
The following simplified sketches explain the implemented boundaries. They omit
|
|
401
|
+
details and are not a stable public API:
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
interface ModelAdapter {
|
|
405
|
+
stream(
|
|
406
|
+
request: ModelRequest,
|
|
407
|
+
signal: AbortSignal,
|
|
408
|
+
): AsyncIterable<ModelStreamEvent>;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
interface AuthenticationManager {
|
|
412
|
+
resolve(provider: string, signal: AbortSignal): Promise<ModelCredential>;
|
|
413
|
+
logout(provider: string): Promise<void>;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
interface ForgeTool<Input, Output> {
|
|
417
|
+
name: string;
|
|
418
|
+
description: string;
|
|
419
|
+
inputSchema: ZodType<Input>;
|
|
420
|
+
risk: ToolRisk;
|
|
421
|
+
execute(input: Input, context: ToolContext): Promise<ToolResult<Output>>;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
interface ApprovalPolicy {
|
|
425
|
+
evaluate(action: ProposedAction): Promise<ApprovalDecision>;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
interface TraceWriter {
|
|
429
|
+
append(event: RunEvent): Promise<void>;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
interface SessionStore {
|
|
433
|
+
create(workspace: WorkspaceContext): Promise<SessionSnapshot>;
|
|
434
|
+
load(sessionId: string): Promise<SessionSnapshot>;
|
|
435
|
+
list(workspaceRoot: string): Promise<readonly SessionSummary[]>;
|
|
436
|
+
save(snapshot: SessionSnapshot): Promise<void>;
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
These are design sketches, not stable public APIs.
|
|
441
|
+
|
|
442
|
+
## Run lifecycle
|
|
443
|
+
|
|
444
|
+
```text
|
|
445
|
+
created
|
|
446
|
+
|
|
|
447
|
+
v
|
|
448
|
+
running <--------+
|
|
449
|
+
| |
|
|
450
|
+
v |
|
|
451
|
+
awaiting_approval|
|
|
452
|
+
| |
|
|
453
|
+
+-------------+
|
|
454
|
+
|
|
|
455
|
+
+--> completed
|
|
456
|
+
+--> failed
|
|
457
|
+
+--> cancelled
|
|
458
|
+
`--> limit_reached
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Only terminal states may end a run. A natural-language claim of success does not
|
|
462
|
+
override a failed verification result recorded by the runtime.
|
|
463
|
+
|
|
464
|
+
## Dependency direction
|
|
465
|
+
|
|
466
|
+
```text
|
|
467
|
+
CLI ---------------------> Core interfaces
|
|
468
|
+
Native runtime ----------> Core interfaces
|
|
469
|
+
AI SDK adapter ----------> Core interfaces
|
|
470
|
+
Authentication manager --> Core interfaces
|
|
471
|
+
Project context loader --> Core interfaces
|
|
472
|
+
Tools -------------------> Core interfaces
|
|
473
|
+
Trace implementations ---> Core interfaces
|
|
474
|
+
Plugin host --------------> Core extension interfaces
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Milestone 10 keeps context ownership split across these boundaries: core owns
|
|
478
|
+
categories, budget arithmetic, events, and stop decisions; adapters own model
|
|
479
|
+
windows, estimation, overflow classification, and continuation projection;
|
|
480
|
+
persistence owns session-v2 checkpoints; the CLI owns `/context`, `/compact`,
|
|
481
|
+
Codex wrapper budgeting, and inspection rendering. The canonical transcript is
|
|
482
|
+
never replaced by its active model view.
|
|
483
|
+
|
|
484
|
+
Configured compatibility routes follow the same ownership rule. Core treats
|
|
485
|
+
continuation as opaque adapter state; the protocol transport retains AI SDK
|
|
486
|
+
reasoning parts and provider metadata for stateless replay instead of teaching
|
|
487
|
+
the runtime vendor fields. Optional `/models` capability extensions are parsed
|
|
488
|
+
conservatively, while missing metadata remains unknown. Provider-specific
|
|
489
|
+
behavior is never selected by matching the user's route name.
|
|
490
|
+
|
|
491
|
+
The core does not import CLI rendering, a specific provider implementation, a
|
|
492
|
+
plugin implementation, or a future LangChain adapter.
|
|
493
|
+
|
|
494
|
+
## Testing strategy
|
|
495
|
+
|
|
496
|
+
- Unit tests cover path validation, stop conditions, policy rules, and event state.
|
|
497
|
+
- Policy tests cover external paths, symlinks, missing UI, and decision precedence.
|
|
498
|
+
- Tool tests use temporary workspaces.
|
|
499
|
+
- Runtime tests use deterministic fake model adapters.
|
|
500
|
+
- Integration tests cover AI SDK message and tool-call translation.
|
|
501
|
+
- Authentication tests use fake credentials and App Server transports.
|
|
502
|
+
- Context-loader tests cover hierarchy, overrides, case sensitivity, size limits,
|
|
503
|
+
canonical roots, and provenance
|
|
504
|
+
- Plugin-contract tests prove that hooks cannot weaken core decisions.
|
|
505
|
+
- End-to-end tests use small fixture repositories and external graders.
|
|
506
|
+
|
|
507
|
+
Real model calls should not be required for the default test suite.
|
|
508
|
+
|
|
509
|
+
## Deferred decisions
|
|
510
|
+
|
|
511
|
+
The following choices remain deferred until a concrete milestone and acceptance
|
|
512
|
+
gate need them:
|
|
513
|
+
|
|
514
|
+
- SQLite schema and migration library
|
|
515
|
+
- HTTP server framework
|
|
516
|
+
- LangChain or LangGraph integration shape
|
|
517
|
+
- Operating-system-level sandboxing
|
|
518
|
+
- Restricted plugin process and capability enforcement
|
|
519
|
+
- Cross-machine session synchronization and retention policy
|