@mikode13/harness 11.1.0 → 11.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.
Files changed (69) hide show
  1. package/README.md +48 -10
  2. package/dist/agent/domain/agent.d.ts +17 -0
  3. package/dist/agent/domain/agent.js.map +1 -1
  4. package/dist/agent/domain/runContext.d.ts +5 -0
  5. package/dist/agent/domain/runContext.js.map +1 -1
  6. package/dist/engines/domain/model/compactor.d.ts +40 -0
  7. package/dist/engines/domain/model/compactor.js +5 -0
  8. package/dist/engines/domain/model/compactor.js.map +1 -0
  9. package/dist/engines/domain/model/llmAgent.d.ts +48 -2
  10. package/dist/engines/domain/model/llmAgent.js +351 -14
  11. package/dist/engines/domain/model/llmAgent.js.map +1 -1
  12. package/dist/engines/domain/model/recentCompaction.d.ts +39 -0
  13. package/dist/engines/domain/model/recentCompaction.js +119 -0
  14. package/dist/engines/domain/model/recentCompaction.js.map +1 -0
  15. package/dist/engines/domain/model/stepContext.js +3 -0
  16. package/dist/engines/domain/model/stepContext.js.map +1 -1
  17. package/dist/engines/domain/model/triagedCompaction.d.ts +27 -0
  18. package/dist/engines/domain/model/triagedCompaction.js +60 -0
  19. package/dist/engines/domain/model/triagedCompaction.js.map +1 -0
  20. package/dist/factory/infrastructure/agentLLMFactory.d.ts +40 -12
  21. package/dist/factory/infrastructure/agentLLMFactory.js +114 -18
  22. package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/llm/domain/contextTriage.d.ts +26 -0
  26. package/dist/llm/domain/contextTriage.js +3 -0
  27. package/dist/llm/domain/contextTriage.js.map +1 -0
  28. package/dist/llm/domain/llm.d.ts +18 -1
  29. package/dist/llm/domain/llm.js.map +1 -1
  30. package/dist/llm/domain/message.d.ts +18 -1
  31. package/dist/llm/domain/message.js +20 -0
  32. package/dist/llm/domain/message.js.map +1 -1
  33. package/dist/llm/infrastructure/claudeLLMClient.d.ts +4 -5
  34. package/dist/llm/infrastructure/claudeLLMClient.js +39 -13
  35. package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
  36. package/dist/llm/infrastructure/jevContextTriage.d.ts +18 -0
  37. package/dist/llm/infrastructure/jevContextTriage.js +92 -0
  38. package/dist/llm/infrastructure/jevContextTriage.js.map +1 -0
  39. package/dist/llm/infrastructure/openAILLMClient.d.ts +4 -5
  40. package/dist/llm/infrastructure/openAILLMClient.js +20 -9
  41. package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
  42. package/dist/tools/domain/preparedCall.d.ts +2 -1
  43. package/dist/tools/domain/preparedCall.js +7 -1
  44. package/dist/tools/domain/preparedCall.js.map +1 -1
  45. package/dist/tools/domain/readRegistry.d.ts +11 -0
  46. package/dist/tools/domain/readRegistry.js.map +1 -1
  47. package/dist/tools/infrastructure/agentAsTool.d.ts +6 -0
  48. package/dist/tools/infrastructure/agentAsTool.js +9 -0
  49. package/dist/tools/infrastructure/agentAsTool.js.map +1 -1
  50. package/dist/tools/infrastructure/commandTool.js +4 -1
  51. package/dist/tools/infrastructure/commandTool.js.map +1 -1
  52. package/dist/tools/infrastructure/exploreTool.js +2 -0
  53. package/dist/tools/infrastructure/exploreTool.js.map +1 -1
  54. package/dist/tools/infrastructure/fileTools.js +1 -0
  55. package/dist/tools/infrastructure/fileTools.js.map +1 -1
  56. package/dist/tools/infrastructure/removeWorktreeTool.js +7 -1
  57. package/dist/tools/infrastructure/removeWorktreeTool.js.map +1 -1
  58. package/dist/tools/infrastructure/subagents.d.ts +3 -1
  59. package/dist/tools/infrastructure/subagents.js +2 -1
  60. package/dist/tools/infrastructure/subagents.js.map +1 -1
  61. package/dist/tools/infrastructure/trackedEdits.js +1 -0
  62. package/dist/tools/infrastructure/trackedEdits.js.map +1 -1
  63. package/dist/tools/infrastructure/workspaceWrites.d.ts +13 -1
  64. package/dist/tools/infrastructure/workspaceWrites.js +18 -1
  65. package/dist/tools/infrastructure/workspaceWrites.js.map +1 -1
  66. package/dist/tools/infrastructure/writeCeiling.d.ts +16 -0
  67. package/dist/tools/infrastructure/writeCeiling.js +49 -0
  68. package/dist/tools/infrastructure/writeCeiling.js.map +1 -0
  69. package/package.json +2 -1
package/README.md CHANGED
@@ -558,7 +558,8 @@ has no stable shape; read it, but do not build on it. A callback that fails is l
558
558
  run stays as it ended.
559
559
 
560
560
  `createLLMOrchestrator()` takes the options of `createOrchestrator()` plus `workspace`,
561
- `maxSteps`, `compactAt`, `commands`, `formatter`, `summarizeUndone` and `onTranscript`, and returns a promise. Every role runs on the
561
+ `maxSteps`, `compactAt`, `commands`, `formatter`, `summarizeUndone`, `onTranscript`,
562
+ `removeWorktree` and `agents`, and returns a promise. Every role runs on the
562
563
  model APIs:
563
564
 
564
565
  - the planner and the reviewer read, with a `showChanges` tool that gives the current task's
@@ -572,7 +573,15 @@ model APIs:
572
573
  `subagent` events;
573
574
  - the executor gets its provider's file tools. `autoApprove` reaches only the executor;
574
575
  - each role gets the commands whose `roles` name it; a command names only the executor unless
575
- it says otherwise.
576
+ it says otherwise;
577
+ - each role gets the agents `agents` gives it, from `agentAsTool`, beside its own tools: the
578
+ planner and the reviewer keep their explorers. They only read, so their agents only read too;
579
+ the executor's may write within the `write` roots.
580
+
581
+ ```ts
582
+ // Each from agentAsTool, as in "Agents that hand work to other agents" below.
583
+ createLLMOrchestrator({ workspace, agents: { planner: [architect], executor: [commit] } });
584
+ ```
576
585
 
577
586
  Each role's run stops after a number of calls to the model: 80 for the executor and 65 for
578
587
  the planner and the reviewer by default. `maxSteps` changes it, as one number for every role
@@ -594,18 +603,39 @@ once, **Stop** ends it, and words of your own reach the model. A run stopped thi
594
603
  `ask` to ask, fails with `UnrecoverableError`, and is kept in the conversation with its changes,
595
604
  so your next prompt continues from it.
596
605
 
