@rong/agentscript 0.1.2 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to AgentScript will be documented in this file.
4
4
 
5
+ ## 0.1.3 - 2026-05-08
6
+
7
+ ### Added
8
+
9
+ - Added final expression return for functions.
10
+ - Added literal context labels with `use context as label`.
11
+ - Added agent role and description to prompt identity construction.
12
+ - Added trace and built context output for context labels.
13
+
14
+ ### Changed
15
+
16
+ - Updated README, examples, and language references for labeled context usage.
17
+
5
18
  ## 1.0.0 - 2026-05-07
6
19
 
7
20
  ### Added
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # AgentScript
2
2
 
3
3
  > **Agent context as code.**
4
- > `use` declares what the model can see.
4
+ > `use` declares what the model can see, with optional labels for prompt sections.
5
5
  > `generate` defines the only LLM call site and optional output shape.
6
6
  > Zero runtime dependencies. TypeScript-powered.
7
7
 
8
8
  ```agentscript
9
- use scratch.summary < 2k
9
+ use scratch.summary < 2k as observations
10
10
  generate({ input: "Answer from observations" }) -> {
11
11
  ok boolean
12
12
  text string
@@ -61,8 +61,8 @@ main agent FileSummarizer {
61
61
 
62
62
  main func(input { path string }) {
63
63
  content = File.read({ path: input.path })
64
- use input.path
65
- use content < 8k
64
+ use input.path as source path
65
+ use content < 8k as file content
66
66
 
67
67
  generate({
68
68
  input: "Summarize the file for a busy teammate"
@@ -113,7 +113,7 @@ It is a small language for one thing:
113
113
 
114
114
  > making LLM prompt context explicit, scoped, typed, traceable, and compilable.
115
115
 
116
- It gives you two things that general-purpose languages don't: a first-class `use` keyword that declares *which* data enters the LLM prompt, and a first-class `generate` expression that defines an LLM call with an optional output contract. Everything else — variables, functions, agents, imports, loops — exists to support this core workflow. Scopes enforce context boundaries naturally: what's `use`d in one function stays there; child scopes inherit but never leak upward. Functions can also return their final top-level expression directly, which keeps typical LLM workflows concise.
116
+ It gives you two things that general-purpose languages don't: a first-class `use` keyword that declares *which* data enters the LLM prompt and what role it plays via `as label`, and a first-class `generate` expression that defines an LLM call with an optional output contract. Everything else — variables, functions, agents, imports, loops — exists to support this core workflow. Scopes enforce context boundaries naturally: what's `use`d in one function stays there; child scopes inherit but never leak upward. Functions can also return their final top-level expression directly, which keeps typical LLM workflows concise.
117
117
 
118
118
  ## How it works
119
119
 
@@ -178,10 +178,10 @@ main agent ResearchAgent {
178
178
  main func(input {
179
179
  question string
180
180
  }) {
181
- use input.question
181
+ use input.question as user question
182
182
 
183
183
  scratch = []
184
- use scratch.summary < 2k
184
+ use scratch.summary < 2k as observations
185
185
 
186
186
  done = false
187
187
  loop until done < 6 {
@@ -195,8 +195,8 @@ main agent ResearchAgent {
195
195
  }
196
196
 
197
197
  func answer(question, scratch) {
198
- use question
199
- use scratch.summary < 2k
198
+ use question as user question
199
+ use scratch.summary < 2k as observations
200
200
  generate({ input: "Answer using only the observations" }) -> {
201
201
  ok boolean
202
202
  text string
@@ -208,7 +208,7 @@ main agent ResearchAgent {
208
208
 
209
209
  ## Key ideas
210
210
 
211
- 1. **`use` is explicit context** — nothing enters the LLM prompt unless `use`d
211
+ 1. **`use` is explicit context** — nothing enters the LLM prompt unless `use`d; `as label` names the context section
212
212
  2. **`generate` is the only LLM call site** — with a required input instruction and optional output shape
213
213
  3. **Final expression return keeps flows concise** — a function returns its final top-level expression
214
214
  4. **Scope is context boundary** — functions, agents, and blocks isolate prompt visibility
@@ -219,7 +219,7 @@ main agent ResearchAgent {
219
219
 
220
220
  | | Python / TypeScript | AgentScript |
221
221
  |---|---|---|
222
- | Context management | Implicit (string concatenation, array append) | Explicit (`use` declaration) |
222
+ | Context management | Implicit (string concatenation, array append) | Explicit (`use` declaration, optional `as label`) |
223
223
  | LLM call site | Anywhere in the code | One `generate` expression |
224
224
  | Context isolation | Manual discipline | Scope-inherited, auto-isolated |
225
225
  | Trace / audit | External tooling needed | Built-in, per-call |
@@ -176,13 +176,26 @@ class Parser {
176
176
  const start = this.consume("use").range.start;
177
177
  const value = this.parseLogicalOr();
178
178
  const budget = this.match("<") ? this.parseBudget() : undefined;
179
+ const label = this.match("as") ? this.parseUseLabel() : undefined;
179
180
  return {
180
181
  kind: "UseStmt",
181
182
  value,
182
183
  budget,
184
+ label,
183
185
  range: { start, end: this.previous().range.end }
184
186
  };
185
187
  }
188
+ parseUseLabel() {
189
+ const tokens = [];
190
+ const line = this.previous().range.start.line;
191
+ while (!this.isAtEnd() && !this.check("}") && this.peek().range.start.line === line) {
192
+ tokens.push(this.advance());
193
+ }
194
+ if (tokens.length === 0) {
195
+ throw this.error("Expected context label after 'as'");
196
+ }
197
+ return stringifyLabelTokens(tokens);
198
+ }
186
199
  parseConfigValue(key) {
187
200
  if (key === "model") {
188
201
  return this.parsePostfix();
@@ -656,3 +669,15 @@ class Parser {
656
669
  function isImportResourceKind(value) {
657
670
  return IMPORT_RESOURCE_KINDS.has(value);
658
671
  }
672
+ function stringifyLabelTokens(tokens) {
673
+ let label = tokens[0]?.value ?? "";
674
+ for (let index = 1; index < tokens.length; index += 1) {
675
+ const previous = tokens[index - 1];
676
+ const current = tokens[index];
677
+ if (current.range.start.column > previous.range.end.column) {
678
+ label += " ";
679
+ }
680
+ label += current.value;
681
+ }
682
+ return label;
683
+ }
@@ -51,6 +51,7 @@ function buildContextItem(item, index) {
51
51
  return {
52
52
  index,
53
53
  source: item.source,
54
+ label: item.label,
54
55
  value: clipped.value,
55
56
  text: clipped.text,
56
57
  budget: item.budget,
@@ -60,8 +61,15 @@ function buildContextItem(item, index) {
60
61
  };
61
62
  }
62
63
  function buildSystemPrompt(agentName, identity) {
63
- const lines = [`You are ${agentName}.`];
64
+ const role = typeof identity.role === "string" ? identity.role : agentName;
65
+ const lines = [`You are ${role}.`];
66
+ if (typeof identity.description === "string") {
67
+ lines.push(identity.description);
68
+ }
64
69
  for (const [key, value] of Object.entries(identity)) {
70
+ if (key === "role" || key === "description") {
71
+ continue;
72
+ }
65
73
  lines.push(`${key}: ${renderJson(value)}`);
66
74
  }
67
75
  return lines.join("\n");
@@ -71,8 +79,9 @@ function buildFinalUserMessage(context, instructionText, returnSchema) {
71
79
  if (context.length > 0) {
72
80
  sections.push("Context:");
73
81
  for (const item of context) {
74
- const label = item.source ? `${item.source}:\n` : "";
75
- sections.push(`[${item.index}] ${label}${item.text}`);
82
+ const label = item.label ?? String(item.index);
83
+ const source = item.source ? `source: ${item.source}\n` : "";
84
+ sections.push(`[${label}]\n${source}${item.text}`);
76
85
  }
77
86
  }
78
87
  sections.push("Instruction:", instructionText);
@@ -147,6 +156,7 @@ export function builtContextToJson(context) {
147
156
  context: context.context.map((item) => ({
148
157
  index: item.index,
149
158
  source: item.source ?? null,
159
+ label: item.label ?? null,
150
160
  value: item.value,
151
161
  text: item.text,
152
162
  budget: budgetToJson(item.budget),
@@ -80,6 +80,7 @@ export class Evaluator {
80
80
  for (const item of scope.visibleUses()) {
81
81
  uses.push({
82
82
  source: item.source,
83
+ label: item.label,
83
84
  value: await this.evaluate(item.expr, item.scope),
84
85
  budget: item.budget,
85
86
  });
@@ -209,8 +209,11 @@ class Interpreter {
209
209
  return undefined;
210
210
  case "UseStmt": {
211
211
  const source = formatExpressionSource(stmt.value);
212
- scope.addUse(stmt.value, source, stmt.budget);
213
- this.trace.push({ kind: "use", data: { source, budget: budgetToJson(stmt.budget) } });
212
+ scope.addUse(stmt.value, source, stmt.budget, stmt.label);
213
+ this.trace.push({
214
+ kind: "use",
215
+ data: { source, label: stmt.label ?? null, budget: budgetToJson(stmt.budget) },
216
+ });
214
217
  return undefined;
215
218
  }
216
219
  case "AssignStmt": {
@@ -29,8 +29,8 @@ export class RuntimeScope {
29
29
  }
30
30
  this.define(name, value);
31
31
  }
32
- addUse(expr, source, budget) {
33
- this.uses.push({ expr, source, budget, scope: this });
32
+ addUse(expr, source, budget, label) {
33
+ this.uses.push({ expr, source, budget, label, scope: this });
34
34
  }
35
35
  setConfig(name, value) {
36
36
  this.config.set(name, value);
@@ -171,6 +171,9 @@ class Analyzer {
171
171
  if (stmt.budget && stmt.budget.amount <= 0) {
172
172
  this.error("INVALID_BUDGET", "Budget amount must be greater than 0", stmt.range);
173
173
  }
174
+ if (stmt.label && RESERVED_CONTEXT_LABELS.has(stmt.label)) {
175
+ this.error("RESERVED_CONTEXT_LABEL", `Context label '${stmt.label}' is reserved`, stmt.range);
176
+ }
174
177
  break;
175
178
  case "AssignStmt":
176
179
  this.checkAssignment(stmt, scope);
@@ -501,6 +504,7 @@ class Analyzer {
501
504
  const IMPORTED_BINDING_KINDS = new Set(["tool", "llm", "file", "agent", "memory"]);
502
505
  const NON_CONTEXT_BINDING_KINDS = new Set(["tool", "llm", "agent", "function", "memory"]);
503
506
  const VALID_MEMORY_METHODS = new Set(["add", "query"]);
507
+ const RESERVED_CONTEXT_LABELS = new Set(["system", "assistant", "tool", "developer"]);
504
508
  function functionBinding(agentName, fn) {
505
509
  return {
506
510
  kind: "function",
@@ -59,6 +59,24 @@ func run(input) {
59
59
  }
60
60
  ```
