@borgee/agents-host 0.2.35 → 0.2.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +23 -27
  2. package/dist/agents-host.d.ts +28 -5
  3. package/dist/agents-host.js +274 -236
  4. package/dist/chat/chat-control-plane.d.ts +3 -0
  5. package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
  6. package/dist/chat/sdk-chat-control-plane.js +6 -0
  7. package/dist/cli.js +1 -5
  8. package/dist/compatibility-gates.d.ts +4 -0
  9. package/dist/compatibility-gates.js +18 -1
  10. package/dist/context/claude-file-brief.d.ts +2 -0
  11. package/dist/context/claude-file-brief.js +83 -0
  12. package/dist/context/compaction.d.ts +20 -0
  13. package/dist/context/compaction.js +59 -0
  14. package/dist/context/injection.d.ts +44 -9
  15. package/dist/context/injection.js +360 -53
  16. package/dist/context/main-session-delegation.d.ts +1 -1
  17. package/dist/context/projection-strategy.d.ts +24 -0
  18. package/dist/context/projection-strategy.js +90 -0
  19. package/dist/context/prompt.d.ts +16 -1
  20. package/dist/context/prompt.js +475 -53
  21. package/dist/context/resolved-workspace.d.ts +2 -0
  22. package/dist/context/resolved-workspace.js +64 -0
  23. package/dist/context/skill-manual.d.ts +14 -0
  24. package/dist/context/skill-manual.js +21 -0
  25. package/dist/context/turn-preparation.d.ts +8 -2
  26. package/dist/context/turn-preparation.js +68 -17
  27. package/dist/gateway/localhost-gateway.js +18 -8
  28. package/dist/hosted-turn-content.d.ts +15 -0
  29. package/dist/hosted-turn-content.js +50 -0
  30. package/dist/managed-daemon.d.ts +3 -2
  31. package/dist/managed-daemon.js +198 -37
  32. package/dist/plugin-sdk.js +276 -359
  33. package/dist/plugin-sdk.js.map +4 -4
  34. package/dist/progress-to-activity.d.ts +16 -0
  35. package/dist/progress-to-activity.js +24 -0
  36. package/dist/projection-strategy-values.d.ts +4 -0
  37. package/dist/projection-strategy-values.js +28 -0
  38. package/dist/providers/acp-progress-collector.d.ts +44 -0
  39. package/dist/providers/acp-progress-collector.js +130 -0
  40. package/dist/providers/awaiting-user.d.ts +2 -3
  41. package/dist/providers/awaiting-user.js +5 -7
  42. package/dist/providers/claude/activity-metadata.d.ts +14 -0
  43. package/dist/providers/claude/activity-metadata.js +81 -0
  44. package/dist/providers/claude/adapter.d.ts +3 -1
  45. package/dist/providers/claude/adapter.js +10 -0
  46. package/dist/providers/claude/cli-client.d.ts +9 -1
  47. package/dist/providers/claude/cli-client.js +270 -126
  48. package/dist/providers/codex/adapter.d.ts +3 -1
  49. package/dist/providers/codex/adapter.js +10 -0
  50. package/dist/providers/codex/cli-client.d.ts +10 -2
  51. package/dist/providers/codex/cli-client.js +88 -104
  52. package/dist/providers/codex/project-doc.js +22 -37
  53. package/dist/providers/copilot/adapter.d.ts +3 -1
  54. package/dist/providers/copilot/adapter.js +10 -0
  55. package/dist/providers/copilot/cli-client.d.ts +9 -1
  56. package/dist/providers/copilot/cli-client.js +68 -85
  57. package/dist/providers/create-provider.d.ts +3 -1
  58. package/dist/providers/create-provider.js +36 -9
  59. package/dist/providers/provider-adapter.d.ts +35 -0
  60. package/dist/providers/provider-adapter.js +44 -1
  61. package/dist/state-paths.d.ts +10 -2
  62. package/dist/state-paths.js +25 -6
  63. package/dist/task-thread-resolution.d.ts +3 -2
  64. package/dist/types.d.ts +163 -8
  65. package/package.json +2 -2
  66. package/skills/borgee-agent/SKILL.md +127 -38
  67. package/skills/borgee-agent/references/errors.md +38 -0
  68. package/skills/borgee-agent/references/task-properties.md +33 -0
  69. package/skills/borgee-agent/scripts/borgee-agent.mjs +553 -0
  70. package/skills/borgee-agent/scripts/borgee-agent.py +547 -0
  71. package/dist/durable-cursor-store.d.ts +0 -5
  72. package/dist/durable-cursor-store.js +0 -7
  73. package/skills/borgee-agent/borgee-agent.mjs +0 -562
  74. package/skills/borgee-agent/borgee-agent.py +0 -469
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type ProviderKind = 'claude' | 'codex' | 'copilot';
2
+ export type ProjectionStrategy = 'session-brief' | 'turn-full' | 'message-only' | 'turn-thin' | 'file-brief';
2
3
  export interface ProviderCommandConfig {
3
4
  claudeCommand: string;
4
5
  claudeArgs: string[];
@@ -12,7 +13,9 @@ export interface ProviderCommandConfig {
12
13
  }
13
14
  export interface ProviderRuntimeConfig extends ProviderCommandConfig {
14
15
  provider: ProviderKind;
16
+ borgeeBaseUrl: string;
15
17
  stateRootDir: string;
18
+ agentApiKey: string;
16
19
  resolveStableAgentId?: () => string | undefined;
17
20
  }
18
21
  export interface HostedAgentConfig {
@@ -96,6 +99,7 @@ export interface ChannelMessageEvent {
96
99
  content?: string;
97
100
  body?: string;
98
101
  content_type?: string;
102
+ attachments?: HostedMessageAttachment[];
99
103
  created_at?: number;
100
104
  [key: string]: unknown;
101
105
  }
@@ -115,11 +119,32 @@ export interface ChannelHistoryEntry {
115
119
  authorId: string;
116
120
  body: string;
117
121
  contentType?: string;
122
+ attachments?: HostedMessageAttachment[];
118
123
  type?: string;
119
124
  replyToId?: string;
120
125
  createdAt: number;
121
126
  editedAt?: number;
122
127
  }
128
+ export interface HostedMessageAttachment {
129
+ url: string;
130
+ filename?: string;
131
+ contentType: string;
132
+ kind?: 'image' | 'file';
133
+ sizeBytes?: number;
134
+ }
135
+ export interface HostedTextContentPart {
136
+ type: 'text';
137
+ text: string;
138
+ }
139
+ export interface HostedImageContentPart {
140
+ type: 'image';
141
+ attachment: HostedMessageAttachment;
142
+ }
143
+ export interface HostedFileContentPart {
144
+ type: 'file';
145
+ attachment: HostedMessageAttachment;
146
+ }
147
+ export type HostedTurnContentPart = HostedTextContentPart | HostedImageContentPart | HostedFileContentPart;
123
148
  export interface ReadChannelHistoryInput {
124
149
  channelId: string;
125
150
  before?: number;
@@ -143,7 +168,7 @@ export interface CollaborationDraftSnapshot {
143
168
  }
144
169
  export type ProviderCollaborationTurnMode = 'ordinary' | 'silent-kickoff' | 'protocol-managed';
145
170
  export type CollaborationOutcomeResponseState = 'responded' | 'blocked' | 'superseded' | 'failed';
146
- export type CollaborationOutcomeDeliveryState = 'posted' | 'draft-finalized' | 'not-delivered' | 'delivery-failed';
171
+ export type CollaborationOutcomeDeliveryState = 'posted' | 'not-delivered' | 'delivery-failed';
147
172
  export type CollaborationOutcomeWakeState = 'waiting' | 'suppressed-agent' | 'resumed-human';
148
173
  export interface CollaborationOutcomeBlockedDetails {
149
174
  question: string;
@@ -187,6 +212,22 @@ export interface AttentionSnapshot {
187
212
  turnExecutionId?: string;
188
213
  claimContext?: AttentionClaimContext;
189
214
  }
215
+ export type ProviderCompactionStage = 'started' | 'completed' | 'failed';
216
+ /**
217
+ * Shared host-side projection only. This is the latest host-observed compaction
218
+ * lifecycle snapshot surfaced by a provider adapter; it is not provider- or
219
+ * model-authoritative truth about retained prompt content.
220
+ */
221
+ export interface CompactionSnapshot {
222
+ source: 'host-observed';
223
+ provider: ProviderKind;
224
+ stage: ProviderCompactionStage;
225
+ observedAt: number;
226
+ turnExecutionId?: string;
227
+ usedTokens?: number;
228
+ contextWindowTokens?: number;
229
+ failureReason?: string;
230
+ }
190
231
  export type TaskThreadCollaborationTurnRole = 'assignment' | 'continuation';
191
232
  export type TaskThreadMainResultContract = 'ordinary-final-reply-in-thread';
192
233
  export type TaskThreadAuxiliaryRouteContract = 'optional-auxiliary-send-or-escalation-only';
@@ -209,6 +250,7 @@ export interface TaskThreadCollaborationContract {
209
250
  export interface CollaborationCapabilityDeclaration {
210
251
  collaborationOutcome?: true;
211
252
  attentionSnapshot?: true;
253
+ compactionSnapshot?: true;
212
254
  taskThreadCollaborationContract?: true;
213
255
  missedCollaborationDiagnostic?: true;
214
256
  recoveryExplanation?: 'runtime-local-only';
@@ -234,11 +276,13 @@ export interface ProviderInput {
234
276
  channelId: string;
235
277
  incomingAuthorId: string;
236
278
  incomingContent: string;
279
+ incomingParts?: HostedTurnContentPart[];
237
280
  incomingEventKind?: string;
238
281
  incomingMessageType?: string;
239
282
  collaboration?: ProviderCollaborationContext;
240
283
  collaborationOutcome?: CollaborationOutcomeSnapshot;
241
284
  attentionSnapshot?: AttentionSnapshot;
285
+ compactionSnapshot?: CompactionSnapshot;
242
286
  taskThreadCollaborationContract?: TaskThreadCollaborationContract;
243
287
  collaborationCapabilities?: CollaborationCapabilityDeclaration;
244
288
  missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
@@ -285,17 +329,52 @@ export interface TaskAssignmentThreadContext {
285
329
  active: true;
286
330
  currentTaskId?: string;
287
331
  }
288
- export interface TaskWorkspaceContext {
289
- currentTaskId: string;
332
+ export interface ChannelResolvedWorkspaceContext {
333
+ authority: 'channel';
334
+ owningChannelId: string;
290
335
  rootPath: string;
291
336
  }
337
+ export interface TaskResolvedWorkspaceContext {
338
+ authority: 'task';
339
+ taskId: string;
340
+ rootPath: string;
341
+ }
342
+ export type ResolvedWorkspaceContext = ChannelResolvedWorkspaceContext | TaskResolvedWorkspaceContext;
343
+ export interface RuntimeSurface {
344
+ schemaVersion: 1;
345
+ turnMode: ProviderCollaborationTurnMode;
346
+ gateway: {
347
+ available: boolean;
348
+ readOnlyRoutesAuthorized: boolean;
349
+ inspectBeforeAnswer: boolean;
350
+ };
351
+ task: {
352
+ currentThread: 'parent-channel' | 'task-assignment-thread';
353
+ parentChannelTaskCollection: 'available' | 'parent-channel-only';
354
+ currentTaskShorthand: 'requires-task-id' | 'persisted-current-task' | 'fallback-current-task';
355
+ currentTaskIdPersisted: boolean;
356
+ currentTaskFallback: 'disallowed' | 'visible-parent-channel-thread-scan';
357
+ };
358
+ collaboration: {
359
+ participantLookup: 'unavailable' | 'available';
360
+ draftRead: 'unavailable' | 'available';
361
+ auxiliarySend: 'unavailable' | 'available';
362
+ };
363
+ response: {
364
+ mainVisibleReply: 'model-visible-reply' | 'host-visible-protocol-reply' | 'no-visible-reply';
365
+ auxiliaryMessageUsage: 'unavailable' | 'targeted-escalation-only';
366
+ };
367
+ }
292
368
  export interface PreparedPromptContext {
293
369
  channelContextPayloadPath?: string;
294
- gatewayAuthPath?: string;
370
+ claudeProjectedBriefPath?: string;
371
+ claudeProjectedBriefHash?: string;
372
+ gatewayCredentialPath?: string;
295
373
  collaborationTurnExecutionId?: string;
296
374
  collaborationTurnMode?: ProviderCollaborationTurnMode;
297
375
  collaborationOutcome?: CollaborationOutcomeSnapshot;
298
376
  attentionSnapshot?: AttentionSnapshot;
377
+ compactionSnapshot?: CompactionSnapshot;
299
378
  taskThreadCollaborationContract?: TaskThreadCollaborationContract;
300
379
  collaborationCapabilities?: CollaborationCapabilityDeclaration;
301
380
  missedCollaborationDiagnostic?: MissedCollaborationDiagnostic;
@@ -305,17 +384,25 @@ export interface PreparedPromptContext {
305
384
  skillRuntime?: SkillRuntimeBootstrapMetadata;
306
385
  localhostGateway?: LocalhostGatewayBootstrapMetadata;
307
386
  taskAssignmentContext?: TaskAssignmentThreadContext;
308
- taskWorkspace?: TaskWorkspaceContext;
387
+ resolvedWorkspace?: ResolvedWorkspaceContext;
388
+ runtimeSurface?: RuntimeSurface;
309
389
  }
310
390
  export interface PreparedProviderSessionRouting {
311
391
  key: string;
312
392
  persistence: 'persistent' | 'ephemeral';
313
393
  }
314
394
  export interface PreparedProviderTurnInput {
395
+ agentName?: string;
315
396
  channelId: string;
397
+ incomingContent: string;
398
+ incomingParts: HostedTurnContentPart[];
316
399
  incomingEventKind?: string;
317
400
  incomingMessageType?: string;
318
401
  prompt: string;
402
+ /** Claude-only session append content for ACP systemPrompt.append. */
403
+ claudeSessionPromptAppend?: string;
404
+ /** Shared projection-strategy selector used for provider prompt/session experiments. */
405
+ projectionStrategy?: ProjectionStrategy;
319
406
  promptContext?: PreparedPromptContext;
320
407
  providerSessionRouting?: PreparedProviderSessionRouting;
321
408
  }
@@ -362,7 +449,7 @@ export interface Task {
362
449
  id: string;
363
450
  channelId: string;
364
451
  guildId: string;
365
- seq?: number;
452
+ seq: number;
366
453
  title: string;
367
454
  description: string;
368
455
  status: string;
@@ -372,6 +459,10 @@ export interface Task {
372
459
  threadId: string | null;
373
460
  createdAt: number;
374
461
  updatedAt: number;
462
+ heartbeatIntervalMs: number;
463
+ heartbeatPrompt: string;
464
+ participants: string[];
465
+ properties: Record<string, string>;
375
466
  }
376
467
  export interface CreateTaskInput {
377
468
  channelId: string;
@@ -386,9 +477,73 @@ export interface UpdateTaskInput {
386
477
  title?: string;
387
478
  description?: string;
388
479
  }
389
- export interface ProviderProgressUpdate {
390
- text: string;
480
+ /** The protocol's ten tool kinds, treated as open: an unknown value is not an error. */
481
+ export type ProgressActivityKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'think' | 'fetch' | 'switch_mode' | 'other';
482
+ /** The protocol's four statuses, plus the one it lacks: stopped, waiting on a human. */
483
+ export type ProgressActivityStatus = 'pending' | 'in_progress' | 'waiting' | 'completed' | 'failed';
484
+ /**
485
+ * One entry in an agent's timeline of work. Identity is minted by the producer:
486
+ * a producer that can correlate later progress to an earlier event reuses the
487
+ * id and the entry is patched in place; one that cannot mints a fresh id and
488
+ * entries accumulate. An absent field means unchanged; `paths` replaces.
489
+ */
490
+ export interface ProgressActivity {
491
+ id: string;
492
+ /** The protocol's title. For a shell tool this is the raw command line. */
493
+ label?: string;
494
+ /** The provider's human-readable phrasing, when it offers one. */
495
+ description?: string;
496
+ kind?: ProgressActivityKind;
497
+ status?: ProgressActivityStatus;
498
+ /** Bounded one-line detail: the command, the file, the query. Never raw output. */
499
+ detail?: string;
500
+ /** Files this activity touched. Replaces the previous list. */
501
+ paths?: string[];
502
+ /** The activity that spawned this one. */
503
+ parentId?: string;
504
+ /**
505
+ * Whether this activity is a container for other work rather than work of its
506
+ * own. A reader nests under it, and cannot arrive at that by noticing it has
507
+ * children: a sub-agent's children can be reported before it is, and a
508
+ * sub-agent that has spawned nothing yet is still a sub-agent.
509
+ */
510
+ subagent?: boolean;
511
+ /** Why a terminal activity ended as it did. An open set upstream, so never an enum. */
512
+ reason?: string;
391
513
  }
514
+ /** Plan entries have no identity: the whole list is replaced on every update. */
515
+ export interface ProgressPlanItem {
516
+ label: string;
517
+ status: 'pending' | 'in_progress' | 'completed';
518
+ }
519
+ export interface ProviderCompactionProgress {
520
+ provider: ProviderKind;
521
+ stage: ProviderCompactionStage;
522
+ usedTokens?: number;
523
+ contextWindowTokens?: number;
524
+ failureReason?: string;
525
+ }
526
+ /**
527
+ * A plan, a tool call and a stream of text are three meanings with three
528
+ * cardinalities and three update rules, so they travel as three shapes rather
529
+ * than as one entry with a category field. Text is cumulative rather than a
530
+ * delta, so a consumer that misses one update recovers on the next.
531
+ */
532
+ export type ProviderProgressUpdate = {
533
+ type: 'text';
534
+ stream: 'answer' | 'reasoning';
535
+ text: string;
536
+ messageId?: string;
537
+ } | {
538
+ type: 'activity';
539
+ activity: ProgressActivity;
540
+ } | {
541
+ type: 'plan';
542
+ items: ProgressPlanItem[];
543
+ } | {
544
+ type: 'compaction';
545
+ compaction: ProviderCompactionProgress;
546
+ };
392
547
  export interface ProviderGenerateOptions {
393
548
  onProgress?: (update: ProviderProgressUpdate) => void;
394
549
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@borgee/agents-host",
3
- "version": "0.2.35",
3
+ "version": "0.2.56",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -35,7 +35,7 @@
35
35
  "tsx": "^4.20.5",
36
36
  "typescript": "^5.9.3",
37
37
  "vitest": "^4.1.5",
38
- "@borgee/plugin-sdk": "0.4.0"
38
+ "@borgee/plugin-sdk": "0.10.0"
39
39
  },
40
40
  "scripts": {
41
41
  "predev": "pnpm --filter @borgee/plugin-sdk build",
@@ -1,52 +1,141 @@
1
- # Borgee Agent Skill Bootstrap
1
+ ---
2
+ name: borgee-agent
3
+ description: Read the Borgee channel this turn is running in and act on its tasks — channel history, visible participants, task list/create/get/update, task properties, and short auxiliary mentions — through the packaged local CLI. Use when you need to know what was said in this channel, who is here, or what the current task is, and when you need to record task state or ping another participant.
4
+ ---
2
5
 
3
- This skill runtime surface stays read-only for final visible replies in this slice.
6
+ # Borgee channel agent
4
7
 
5
- Use one of the packaged local CLIs to inspect the current channel bootstrap payload. When `localhost-gateway` is enabled together with `context-injection` and `skill-runtime`, the same CLIs can also call the loopback gateway using the persisted context payload.
8
+ This turn is running as an agent inside a Borgee channel. The packaged CLI is the only way to see that channel and to act on its tasks: it talks to a loopback-only gateway that is already authorized for this channel and this turn. This file is the whole manual — the invocation, every command, what is reachable when, and the limits.
6
9
 
7
- - Node: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --print-bootstrap`
8
- - Python: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --print-bootstrap`
9
- - Node health: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --health`
10
- - Node bootstrap: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --read-bootstrap`
11
- - Node identity: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --get-me`
12
- - Node history: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --read-history --limit 20`
13
- - Node private draft snapshot: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --turn-execution-id turn-id --read-draft`
14
- - Node users: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --list-users`
15
- - Node auxiliary mention: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --turn-execution-id turn-id --send-mention user-id --body "Need review from <@user-id>"`
16
- - Node auxiliary reply: `node ./borgee-agent.mjs --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --turn-execution-id turn-id --send-message --body "Following up here" --reply-to message-id`
17
- - Python health: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --health`
18
- - Python bootstrap: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --read-bootstrap`
19
- - Python identity: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --get-me`
20
- - Python history: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --read-history --limit 20`
21
- - Python private draft snapshot: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --turn-execution-id turn-id --read-draft`
22
- - Python users: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --list-users`
23
- - Python auxiliary mention: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --turn-execution-id turn-id --send-mention user-id --body "Need review from <@user-id>"`
24
- - Python auxiliary reply: `python3 ./borgee-agent.py --context /absolute/path/to/context.json --auth-path /absolute/path/to/.localhost-gateway-auth.json --turn-execution-id turn-id --send-message --body "Following up here" --reply-to message-id`
10
+ ## Authorization
25
11
 
26
- The same CLIs also expose the task commands. Which ones apply depends on where the turn runs, which the injected `context.json` reports through `taskAssignmentContext`:
12
+ Every command below is already authorized on a turn whose prompt names a gateway credential file. Run it directly. Never ask the user for permission first, and never end a turn having asked to read instead of reading.
27
13
 
28
- - Parent channel (no `taskAssignmentContext.active`): `--create-task --title ...`, `--list-tasks`, `--get-task --task-id ...`, `--update-task --task-id ...`, `--read-task-history --task-id ...`, `--set-property <key>=<value> --task-id ...`, `--delete-property <key> --task-id ...`
29
- - Task-assignment thread (`taskAssignmentContext.active: true`): `--get-task`, `--update-task`, `--set-property` and `--delete-property` may omit `--task-id` and resolve the current thread task through the persisted `currentTaskId` or an agents-host local fallback; `--read-task-history --task-id ...` still works for that thread's own task, while `--create-task` and `--list-tasks` stay disabled and must be run from the parent channel
14
+ ## Session contract
30
15
 
31
- `--read-task-history` reads the messages inside a task's thread and always requires an explicit `--task-id`; inside that task's own thread `--read-history` already reads the same messages, so the turn prompt offers the command in the parent channel only. It accepts the same `--limit` / `--before` / `--after` window as `--read-history` and answers the same not-found error for a task outside the current channel as for a task that does not exist.
16
+ This session is the coordination surface for the work that belongs to this Borgee thread: read the channel, answer the human here, break the work down, and keep task state current here.
32
17
 
33
- ## Task properties
18
+ Delegate the heavy execution to workers rather than running everything inline in this session. Research, code edits, long builds and test runs, and broad repository sweeps belong in delegated workers so a newly arriving message can still be handled promptly here.
34
19
 
35
- A task property associates a task with something outside it. Use them to record what a reader would otherwise have to hunt for in the thread — the PR that implements the task, the issue it came from. Read them back with `--get-task`: every task response carries a `properties` object (`{}` when it has none).
20
+ Delegation changes who executes, not who owns. The work still belongs to this thread, and this session still reports the result here itself.
36
21
 
37
- - Set: `node ./borgee-agent.mjs --context ... --auth-path ... --set-property "link.pr=https://github.com/org/repo/pull/12"`
38
- - Delete: `node ./borgee-agent.mjs --context ... --auth-path ... --delete-property "link.pr"`
22
+ ## When to use it
39
23
 
40
- Pass `key=value` as ONE shell token so a value containing `=` survives intact. Each call writes exactly one key, which is what makes it safe to write a property while another agent is writing a different one on the same task — so never try to set several at once, just call again.
24
+ - You are asked what was said here, who is here, or what you are supposed to be working on: read it, do not guess.
25
+ - You have made progress worth recording on the task: a status change, or a property such as the pull request that implements it.
41
26
 
42
- Registered keys:
27
+ ## When not to use it
43
28
 
44
- - `link.pr` — the pull request implementing this task. Set it as soon as the PR exists, not at the end.
45
- - `link.issue` — the issue or ticket the task originates from.
46
- - `agent.session_id` — **do not write this**. agents-host records the provider session itself after each turn.
29
+ - For your final answer. The host posts your turn's reply itself; sending it again through this CLI double-posts it.
30
+ - To find the gateway credential file. The absolute path is in this turn's prompt; the CLI never searches for it, and a file found by searching is not trusted.
47
31
 
48
- An unregistered key is rejected; a value is a plain string capped at 8 KiB. Properties are references, not notes — anything that needs prose belongs in the task description or a thread message.
32
+ ## Invocation
49
33
 
50
- The private draft snapshot is read-only, collaboration-scoped, and separate from ordinary public channel messages.
51
- Auxiliary sends are only for short targeted escalation, reply-thread nudges, or mentions. They must not be used for the main final answer body, which still belongs to AgentsHost.
52
- Both CLIs are local-only, may only access the loopback gateway described above, and must not mutate files.
34
+ `scripts/borgee-agent.mjs` and `scripts/borgee-agent.py`, both in the `scripts/` directory beside this file, are the same tool: they accept the same grammar and print the same bytes. Neither is marked executable, so name the interpreter your host has and give the script its absolute path — this file's own directory plus `scripts/`:
35
+
36
+ ```
37
+ node <this file's directory>/scripts/borgee-agent.mjs --gateway <absolute path> <command> [arguments]
38
+ python3 <this file's directory>/scripts/borgee-agent.py --gateway <absolute path> <command> [arguments]
39
+ ```
40
+
41
+ Everything below writes that leading interpreter and script path as `borgee-agent`.
42
+
43
+ `--gateway` takes the absolute path this turn's prompt gives you. That one file is the whole handoff: which channel you are in, where the gateway listens, and the token that authorizes you. The token is rotated every turn, so read the path out of the current prompt rather than reusing one you remember.
44
+
45
+ ## Commands
46
+
47
+ | Command | What you get |
48
+ | --- | --- |
49
+ | `health` | Reachability. The only command that answers without the token, so it tells a dead gateway from a rotated one. |
50
+ | `bootstrap` | The channel context the host published for this turn: the channel id, whether collaboration is live, and inside a task thread the current task id. The one command whose answer you cannot predict. |
51
+ | `whoami` | This agent's own id and display name. |
52
+ | `history [--limit <n>] [--before <n>] [--after <n>]` | Recent messages in this channel. At most 20 per call — a larger `--limit` is silently reduced to 20, so page with `--before` rather than asking for more. `--before` and `--after` are `createdAt` epoch-millisecond cursors copied from a message you already hold, not message ids and not counts. |
53
+ | `users` | Every participant you share a channel, DM or thread with. That is a superset of this channel's members, not its roster. |
54
+ | `draft --turn-execution-id <id>` | This turn's host-private in-flight draft. It exists only once this turn has produced visible reply text, and answers `not_found` before that. |
55
+ | `send --body <text> --turn-execution-id <id> (--reply-to <message-id> \| --mention <user-id>)...` | Post a short auxiliary message. It must address someone. |
56
+ | `mention <user-id> --body <text> --turn-execution-id <id> [--reply-to <message-id>]` | Post a short auxiliary message addressed to one participant. |
57
+ | `task list` | Tasks in this channel. Parent channel only. |
58
+ | `task create --title <text> [--description <text>] [--assignee-id <user-id>]` | Create a task. Parent channel only. |
59
+ | `task get [<task-id>]` | One task with its properties. |
60
+ | `task update [<task-id>] [--status <open\|in_progress\|in_review\|done\|cancelled>] [--title <text>] [--description <text>] [--assignee-id <user-id>]` | Update the task. At least one field is required. |
61
+ | `task history <task-id> [--limit <n>] [--before <n>] [--after <n>]` | Messages in that task's thread. Same 20-per-call cap and the same `createdAt` cursors as `history`. |
62
+ | `task set-property [<task-id>] --key <key> --value <value>` | Set one task property. |
63
+ | `task delete-property [<task-id>] --key <key>` | Remove one task property. |
64
+
65
+ On `task get`, `task update`, `task set-property` and `task delete-property` the task id is an optional leading positional. Inside a task thread, omitting it addresses this thread's own task: the host's published binding names it, and where the host published none the gateway scans the parent channel's visible tasks for this thread instead — so pass the id explicitly when that scan cannot land on a single task. `task list` and `task create` are refused inside a task thread; they belong to the parent channel. In a parent channel every one of those ids is required, and omitting it answers `not_found`.
66
+
67
+ The keys `task set-property` and `task delete-property` accept are a closed set, and a value has a size limit: see `references/task-properties.md`. In this slice, `workspace.mode` is the formal task-workspace switch: `inherit` keeps a task thread on its channel workspace, while `isolated` moves that task thread onto its own task workspace.
68
+
69
+ ## What is available on a turn
70
+
71
+ `health`, `bootstrap`, `whoami`, `history` and the task commands are live on every turn whose prompt names a gateway credential file.
72
+
73
+ `users`, `draft`, `send` and `mention` are live only where collaboration is enabled; the prompt says when it is not, and the gateway answers `not_found` for all four. `draft`, `send` and `mention` additionally need the turn execution id the prompt carries: no command returns that id and the gateway credential file does not hold it.
74
+
75
+ A `not_found` from `draft` therefore has two readings — collaboration is off, or the host holds no draft for this turn yet — so take it as an answer about the draft, not as evidence that `users`, `send` and `mention` have gone.
76
+
77
+ Whether you are in a parent channel or inside a task assignment thread is in the prompt too, and it decides which task grammar above applies.
78
+
79
+ ## Inside a task thread
80
+
81
+ The assigned work belongs to that thread, and your ordinary final reply is the completion report.
82
+
83
+ - `task update --status in_progress` when you start.
84
+ - `task update --status in_review` when you finish.
85
+ - Never `send` or `mention` the completion report. Those are for intentional targeted escalation or cross-channel notification.
86
+
87
+ ## Talking to another participant
88
+
89
+ Asked to mention, ping, notify, or send a short note to someone visible: run `mention` yourself, then confirm what you sent. What notifies them is the visible `<@targetId>` token in the body — naming someone in prose alone does not reach them — and the CLI appends that token when your body leaves it out. If the user says "the other agent" and only one other agent is visible, resolve that id with `users` first.
90
+
91
+ Wanting to collaborate with another agent is the same act — send the short mention or reply-thread note yourself. There is no host-orchestrated protocol to ask for.
92
+
93
+ ## Errors
94
+
95
+ A failure prints one `error: …` line on stderr. Exit `2` is a wrong invocation: the request was never sent, so nothing changed.
96
+
97
+ `references/errors.md` maps every gateway error to its cause and its correction, and holds the exit-code contract.
98
+
99
+ ## A worked turn
100
+
101
+ You are running in a task thread, you have opened the pull request that implements the task, and you want the reviewer to know.
102
+
103
+ ```
104
+ $ borgee-agent --gateway /state/channels/channel-a/.borgee-agent-gateway.json task get
105
+ {
106
+ "id": "task-42",
107
+ "title": "Deliver the daily digest",
108
+ "status": "open",
109
+ "assigneeId": "agent-1",
110
+ "properties": {}
111
+ }
112
+
113
+ $ borgee-agent --gateway /state/channels/channel-a/.borgee-agent-gateway.json \
114
+ task set-property --key link.pr --value https://github.com/org/repo/pull/12
115
+ {
116
+ "id": "task-42",
117
+ "properties": { "link.pr": "https://github.com/org/repo/pull/12" }
118
+ }
119
+
120
+ $ borgee-agent --gateway /state/channels/channel-a/.borgee-agent-gateway.json \
121
+ task update --status in_progress
122
+ {
123
+ "id": "task-42",
124
+ "status": "in_progress"
125
+ }
126
+
127
+ $ borgee-agent --gateway /state/channels/channel-a/.borgee-agent-gateway.json \
128
+ mention user-7 --body "Digest PR is up for review" --turn-execution-id turn-9f3
129
+ {
130
+ "id": "message-311",
131
+ "body": "Digest PR is up for review <@user-7>"
132
+ }
133
+ ```
134
+
135
+ ## Constraints
136
+
137
+ - Local only. The CLI reaches the loopback gateway and nothing else.
138
+ - It does not read or write files anywhere except the one gateway credential file it is handed.
139
+ - Auxiliary sends are short notices — one line, at most twelve words counting the `<@id>` the CLI appends, addressed to someone or attached to a message. They are not a place for the answer.
140
+ - Exactly one auxiliary send per turn, whoever it addresses. A send the gateway rejects does not spend it.
141
+ - The draft is host-private. Read it to see what the host is about to post; never re-post it.
@@ -0,0 +1,38 @@
1
+ # Errors
2
+
3
+ ## Exit codes
4
+
5
+ | Code | Meaning |
6
+ | --- | --- |
7
+ | `0` | The command succeeded; its JSON is on stdout. |
8
+ | `1` | The gateway refused the request or could not be reached. The message carries the HTTP status and the gateway's JSON body. |
9
+ | `2` | A wrong invocation; the message names it. Nothing was sent. |
10
+
11
+ ## Gateway errors
12
+
13
+ | Status and error | Cause | Correction |
14
+ | --- | --- | --- |
15
+ | 401 `missing_or_invalid_token`, 401 `invalid_token` | The gateway credential file is from an earlier turn; its token has been rotated away. | Re-read the credential path this turn's prompt gives you. |
16
+ | 403 `channel_mismatch` | The task or channel is outside the channel this turn is bound to. | Only this channel is reachable. Work from `task list` in this channel. |
17
+ | 403 `permission_denied` | This agent is not allowed to perform that action in this channel. | Report it; do not retry the same call. |
18
+ | 403 `collaboration_not_enabled` | An auxiliary send on a turn where collaboration is off. | Do not send; put what you wanted to say in your final answer. |
19
+ | 403 `protocol_managed_turn` | An auxiliary send during a host-managed collaboration turn. | The host delivers this turn's reply. Do not send yourself. |
20
+ | 409 `stale_turn_execution_id` | The turn execution id belongs to an earlier turn, or the turn it names has already ended. | Use the turn execution id this turn's prompt carries; never one you remember. |
21
+ | 429 `collaboration_quota_exceeded` | This turn has already spent its one auxiliary send. | Say the rest in your final answer. |
22
+ | 429 `collaboration_target_cooldown` | The same reply target or mention set was addressed moments ago. | Do not repeat it. |
23
+ | 404 `not_found` on `task get` / `task update` / a property command with no task id | You are not inside a task thread, so there is no current task to resolve. | Pass the task id. |
24
+ | 404 `not_found` on a task command with a task id | The task does not exist, or belongs to another channel. | Check the id with `task list`. |
25
+ | 404 `not_found` on `users`, `send`, `mention` | Collaboration is not enabled for this turn, so those commands do not exist. | Do not use them; the turn prompt says when they are live. |
26
+ | 404 `not_found` on `draft` | Either collaboration is not enabled for this turn, or the host holds no draft for it yet — a draft exists only once the turn has produced visible reply text. | Not a verdict on the other collaboration commands: `users`, `send` and `mention` may well answer on this same turn. Carry on and read the draft later if you still need it. |
27
+ | 404 `bootstrap_unavailable` | The host has not published this turn's channel payload yet. | Retry the read once; if it persists, continue without it. |
28
+ | 400 `task_thread_collection_not_allowed` | `task list` or `task create` inside a task thread. | Those belong to the parent channel. |
29
+ | 400 `multiline_message_body_not_allowed`, `message_body_too_verbose` | An auxiliary send must be one line of at most 12 words, and the `<@id>` the CLI appends counts as one of them — each extra `--mention` costs another. | Shorten it to a single-line notice. |
30
+ | 400 `missing_reply_or_mentions` | A send that addresses nobody: no `--reply-to` and no visible mention. | Reply to a message, or mention the participant you mean. |
31
+ | 400 `invalid_status` | `--status` is not one of `open`, `in_progress`, `in_review`, `done`, `cancelled`. | Send one of those five values. |
32
+ | 400 `unknown_property_key` | The property key is not in the registry. | See `task-properties.md` for the registered keys. |
33
+ | 400 `property_value_too_long` | The property value is over 8 KiB. | Store a reference, not a document. |
34
+ | 400 `invalid_json`, `invalid_json_body`, `invalid_message_body`, `deprecated_mentions_not_allowed`, `bad_request`, `missing_turn_execution_id`, `empty_message_body`, `title_required`, `invalid_property_value`, `no_updates`; 403 `browser_origin_not_allowed`; 405 `method_not_allowed` | The request was not the shape, the header set or the invocation the route accepts. | The CLI builds these requests itself and refuses the bad invocations with exit `2` before sending, so reaching one of these means the call was wrapped or rewritten. Run the CLI directly. |
35
+ | 404 `property_not_found` | `task delete-property` for a key the task does not carry. | Read the task's `properties` first. |
36
+ | 413 `request_body_too_large` | An auxiliary send over the 512-byte request cap. | Shorten it. Non-ASCII characters cost several bytes each. |
37
+ | 502 `upstream_error` on a send | Most often a mention of someone who is not a member of this channel: the server refuses the mention, and the gateway has no case for that refusal, so it surfaces as a bare upstream failure. | Drop or correct the mention — `users` spans every channel you belong to, so a participant it lists need not be in this one. Do not repeat the same body. |
38
+ | 502 `upstream_error`, 500 `internal_error` | The gateway reached the server and the call failed there. | On a read, retry once. On a send, take the row above first; a repeat of the same body is not a fix. Report the failure in your answer rather than working around it. |
@@ -0,0 +1,33 @@
1
+ # Task properties
2
+
3
+ A task property associates a task with something that lives outside it, or with a closed task-level runtime choice. Use one to record what a reader would otherwise have to hunt for in the thread — the pull request that implements the task, the issue it came from, or the task workspace mode it should run with.
4
+
5
+ Read them back with `task get`: every task response carries a `properties` object, `{}` when the task has none.
6
+
7
+ ```
8
+ borgee-agent --gateway <path> task set-property <task-id> --key link.pr --value https://github.com/org/repo/pull/12
9
+ borgee-agent --gateway <path> task delete-property <task-id> --key link.pr
10
+ borgee-agent --gateway <path> task set-property <task-id> --key workspace.mode --value isolated
11
+ borgee-agent --gateway <path> task delete-property <task-id> --key workspace.mode
12
+ ```
13
+
14
+ The task id is omitted only inside that task's own thread, where the request resolves to the thread's task.
15
+
16
+ ## Registered keys
17
+
18
+ The key set is closed; writing an unregistered key is rejected with `unknown_property_key`.
19
+
20
+ | Key | What it holds |
21
+ | --- | --- |
22
+ | `link.pr` | The pull request that implements this task. Set it as soon as the PR exists, not at the end. |
23
+ | `link.issue` | The issue or ticket the task originates from. |
24
+ | `workspace.mode` | The task workspace binding for task threads. `inherit` keeps the task on its channel workspace. `isolated` switches it to its task-isolated workspace. Deleting the key returns to the default `inherit` behavior. |
25
+ | `agent.session_id` | Do not write this. It is registered, so a write is accepted and lands — overwriting the host's record of which provider session worked this task. The host writes it itself after each turn. |
26
+
27
+ ## One key per call
28
+
29
+ Each call writes exactly one key, and that is what makes it safe to write a property while another agent writes a different one on the same task: neither write can clobber the other's key.
30
+
31
+ ## Value
32
+
33
+ A value is a plain string of at most 8 KiB; a longer one is rejected with `property_value_too_long`. It is a reference — a URL, an identifier — never a document. Some registered keys are closed enums: `workspace.mode` accepts only `inherit` or `isolated`.