597
- No conversation is compacted unless you ask. `compactAt` is the number of input tokens at
598
- which the provider compacts it on its side, replacing the turns before that call with its own
599
- summary, which only its model reads back; the conversation keeps every original turn. The
600
- call that compacts can lose its cache, as it did on OpenAI, so set it well above what a task
601
- needs. Like
602
- `maxSteps`, it is one number for every role or per role, and `createLLMAgent` takes one. On
603
- Anthropic it must be at least 50,000:
606
+ Each agent's context is compacted once it passes its model's threshold: 450,000 tokens for
607
+ Claude Opus, Fable and Sonnet and GPT astra, sol and terra, and 125,000 for Claude Haiku and
608
+ GPT luna. The provider compacts it on its side, inside the call that passes the threshold,
609
+ replacing the turns before that call with its own summary, which only its model reads back; the
610
+ conversation keeps every original turn. The call that compacts can lose its cache, as it did on
611
+ OpenAI, so keep the threshold well above what a task needs. `compactAt` changes it: like
612
+ `maxSteps`, one number for every role or per role, and `createLLMAgent` takes one; on Anthropic
613
+ it must be at least 50,000. `false` turns compaction off:
604
614
 
605
615
  ```ts
606
616
  createLLMOrchestrator({ workspace, compactAt: { executor: 200_000 } });
607
617
  ```
608
618
 
619
+ `compaction: 'recent'` has the harness compact it instead, for the agent and for every role:
620
+ before the next call, the agent's own model summarizes the context, reading it from its
621
+ provider's cache, and only the run's prompt and its last 10 answers, each with its tool
622
+ results, are kept word for word after the summary, without their signed reasoning; earlier runs
623
+ are always summarized. The
624
+ conversation still keeps every turn; only what the model is sent starts at the summary.
625
+
626
+ `compaction: 'jev'` summarizes the same way, but TypeSafe's Jev chooses what is kept: the run's
627
+ prompt, the model's last answer with its tool results, and each earlier tool result Jev judges
628
+ the agent still needs word for word, with its call. Jev is sent the run's prompt, the model's
629
+ last answer and each of those tool results, so what the tools read leaves for TypeSafe. It reads
630
+ its key from `TYPESAFE_API_KEY`;
631
+ without one, or when Jev fails, the recent choice is made instead, and logged.
632
+
633
+ Either way, a context that no longer fits the model is compacted once and the call tried again;
634
+ in the recent mode the model summarizes a transcript of it. When it still does not fit, the run
635
+ fails with `UnrecoverableError`. A `compaction` event says who compacted, why, and how many
636
+ tokens of context it compacted: after the `step` of the call that compacted, or once the
637
+ harness's summary is written. The summary's tokens count in the run's.
638
+
609
639
  Without `workspace` it works on the current directory, read only. One run of the
610
640
  orchestrator is one run of the history, every round included.
611
641
 
@@ -641,6 +671,13 @@ The agent works inside the caller's run:
641
671
 
642
672
  - what it writes is the same run of the history, so one undo reverts both; it keeps its own
643
673
  tools' access policy, and edits only what it read itself;
674
+ - it never writes more than the agent that called it: only within the `write` roots of that
675
+ agent's `workspace`, and nowhere when that workspace only reads. A calling agent with no
676
+ `workspace`, such as a coordinator that only hands out tasks, sets no limit of its own. Its
677
+ tools that may write elsewhere, a command included, are refused before anyone is asked, and
678
+ the model is told why. Where your own `Tool` writes is unknown, so under an agent that only
679
+ reads, a call your tool judges `mutating` or `destructive` is refused too; under an agent
680
+ that writes, it writes wherever your tool does;
644
681
  - what it spends counts in the caller's `tokens`, a failed attempt included;
645
682
  - it asks and is approved through the run's `ask` and `approve`, which agents working at once
646
683
  may call before an earlier call has its answer; one person at one terminal needs them queued,