61
61
 
62
+ 这条规则适用于所有表达式形式,包括调用表达式。被调用者解析为本地函数还是 agent 调用,都不影响 final expression return。例如,直接用 agent 名称调用另一个 agent 时,会调用该 agent 的 `main func`,其结果会被隐式返回:
63
+
64
+ ```agentscript
65
+ agent Planner {
66
+ main func(input) {
67
+ generate({ input: "Create a plan" }) -> {
68
+ steps list[string]
69
+ }
70
+ }
71
+ }
72
+
73
+ agent Controller {
74
+ func run(input) {
75
+ Planner(input)
76
+ }
77
+ }
78
+ ```
79
+
62
80
  ```agentscript
63
81
  func get_result(result) {
64
82
  result.value
@@ -158,6 +158,8 @@ Shape 不是完整的静态类型系统。
158
158
  use input.question
159
159
  use Requirements < 4k
160
160
  use past_lessons < 2k
161
+ use input.question as user
162
+ use docs.summary < 4k as evidence
161
163
  ```
162
164
 
163
165
  ### 规则
@@ -167,10 +169,24 @@ use past_lessons < 2k
167
169
  - Memory 查询结果不会自动进入 prompt。
168
170
  - Trace 事件不会自动进入 prompt。
169
171
  - `use value < n` 应用上下文预算。
