agentic-lang 0.2.0
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/COMMUNITY.md +220 -0
- package/CONTRIBUTING.md +194 -0
- package/FINAL_REPORT.md +398 -0
- package/FOR_OTHER_LLMS.md +286 -0
- package/IMPROVEMENTS.md +319 -0
- package/LAUNCH_GUIDE.md +388 -0
- package/LICENSE +21 -0
- package/NPM_PUBLISH.md +257 -0
- package/PROJECT_COMPLETE.md +414 -0
- package/PROJECT_OVERVIEW.md +265 -0
- package/PROJECT_TREE.txt +228 -0
- package/PUBLISHING_GUIDE.md +426 -0
- package/PUBLISH_NOW.md +337 -0
- package/QUICKSTART.md +207 -0
- package/README.md +195 -0
- package/README_ENHANCED.md +329 -0
- package/READY_TO_LAUNCH.txt +56 -0
- package/REFACTOR_PLAN.md +179 -0
- package/ROADMAP.md +201 -0
- package/SUMMARY.md +315 -0
- package/bin/agentic.js +3 -0
- package/blog/001-introducing-agentic.md +382 -0
- package/blog/002-confidence-driven-development.md +490 -0
- package/blog/003-formal-verification.md +427 -0
- package/blog/004-multi-agent-production.md +436 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +151 -0
- package/dist/cli.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +115 -0
- package/dist/diagnostics/diagnostic.d.ts.map +1 -0
- package/dist/diagnostics/diagnostic.js +101 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/formatter.d.ts +36 -0
- package/dist/diagnostics/formatter.d.ts.map +1 -0
- package/dist/diagnostics/formatter.js +263 -0
- package/dist/diagnostics/formatter.js.map +1 -0
- package/dist/effects/effect-system.d.ts +64 -0
- package/dist/effects/effect-system.d.ts.map +1 -0
- package/dist/effects/effect-system.js +197 -0
- package/dist/effects/effect-system.js.map +1 -0
- package/dist/generator/typescript-generator.d.ts +31 -0
- package/dist/generator/typescript-generator.d.ts.map +1 -0
- package/dist/generator/typescript-generator.js +308 -0
- package/dist/generator/typescript-generator.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/lean4/exporter.d.ts +24 -0
- package/dist/lean4/exporter.d.ts.map +1 -0
- package/dist/lean4/exporter.js +142 -0
- package/dist/lean4/exporter.js.map +1 -0
- package/dist/lsp/server.d.ts +6 -0
- package/dist/lsp/server.d.ts.map +1 -0
- package/dist/lsp/server.js +131 -0
- package/dist/lsp/server.js.map +1 -0
- package/dist/parser/lexer.d.ts +79 -0
- package/dist/parser/lexer.d.ts.map +1 -0
- package/dist/parser/lexer.js +296 -0
- package/dist/parser/lexer.js.map +1 -0
- package/dist/parser/parser-enhanced.d.ts +12 -0
- package/dist/parser/parser-enhanced.d.ts.map +1 -0
- package/dist/parser/parser-enhanced.js +206 -0
- package/dist/parser/parser-enhanced.js.map +1 -0
- package/dist/parser/parser.d.ts +34 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +507 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/property-tests/generator-enhanced.d.ts +27 -0
- package/dist/property-tests/generator-enhanced.d.ts.map +1 -0
- package/dist/property-tests/generator-enhanced.js +209 -0
- package/dist/property-tests/generator-enhanced.js.map +1 -0
- package/dist/property-tests/generator-fixed.d.ts +2 -0
- package/dist/property-tests/generator-fixed.d.ts.map +1 -0
- package/dist/property-tests/generator-fixed.js +7 -0
- package/dist/property-tests/generator-fixed.js.map +1 -0
- package/dist/property-tests/generator.d.ts +28 -0
- package/dist/property-tests/generator.d.ts.map +1 -0
- package/dist/property-tests/generator.js +284 -0
- package/dist/property-tests/generator.js.map +1 -0
- package/dist/refinements/refinement-types.d.ts +96 -0
- package/dist/refinements/refinement-types.d.ts.map +1 -0
- package/dist/refinements/refinement-types.js +234 -0
- package/dist/refinements/refinement-types.js.map +1 -0
- package/dist/repl.d.ts +21 -0
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +317 -0
- package/dist/repl.js.map +1 -0
- package/dist/runtime/agents.d.ts +97 -0
- package/dist/runtime/agents.d.ts.map +1 -0
- package/dist/runtime/agents.js +258 -0
- package/dist/runtime/agents.js.map +1 -0
- package/dist/runtime/index.d.ts +98 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +253 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/types-extended.d.ts +197 -0
- package/dist/types-extended.d.ts.map +1 -0
- package/dist/types-extended.js +7 -0
- package/dist/types-extended.js.map +1 -0
- package/dist/types.d.ts +129 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/verification/z3-engine.d.ts +75 -0
- package/dist/verification/z3-engine.d.ts.map +1 -0
- package/dist/verification/z3-engine.js +234 -0
- package/dist/verification/z3-engine.js.map +1 -0
- package/examples/advanced-features.agentic +98 -0
- package/examples/annotations.agentic +37 -0
- package/examples/auth.agentic +53 -0
- package/examples/enterprise-example.agentic +360 -0
- package/examples/minimal.agentic +3 -0
- package/examples/minimal.ts +7 -0
- package/examples/ml-pipeline.agentic +350 -0
- package/examples/multi-agent-example.agentic +212 -0
- package/examples/onboarding-tutorial.agentic +263 -0
- package/examples/production-api.agentic +304 -0
- package/examples/real-world-chatbot.agentic +351 -0
- package/examples/result-handling.agentic +34 -0
- package/examples/runtime.ts +24 -0
- package/examples/showcase.agentic +22 -0
- package/examples/showcase.ts +28 -0
- package/examples/simple-test.agentic +4 -0
- package/examples/simple-test.ts +7 -0
- package/examples/simple.agentic +20 -0
- package/examples/test2.agentic +4 -0
- package/examples/test2.ts +9 -0
- package/examples/test3.agentic +4 -0
- package/examples/test3.ts +9 -0
- package/package.json +70 -0
- package/playground/index.html +221 -0
- package/playground/playground.js +291 -0
- package/registry/package-registry.ts +319 -0
- package/scripts/build.js +50 -0
- package/scripts/validate-confidence-mutation.ts +112 -0
- package/stdlib/async/promise.agentic +216 -0
- package/stdlib/database/pool.agentic +235 -0
- package/stdlib/file/io.agentic +194 -0
- package/stdlib/http/client.agentic +168 -0
- package/video-scripts/001-agentic-in-100-seconds.md +175 -0
- package/vscode-extension/README.md +67 -0
- package/vscode-extension/language-configuration.json +31 -0
- package/vscode-extension/package.json +46 -0
- package/vscode-extension/syntaxes/agentic.tmLanguage.json +134 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// Production-Ready REST API Example
|
|
2
|
+
// Demonstrates: error handling, health checks, observability, rate limiting
|
|
3
|
+
|
|
4
|
+
@module("api")
|
|
5
|
+
|
|
6
|
+
// User authentication endpoint
|
|
7
|
+
@confidence(0.93)
|
|
8
|
+
@complete
|
|
9
|
+
@needs(database: Database, jwt: JWTService, logger: Logger)
|
|
10
|
+
@effects(database, io, llm_call)
|
|
11
|
+
@property("rejects empty tokens")
|
|
12
|
+
@property("rejects expired tokens")
|
|
13
|
+
@property("validates token signature")
|
|
14
|
+
func authenticate(token: string) -> Result<User, AuthError> {
|
|
15
|
+
@trace("authentication_start", { tokenLength: token.length })
|
|
16
|
+
|
|
17
|
+
if token.isEmpty() {
|
|
18
|
+
logger.warn("Authentication failed: empty token")
|
|
19
|
+
return Err(AuthError.MISSING_TOKEN)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@retry(maxAttempts: 2, backoff: "exponential")
|
|
23
|
+
decoded = jwt.decode(token) match {
|
|
24
|
+
Ok(payload) -> payload,
|
|
25
|
+
Err(error) -> {
|
|
26
|
+
@context {
|
|
27
|
+
what_failed: "JWT decoding",
|
|
28
|
+
suggestions: ["Check token format", "Verify JWT_SECRET", "Check token expiration"],
|
|
29
|
+
recovery: { action: "refresh_token", command: "POST /auth/refresh" }
|
|
30
|
+
}
|
|
31
|
+
logger.error("JWT decode failed", error)
|
|
32
|
+
return Err(AuthError.INVALID_TOKEN)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if decoded.exp < now() {
|
|
37
|
+
logger.warn("Authentication failed: token expired")
|
|
38
|
+
return Err(AuthError.EXPIRED_TOKEN)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@circuit_breaker(threshold: 5, timeout: 30s)
|
|
42
|
+
user = database.users.find(decoded.userId) match {
|
|
43
|
+
Ok(Some(u)) -> u,
|
|
44
|
+
Ok(None) -> return Err(AuthError.USER_NOT_FOUND),
|
|
45
|
+
Err(e) -> {
|
|
46
|
+
@context {
|
|
47
|
+
what_failed: "User lookup",
|
|
48
|
+
userId: decoded.userId,
|
|
49
|
+
suggestions: ["Check database connection", "Verify user exists"]
|
|
50
|
+
}
|
|
51
|
+
return Err(AuthError.DATABASE_ERROR(e))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
logger.info("User ${user.id} authenticated successfully")
|
|
56
|
+
|
|
57
|
+
return Ok(user)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Create new user with validation
|
|
61
|
+
@confidence(0.91)
|
|
62
|
+
@complete
|
|
63
|
+
@needs(database: Database, validator: Validator, logger: Logger)
|
|
64
|
+
@effects(database, io)
|
|
65
|
+
@property("rejects invalid emails")
|
|
66
|
+
@property("rejects weak passwords")
|
|
67
|
+
@property("prevents duplicate users")
|
|
68
|
+
@requires_approval(threshold: "high_risk_signup")
|
|
69
|
+
func createUser(email: string, password: string) -> Result<User, CreateUserError> {
|
|
70
|
+
// Validate email
|
|
71
|
+
@confidence(0.95)
|
|
72
|
+
validEmail = validator.validateEmail(email) match {
|
|
73
|
+
Ok(e) -> e,
|
|
74
|
+
Err(e) -> return Err(CreateUserError.INVALID_EMAIL(e))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Validate password strength
|
|
78
|
+
@confidence(0.93)
|
|
79
|
+
@property("enforces minimum 8 characters")
|
|
80
|
+
@property("requires mixed case")
|
|
81
|
+
@property("requires special character")
|
|
82
|
+
validator.validatePassword(password) match {
|
|
83
|
+
Ok(_) -> {},
|
|
84
|
+
Err(e) -> return Err(CreateUserError.WEAK_PASSWORD(e))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check for duplicates
|
|
88
|
+
existing = database.users.findByEmail(email) match {
|
|
89
|
+
Ok(Some(_)) -> return Err(CreateUserError.EMAIL_ALREADY_EXISTS),
|
|
90
|
+
Ok(None) -> {},
|
|
91
|
+
Err(e) -> return Err(CreateUserError.DATABASE_ERROR(e))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create user with transaction
|
|
95
|
+
@transaction
|
|
96
|
+
user = database.users.create({
|
|
97
|
+
email: validEmail,
|
|
98
|
+
passwordHash: hash(password),
|
|
99
|
+
createdAt: now()
|
|
100
|
+
}) match {
|
|
101
|
+
Ok(u) -> u,
|
|
102
|
+
Err(e) -> {
|
|
103
|
+
@context {
|
|
104
|
+
what_failed: "User creation",
|
|
105
|
+
email: validEmail,
|
|
106
|
+
suggestions: ["Check database constraints", "Verify unique email"]
|
|
107
|
+
}
|
|
108
|
+
return Err(CreateUserError.CREATE_FAILED(e))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
logger.info("User created: ${user.id}")
|
|
113
|
+
|
|
114
|
+
return Ok(user)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Health check endpoint
|
|
118
|
+
@healthcheck(interval: 30s)
|
|
119
|
+
@confidence(0.96)
|
|
120
|
+
@effects(database, network)
|
|
121
|
+
func checkAPIHealth() -> HealthStatus {
|
|
122
|
+
checks = [
|
|
123
|
+
checkDatabaseConnection(),
|
|
124
|
+
checkRedisConnection(),
|
|
125
|
+
checkExternalAPIs()
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
failures = checks.filter(c => c.status == "failed")
|
|
129
|
+
|
|
130
|
+
if failures.length == 0 {
|
|
131
|
+
return HealthStatus.OK
|
|
132
|
+
} else if failures.length < checks.length {
|
|
133
|
+
return HealthStatus.DEGRADED {
|
|
134
|
+
message: "${failures.length} services unhealthy",
|
|
135
|
+
details: failures
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
return HealthStatus.FAILED {
|
|
139
|
+
message: "All services unhealthy",
|
|
140
|
+
details: failures
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@recovery(for: checkAPIHealth)
|
|
146
|
+
@confidence(0.84)
|
|
147
|
+
func recoverAPI() -> Result<void, Error> {
|
|
148
|
+
@escalate_to_human("API health critical, human intervention needed")
|
|
149
|
+
|
|
150
|
+
// Attempt reconnections
|
|
151
|
+
database.reconnect()
|
|
152
|
+
redis.reconnect()
|
|
153
|
+
|
|
154
|
+
return Ok(void)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Rate limiting
|
|
158
|
+
@rate_limit(
|
|
159
|
+
window: 1m,
|
|
160
|
+
maxRequests: 100,
|
|
161
|
+
perUser: true,
|
|
162
|
+
action: "reject_with_429"
|
|
163
|
+
)
|
|
164
|
+
@confidence(0.94)
|
|
165
|
+
@effects(database, state)
|
|
166
|
+
func getUser(userId: string, requestingUser: User) -> Result<User, Error> {
|
|
167
|
+
// Automatically rate-limited
|
|
168
|
+
// Returns 429 Too Many Requests if limit exceeded
|
|
169
|
+
|
|
170
|
+
@cache(ttl: 5m, key: "user:${userId}")
|
|
171
|
+
user = database.users.find(userId) match {
|
|
172
|
+
Ok(Some(u)) -> u,
|
|
173
|
+
Ok(None) -> return Err(Error.USER_NOT_FOUND),
|
|
174
|
+
Err(e) -> return Err(Error.DATABASE_ERROR(e))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return Ok(user)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// List users with pagination
|
|
181
|
+
@confidence(0.92)
|
|
182
|
+
@complete
|
|
183
|
+
@needs(database: Database)
|
|
184
|
+
@effects(database, io)
|
|
185
|
+
@property("respects limit parameter")
|
|
186
|
+
@property("returns empty array when no results")
|
|
187
|
+
@property("handles invalid pagination")
|
|
188
|
+
func listUsers(
|
|
189
|
+
page: number = 1,
|
|
190
|
+
limit: number = 20,
|
|
191
|
+
filter: Option<UserFilter> = None
|
|
192
|
+
) -> Result<PaginatedResponse<User>, Error> {
|
|
193
|
+
if limit < 1 or limit > 100 {
|
|
194
|
+
return Err(Error.INVALID_LIMIT)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if page < 1 {
|
|
198
|
+
return Err(Error.INVALID_PAGE)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
offset = (page - 1) * limit
|
|
202
|
+
|
|
203
|
+
query = database.users.select()
|
|
204
|
+
.limit(limit)
|
|
205
|
+
.offset(offset)
|
|
206
|
+
|
|
207
|
+
if filter {
|
|
208
|
+
query = query.where("status", "=", filter.status)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@circuit_breaker(threshold: 3, timeout: 30s)
|
|
212
|
+
users = query.execute() match {
|
|
213
|
+
Ok(u) -> u,
|
|
214
|
+
Err(e) -> return Err(Error.DATABASE_ERROR(e))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
total = database.users.count() match {
|
|
218
|
+
Ok(c) -> c,
|
|
219
|
+
Err(e) -> return Err(Error.DATABASE_ERROR(e))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return Ok(PaginatedResponse {
|
|
223
|
+
data: users,
|
|
224
|
+
page: page,
|
|
225
|
+
limit: limit,
|
|
226
|
+
total: total,
|
|
227
|
+
totalPages: ceil(total / limit)
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Error handling middleware
|
|
232
|
+
@confidence(0.89)
|
|
233
|
+
@effects(io)
|
|
234
|
+
func errorHandler(error: Error, request: Request) -> Response {
|
|
235
|
+
@trace_error(error, {
|
|
236
|
+
path: request.path,
|
|
237
|
+
method: request.method,
|
|
238
|
+
userId: request.user?.id
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
error match {
|
|
242
|
+
AuthError.MISSING_TOKEN -> Response {
|
|
243
|
+
status: 401,
|
|
244
|
+
body: { error: "Missing authentication token" }
|
|
245
|
+
},
|
|
246
|
+
AuthError.EXPIRED_TOKEN -> Response {
|
|
247
|
+
status: 401,
|
|
248
|
+
body: { error: "Token expired", action: "refresh_token" }
|
|
249
|
+
},
|
|
250
|
+
Error.RATE_LIMIT_EXCEEDED -> Response {
|
|
251
|
+
status: 429,
|
|
252
|
+
body: { error: "Too many requests", retryAfter: 60 }
|
|
253
|
+
},
|
|
254
|
+
DatabaseError(_) -> Response {
|
|
255
|
+
status: 503,
|
|
256
|
+
body: { error: "Service temporarily unavailable" }
|
|
257
|
+
},
|
|
258
|
+
_ -> Response {
|
|
259
|
+
status: 500,
|
|
260
|
+
body: { error: "Internal server error", requestId: request.id }
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Main API server
|
|
266
|
+
@confidence(0.87)
|
|
267
|
+
@effects(network, database, io)
|
|
268
|
+
@observability(
|
|
269
|
+
tracing: true,
|
|
270
|
+
metrics: true,
|
|
271
|
+
logging: true
|
|
272
|
+
)
|
|
273
|
+
func startServer(port: number) -> Result<void, ServerError> {
|
|
274
|
+
server = createHTTPServer({
|
|
275
|
+
port: port,
|
|
276
|
+
routes: [
|
|
277
|
+
{ method: "POST", path: "/auth/login", handler: authenticate },
|
|
278
|
+
{ method: "POST", path: "/users", handler: createUser },
|
|
279
|
+
{ method: "GET", path: "/users/:id", handler: getUser },
|
|
280
|
+
{ method: "GET", path: "/users", handler: listUsers },
|
|
281
|
+
{ method: "GET", path: "/health", handler: checkAPIHealth }
|
|
282
|
+
],
|
|
283
|
+
errorHandler: errorHandler,
|
|
284
|
+
middleware: [
|
|
285
|
+
rateLimitMiddleware,
|
|
286
|
+
authMiddleware,
|
|
287
|
+
loggingMiddleware
|
|
288
|
+
]
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
@healthcheck(interval: 30s)
|
|
292
|
+
monitorHealth()
|
|
293
|
+
|
|
294
|
+
server.listen() match {
|
|
295
|
+
Ok(_) -> {
|
|
296
|
+
logger.info("API server started on port ${port}")
|
|
297
|
+
return Ok(void)
|
|
298
|
+
},
|
|
299
|
+
Err(e) -> {
|
|
300
|
+
logger.error("Failed to start server", e)
|
|
301
|
+
return Err(ServerError.STARTUP_FAILED(e))
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// Real-World Example: Production Chatbot with Multi-Agent Coordination
|
|
2
|
+
// Demonstrates: HITL, cost tracking, session persistence, observability
|
|
3
|
+
|
|
4
|
+
@module("chatbot")
|
|
5
|
+
|
|
6
|
+
// Main chatbot agent
|
|
7
|
+
@agent(role: "chatbot", capabilities: ["conversation", "task_routing"])
|
|
8
|
+
agent ChatbotAgent {
|
|
9
|
+
inbox: Channel<UserMessage>
|
|
10
|
+
outbox: Channel<AssistantMessage>
|
|
11
|
+
|
|
12
|
+
@session(storage: "redis", ttl: 24h)
|
|
13
|
+
@budget_limit(user_daily: 5.00, action: "throttle")
|
|
14
|
+
@confidence(0.87)
|
|
15
|
+
func handleMessage(message: UserMessage) -> AssistantMessage {
|
|
16
|
+
@trace_conversation({
|
|
17
|
+
userId: message.userId,
|
|
18
|
+
messageId: message.id,
|
|
19
|
+
intent: classifyIntent(message.text)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Restore session context
|
|
23
|
+
session = Session.restore(message.userId) or {
|
|
24
|
+
session = Session.new(message.userId)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Add to conversation history
|
|
28
|
+
session.addMessage({ role: "user", content: message.text })
|
|
29
|
+
|
|
30
|
+
// Check budget
|
|
31
|
+
budget = costRuntime.checkBudget(message.userId)
|
|
32
|
+
if budget.remaining < 0.10 {
|
|
33
|
+
return AssistantMessage {
|
|
34
|
+
text: "You've reached your daily limit. Upgrade to continue.",
|
|
35
|
+
metadata: { budgetExceeded: true }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Route based on intent
|
|
40
|
+
intent = classifyIntent(message.text)
|
|
41
|
+
|
|
42
|
+
response = intent match {
|
|
43
|
+
"simple_question" -> answerWithCheapModel(message, session),
|
|
44
|
+
"complex_analysis" -> delegateToSpecialist(message, session),
|
|
45
|
+
"sensitive_action" -> requireHumanApproval(message, session),
|
|
46
|
+
_ -> defaultResponse(message, session)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Save session
|
|
50
|
+
session.addMessage({ role: "assistant", content: response.text })
|
|
51
|
+
session.save()
|
|
52
|
+
|
|
53
|
+
return response
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Simple questions use cheap model
|
|
58
|
+
@confidence(0.88)
|
|
59
|
+
@cost_optimized(model: "gpt-4.5-turbo")
|
|
60
|
+
@effects(llm_call, cost)
|
|
61
|
+
func answerWithCheapModel(
|
|
62
|
+
message: UserMessage,
|
|
63
|
+
session: Session
|
|
64
|
+
) -> AssistantMessage {
|
|
65
|
+
@llm_call(
|
|
66
|
+
model: "gpt-4.5-turbo",
|
|
67
|
+
temperature: 0.7,
|
|
68
|
+
max_tokens: 300
|
|
69
|
+
)
|
|
70
|
+
@cost_tracked(
|
|
71
|
+
userId: message.userId,
|
|
72
|
+
feature: "simple_qa",
|
|
73
|
+
model: "gpt-4.5-turbo"
|
|
74
|
+
)
|
|
75
|
+
response = llm.complete({
|
|
76
|
+
system: "You are a helpful assistant. Be concise.",
|
|
77
|
+
messages: session.getRecentMessages(10),
|
|
78
|
+
user: message.text
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return AssistantMessage {
|
|
82
|
+
text: response.text,
|
|
83
|
+
metadata: {
|
|
84
|
+
model: "gpt-4.5-turbo",
|
|
85
|
+
cost: response.cost,
|
|
86
|
+
confidence: 0.88
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Complex questions delegate to specialist agent
|
|
92
|
+
@confidence(0.82)
|
|
93
|
+
@effects(llm_call, cost, async)
|
|
94
|
+
@needs(specialist: SpecialistAgent)
|
|
95
|
+
func delegateToSpecialist(
|
|
96
|
+
message: UserMessage,
|
|
97
|
+
session: Session
|
|
98
|
+
) -> AssistantMessage {
|
|
99
|
+
@handoff {
|
|
100
|
+
to: "specialist_agent",
|
|
101
|
+
reason: "Complex analysis required",
|
|
102
|
+
context: {
|
|
103
|
+
conversationHistory: session.getAllMessages(),
|
|
104
|
+
intent: "complex_analysis",
|
|
105
|
+
userPreferences: session.getUserPreferences()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@timeout(30s)
|
|
110
|
+
response = specialist.inbox.send(SpecialistRequest {
|
|
111
|
+
message: message,
|
|
112
|
+
context: session
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
result = specialist.outbox.receive() match {
|
|
116
|
+
Some(r) -> r,
|
|
117
|
+
None -> {
|
|
118
|
+
// Specialist timed out, use fallback
|
|
119
|
+
return AssistantMessage {
|
|
120
|
+
text: "This is complex. Let me get a human expert to help.",
|
|
121
|
+
metadata: { requiresHuman: true }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return AssistantMessage {
|
|
127
|
+
text: result.text,
|
|
128
|
+
metadata: {
|
|
129
|
+
handledBy: "specialist",
|
|
130
|
+
confidence: result.confidence
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Sensitive actions require human approval
|
|
136
|
+
@confidence(0.90)
|
|
137
|
+
@requires_approval(
|
|
138
|
+
channel: "slack",
|
|
139
|
+
approvers: ["support_team"],
|
|
140
|
+
timeout: 10m,
|
|
141
|
+
fallback: "reject"
|
|
142
|
+
)
|
|
143
|
+
@effects(human_interaction, io)
|
|
144
|
+
func requireHumanApproval(
|
|
145
|
+
message: UserMessage,
|
|
146
|
+
session: Session
|
|
147
|
+
) -> AssistantMessage {
|
|
148
|
+
@trace_decision("human_approval_requested", {
|
|
149
|
+
reason: "Sensitive action detected",
|
|
150
|
+
action: classifyAction(message.text),
|
|
151
|
+
userId: message.userId
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
approval = approvalRuntime.requestApproval(
|
|
155
|
+
"User requesting: ${message.text}",
|
|
156
|
+
{
|
|
157
|
+
userId: message.userId,
|
|
158
|
+
conversationHistory: session.getRecentMessages(5),
|
|
159
|
+
riskLevel: assessRisk(message)
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
channel: "slack",
|
|
163
|
+
approvers: ["support_team"],
|
|
164
|
+
timeout: 10 * 60 * 1000, // 10 minutes
|
|
165
|
+
fallback: "reject"
|
|
166
|
+
}
|
|
167
|
+
) match {
|
|
168
|
+
Ok({ approved: true, reason }) -> {
|
|
169
|
+
// Approved - proceed with action
|
|
170
|
+
return executeApprovedAction(message, session, reason)
|
|
171
|
+
},
|
|
172
|
+
Ok({ approved: false, reason }) -> {
|
|
173
|
+
// Rejected
|
|
174
|
+
return AssistantMessage {
|
|
175
|
+
text: "I cannot complete that action. Reason: ${reason}",
|
|
176
|
+
metadata: { approved: false }
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
Err(e) -> {
|
|
180
|
+
// Approval system failed
|
|
181
|
+
return AssistantMessage {
|
|
182
|
+
text: "Unable to get approval. Please try again later.",
|
|
183
|
+
metadata: { error: e }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Specialist agent for complex queries
|
|
190
|
+
@agent(role: "specialist", capabilities: ["deep_analysis", "research"])
|
|
191
|
+
agent SpecialistAgent {
|
|
192
|
+
inbox: Channel<SpecialistRequest>
|
|
193
|
+
outbox: Channel<SpecialistResponse>
|
|
194
|
+
|
|
195
|
+
@handler("specialist_request")
|
|
196
|
+
@confidence(0.85)
|
|
197
|
+
@effects(llm_call, network, cost)
|
|
198
|
+
func handleRequest(request: SpecialistRequest) -> SpecialistResponse {
|
|
199
|
+
// Use more powerful model
|
|
200
|
+
@llm_call(
|
|
201
|
+
model: "gpt-4.5",
|
|
202
|
+
temperature: 0.8,
|
|
203
|
+
max_tokens: 1000
|
|
204
|
+
)
|
|
205
|
+
@cost_tracked(
|
|
206
|
+
userId: request.message.userId,
|
|
207
|
+
feature: "specialist_analysis",
|
|
208
|
+
model: "gpt-4.5"
|
|
209
|
+
)
|
|
210
|
+
analysis = llm.complete({
|
|
211
|
+
system: "You are an expert analyst. Provide detailed, accurate analysis.",
|
|
212
|
+
messages: request.context.getAllMessages(),
|
|
213
|
+
user: request.message.text
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// If still uncertain, use ensemble
|
|
217
|
+
if analysis.confidence < 0.75 {
|
|
218
|
+
@ensemble([
|
|
219
|
+
llm("gpt-4.5"),
|
|
220
|
+
llm("claude-sonnet-4-5"),
|
|
221
|
+
llm("gemini-2.0-pro")
|
|
222
|
+
])
|
|
223
|
+
analysis = ensembleAnalysis(request)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return SpecialistResponse {
|
|
227
|
+
text: analysis.text,
|
|
228
|
+
confidence: analysis.confidence,
|
|
229
|
+
sources: analysis.sources,
|
|
230
|
+
timestamp: now()
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Input guardrails for safety
|
|
236
|
+
@input_guardrails(
|
|
237
|
+
pii_detection: true,
|
|
238
|
+
prompt_injection: true,
|
|
239
|
+
toxicity_threshold: 0.8,
|
|
240
|
+
action: "reject"
|
|
241
|
+
)
|
|
242
|
+
@confidence(0.94)
|
|
243
|
+
func validateUserInput(text: string) -> Result<string, ValidationError> {
|
|
244
|
+
// Automatic validation before processing
|
|
245
|
+
// Checks for: PII, prompt injection, toxicity, etc.
|
|
246
|
+
|
|
247
|
+
validated = InputGuard.check(text)
|
|
248
|
+
|
|
249
|
+
if validated.has_pii {
|
|
250
|
+
@audit_security("pii_detected", { userId, inputHash: hash(text) })
|
|
251
|
+
return Err(ValidationError.PII_DETECTED)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if validated.injection_score > 0.7 {
|
|
255
|
+
@audit_security("injection_attempt", { score: validated.injection_score })
|
|
256
|
+
return Err(ValidationError.INJECTION_ATTEMPT)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if validated.toxicity > 0.8 {
|
|
260
|
+
return Err(ValidationError.TOXIC_CONTENT)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return Ok(validated.sanitized)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Output guardrails
|
|
267
|
+
@output_guardrails(
|
|
268
|
+
checks: ["toxicity", "bias", "pii", "factuality"],
|
|
269
|
+
enforcement: "block",
|
|
270
|
+
fallback: "I cannot provide that information."
|
|
271
|
+
)
|
|
272
|
+
@confidence(0.92)
|
|
273
|
+
func validateAssistantOutput(text: string) -> Result<string, ValidationError> {
|
|
274
|
+
// Automatic output validation
|
|
275
|
+
// Ensures responses are safe, unbiased, and factual
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Health monitoring
|
|
279
|
+
@healthcheck(interval: 30s)
|
|
280
|
+
@confidence(0.95)
|
|
281
|
+
func checkChatbotHealth() -> HealthStatus {
|
|
282
|
+
checks = [
|
|
283
|
+
database.ping(),
|
|
284
|
+
redis.ping(),
|
|
285
|
+
llmAPI.ping()
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
allHealthy = checks.all(c => c == HealthStatus.OK)
|
|
289
|
+
|
|
290
|
+
return allHealthy ? HealthStatus.OK : HealthStatus.DEGRADED
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Cost analytics
|
|
294
|
+
@confidence(0.93)
|
|
295
|
+
func generateCostReport(userId: string, period: Period) -> CostReport {
|
|
296
|
+
costs = costRuntime.getReport({
|
|
297
|
+
userId: userId,
|
|
298
|
+
period: period,
|
|
299
|
+
groupBy: ["feature", "model"]
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
return CostReport {
|
|
303
|
+
totalCost: costs.total,
|
|
304
|
+
breakdown: costs.byFeature,
|
|
305
|
+
topModels: costs.byModel,
|
|
306
|
+
recommendations: generateCostOptimizations(costs)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Main entry point
|
|
311
|
+
@confidence(0.89)
|
|
312
|
+
@observable(tracing: true, metrics: true, logging: true)
|
|
313
|
+
@effects(network, database, llm_call, io, cost, human_interaction)
|
|
314
|
+
func runChatbot(port: number) -> Result<void, Error> {
|
|
315
|
+
// Initialize agents
|
|
316
|
+
chatbot = spawn ChatbotAgent()
|
|
317
|
+
specialist = spawn SpecialistAgent()
|
|
318
|
+
|
|
319
|
+
// Start health monitoring
|
|
320
|
+
@background
|
|
321
|
+
monitorHealth()
|
|
322
|
+
|
|
323
|
+
// Start HTTP server
|
|
324
|
+
server = createServer({
|
|
325
|
+
port: port,
|
|
326
|
+
routes: [
|
|
327
|
+
{ method: "POST", path: "/chat", handler: chatbot.handleMessage },
|
|
328
|
+
{ method: "GET", path: "/health", handler: checkChatbotHealth },
|
|
329
|
+
{ method: "GET", path: "/costs", handler: generateCostReport }
|
|
330
|
+
]
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
server.listen()
|
|
334
|
+
|
|
335
|
+
logger.info("🤖 Chatbot started on port ${port}")
|
|
336
|
+
|
|
337
|
+
return Ok(void)
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
This is a **production-ready chatbot** with:
|
|
342
|
+
- Multi-agent coordination
|
|
343
|
+
- Cost tracking and budgets
|
|
344
|
+
- Safety guardrails (input + output)
|
|
345
|
+
- Human-in-the-loop for sensitive actions
|
|
346
|
+
- Session persistence
|
|
347
|
+
- Health monitoring
|
|
348
|
+
- Observability (tracing, metrics, logging)
|
|
349
|
+
- Ensemble models for high-stakes queries
|
|
350
|
+
|
|
351
|
+
**Try building this in another language - it's 3-5x more code!**
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Example: Result type handling in Agentic
|
|
2
|
+
// Demonstrates pattern matching with Ok/Err
|
|
3
|
+
|
|
4
|
+
@confidence(0.95, "Production ready")
|
|
5
|
+
@complete
|
|
6
|
+
func divide(a: number, b: number) -> Result<number, string> {
|
|
7
|
+
if b == 0 {
|
|
8
|
+
return Err("Division by zero")
|
|
9
|
+
}
|
|
10
|
+
return Ok(a / b)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@confidence(0.92, "Well tested")
|
|
14
|
+
@complete
|
|
15
|
+
func safeDivide(a: number, b: number) -> number {
|
|
16
|
+
divide(a, b) match {
|
|
17
|
+
Ok(result) -> return result,
|
|
18
|
+
Err(error) -> return 0
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@partial
|
|
23
|
+
@confidence(0.75, "Basic implementation")
|
|
24
|
+
func chainedCalculation(x: number, y: number) -> Result<number, string> {
|
|
25
|
+
divide(x, 2) match {
|
|
26
|
+
Ok(half) -> {
|
|
27
|
+
divide(half, y) match {
|
|
28
|
+
Ok(final) -> return Ok(final),
|
|
29
|
+
Err(e) -> return Err(e)
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
Err(e) -> return Err(e)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime stub for examples
|
|
3
|
+
* In production, this would be @agentic/runtime npm package
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
|
|
7
|
+
|
|
8
|
+
export function Ok<T>(value: T): Result<T, never> {
|
|
9
|
+
return { ok: true, value };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Err<E>(error: E): Result<never, E> {
|
|
13
|
+
return { ok: false, error };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class AgenticRuntime {
|
|
17
|
+
static confidence = {
|
|
18
|
+
register: (name: string, level: number, reason?: string) => {
|
|
19
|
+
if (level < 0.80) {
|
|
20
|
+
console.warn(`⚠️ Low confidence (${level}) in ${name}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|