@@ -924,7 +961,8 @@ is told to decide and say what it assumed.
924
961
  With `--llm`, the agents can also [run commands](#running-the-projects-commands) you
925
962
  configure for the repository, in `~/.config/mikode-harness/config.json` (or under
926
963
  `$XDG_CONFIG_HOME`). The file maps each repository's real path to its `commands` and,
927
- optionally, its `maxSteps`, `compactAt` and [`formatter`](#formatting-what-the-agent-writes). It lives outside every repository, so neither a cloned repository
964
+ optionally, its `maxSteps`, its `compactAt`, or `false` to turn compaction off, its
965
+ `compaction` (`provider`, `recent` or `jev`, as `createLLMOrchestrator` takes them) and [`formatter`](#formatting-what-the-agent-writes). It lives outside every repository, so neither a cloned repository
928
966
  nor the agent decides what runs, or for how long:
929
967
 
930
968
  ```json
@@ -167,6 +167,23 @@ export type ProgressEvent = {
167
167
  context: StepContext;
168
168
  agent?: ProgressAgent;
169
169
  }
170
+ /**
171
+ * The context an agent on the model APIs sends was compacted: summarized from some point back,
172
+ * while its conversation keeps every part. `by` is who compacted it, the model's provider or
173
+ * the harness. `reason` is `threshold` when the context passed the agent's `compactAt`, and
174
+ * `maxContext` when it no longer fit the model, which compacts it even with compaction off.
175
+ * `contextTokens` is how many tokens of context were compacted, when known. The provider's
176
+ * compaction follows the `step` event of the call that compacted, whose tokens include the
177
+ * compaction's. The harness's is narrated once its summary is written, before the next call's
178
+ * `step`: the summary call has no `step` of its own, and its tokens are only in the run's.
179
+ */
180
+ | {
181
+ type: 'compaction';
182
+ by: 'provider' | 'harness';
183
+ reason: 'threshold' | 'maxContext';
184
+ contextTokens?: number;
185
+ agent?: ProgressAgent;
186
+ }
170
187
  /**
171
188
  * A workflow's stage asks its agent for an answer; `attempt` is 2 or more when the previous
172
189
  * answer was invalid. Stages run one at a time, so the events that follow, until the next
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/agent/domain/agent.ts"],"names":[],"mappings":"AA+EA,gDAAgD;AAChD,MAAM,CAAC,MAAM,cAAc,GAAa,GAAG,EAAE,CAAC,SAAS,CAAC","sourcesContent":["import type { Tokens } from '#src/shared/domain/tokens';\nimport type { Approver } from './approval.ts';\nimport type { Inbox } from './inbox.ts';\nimport type { Asker } from './question.ts';\nimport type { Session } from '#src/shared/domain/session';\n\n/**\n * How a run ended when it did not fail. Every token a run spends travels with its end: here\n * when it finishes, in `RecoverableError.tokens` or `UnrecoverableError.tokens` when it fails.\n */\nexport interface AgentResponse {\n\t/** The final text. Empty when the run produced none, for example a turn that only edited files. */\n\tresponse: string;\n\t/**\n\t * Missing when any call in the run completed without reporting usage, which is not the same\n\t * as zero: a partial sum would look complete.\n\t */\n\ttokens?: Tokens;\n\t/** Wall-clock seconds the consumer waited for the run, retries and every role included. */\n\tduration: number;\n\t/**\n\t * The run the workspace's history recorded, when this run wrote through the harness's write\n\t * tools. Absent for a run that wrote nothing, and from the Agent SDK engines, whose changes\n\t * the history does not record.\n\t */\n\trunId?: string;\n\t/**\n\t * The session the run belonged to: the one `RunOptions.session` gave, or the agent's own,\n\t * which the harness creates for each agent. Pass it to later runs, on this agent or one built\n\t * again, to keep them in the same session. Every agent the harness builds sets it.\n\t */\n\tsession?: Session;\n}\n\nexport type Callback = (item: ProgressEvent) => void;\n\n/**\n * What a run needs besides its prompt. InstructedAgent passes it on whole. OrchestratorAgent\n * copies its own enumerable fields, replaces `onProgress` per role and drops `inbox`;\n * RetryingAgent does the same for a run with `ask`, replacing only `ask`. A field must therefore\n * be an own enumerable one to reach every agent.\n */\nexport interface RunOptions {\n\t/**\n\t * Cancels the run, which then rejects with the signal's reason: an `AbortError`, or a\n\t * `TimeoutError` from `AbortSignal.timeout`. `isAbortError` recognizes both.\n\t */\n\tsignal: AbortSignal;\n\t/** Receives the run's live activity. Without it, the run is silent. */\n\tonProgress?: Callback | undefined;\n\t/**\n\t * Asked before a model-backed agent runs a `destructive` tool call. Without it, such a call\n\t * is denied. An agent built with `autoApprove` never asks, and the Agent SDK engines ignore\n\t * it: their own permission systems decide. Agents a delegation runs at once may call it before\n\t * an earlier call has its answer.\n\t */\n\tapprove?: Approver | undefined;\n\t/**\n\t * Answers what a model-backed agent asks the user, through its `askUser` tool, when a\n\t * decision is the user's to make. Without it the agent has no such tool. The Agent SDK\n\t * engines ignore it. Agents a delegation runs at once may call it before an earlier call has\n\t * its answer.\n\t */\n\task?: Asker | undefined;\n\t/**\n\t * Messages sent to the run while it works. A model-backed agent reads them once the step in\n\t * progress ends, as a message from the user, and wakes for them while it waits for agents in\n\t * the background. The Agent SDK engines, the orchestrators and the workflows ignore it, so\n\t * `Inbox.close` returns everything sent to them.\n\t */\n\tinbox?: Inbox | undefined;\n\t/**\n\t * The session this run belongs to, such as the API's, recorded with each run that writes.\n\t * Without it, the run belongs to the agent's own session, the same for each of its runs, which\n\t * `AgentResponse.session` returns. An agent another agent started belongs to the outer run's.\n\t */\n\tsession?: Session | undefined;\n}\n\n/** The `onProgress` of a run that gave none. */\nexport const ignoreProgress: Callback = () => undefined;\n\n/**\n * Live activity during a run, for narration only: the result travels in `AgentResponse`.\n * New event types can arrive in a minor release, so render the types you know and ignore\n * the rest instead of switching over them exhaustively with a `never` check.\n */\nexport type ProgressEvent =\n\t| { type: 'command'; command: string; exitCode?: number; agent?: ProgressAgent }\n\t| { type: 'reasoning'; message: string; agent?: ProgressAgent }\n\t| { type: 'search'; query: string; agent?: ProgressAgent }\n\t| {\n\t\t\ttype: 'fileChange';\n\t\t\tchanges: { path: string; kind: 'add' | 'update' | 'delete' }[];\n\t\t\tagent?: ProgressAgent;\n\t }\n\t| { type: 'mcpTool'; server: string; tool: string; status: string; agent?: ProgressAgent }\n\t| { type: 'agentMessage'; message: string; agent?: ProgressAgent }\n\t/** `id` pairs a call's end with its start when one step calls the same tool more than once. */\n\t| {\n\t\t\ttype: 'tool';\n\t\t\tid: string;\n\t\t\tname: string;\n\t\t\tagent?: ProgressAgent;\n\t\t\t/** A short, tool-owned description of the call. Narration only. */\n\t\t\tsummary?: string;\n\t\t\t/** The exit code of a configured command that ended with an error. Narration only. */\n\t\t\texitCode?: number;\n\t\t\t/**\n\t\t\t * On `error`, for an agent on the model APIs: what the model was told went wrong, such\n\t\t\t * as a patch that did not apply or a failed command's output. Narration only.\n\t\t\t */\n\t\t\terror?: string;\n\t\t\t/**\n\t\t\t * `in_progress` only once the call is about to run, after any approval. `denied` when\n\t\t\t * it never ran because no one allowed it, with no `in_progress` before it. `sent` when\n\t\t\t * it sent subagents to work in the background, such as an `explore` call: they go on\n\t\t\t * after it, as `subagent` events, until a `subagentEnded` for each.\n\t\t\t */\n\t\t\tstatus: 'in_progress' | 'completed' | 'error' | 'denied' | 'sent';\n\t\t\t/**\n\t\t\t * On `completed`, for a call that changed a file through the harness's write tools: the\n\t\t\t * change as a unified diff, for the person watching. Narration only; the run's whole\n\t\t\t * record is the history's `changes`.\n\t\t\t */\n\t\t\tdiff?: string;\n\t }\n\t| { type: 'todoList'; items: { text: string; completed: boolean }[]; agent?: ProgressAgent }\n\t| { type: 'turnStarted'; agent?: ProgressAgent }\n\t| { type: 'turnEnded'; agent?: ProgressAgent }\n\t/**\n\t * One billed call to the model by an agent on the model APIs, including one the model\n\t * stopped early or the client turned into an error: what the call sent and what it cost.\n\t * Narration only: the run's total is `AgentResponse.tokens`, or the failure's `tokens`.\n\t */\n\t| {\n\t\t\ttype: 'step';\n\t\t\t/** 1 for the first call of the run. */\n\t\t\tstep: number;\n\t\t\t/** Missing when the provider did not report this call's usage. */\n\t\t\ttokens?: Tokens;\n\t\t\tcontext: StepContext;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * A workflow's stage asks its agent for an answer; `attempt` is 2 or more when the previous\n\t * answer was invalid. Stages run one at a time, so the events that follow, until the next\n\t * workflow event, are this stage's agent's.\n\t */\n\t| { type: 'workflowStage'; stage: string; attempt: number; agent?: ProgressAgent }\n\t/** The transition a stage's valid answer selected, and the stage it leads to. */\n\t| {\n\t\t\ttype: 'workflowTransition';\n\t\t\tstage: string;\n\t\t\ttransition: 'go' | 'stop' | 'complete';\n\t\t\t/** Absent after `stop` or `complete`. */\n\t\t\tnextStage?: string;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * An event of a subagent that a tool call started: one of the explorers an `explore` call\n\t * sends, or the agent a tool from `agentAsTool` delegates to. `callId` is the `id` of that\n\t * call's `tool` events, and `subagent` numbers its subagents from 1, so the events of agents\n\t * running in parallel stay apart. Subagents work in the background: the call's own `tool`\n\t * event ends with `status: 'sent'`, and each subagent's events keep arriving after it, until\n\t * its `subagentEnded`, whether it answered, failed or was stopped. A subagent's own subagents\n\t * arrive as `subagent` events inside this one's `event`.\n\t */\n\t| {\n\t\t\ttype: 'subagent';\n\t\t\tcallId: string;\n\t\t\tsubagent: number;\n\t\t\t/** Which agent it is: `explorer`, or the name of the tool that delegates to it. */\n\t\t\tname: string;\n\t\t\t/** What the subagent was asked. */\n\t\t\ttask: string;\n\t\t\tevent: ProgressEvent;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * A subagent that a call with `status: 'sent'` sent has ended: it `answered`, it `failed` and\n\t * its answer says why, or it was `stopped` because the run ended. `callId`, `subagent` and\n\t * `name` are those of its `subagent` events. Narration only: its answer reaches the model that\n\t * sent it, not the consumer.\n\t */\n\t| {\n\t\t\ttype: 'subagentEnded';\n\t\t\tcallId: string;\n\t\t\tsubagent: number;\n\t\t\tname: string;\n\t\t\toutcome: 'answered' | 'failed' | 'stopped';\n\t\t\tagent?: ProgressAgent;\n\t };\n\n/**\n * What one call to the model sent, in characters by where it came from: an estimate of size\n * for finding what fills a context, not a token count.\n */\nexport interface StepContext {\n\t/** The system prompt. */\n\tinstructions: number;\n\t/** The tool definitions. */\n\ttools: number;\n\t/**\n\t * The text of the user's prompts and the model's answers, and the provider's summary of the\n\t * turns before it once it has compacted them: only what the call sent.\n\t */\n\tmessages: number;\n\t/** Reasoning the model produced earlier and is sent back: its provider's own signed or encrypted blocks. */\n\treasoning: number;\n\t/** The input of every tool call so far. */\n\ttoolCalls: number;\n\t/** Every tool result so far, by tool, largest first. */\n\ttoolResults: { tool: string; characters: number; calls: number }[];\n\t/** The largest single results, each with its call's summary when the tool gives one. */\n\tlargestResults: { tool: string; characters: number; summary?: string }[];\n}\n\n/** A role in the fixed planner → executor → reviewer workflow. */\nexport type ProgressAgent = 'planner' | 'executor' | 'reviewer';\n\n/**\n * The contract every engine (CodexAgent, ClaudeAgent, future providers) and every\n * decorator (RetryingAgent, OrchestratorAgent) is built against.\n *\n * Implementers MUST only ever reject with `RecoverableError` or `UnrecoverableError`\n * (see src/shared/domain/errors.ts) — never a raw SDK error, a plain `Error`, or anything else leaked\n * unclassified. Every consumer of `Agent` (RetryingAgent's retry decision,\n * OrchestratorAgent's failure handling) `instanceof`-checks against those two types to\n * decide what to do next; a leaked, unclassified error bypasses that decision\n * entirely — it gets retried when it shouldn't be, or crashes a run that a retry\n * would have recovered. Wrap every call into the underlying SDK so nothing escapes\n * unclassified, including failures the SDK itself doesn't model as a domain error\n * (network errors, malformed responses, etc.); `classifyProviderFailure` does this.\n *\n * Cancellation is the one exception: an `AbortError`, or the `TimeoutError` of\n * `AbortSignal.timeout`, must propagate unchanged, because\n * consumers check for it before either error type.\n */\nexport interface Agent {\n\trun(prompt: string, options: RunOptions): Promise<AgentResponse>;\n}\n"]}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/agent/domain/agent.ts"],"names":[],"mappings":"AA+EA,gDAAgD;AAChD,MAAM,CAAC,MAAM,cAAc,GAAa,GAAG,EAAE,CAAC,SAAS,CAAC","sourcesContent":["import type { Tokens } from '#src/shared/domain/tokens';\nimport type { Approver } from './approval.ts';\nimport type { Inbox } from './inbox.ts';\nimport type { Asker } from './question.ts';\nimport type { Session } from '#src/shared/domain/session';\n\n/**\n * How a run ended when it did not fail. Every token a run spends travels with its end: here\n * when it finishes, in `RecoverableError.tokens` or `UnrecoverableError.tokens` when it fails.\n */\nexport interface AgentResponse {\n\t/** The final text. Empty when the run produced none, for example a turn that only edited files. */\n\tresponse: string;\n\t/**\n\t * Missing when any call in the run completed without reporting usage, which is not the same\n\t * as zero: a partial sum would look complete.\n\t */\n\ttokens?: Tokens;\n\t/** Wall-clock seconds the consumer waited for the run, retries and every role included. */\n\tduration: number;\n\t/**\n\t * The run the workspace's history recorded, when this run wrote through the harness's write\n\t * tools. Absent for a run that wrote nothing, and from the Agent SDK engines, whose changes\n\t * the history does not record.\n\t */\n\trunId?: string;\n\t/**\n\t * The session the run belonged to: the one `RunOptions.session` gave, or the agent's own,\n\t * which the harness creates for each agent. Pass it to later runs, on this agent or one built\n\t * again, to keep them in the same session. Every agent the harness builds sets it.\n\t */\n\tsession?: Session;\n}\n\nexport type Callback = (item: ProgressEvent) => void;\n\n/**\n * What a run needs besides its prompt. InstructedAgent passes it on whole. OrchestratorAgent\n * copies its own enumerable fields, replaces `onProgress` per role and drops `inbox`;\n * RetryingAgent does the same for a run with `ask`, replacing only `ask`. A field must therefore\n * be an own enumerable one to reach every agent.\n */\nexport interface RunOptions {\n\t/**\n\t * Cancels the run, which then rejects with the signal's reason: an `AbortError`, or a\n\t * `TimeoutError` from `AbortSignal.timeout`. `isAbortError` recognizes both.\n\t */\n\tsignal: AbortSignal;\n\t/** Receives the run's live activity. Without it, the run is silent. */\n\tonProgress?: Callback | undefined;\n\t/**\n\t * Asked before a model-backed agent runs a `destructive` tool call. Without it, such a call\n\t * is denied. An agent built with `autoApprove` never asks, and the Agent SDK engines ignore\n\t * it: their own permission systems decide. Agents a delegation runs at once may call it before\n\t * an earlier call has its answer.\n\t */\n\tapprove?: Approver | undefined;\n\t/**\n\t * Answers what a model-backed agent asks the user, through its `askUser` tool, when a\n\t * decision is the user's to make. Without it the agent has no such tool. The Agent SDK\n\t * engines ignore it. Agents a delegation runs at once may call it before an earlier call has\n\t * its answer.\n\t */\n\task?: Asker | undefined;\n\t/**\n\t * Messages sent to the run while it works. A model-backed agent reads them once the step in\n\t * progress ends, as a message from the user, and wakes for them while it waits for agents in\n\t * the background. The Agent SDK engines, the orchestrators and the workflows ignore it, so\n\t * `Inbox.close` returns everything sent to them.\n\t */\n\tinbox?: Inbox | undefined;\n\t/**\n\t * The session this run belongs to, such as the API's, recorded with each run that writes.\n\t * Without it, the run belongs to the agent's own session, the same for each of its runs, which\n\t * `AgentResponse.session` returns. An agent another agent started belongs to the outer run's.\n\t */\n\tsession?: Session | undefined;\n}\n\n/** The `onProgress` of a run that gave none. */\nexport const ignoreProgress: Callback = () => undefined;\n\n/**\n * Live activity during a run, for narration only: the result travels in `AgentResponse`.\n * New event types can arrive in a minor release, so render the types you know and ignore\n * the rest instead of switching over them exhaustively with a `never` check.\n */\nexport type ProgressEvent =\n\t| { type: 'command'; command: string; exitCode?: number; agent?: ProgressAgent }\n\t| { type: 'reasoning'; message: string; agent?: ProgressAgent }\n\t| { type: 'search'; query: string; agent?: ProgressAgent }\n\t| {\n\t\t\ttype: 'fileChange';\n\t\t\tchanges: { path: string; kind: 'add' | 'update' | 'delete' }[];\n\t\t\tagent?: ProgressAgent;\n\t }\n\t| { type: 'mcpTool'; server: string; tool: string; status: string; agent?: ProgressAgent }\n\t| { type: 'agentMessage'; message: string; agent?: ProgressAgent }\n\t/** `id` pairs a call's end with its start when one step calls the same tool more than once. */\n\t| {\n\t\t\ttype: 'tool';\n\t\t\tid: string;\n\t\t\tname: string;\n\t\t\tagent?: ProgressAgent;\n\t\t\t/** A short, tool-owned description of the call. Narration only. */\n\t\t\tsummary?: string;\n\t\t\t/** The exit code of a configured command that ended with an error. Narration only. */\n\t\t\texitCode?: number;\n\t\t\t/**\n\t\t\t * On `error`, for an agent on the model APIs: what the model was told went wrong, such\n\t\t\t * as a patch that did not apply or a failed command's output. Narration only.\n\t\t\t */\n\t\t\terror?: string;\n\t\t\t/**\n\t\t\t * `in_progress` only once the call is about to run, after any approval. `denied` when\n\t\t\t * it never ran because no one allowed it, with no `in_progress` before it. `sent` when\n\t\t\t * it sent subagents to work in the background, such as an `explore` call: they go on\n\t\t\t * after it, as `subagent` events, until a `subagentEnded` for each.\n\t\t\t */\n\t\t\tstatus: 'in_progress' | 'completed' | 'error' | 'denied' | 'sent';\n\t\t\t/**\n\t\t\t * On `completed`, for a call that changed a file through the harness's write tools: the\n\t\t\t * change as a unified diff, for the person watching. Narration only; the run's whole\n\t\t\t * record is the history's `changes`.\n\t\t\t */\n\t\t\tdiff?: string;\n\t }\n\t| { type: 'todoList'; items: { text: string; completed: boolean }[]; agent?: ProgressAgent }\n\t| { type: 'turnStarted'; agent?: ProgressAgent }\n\t| { type: 'turnEnded'; agent?: ProgressAgent }\n\t/**\n\t * One billed call to the model by an agent on the model APIs, including one the model\n\t * stopped early or the client turned into an error: what the call sent and what it cost.\n\t * Narration only: the run's total is `AgentResponse.tokens`, or the failure's `tokens`.\n\t */\n\t| {\n\t\t\ttype: 'step';\n\t\t\t/** 1 for the first call of the run. */\n\t\t\tstep: number;\n\t\t\t/** Missing when the provider did not report this call's usage. */\n\t\t\ttokens?: Tokens;\n\t\t\tcontext: StepContext;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * The context an agent on the model APIs sends was compacted: summarized from some point back,\n\t * while its conversation keeps every part. `by` is who compacted it, the model's provider or\n\t * the harness. `reason` is `threshold` when the context passed the agent's `compactAt`, and\n\t * `maxContext` when it no longer fit the model, which compacts it even with compaction off.\n\t * `contextTokens` is how many tokens of context were compacted, when known. The provider's\n\t * compaction follows the `step` event of the call that compacted, whose tokens include the\n\t * compaction's. The harness's is narrated once its summary is written, before the next call's\n\t * `step`: the summary call has no `step` of its own, and its tokens are only in the run's.\n\t */\n\t| {\n\t\t\ttype: 'compaction';\n\t\t\tby: 'provider' | 'harness';\n\t\t\treason: 'threshold' | 'maxContext';\n\t\t\tcontextTokens?: number;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * A workflow's stage asks its agent for an answer; `attempt` is 2 or more when the previous\n\t * answer was invalid. Stages run one at a time, so the events that follow, until the next\n\t * workflow event, are this stage's agent's.\n\t */\n\t| { type: 'workflowStage'; stage: string; attempt: number; agent?: ProgressAgent }\n\t/** The transition a stage's valid answer selected, and the stage it leads to. */\n\t| {\n\t\t\ttype: 'workflowTransition';\n\t\t\tstage: string;\n\t\t\ttransition: 'go' | 'stop' | 'complete';\n\t\t\t/** Absent after `stop` or `complete`. */\n\t\t\tnextStage?: string;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * An event of a subagent that a tool call started: one of the explorers an `explore` call\n\t * sends, or the agent a tool from `agentAsTool` delegates to. `callId` is the `id` of that\n\t * call's `tool` events, and `subagent` numbers its subagents from 1, so the events of agents\n\t * running in parallel stay apart. Subagents work in the background: the call's own `tool`\n\t * event ends with `status: 'sent'`, and each subagent's events keep arriving after it, until\n\t * its `subagentEnded`, whether it answered, failed or was stopped. A subagent's own subagents\n\t * arrive as `subagent` events inside this one's `event`.\n\t */\n\t| {\n\t\t\ttype: 'subagent';\n\t\t\tcallId: string;\n\t\t\tsubagent: number;\n\t\t\t/** Which agent it is: `explorer`, or the name of the tool that delegates to it. */\n\t\t\tname: string;\n\t\t\t/** What the subagent was asked. */\n\t\t\ttask: string;\n\t\t\tevent: ProgressEvent;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * A subagent that a call with `status: 'sent'` sent has ended: it `answered`, it `failed` and\n\t * its answer says why, or it was `stopped` because the run ended. `callId`, `subagent` and\n\t * `name` are those of its `subagent` events. Narration only: its answer reaches the model that\n\t * sent it, not the consumer.\n\t */\n\t| {\n\t\t\ttype: 'subagentEnded';\n\t\t\tcallId: string;\n\t\t\tsubagent: number;\n\t\t\tname: string;\n\t\t\toutcome: 'answered' | 'failed' | 'stopped';\n\t\t\tagent?: ProgressAgent;\n\t };\n\n/**\n * What one call to the model sent, in characters by where it came from: an estimate of size\n * for finding what fills a context, not a token count.\n */\nexport interface StepContext {\n\t/** The system prompt. */\n\tinstructions: number;\n\t/** The tool definitions. */\n\ttools: number;\n\t/**\n\t * The text of the user's prompts and the model's answers, and the provider's summary of the\n\t * turns before it once it has compacted them: only what the call sent.\n\t */\n\tmessages: number;\n\t/** Reasoning the model produced earlier and is sent back: its provider's own signed or encrypted blocks. */\n\treasoning: number;\n\t/** The input of every tool call so far. */\n\ttoolCalls: number;\n\t/** Every tool result so far, by tool, largest first. */\n\ttoolResults: { tool: string; characters: number; calls: number }[];\n\t/** The largest single results, each with its call's summary when the tool gives one. */\n\tlargestResults: { tool: string; characters: number; summary?: string }[];\n}\n\n/** A role in the fixed planner → executor → reviewer workflow. */\nexport type ProgressAgent = 'planner' | 'executor' | 'reviewer';\n\n/**\n * The contract every engine (CodexAgent, ClaudeAgent, future providers) and every\n * decorator (RetryingAgent, OrchestratorAgent) is built against.\n *\n * Implementers MUST only ever reject with `RecoverableError` or `UnrecoverableError`\n * (see src/shared/domain/errors.ts) — never a raw SDK error, a plain `Error`, or anything else leaked\n * unclassified. Every consumer of `Agent` (RetryingAgent's retry decision,\n * OrchestratorAgent's failure handling) `instanceof`-checks against those two types to\n * decide what to do next; a leaked, unclassified error bypasses that decision\n * entirely — it gets retried when it shouldn't be, or crashes a run that a retry\n * would have recovered. Wrap every call into the underlying SDK so nothing escapes\n * unclassified, including failures the SDK itself doesn't model as a domain error\n * (network errors, malformed responses, etc.); `classifyProviderFailure` does this.\n *\n * Cancellation is the one exception: an `AbortError`, or the `TimeoutError` of\n * `AbortSignal.timeout`, must propagate unchanged, because\n * consumers check for it before either error type.\n */\nexport interface Agent {\n\trun(prompt: string, options: RunOptions): Promise<AgentResponse>;\n}\n"]}
@@ -45,6 +45,11 @@ export interface Delegation {
45
45
  /** Which of the agents that call started, from 1. */
46
46
  readonly subagent: number;
47
47
  readonly depth: number;
48
+ /**
49
+ * The folders it may write in, as real paths, so it never writes more than the agents that
50
+ * delegated to it: empty when it may write nowhere, absent when none set a limit.
51
+ */
52
+ readonly writeCeiling?: readonly string[];
48
53
  }
49
54
  /** The call that started the agent these options reach, if another agent's tool did. */
50
55
  export declare function delegationOf(options: RunOptions): Delegation | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"runContext.js","sourceRoot":"","sources":["../../../src/agent/domain/runContext.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,MAAM,OAAO,UAAU;IACL,SAAS,GAAuC,EAAE,CAAC;IAEpE,gFAAgF;IACvE,OAAO,CAAU;IAE1B;;;OAGG;IACM,UAAU,GAAuB,EAAE,CAAC;IAE7C,YAAY,OAAgB;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,yDAAyD;IACzD,QAAQ,CAAC,QAAwC;QAChD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW;QACvB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC;gBACJ,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACF,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;CACD;AAED,MAAM,aAAa,GAAG,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAM1D,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,OAAmB;IAC/C,OAAQ,OAAgC,CAAC,aAAa,CAAC,CAAC;AACzD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,OAAmB,EAAE,OAAmB;IACtE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,OAAO,EAA0B,CAAC;AACzE,CAAC;AAgBD,MAAM,aAAa,GAAG,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAM1D,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,OAAmB;IAC/C,OAAQ,OAA+B,CAAC,aAAa,CAAC,CAAC;AACxD,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,cAAc,CAAC,OAAmB,EAAE,UAAsB;IACzE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,UAAU,EAAyB,CAAC;AAC3E,CAAC","sourcesContent":["import type { RunOptions } from './agent.ts';\nimport type { Session } from '#src/shared/domain/session';\n\nexport type RunEnd = 'completed' | 'failed' | 'cancelled';\n\n/**\n * What one top-level run shares with every agent and tool inside it, across all its roles,\n * rounds and retries, such as the record of what it wrote. Whatever a tool starts for the run\n * registers how to end it, and the agent that created the context ends it with the run.\n *\n * Internal: it travels in `RunOptions` under a symbol, so decorators, which pass the options on\n * whole, carry it without knowing it exists, and no consumer can see or set it.\n */\nexport class RunContext {\n\tprivate readonly finishers: ((end: RunEnd) => Promise<void>)[] = [];\n\n\t/** The session the run belongs to, which every agent inside it answers with. */\n\treadonly session: Session;\n\n\t/**\n\t * The run the workspace's history recorded for this run, once it first wrote. The object is\n\t * shared, not copied: a turn recorded before the first write still learns its run.\n\t */\n\treadonly historyRun: { runId?: string } = {};\n\n\tconstructor(session: Session) {\n\t\tthis.session = session;\n\t}\n\n\t/** Ends something the run started, when the run ends. */\n\tonFinish(finisher: (end: RunEnd) => Promise<void>): void {\n\t\tthis.finishers.push(finisher);\n\t}\n\n\t/**\n\t * Ends everything registered, all of it even when one fails, and then throws the first\n\t * failure.\n\t */\n\tasync finish(end: RunEnd): Promise<void> {\n\t\tconst failures: unknown[] = [];\n\t\tfor (const finisher of this.finishers.splice(0)) {\n\t\t\ttry {\n\t\t\t\tawait finisher(end);\n\t\t\t} catch (error) {\n\t\t\t\tfailures.push(error);\n\t\t\t}\n\t\t}\n\t\tif (failures.length > 0) throw failures[0];\n\t}\n}\n\nconst runContextKey = Symbol('mikode-harness.runContext');\n\ninterface ContextualRunOptions extends RunOptions {\n\t[runContextKey]?: RunContext;\n}\n\n/** The context of the run these options belong to, if an outer agent created one. */\nexport function runContextOf(options: RunOptions): RunContext | undefined {\n\treturn (options as ContextualRunOptions)[runContextKey];\n}\n\n/** The same options, carrying `context` for every agent the run reaches. */\nexport function withRunContext(options: RunOptions, context: RunContext): RunOptions {\n\treturn { ...options, [runContextKey]: context } as ContextualRunOptions;\n}\n\n/**\n * Where an agent another agent's tool started runs: the call that started it, and how many\n * delegations deep it is, 1 for an agent the top-level agent started.\n */\nexport interface Delegation {\n\t/** The name of the tool that started it, such as `explore` or a specialist's. */\n\treadonly tool: string;\n\t/** The `id` of that call's `tool` events. */\n\treadonly callId: string;\n\t/** Which of the agents that call started, from 1. */\n\treadonly subagent: number;\n\treadonly depth: number;\n}\n\nconst delegationKey = Symbol('mikode-harness.delegation');\n\ninterface DelegatedRunOptions extends RunOptions {\n\t[delegationKey]?: Delegation;\n}\n\n/** The call that started the agent these options reach, if another agent's tool did. */\nexport function delegationOf(options: RunOptions): Delegation | undefined {\n\treturn (options as DelegatedRunOptions)[delegationKey];\n}\n\n/** The same options, for an agent `delegation` started. */\nexport function withDelegation(options: RunOptions, delegation: Delegation): RunOptions {\n\treturn { ...options, [delegationKey]: delegation } as DelegatedRunOptions;\n}\n"]}
1
+ {"version":3,"file":"runContext.js","sourceRoot":"","sources":["../../../src/agent/domain/runContext.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,MAAM,OAAO,UAAU;IACL,SAAS,GAAuC,EAAE,CAAC;IAEpE,gFAAgF;IACvE,OAAO,CAAU;IAE1B;;;OAGG;IACM,UAAU,GAAuB,EAAE,CAAC;IAE7C,YAAY,OAAgB;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,yDAAyD;IACzD,QAAQ,CAAC,QAAwC;QAChD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW;QACvB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC;gBACJ,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACF,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;CACD;AAED,MAAM,aAAa,GAAG,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAM1D,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,OAAmB;IAC/C,OAAQ,OAAgC,CAAC,aAAa,CAAC,CAAC;AACzD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,OAAmB,EAAE,OAAmB;IACtE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,OAAO,EAA0B,CAAC;AACzE,CAAC;AAqBD,MAAM,aAAa,GAAG,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAM1D,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,OAAmB;IAC/C,OAAQ,OAA+B,CAAC,aAAa,CAAC,CAAC;AACxD,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,cAAc,CAAC,OAAmB,EAAE,UAAsB;IACzE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,UAAU,EAAyB,CAAC;AAC3E,CAAC","sourcesContent":["import type { RunOptions } from './agent.ts';\nimport type { Session } from '#src/shared/domain/session';\n\nexport type RunEnd = 'completed' | 'failed' | 'cancelled';\n\n/**\n * What one top-level run shares with every agent and tool inside it, across all its roles,\n * rounds and retries, such as the record of what it wrote. Whatever a tool starts for the run\n * registers how to end it, and the agent that created the context ends it with the run.\n *\n * Internal: it travels in `RunOptions` under a symbol, so decorators, which pass the options on\n * whole, carry it without knowing it exists, and no consumer can see or set it.\n */\nexport class RunContext {\n\tprivate readonly finishers: ((end: RunEnd) => Promise<void>)[] = [];\n\n\t/** The session the run belongs to, which every agent inside it answers with. */\n\treadonly session: Session;\n\n\t/**\n\t * The run the workspace's history recorded for this run, once it first wrote. The object is\n\t * shared, not copied: a turn recorded before the first write still learns its run.\n\t */\n\treadonly historyRun: { runId?: string } = {};\n\n\tconstructor(session: Session) {\n\t\tthis.session = session;\n\t}\n\n\t/** Ends something the run started, when the run ends. */\n\tonFinish(finisher: (end: RunEnd) => Promise<void>): void {\n\t\tthis.finishers.push(finisher);\n\t}\n\n\t/**\n\t * Ends everything registered, all of it even when one fails, and then throws the first\n\t * failure.\n\t */\n\tasync finish(end: RunEnd): Promise<void> {\n\t\tconst failures: unknown[] = [];\n\t\tfor (const finisher of this.finishers.splice(0)) {\n\t\t\ttry {\n\t\t\t\tawait finisher(end);\n\t\t\t} catch (error) {\n\t\t\t\tfailures.push(error);\n\t\t\t}\n\t\t}\n\t\tif (failures.length > 0) throw failures[0];\n\t}\n}\n\nconst runContextKey = Symbol('mikode-harness.runContext');\n\ninterface ContextualRunOptions extends RunOptions {\n\t[runContextKey]?: RunContext;\n}\n\n/** The context of the run these options belong to, if an outer agent created one. */\nexport function runContextOf(options: RunOptions): RunContext | undefined {\n\treturn (options as ContextualRunOptions)[runContextKey];\n}\n\n/** The same options, carrying `context` for every agent the run reaches. */\nexport function withRunContext(options: RunOptions, context: RunContext): RunOptions {\n\treturn { ...options, [runContextKey]: context } as ContextualRunOptions;\n}\n\n/**\n * Where an agent another agent's tool started runs: the call that started it, and how many\n * delegations deep it is, 1 for an agent the top-level agent started.\n */\nexport interface Delegation {\n\t/** The name of the tool that started it, such as `explore` or a specialist's. */\n\treadonly tool: string;\n\t/** The `id` of that call's `tool` events. */\n\treadonly callId: string;\n\t/** Which of the agents that call started, from 1. */\n\treadonly subagent: number;\n\treadonly depth: number;\n\t/**\n\t * The folders it may write in, as real paths, so it never writes more than the agents that\n\t * delegated to it: empty when it may write nowhere, absent when none set a limit.\n\t */\n\treadonly writeCeiling?: readonly string[];\n}\n\nconst delegationKey = Symbol('mikode-harness.delegation');\n\ninterface DelegatedRunOptions extends RunOptions {\n\t[delegationKey]?: Delegation;\n}\n\n/** The call that started the agent these options reach, if another agent's tool did. */\nexport function delegationOf(options: RunOptions): Delegation | undefined {\n\treturn (options as DelegatedRunOptions)[delegationKey];\n}\n\n/** The same options, for an agent `delegation` started. */\nexport function withDelegation(options: RunOptions, delegation: Delegation): RunOptions {\n\treturn { ...options, [delegationKey]: delegation } as DelegatedRunOptions;\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import type { ContextTriage } from '#src/llm/domain/contextTriage';
2
+ /**
3
+ * How an `LLMAgent` keeps its context within the model: the conversation always keeps every part,
4
+ * and only the context sent to the model is compacted. Internal: the harness's compactors are the
5
+ * only ones.
6
+ *
7
+ * The provider's compactor hands its threshold to the client, and the provider compacts inside
8
+ * the call that passes it, answering with its own summary first.
9
+ */
10
+ export interface ProviderCompactor {
11
+ readonly by: 'provider';
12
+ /**
13
+ * The tokens of context at which the provider compacts it, or `undefined` when compaction is
14
+ * off. A context that no longer fits the model is compacted once all the same.
15
+ */
16
+ readonly at: number | undefined;
17
+ }
18
+ /**
19
+ * The harness's compactor: once the context passes `at`, before the next call, the agent's own
20
+ * model summarizes it, and what is kept follows the summary word for word. Without a `triage`,
21
+ * the recent compactor keeps the run's prompt and the run's last `keptSteps` answers of the
22
+ * model, each with its tool results.
23
+ */
24
+ export interface RecentCompactor {
25
+ readonly by: 'harness';
26
+ /** As `ProviderCompactor.at`. */
27
+ readonly at: number | undefined;
28
+ readonly keptSteps: number;
29
+ /**
30
+ * Jev's: it keeps the run's prompt, the model's last answer with its tool results, and each
31
+ * earlier tool result the triage scores `keptScore` or more, with its call. When the triage
32
+ * fails, the recent compactor chooses instead.
33
+ */
34
+ readonly triage?: ContextTriage;
35
+ }
36
+ export type Compactor = ProviderCompactor | RecentCompactor;
37
+ /** The provider's compactor, off: only a context that no longer fits is compacted. */
38
+ export declare const compactionOff: Compactor;
39
+ /** How many answers of the model, with their tool results, the recent compactor keeps. */
40
+ export declare const recentKeptSteps = 10;
@@ -0,0 +1,5 @@
1
+ /** The provider's compactor, off: only a context that no longer fits is compacted. */
2
+ export const compactionOff = { by: 'provider', at: undefined };
3
+ /** How many answers of the model, with their tool results, the recent compactor keeps. */
4
+ export const recentKeptSteps = 10;
5
+ //# sourceMappingURL=compactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compactor.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/compactor.ts"],"names":[],"mappings":"AAwCA,sFAAsF;AACtF,MAAM,CAAC,MAAM,aAAa,GAAc,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;AAE1E,0FAA0F;AAC1F,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC","sourcesContent":["import type { ContextTriage } from '#src/llm/domain/contextTriage';\n\n/**\n * How an `LLMAgent` keeps its context within the model: the conversation always keeps every part,\n * and only the context sent to the model is compacted. Internal: the harness's compactors are the\n * only ones.\n *\n * The provider's compactor hands its threshold to the client, and the provider compacts inside\n * the call that passes it, answering with its own summary first.\n */\nexport interface ProviderCompactor {\n\treadonly by: 'provider';\n\t/**\n\t * The tokens of context at which the provider compacts it, or `undefined` when compaction is\n\t * off. A context that no longer fits the model is compacted once all the same.\n\t */\n\treadonly at: number | undefined;\n}\n\n/**\n * The harness's compactor: once the context passes `at`, before the next call, the agent's own\n * model summarizes it, and what is kept follows the summary word for word. Without a `triage`,\n * the recent compactor keeps the run's prompt and the run's last `keptSteps` answers of the\n * model, each with its tool results.\n */\nexport interface RecentCompactor {\n\treadonly by: 'harness';\n\t/** As `ProviderCompactor.at`. */\n\treadonly at: number | undefined;\n\treadonly keptSteps: number;\n\t/**\n\t * Jev's: it keeps the run's prompt, the model's last answer with its tool results, and each\n\t * earlier tool result the triage scores `keptScore` or more, with its call. When the triage\n\t * fails, the recent compactor chooses instead.\n\t */\n\treadonly triage?: ContextTriage;\n}\n\nexport type Compactor = ProviderCompactor | RecentCompactor;\n\n/** The provider's compactor, off: only a context that no longer fits is compacted. */\nexport const compactionOff: Compactor = { by: 'provider', at: undefined };\n\n/** How many answers of the model, with their tool results, the recent compactor keeps. */\nexport const recentKeptSteps = 10;\n"]}
@@ -1,9 +1,10 @@
1
1
  import { type Agent, type AgentResponse, type RunOptions } from '#src/agent/domain/agent';
2
2
  import type { LLMClient } from '#src/llm/domain/llm';
3
- import type { Message } from '#src/llm/domain/message';
3
+ import { type Message } from '#src/llm/domain/message';
4
4
  import type { TranscriptCallback } from '#src/agent/domain/transcript';
5
5
  import { type AgentTool } from '#src/tools/domain/preparedCall';
6
6
  import type { ILogger } from '#src/shared/domain/logger';
7
+ import { type Compactor } from './compactor.ts';
7
8
  import { type HistoryReader } from './historyFollower.ts';
8
9
  /**
9
10
  * An agent whose conversation is owned by MiKode: every run sends the whole context to a
@@ -24,11 +25,14 @@ export declare class LLMAgent implements Agent {
24
25
  private readonly history;
25
26
  private readonly summarizer;
26
27
  private readonly onTranscript;
28
+ private readonly writeRoots;
29
+ private readonly compactor;
30
+ private contextTokens;
27
31
  private seenRevision;
28
32
  private readonly reads;
29
33
  private readonly runs;
30
34
  private readonly session;
31
- constructor({ llmClient, messages, tools, maxSteps, autoApprove, logger, history, summarizer, onTranscript, }: {
35
+ constructor({ llmClient, messages, tools, maxSteps, autoApprove, logger, history, summarizer, onTranscript, writeRoots, compactor, }: {
32
36
  llmClient: LLMClient;
33
37
  messages?: Message[];
34
38
  tools?: AgentTool[];
@@ -51,6 +55,13 @@ export declare class LLMAgent implements Agent {
51
55
  summarizer?: LLMClient;
52
56
  /** Receives what the model saw in each run, once the run ended. */
53
57
  onTranscript?: TranscriptCallback;
58
+ /**
59
+ * The `write` roots of the agent's workspace: an agent it delegates to writes only within
60
+ * them. Empty when it only reads; without it, it sets no limit of its own.
61
+ */
62
+ writeRoots?: readonly string[];
63
+ /** How the context is compacted. Defaults to off: only a context that no longer fits is. */
64
+ compactor?: Compactor;
54
65
  });
55
66
  private followHistory;
56
67
  /**
@@ -59,6 +70,41 @@ export declare class LLMAgent implements Agent {
59
70
  */
60
71
  private summarizeUndone;
61
72
  private warnSummaryFailed;
73
+ private warnContextOutgrown;
74
+ private warnTriageFailed;
75
+ private warnCompactionFailed;
76
+ /**
77
+ * Warns that a summary the model was called for failed. A logger that throws ends the run,
78
+ * carrying what that call spent, which the run has not counted yet.
79
+ */
80
+ private warnSummaryCallFailed;
81
+ /**
82
+ * Has the agent's own model summarize the context before the harness compactor's cut, and
83
+ * records the summary in the run, with what it keeps word for word. With a triage, Jev chooses
84
+ * what is kept; when it fails, which it logs, the recent cut does. `compacted` is false when
85
+ * the model had answered too few times, which costs no call, when Jev kept every answer
86
+ * whole, unless the context no longer fits, or when the summary failed as `summarize` says.
87
+ */
88
+ private compactHarness;
89
+ /**
90
+ * Where the harness compactor cuts `context`, and what it keeps, as the summary request says
91
+ * it. `triaged` when Jev was asked and answered.
92
+ */
93
+ private harnessCut;
94
+ private recentChoice;
95
+ /**
96
+ * Has the agent's own model summarize what `cut` does not keep, and records the summary in the
97
+ * run. Once the threshold passed, the model reads `context`, the context as it was last sent,
98
+ * from its provider's cache; once the context no longer fit, it reads what it summarizes as
99
+ * text. `compacted` is false when the model wrote no summary or the provider failed in
100
+ * passing, which it logs; any other failure is thrown. The summary's usage is the run's either
101
+ * way.
102
+ *
103
+ * @param kept what `cut` keeps, as the summary request tells the model.
104
+ * @param endsRun whether a failure ends the run, which throws why instead of logging it: the
105
+ * run's context no longer fits. `outgrown` alone may be this call's own fallback.
106
+ */
107
+ private summarize;
62
108
  private llmCall;
63
109
  private narrateStep;
64
110
  /**