172
+ - `use value as label` 为选中的 context source 附加字面标签。
173
+ - `use value < n as label` 先应用预算,再附加标签。
170
174
  - `llm`、`tool`、`agent`、`memory` 绑定不能被 `use`。
171
175
  - 函数绑定不能被 `use`。
172
176
  - `use` 声明被子作用域继承。
173
177
 
178
+ ### Context label
179
+
180
+ `as` 后面的 label 是字面标签文本,不是表达式,不会求值,也不会读取作用域中的变量。
181
+
182
+ ```agentscript
183
+ use docs as evidence
184
+ use docs.summary < 4k as retrieved evidence
185
+ use input.question as user
186
+ ```
187
+
188
+ 即使当前作用域中存在名为 `evidence` 的变量,`as evidence` 也只是把 context section 标记为 `evidence`。Label 用于组织 prompt section 和 trace 输出;它不会改变 `system`、`user`、`assistant` 等 provider message role。
189
+
174
190
  ### 延迟求值
175
191
 
176
192
  `use expr < budget` 声明的是 context source,而不是当前值的快照。当 `generate` 构建 prompt 时,表达式会被重新求值。这意味着在 `use` 之后、`generate` 之前对变量的修改在生成时刻是可见的。
@@ -0,0 +1,492 @@
1
+ # AgentScript Role / Label 设计
2
+
3
+ 本文档定义 AgentScript 中 `agent role`、`context label` 和底层 provider message role 的分层设计,用于指导当前 prompt 构造、`use ... as ...` 语法和 trace 展示的实现。
4
+
5
+ ## 1. 核心结论
6
+
7
+ AgentScript 应明确区分三类概念:
8
+
9
+ ```text
10
+ agent role 表示“谁在生成 / 谁在发言”。
11
+ context label 表示“这段上下文在当前 prompt 中的用途”。
12
+ provider role 表示底层 LLM API message 的 system/user/assistant/tool。
13
+ ```
14
+
15
+ 这三者属于不同层级,不能混用。
16
+
17
+ - `agent role` 是 AgentScript 层的生成身份。
18
+ - `context label` 是 prompt 内部的上下文组织标签。
19
+ - `provider role` 是 OpenAI / Anthropic / Ollama 等 adapter 的传输协议细节。
20
+
21
+ 用户不应通过 `use ... as system` 之类的写法直接操纵底层 provider role。
22
+
23
+ ## 2. Agent Role
24
+
25
+ `role` 是 agent 的身份声明:
26
+
27
+ ```agentscript
28
+ agent ResearchAgent {
29
+ model Qwen
30
+ role "Senior Researcher"
31
+ description "Answer questions with search and structured reasoning."
32
+ }
33
+ ```
34
+
35
+ 它的含义是:
36
+
37
+ ```text
38
+ 当前 agent 以什么身份进行 generate。
39
+ ```
40
+
41
+ 每次 `generate` 都应把当前 agent 的 `role` 和 `description` 放入 identity 层 prompt。例如:
42
+
43
+ ```text
44
+ You are a Senior Researcher.
45
+ Answer questions with search and structured reasoning.
46
+ ```
47
+
48
+ 因此当前实现目标是:
49
+
50
+ ```text
51
+ agent.role -> generation identity
52
+ agent.description -> generation identity
53
+ ```
54
+
55
+ 在 multi-agent 场景中,agent 输出被其他 agent 使用时,`role` 也可以作为 speaker provenance 展示:
56
+
57
+ ```text
58
+ [plan]
59
+ Produced by: Planner
60
+ Role: Planner
61
+ Output:
62
+ ...
63
+ ```
64
+
65
+ 这里的 `Role: Planner` 仍然是 AgentScript 层的 agent role,不是 provider message role。
66
+
67
+ ## 3. Context Label
68
+
69
+ Context label 是 `use` 注入上下文时的语义标签。
70
+
71
+ 当前语法支持:
72
+
73
+ ```agentscript
74
+ use expr
75
+ use expr < budget
76
+ use expr as label
77
+ use expr < budget as label
78
+ ```
79
+
80
+ 固定顺序是:
81
+
82
+ ```text
83
+ 选择什么 -> 限制多少 -> 作为何种上下文
84
+ ```
85
+
86
+ 例如:
87
+
88
+ ```agentscript
89
+ use input.question as user
90
+ use scratch.summary < 2k as memory
91
+ use docs.summary < 4k as evidence
92
+ use search_result as observation
93
+ use policy as constraint
94
+ ```
95
+
96
+ `context label` 的含义是:
97
+
98
+ ```text
99
+ 这段内容在当前 generate prompt 中作为何种材料被呈现。
100
+ ```
101
+
102
+ 它只影响:
103
+
104
+ - prompt section label
105
+ - trace display
106
+ - context organization
107
+ - debug / audit readability
108
+
109
+ 它不改变:
110
+
111
+ - agent identity
112
+ - provider message role
113
+ - tool 权限
114
+ - system / user / assistant 权限
115
+
116
+ ## 4. Provider Role
117
+
118
+ Provider role 是底层 LLM API 的 message role,例如:
119
+
120
+ ```text
121
+ system
122
+ user
123
+ assistant
124
+ tool
125
+ ```
126
+
127
+ 它属于 provider adapter 的实现细节。
128
+
129
+ 默认映射:
130
+
131
+ ```text
132
+ system = 当前 agent identity + runtime rules
133
+ user = generate instruction + selected context + output shape
134
+ ```
135
+
136
+ 也就是说,AgentScript 的 context label 应作为 prompt 内部结构渲染,而不是映射为多条 provider message role。
137
+
138
+ Runtime 不把 memory、evidence、observation 等材料伪装成 assistant message:
139
+
140
+ ```text
141
+ assistant: [memory] ...
142
+ assistant: [evidence] ...
143
+ ```
144
+
145
+ 原因是 `assistant` 在 chat protocol 中表示模型真实历史输出,而 memory / evidence / observation 是外部材料。
146
+
147
+ ## 5. Prompt 构造模型
148
+
149
+ 一次 `generate` 的 prompt 分为四层。
150
+
151
+ ### 5.1 Agent Identity
152
+
153
+ 来自当前 agent 的配置:
154
+
155
+ ```agentscript
156
+ role "Senior Researcher"
157
+ description "Answer questions with search and structured reasoning."
158
+ ```
159
+
160
+ 渲染为 provider `system` message 的一部分:
161
+
162
+ ```text
163
+ You are a Senior Researcher.
164
+ Answer questions with search and structured reasoning.
165
+ ```
166
+
167
+ ### 5.2 Generate Instruction
168
+
169
+ 来自 `generate(...)` 的 `input` 字段:
170
+
171
+ ```agentscript
172
+ generate({
173
+ input: "Answer using only the selected context"
174
+ }) -> {
175
+ ok boolean
176
+ answer string
177
+ }
178
+ ```
179
+
180
+ 渲染为:
181
+
182
+ ```text
183
+ Instruction:
184
+ Answer using only the selected context.
185
+ ```
186
+
187
+ ### 5.3 Selected Context
188
+
189
+ 来自当前 scope 和父 scope 中可见的 `use` 声明:
190
+
191
+ ```agentscript
192
+ use input.question as user
193
+ use scratch.summary < 2k as memory
194
+ use docs.summary < 4k as evidence
195
+ ```
196
+
197
+ 渲染为:
198
+
199
+ ```text
200
+ Context:
201
+
202
+ [user]
203
+ ...
204
+
205
+ [memory]
206
+ ...
207
+
208
+ [evidence]
209
+ ...
210
+ ```
211
+
212
+ 如果没有 label,可以使用 source expression 作为默认 label 或显示为普通 context item。
213
+
214
+ ### 5.4 Output Shape
215
+
216
+ 来自 `generate(...) -> shape`:
217
+
218
+ ```agentscript
219
+ generate({ input: "Answer" }) -> {
220
+ ok boolean
221
+ answer string
222
+ }
223
+ ```
224
+
225
+ 渲染为:
226
+
227
+ ```text
228
+ Required output shape:
229
+ {
230
+ ok: boolean
231
+ answer: string
232
+ }
233
+ ```
234
+
235
+ 当 `generate` 没有 `-> shape` 时,不应注入输出 schema,也不应要求 provider 结构化输出。
236
+
237
+ ## 6. `use ... as label` 规则
238
+
239
+ ### 6.1 Label 类型
240
+
241
+ `as` 后面的 label 是语法层面的字面标签,不是表达式,不参与变量解析,也不会求值。
242
+
243
+ ```agentscript
244
+ use docs as evidence
245
+ use scratch as memory
246
+ use docs.summary < 4k as retrieved-evidence
247
+ ```
248
+
249
+ 其中:
250
+
251
+ ```text
252
+ evidence
253
+ memory
254
+ retrieved-evidence
255
+ ```
256
+
257
+ 都是 context label 的字面值。即使当前作用域中存在名为 `evidence` 或 `memory` 的变量,`as evidence` 和 `as memory` 也不会读取这些变量。
258
+
259
+ Label 不需要双引号。它不是字符串表达式,而是 `use` 语句的一部分:
260
+
261
+ ```agentscript
262
+ use docs as retrieved evidence
263
+ ```
264
+
265
+ 上例中的 label 是字面标签 `retrieved evidence`。Parser 应把 `as` 之后到语句结束之间的内容作为 label 文本;如果存在预算,则 label 位于预算之后:
266
+
267
+ ```agentscript
268
+ use docs.summary < 4k as retrieved evidence
269
+ ```
270
+
271
+ Label 文本应在 AST 中保存为普通字符串,例如:
272
+
273
+ ```json
274
+ {
275
+ "label": "retrieved evidence"
276
+ }
277
+ ```
278
+
279
+ Label 不支持嵌套表达式、函数调用、字段访问或插值:
280
+
281
+ ```agentscript
282
+ use docs as label_name -- label 是 "label_name",不是变量 label_name 的值
283
+ use docs as input.label -- label 是 "input.label",不是字段访问结果
284
+ use docs as label() -- label 是 "label()",不会调用函数
285
+ ```
286
+
287
+ ### 6.2 保留 label
288
+
289
+ 为避免和 provider role 混淆,以下 label 保留,不作为普通 context label 使用:
290
+
291
+ ```text
292
+ system
293
+ assistant
294
+ tool
295
+ developer
296
+ ```
297
+
298
+ `user` 允许作为 context label,但它只是 context label,不是 provider role:
299
+
300
+ ```agentscript
301
+ use input.question as user
302
+ ```
303
+
304
+ 这里的 `user` 表示“这段上下文是用户输入”,不是把该 context item 变成 provider `user` message。
305
+
306
+ ### 6.3 继承规则
307
+
308
+ `use` 声明原本可被 child scope 继承。加入 label 后,label 应随 use declaration 一起继承。
309
+
310
+ ```agentscript
311
+ use input.question as user
312
+
313
+ func answer() {
314
+ generate({ input: "Answer" }) -> {
315
+ answer string
316
+ }
317
+ }
318
+ ```
319
+
320
+ `answer()` 内部的 `generate` 仍可看到:
321
+
322
+ ```text
323
+ [user]
324
+ input.question
325
+ ```
326
+
327
+ ### 6.4 重复 label
328
+
329
+ 允许多个 context source 使用同一个 label:
330
+
331
+ ```agentscript
332
+ use docs1 as evidence
333
+ use docs2 as evidence
334
+ ```
335
+
336
+ 重复 label 按声明顺序分别渲染:
337
+
338
+ ```text
339
+ [evidence]
340
+ docs1...
341
+
342
+ [evidence]
343
+ docs2...
344
+ ```
345
+
346
+ ## 7. Multi-Agent Provenance
347
+
348
+ Agent 调用结果应在 trace 或内部 metadata 中保留 provenance:
349
+
350
+ ```json
351
+ {
352
+ "agent": "Planner",
353
+ "role": "Planner",
354
+ "function": "main",
355
+ "trace_id": "..."
356
+ }
357
+ ```
358
+
359
+ 这类 metadata 不一定暴露给用户代码,但应可用于 trace 和 context renderer。
360
+
361
+ 用法:
362
+
363
+ ```agentscript
364
+ plan = Planner(input)
365
+ critique = Critic(plan)
366
+
367
+ use plan.summary < 2k as plan
368
+ use critique.summary < 2k as critique
369
+ ```
370
+
371
+ 渲染时可以结合 provenance:
372
+
373
+ ```text
374
+ [plan]
375
+ Produced by: Planner
376
+ Role: Planner
377
+ Output:
378
+ ...
379
+
380
+ [critique]
381
+ Produced by: Critic
382
+ Role: Skeptical Reviewer
383
+ Output:
384
+ ...
385
+ ```
386
+
387
+ 这样能表达 multi-agent 中“不同角色发言”的感觉,同时不污染 provider message role。
388
+
389
+ ## 8. Trace 展示
390
+
391
+ Trace 同时显示 agent identity、instruction、context label、source expression 和 budget。
392
+
393
+ ```text
394
+ Generate #1
395
+ Agent:
396
+ name: ResearchAgent
397
+ role: Senior Researcher
398
+
399
+ Instruction:
400
+ Answer using only the selected context.
401
+
402
+ Selected context:
403
+ [user]
404
+ source: input.question
405
+ budget: none
406
+
407
+ [memory]
408
+ source: scratch.summary
409
+ budget: 2k
410
+
411
+ [evidence]
412
+ source: docs.summary
413
+ budget: 4k
414
+
415
+ Output shape:
416
+ ok boolean
417
+ answer string
418
+ ```
419
+
420
+ Multi-agent trace 可以显示:
421
+
422
+ ```text
423
+ Agent call:
424
+ Planner.main
425
+ role: Planner
426
+
427
+ Agent call:
428
+ Critic.main
429
+ role: Skeptical Reviewer
430
+
431
+ Generate #3
432
+ Agent:
433
+ Coordinator
434
+ role: Coordinator
435
+
436
+ Selected context:
437
+ [plan]
438
+ produced_by: Planner
439
+ producer_role: Planner
440
+
441
+ [critique]
442
+ produced_by: Critic
443
+ producer_role: Skeptical Reviewer
444
+ ```
445
+
446
+ ## 9. 当前实现方案
447
+
448
+ 当前实现包括:
449
+
450
+ 1. `role` 和 `description` 进入每次 `generate` 的 system / identity prompt。
451
+ 2. `UseStmt` 增加可选 `label` 字段,保存 label 字面文本。
452
+ 3. Parser 支持 `use expr as label` 和 `use expr < budget as label`。
453
+ 4. Parser 将 `as` 后面的 label 保存为原始标签文本,不按表达式解析。
454
+ 5. Semantic analyzer 校验保留 label,避免和 provider role 混淆。
455
+ 6. Context builder 在 trace 和 prompt renderer 中使用 label。
456
+ 7. Agent call trace 保留 agent name、role、function 等 provenance。
457
+
458
+ 当前设计保持执行和上下文注入分离:
459
+
460
+ ```agentscript
461
+ critique = Critic(draft)
462
+ use critique as critique
463
+ ```
464
+
465
+ ## 10. 术语表
466
+
467
+ ```text
468
+ provider role:
469
+ 底层 chat message role,例如 system/user/assistant/tool。
470
+
471
+ agent role:
472
+ agent 生成输出时使用的身份,也可作为 agent 输出的 speaker provenance。
473
+
474
+ context label:
475
+ 通过 use ... as ... 附加到 selected context 的用途标签。
476
+
477
+ speaker provenance:
478
+ agent 调用产物携带的来源元信息,包括 agent name、role、function 和 trace id。
479
+
480
+ agent transcript:
481
+ 在 prompt 或 trace 中按 agent 来源组织的多 agent 输出展示形式。
482
+ ```
483
+
484
+ ## 11. 一句话总结
485
+
486
+ ```text
487
+ provider role 是消息协议;
488
+ agent role 是生成身份和发言者身份;
489
+ context label 是上下文用途标签。
490
+ ```
491
+
492
+ AgentScript 应在语言层构造 labeled context 和 agent transcript,再由 runtime 映射到底层 provider messages。
@@ -59,6 +59,24 @@ func run(input) {
59
59
  }
