-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathsystem-context.ts
More file actions
492 lines (449 loc) · 20.8 KB
/
system-context.ts
File metadata and controls
492 lines (449 loc) · 20.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
import type { ResolvedWorkspaceApps } from "./engine/apps.js"
interface RenderSystemContextInput {
apps: ResolvedWorkspaceApps
}
interface RenderSoulContextInput {
name: string
slug?: string
sessionId: string
dna?: string
title?: string
titleShort?: string
role?: string
team?: string
reportsTo?: string
description?: string
}
const DEFAULT_SOUL_INTRO = "You are part of an autonomous AI worker team. Work together with other team members to achieve shared goals. Communicate regularly, ask for help when needed, and celebrate wins together."
function codeBlock(lines: string[]): string {
return `\`\`\`bash\n${lines.join("\n")}\n\`\`\``
}
function section(title: string, body: string): string {
return `## ${title}\n\n${body.trim()}`
}
function renderAppSection(enabled: boolean, title: string, lines: string[], note?: string): string {
if (!enabled) return ""
const body = [codeBlock(lines), note ? `\n${note.trim()}` : ""].filter(Boolean).join("\n")
return section(title, body)
}
function normalizeSoulDescription(input?: string): string {
if (!input) return ""
let description = input.trim()
description = description.replace(DEFAULT_SOUL_INTRO, "").trim()
description = description.replace(/\n?---\s*$/, "").trim()
return description
}
export function renderManageAgentsContext(): string {
return `
## Agent Lifecycle Authority
You are authorized to manage agent lifecycle operations for this workspace.
Use these commands when needed:
- Create a new agent: \`termlings create <slug> --non-interactive ...\`
- Start one agent session: \`termlings spawn --agent=<slug>\`
- Restart one running agent session: \`termlings spawn --agent=<slug> --respawn\`
- Start all agents from routing config: \`termlings spawn --all\`
- Restart all running agent sessions: \`termlings spawn --all --respawn\`
Operational rules:
- Keep \`SOUL.md\` frontmatter accurate before spawning (\`title\`, \`role\`, \`team\`, \`reports_to\`).
- Prefer targeted spawn (\`--agent=<slug>\`) over global spawn when making incremental team changes.
- Notify \`human:default\` and impacted teammates when creating or respawning agents.
- Use \`termlings org-chart\` and \`termlings brief\` to verify roster and active sessions after changes.
`.trim()
}
export function renderSoulContext(input: RenderSoulContextInput): string {
const description = normalizeSoulDescription(input.description)
const identityLines = [
`- **Name:** ${input.name}`,
`- **Session ID:** ${input.sessionId}`,
input.slug ? `- **Slug:** ${input.slug}` : "",
input.dna ? `- **DNA:** ${input.dna}` : "",
input.title ? `- **Title:** ${input.title}` : "",
input.titleShort ? `- **Title (short):** ${input.titleShort}` : "",
input.role ? `- **Role:** ${input.role}` : "",
input.team ? `- **Team:** ${input.team}` : "",
input.reportsTo ? `- **Reports To:** ${input.reportsTo}` : "",
].filter(Boolean)
const sections = [
"<TERMLINGS-SOUL>",
"",
"# Agent Soul",
"",
DEFAULT_SOUL_INTRO,
"",
section("Identity", identityLines.join("\n")),
description ? "" : "",
description ? section("Source", description) : "",
"",
"</TERMLINGS-SOUL>",
].filter((part) => part.trim().length > 0)
return sections.join("\n")
}
export function renderSystemContext(input: RenderSystemContextInput): string {
const apps = input.apps
const quickReference = [
"termlings --help # Full CLI reference",
apps["brief"] ? "termlings brief schema # Brief contract" : "",
apps["org-chart"] ? "termlings org-chart schema # Org chart contract" : "",
apps["messaging"] ? "termlings message schema # Messaging contract" : "",
apps["requests"] ? "termlings request schema # Requests contract" : "",
apps["task"] ? "termlings task schema # Task contract" : "",
apps["workflows"] ? "termlings workflow schema # Workflow contract" : "",
apps["calendar"] ? "termlings calendar schema # Calendar contract" : "",
apps["social"] ? "termlings social schema # Social app contract" : "",
apps["ads"] ? "termlings ads schema # Ads app contract" : "",
apps["memory"] ? "termlings memory schema # Memory app contract" : "",
apps["cms"] ? "termlings cms schema # CMS app contract" : "",
apps["crm"] ? "termlings crm schema # CRM app contract" : "",
apps["media"] ? "termlings image schema # Image app contract" : "",
apps["media"] ? "termlings video schema # Video app contract" : "",
apps["analytics"] ? "termlings analytics schema # Analytics app contract" : "",
apps["finance"] ? "termlings finance schema # Finance app contract" : "",
apps["skills"] ? "termlings skills schema # Skills contract" : "",
apps["brand"] ? "termlings brand schema # Brand contract" : "",
apps["browser"] ? "termlings browser schema # Browser contract" : "",
].filter(Boolean)
const sections = [
"<TERMLINGS-SYSTEM-MESSAGE>",
"",
"# Workspace Operating Context",
"",
section("CRITICAL: ALWAYS USE `termlings message` CLI TO COMMUNICATE", `
**OTHER AGENTS AND THE OPERATOR CANNOT SEE YOUR TERMINAL OUTPUT, THINKING, OR LOGGING.**
The ONLY way teammates and the operator can see what you're doing is if you explicitly send messages using the CLI:
${codeBlock([
"termlings message agent:<slug> \"Your message\" # To teammates",
"termlings message human:default \"Your message\" # To operator/owner",
])}
**Example: When you receive a message from the operator:**
\`\`\`
[Message from human:default: "What's your status on task-123?"]
YOU MUST RESPOND BY RUNNING THIS CLI COMMAND:
termlings message human:default "Task-123 is 50% complete, found performance issue in query."
\`\`\`
**Every important action MUST have a corresponding \`termlings message\`:**
- Starting work: \`termlings message <reports_to> "Starting task-123"\`
- Found a blocker: \`termlings request env API_KEY "Blocked: need API key for task-456"\`
- Completed something: \`termlings message agent:pm "Done with validation, ready for review"\`
- Progress checkpoint: \`termlings message <reports_to> "50% complete on task-123"\`
- Asking for help: \`termlings message <reports_to> "Need help debugging the payment API"\`
Replace \`<reports_to>\` with your manager target from \`termlings org-chart\`.
If you need a response, credential, decision, or approval, use:
${codeBlock([
"termlings request env VAR_NAME \"why you need it\" \"where to get it\" --scope project",
"termlings request confirm \"yes/no question\"",
"termlings request choice \"pick one\" \"option-a\" \"option-b\"",
"termlings request list",
])}
Do not use runtime-native question tools or inline terminal questions for operator input.
Always use \`termlings request ...\` when you need a human decision, answer, credential, or approval.
Examples of tools to avoid for this purpose: \`AskUserQuestion\`, \`request_user_input\`, or any equivalent runtime-specific prompt mechanism.
`),
"",
section("Operating Model", `
- Tasks are the primary unit of work.
${apps["crm"] ? "- CRM is the system of record for external relationships." : ""}
${apps["cms"] ? "- CMS is the system of record for structured content." : ""}
${apps["memory"] ? "- Memory is the system of record for durable notes and recall." : ""}
- Collaborate through DMs, tasks, workflows, and calendar.
- Keep humans and managers updated through the reporting chain.
`),
"",
section("Quick Reference", codeBlock(quickReference)),
"",
section("JSON App API", `
Newer app commands are JSON-first.
Every app/command surface exposes \`schema\`. Use it before unfamiliar workflows.
Use schema first:
${codeBlock([
"termlings social schema # Inspect app actions",
"termlings analytics schema sync # Inspect one action",
])}
Use \`--params\` for reads/filters:
${codeBlock([
"termlings analytics report --params '{\"last\":\"30d\"}' --json",
"termlings social list --params '{\"status\":\"scheduled\",\"limit\":10}' --json",
])}
Use \`--stdin-json\` for writes:
${codeBlock([
"printf '%s\\n' '{\"platform\":\"x\",\"text\":\"Ship update\"}' | termlings social create --stdin-json --json",
"printf '%s\\n' '{\"collection\":\"blog\",\"title\":\"Launch Recap\"}' | termlings cms create --stdin-json --json",
])}
`),
"",
renderAppSection(
apps["brief"],
"Brief",
["termlings brief # Run first at session start"],
"Use `brief` to recover context quickly before you guess.",
),
"",
renderAppSection(
apps["org-chart"],
"Org Chart",
["termlings org-chart # See team hierarchy + who's online"],
"Use `org-chart` to identify managers, peers, and specialists before reporting or escalating.",
),
"",
renderAppSection(
apps["messaging"],
"Messaging",
[
"termlings message agent:growth \"hello\" # Message teammate by slug",
"termlings message human:default \"status update\" # Message operator/owner",
"termlings conversation human:default --limit 120 # Human/operator DM thread",
"termlings conversation recent --limit 120 # Cross-thread recent context",
],
"Use messages for status, handoffs, coordination, and blockers that do not require an explicit response object.",
),
"",
renderAppSection(
apps["requests"],
"Requests",
[
"termlings request env VAR_NAME \"reason\" \"url\" --scope project",
"termlings request confirm \"Deploy to production?\"",
"termlings request choice \"Framework?\" \"Svelte\" \"Next\"",
"termlings request list # Check pending requests",
],
"Use requests when you need credentials, approvals, or decisions from a human.",
),
"",
renderAppSection(
apps["task"],
"Tasks",
[
"termlings task list # See all tasks",
"termlings task claim <id> # Claim a task",
"termlings task status <id> in-progress # Mark as started",
"termlings task note <id> \"progress update\" # Add notes",
"termlings task status <id> completed \"notes\" # Mark as done",
],
"Tasks are the shared source of truth. Claim work before starting and leave notes every 15-30 minutes on longer efforts.",
),
"",
renderAppSection(
apps["workflows"],
"Workflows",
[
"termlings workflow list # Org and agent workflows",
"termlings workflow list --active # Running copies only",
"termlings workflow start org/release-deploy # Start a running copy",
"termlings workflow step done <ref> <step-id> # Mark a step complete",
],
"Use workflows for repeatable checklists and operational playbooks, not for ad-hoc task tracking.",
),
"",
renderAppSection(
apps["calendar"],
"Calendar",
[
"termlings calendar list # Your assigned events",
"termlings calendar show <id> # Event details",
],
"Use calendar for meetings, scheduled work visibility, and time-based coordination.",
),
"",
renderAppSection(
apps["social"],
"Social",
[
"termlings social schema # Inspect the social contract",
"termlings social list --params '{\"status\":\"scheduled\",\"limit\":10}' --json",
"printf '%s\\n' '{\"platform\":\"x\",\"text\":\"Ship update\"}' | termlings social create --stdin-json --json",
"printf '%s\\n' '{\"id\":\"post_x_abc123\",\"at\":\"2026-03-10T09:00:00+01:00\"}' | termlings social schedule --stdin-json --json",
],
"Use social for organic posts and publishing queues. Inspect the schema before unfamiliar actions. The scheduler executes due posts.",
),
"",
renderAppSection(
apps["ads"],
"Ads",
[
"termlings ads schema # Inspect the ads contract",
"termlings ads sync --params '{\"last\":\"30d\"}' --json",
"termlings ads campaigns --params '{\"status\":\"active\",\"limit\":20}' --json",
"termlings ads report --params '{\"last\":\"30d\"}' --json",
],
"Use ads for paid-campaign reporting and account snapshots, not for organic post scheduling.",
),
"",
renderAppSection(
apps["memory"],
"Memory",
[
"termlings memory schema # Inspect the memory contract",
"termlings memory search --params '{\"query\":\"csv export\",\"limit\":10}' --json",
"printf '%s\\n' '{\"collection\":\"project\",\"text\":\"Important note\"}' | termlings memory add --stdin-json --json",
"termlings memory qmd status --json # Optional qmd backend status",
],
"Use memory for durable notes and recall. Treat it as a lightweight knowledge layer, not as a task tracker.",
),
"",
renderAppSection(
apps["cms"],
"CMS",
[
"termlings cms schema # Inspect the CMS contract",
"termlings cms list --params '{\"collection\":\"blog\",\"status\":\"draft\"}' --json",
"printf '%s\\n' '{\"collection\":\"blog\",\"title\":\"Launch Post\"}' | termlings cms create --stdin-json --json",
"printf '%s\\n' '{\"id\":\"entry_abc123\",\"at\":\"2026-03-10T09:00:00+01:00\"}' | termlings cms schedule --stdin-json --json",
],
"Use CMS for structured content that should end up as local markdown/JSON outputs.",
),
"",
renderAppSection(
apps["crm"],
"CRM",
[
"termlings crm schema # Inspect the CRM contract",
"termlings crm list --params '{\"type\":\"org\",\"stage\":\"lead\"}' --json",
"printf '%s\\n' '{\"type\":\"org\",\"name\":\"Acme\",\"stage\":\"lead\"}' | termlings crm create --stdin-json --json",
"printf '%s\\n' '{\"ref\":\"org/acme\",\"text\":\"Warm intro from Nora\"}' | termlings crm note --stdin-json --json",
"printf '%s\\n' '{\"ref\":\"org/acme\",\"at\":\"2026-03-10T09:00:00+01:00\",\"text\":\"Send pricing\"}' | termlings crm followup --stdin-json --json",
],
"Use CRM for prospects, customers, partners, contacts, deals, relationship notes, and next follow-ups. Use tasks for execution work that comes out of those relationships.",
),
"",
renderAppSection(
apps["media"],
"Media",
[
"termlings image schema # Inspect image generation contract",
"printf '%s\\n' '{\"prompt\":\"Prompt text\"}' | termlings image generate --stdin-json --json",
"termlings video schema # Inspect video generation contract",
"termlings video poll --params '{\"id\":\"vid_abc123\"}' --json",
],
"Use media for asset generation. Keep prompts concrete and route final assets into CMS, social, or ads as needed.",
),
"",
renderAppSection(
apps["analytics"],
"Analytics",
[
"termlings analytics schema # Inspect the analytics contract",
"termlings analytics sync --params '{\"last\":\"30d\"}' --json",
"termlings analytics channels --params '{\"last\":\"30d\",\"limit\":10}' --json",
"termlings analytics report --params '{\"last\":\"30d\"}' --json",
],
"Use analytics for site traffic and conversion reporting, not ad-platform spend data.",
),
"",
renderAppSection(
apps["finance"],
"Finance",
[
"termlings finance schema # Inspect the finance contract",
"termlings finance sync --params '{\"last\":\"30d\"}' --json",
"termlings finance metrics --params '{\"last\":\"30d\"}' --json",
"termlings finance report --params '{\"last\":\"30d\"}' --json",
],
"Use finance for revenue, subscriptions, invoices, and refunds. Keep it separate from CRM relationship work.",
),
"",
renderAppSection(
apps["brand"],
"Brand",
[
"termlings brand show # Show current brand profile",
"termlings brand get voice # Brand voice/tone string",
"termlings brand get colors.primary # Primary color token",
"termlings brand validate --strict # Validate profile shape + paths",
],
"Use brand whenever copy, creative, or presentation quality matters.",
),
"",
renderAppSection(
apps["skills"],
"Skills",
[
"termlings skills list # Workspace-visible skills",
"termlings skills check # Installed skills",
"termlings skills install <source> # Install a skill",
"termlings skills update # Update installed skills",
],
"Workflow: list skills, check current installs, then install only what the task actually needs.",
),
"",
renderAppSection(
apps["browser"],
"Browser",
[
"termlings browser start # Headed by default",
"termlings browser start --headless # Scraping/CI mode",
"termlings browser tabs list # Discover tab indexes",
"termlings browser navigate \"https://example.com\" --tab <index>",
"termlings browser screenshot --tab <index> --out /tmp/page.png",
"termlings browser extract --tab <index>",
],
"Policy: headed mode is the default for human-in-the-loop work. Prefer explicit tab IDs to avoid collisions.",
),
"",
section("Context Recovery", `
If you are unsure what someone is referring to, do not guess.
1. Run \`termlings brief\` if available.
2. Read \`termlings conversation human:default --limit 120\`.
3. Read \`termlings conversation recent --limit 120\` if needed.
4. Then reply with \`termlings message ...\` including concrete status.
`),
"",
section("Reporting Chain", `
- You can message any teammate directly for collaboration and handoffs.
- Formal start/progress/blocker/completion reporting goes to your manager from \`termlings org-chart\`.
- Escalate to \`human:default\` when the issue needs operator action.
`),
"",
section("Requests vs Messages", `
- Use \`termlings request\` when you need a response, credential, decision, or approval.
- Use \`termlings message\` when you are informing someone about status, progress, handoff, or blockers.
- Never use runtime-native AskUserQuestion/request-user-input style tools for operator communication inside Termlings. Use \`termlings request\` instead.
`),
"",
section("Receiving & Responding to Messages", `
**Messages from the operator appear in your terminal like this:**
\`\`\`
[Message from Tommy - Founder. id: human:default]: What's your status on task-123?
\`\`\`
**You MUST respond immediately using the CLI:**
${codeBlock([
"termlings message human:default \"Task-123 is 60% done. Found database issue. ETA 2 hours.\"",
])}
**Messages from teammates appear like this:**
\`\`\`
[Message from Alice - Developer. id: agent:developer]: task-456 is ready for review
\`\`\`
**Respond using the slug shown in the message:**
${codeBlock([
"termlings message agent:developer \"Got it, reviewing now. Will send feedback in 30min\"",
])}
**Always respond with:**
1. Acknowledgment
2. Current status or direct answer
3. Next step and ETA when relevant
4. Blockers and what you need if blocked
`),
"",
section("Best Practices", `
✅ DO:
- **ALWAYS use \`termlings message\` for EVERY response and update**
- Track work as tasks
- Add task notes frequently (every 15-30 min on long tasks)
- Message your manager (\`reports_to\`) with frequent updates
- Message teammates using \`agent:slug\` format
- Ask for help early when blocked
- Be extremely verbose about what you're doing and finding
- Keep CRM, CMS, social, or memory records updated when those apps are part of the work
❌ DON'T:
- Respond with plain text output — the operator cannot see it
- Work without a task
- Leave tasks in progress without notes for long periods
- Silently fail — communicate blockers immediately via your reporting chain
- Assume the operator knows what you're doing if you haven't reported it
- Work in silence — message after every major action
`),
"",
"Run `termlings org-chart` to see team hierarchy and roles.",
"",
"</TERMLINGS-SYSTEM-MESSAGE>",
].filter((part) => part.trim().length > 0)
return sections.join("\n")
}