60
60
  ```
61
61
 
62
+ This rule applies to all expression forms, including calls. It does not matter whether the callee resolves to a local function or an agent call. For example, calling another agent by name invokes that agent's `main func`, and the result is returned implicitly:
63
+
64
+ ```agentscript
65
+ agent Planner {
66
+ main func(input) {
67
+ generate({ input: "Create a plan" }) -> {
68
+ steps list[string]
69
+ }
70
+ }
71
+ }
72
+
73
+ agent Controller {
74
+ func run(input) {
75
+ Planner(input)
76
+ }
77
+ }
78
+ ```
79
+
62
80
  ```agentscript
63
81
  func get_result(result) {
64
82
  result.value
@@ -158,6 +158,8 @@ Shapes are not a full static type system.
158
158
  use input.question
159
159
  use Requirements < 4k
160
160
  use past_lessons < 2k
161
+ use input.question as user
162
+ use docs.summary < 4k as evidence
161
163
  ```
162
164
 
163
165
  ### Rules
@@ -167,10 +169,24 @@ use past_lessons < 2k
167
169
  - Memory query results do not automatically enter prompts.
168
170
  - Trace events do not automatically enter prompts.
169
171
  - `use value < n` applies a context budget.
172
+ - `use value as label` attaches a literal context label to the selected source.
173
+ - `use value < n as label` applies the budget first, then attaches the label.
170
174
  - `llm`, `tool`, `agent`, `memory` bindings cannot be used.
171
175
  - Function bindings cannot be used.
172
176
  - `use` declarations are inherited by child scopes.
173
177
 
178
+ ### Context labels
179
+
180
+ The label after `as` is literal label text. It is not an expression, is not evaluated, and does not read variables from scope.
181
+
182
+ ```agentscript
183
+ use docs as evidence
184
+ use docs.summary < 4k as retrieved evidence
185
+ use input.question as user
186
+ ```
187
+
188
+ `as evidence` labels the context section as `evidence` even if a variable named `evidence` exists. Labels organize prompt sections and trace output; they do not change provider message roles such as `system`, `user`, or `assistant`.
189
+
174
190
  ### Deferred evaluation
175
191
 
176
192
  `use expr < budget` declares a context source, not a snapshot. The expression is re-evaluated when `generate` builds the prompt. This means updates to a variable made after `use` but before `generate` are visible at generation time.
@@ -13,8 +13,8 @@ main agent ChangelogWriter {
13
13
  path: input.diff_path
14
14
  })
15
15
 
16
- use input.diff_path
17
- use diff < 10k
16
+ use input.diff_path as diff path
17
+ use diff < 10k as git diff
18
18
 
19
19
  generate({ input: "Write a changelog from this git diff", limit: 1200 }) -> {
20
20
  title string
@@ -14,8 +14,8 @@ main agent ApiExtractor {
14
14
  timeout: 10000
15
15
  })
16
16
 
17
- use input.url
18
- use response < 8k
17
+ use input.url as endpoint
18
+ use response < 8k as api response
19
19
 
20
20
  generate({ input: "Extract normalized data from the API response", limit: 1200 }) -> {
21
21
  records list[json]
@@ -22,9 +22,9 @@ main agent CodeReviewAssistant {
22
22
  max: 100
23
23
  })
24
24
 
25
- use input.path
26
- use todos < 4k
27
- use fixmes < 4k
25
+ use input.path as source path
26
+ use todos < 4k as todo findings
27
+ use fixmes < 4k as fixme findings
28
28
 
29
29
  generate({ input: "Turn TODO and FIXME scan results into prioritized repair suggestions", limit: 1200 }) -> {
30
30
  summary string
@@ -13,8 +13,8 @@ main agent FileSummarizer {
13
13
  path: input.path
14
14
  })
15
15
 
16
- use input.path
17
- use content < 8k
16
+ use input.path as source path
17
+ use content < 8k as file content
18
18
 
19
19
  generate({ input: "Summarize the file for a busy teammate", limit: 1000 }) -> {
20
20
  title string
@@ -17,9 +17,9 @@ main agent MarkdownTranslator {
17
17
  max: 50
18
18
  })
19
19
 
20
- use input.path
21
- use input.target_language
22
- use files < 4k
20
+ use input.path as source path
21
+ use input.target_language as target language
22
+ use files < 4k as markdown files
23
23
 
24
24
  generate({ input: "Create a practical markdown translation plan", limit: 1000 }) -> {
25
25
  target_language string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rong/agentscript",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "AgentScript context engineering language runtime",
5
5
  "main": "dist/index.js",
6
6
  "bin": {