tardie 0.3.0 → 0.4.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.
@@ -1,11 +1,11 @@
1
1
  import { Cause, Clock, Context, Effect } from "effect"
2
2
  import { EventLog } from "tardie/core/event-log"
3
3
  import { transition, type Reactor } from "tardie/core/actor"
4
- import { modelCalled, textReturned, turnFailed } from "./events"
4
+ import { modelCalled, textReturned, turnFailed } from "../events"
5
5
  import type { Event } from "tardie/core/event"
6
- import type { Action } from "./events"
6
+ import type { Action } from "../events"
7
7
  import { trajectoryOf, turnEpochOf, turnView } from "tardie/code/turns"
8
- import type { ContextPolicy } from "./compaction"
8
+ import type { ContextPolicy } from "../components/compaction"
9
9
 
10
10
  // The infer reactor: the model loop, and nothing else. A think is owed when the current turn
11
11
  // has no unanswered tool call and no terminal; serving marks the attempt, does inference, then
@@ -28,9 +28,9 @@ export const DEFAULT_INFER_POLICY: InferPolicy = { giveUpAfter: 3, repairAtMost:
28
28
  export interface InferRequest {
29
29
  readonly trajectory: ReadonlyArray<Event>
30
30
  readonly system: string
31
- readonly tools: ReadonlyArray<import("./request").ToolSpec>
31
+ readonly tools: ReadonlyArray<import("../request").ToolSpec>
32
32
  // What the render truncates and where, stated by the assembly so the binding renders against
33
- // the same numbers the compaction guard fires on (capability.ts, compactionFor).
33
+ // the same numbers the compaction guard fires on (components/compaction.ts, compactionFor).
34
34
  readonly context?: Partial<ContextPolicy>
35
35
  }
36
36
 
@@ -111,11 +111,11 @@ const terminated = (slice: ReadonlyArray<Event>): boolean =>
111
111
  const terminalKey = (turn: string, epoch: number): string =>
112
112
  epoch === 0 ? `tn:${turn}` : `tn:${turn}/${epoch}`
113
113
 
114
- // Render derives what the model is shown over this log: the assembly owns it (capability.ts,
114
+ // Render derives what the model is shown over this log: the assembly owns it (runtime/agent.ts,
115
115
  // renderOf).
116
116
  export type Render = (log: ReadonlyArray<Event>) => {
117
117
  readonly system: string
118
- readonly tools: ReadonlyArray<import("./request").ToolSpec>
118
+ readonly tools: ReadonlyArray<import("../request").ToolSpec>
119
119
  readonly context?: Partial<ContextPolicy>
120
120
  }
121
121
 
@@ -1,13 +1,13 @@
1
1
  import { Clock, Effect } from "effect"
2
2
  import { transition, type Reactor, type Transition } from "tardie/core/actor"
3
- import { budgetRequested, toolReturned } from "./events"
3
+ import { budgetRequested, toolReturned } from "../events"
4
4
  import type { Event } from "tardie/core/event"
5
5
  import { turnView } from "tardie/code/turns"
6
- import { budgetSpent } from "./budget"
7
- import { answerErrors, outputSchemaOf, repairText } from "./contract"
6
+ import { budgetSpent } from "../components/budget"
7
+ import { answerErrors, outputSchemaOf, repairText } from "../contract"
8
8
  // The tools reactor: the agent's side of the tool table. The policy here is the same whatever
9
9
  // the tools are: the answer contract, the escalation ask, the budget wall, and the unknown-tool
10
- // error. The work tools themselves come from the mounted capabilities (capability.ts), so an
10
+ // error. The work tools themselves come from the mounted components (runtime/agent.ts), so an
11
11
  // agent measured against a fixed tool table mounts `toolList` and keeps every behavior here.
12
12
  //
13
13
 
@@ -24,7 +24,7 @@ export interface PendingCall {
24
24
  export type Answer = (result: unknown) => Transition<never, never>
25
25
 
26
26
  // Serve returns the transitions one call owes: an empty array while the world still works, and
27
- // undefined when the call names no tool of its capability.
27
+ // undefined when the call names no derived tool.
28
28
  export type Serve<R = never> = (
29
29
  call: PendingCall,
30
30
  log: ReadonlyArray<Event>,
@@ -79,15 +79,15 @@ const decisionFor = (
79
79
 
80
80
  // toolsReactorFor derives one transition per pending call, branch by branch. The records each
81
81
  // branch appends carry the keys: an answer tr:<callId>, an escalation ask br:<callId>, and
82
- // whatever key the capability's own dispatch mints. An escalating call with no parental decision
82
+ // whatever key the component's own dispatch mints. An escalating call with no parental decision
83
83
  // derives nothing (the turn is durably paused); the decision's arrival re-derives the answer.
84
- // Every branch here is capability-independent policy; a capability's serve decides only how a
84
+ // Every branch here is component-independent policy; a tool binding decides only how a
85
85
  // work call becomes events.
86
- // toolsReactorFrom is the policy over a routed serve and a derived tool list: the capability
87
- // assembly's entry (capability.ts), where the tools are a projection of the log.
86
+ // toolsReactorFrom is the policy over a routed serve and a derived tool list: the component
87
+ // assembly's entry (runtime/agent.ts), where the tools are information derived from the log.
88
88
  export const toolsReactorFrom = <R = never>(
89
89
  serve: Serve<R>,
90
- toolsFor: (log: ReadonlyArray<Event>) => ReadonlyArray<{ readonly name: string }>
90
+ toolsFor: (log: ReadonlyArray<Event>, call: PendingCall) => ReadonlyArray<{ readonly name: string }>
91
91
  ): Reactor<R> => (log) => {
92
92
  const call = pendingCall(log)
93
93
  if (call === undefined) return []
@@ -138,7 +138,7 @@ export const toolsReactorFrom = <R = never>(
138
138
  return [answering({ error: repairText(errors.length > 0 ? errors : ["the answer tool was called with no arguments"]) })]
139
139
  }
140
140
  // The budget gate: the wall on the turn refuses the work and keeps what the model has. It
141
- // precedes the serve so a spent turn cannot dispatch, whatever a capability would have done.
141
+ // precedes the serve so a spent turn cannot dispatch, whatever a component would have done.
142
142
  if (budgetSpent(log)) {
143
143
  return [
144
144
  answering({
@@ -148,7 +148,7 @@ export const toolsReactorFrom = <R = never>(
148
148
  }
149
149
  const served = serve(call, log, answering)
150
150
  if (served === undefined) {
151
- return [answering({ error: `unknown tool: ${call.name}. Call one of: ${toolsFor(log).map((t) => t.name).join(", ")}.` })]
151
+ return [answering({ error: `unknown tool: ${call.name}. Call one of: ${toolsFor(log, call).map((t) => t.name).join(", ")}.` })]
152
152
  }
153
153
  return served
154
154
  }
@@ -3,7 +3,7 @@ import { Router } from "tardie/core/router"
3
3
  import { Self } from "tardie/core/actor"
4
4
  import { Facets } from "tardie/core/facets"
5
5
  import type { Package } from "tardie/code/packages"
6
- import { budgetPolicyOf, type BudgetPolicy } from "./budget"
6
+ import { budgetPolicyOf, type BudgetPolicy } from "./components/budget"
7
7
  import { Park } from "tardie/code/errors"
8
8
  import { address as addressOf, readAddress } from "tardie/core/router"
9
9
  import { replyId } from "tardie/core/message"
@@ -51,7 +51,7 @@ import { replyId } from "tardie/core/message"
51
51
  // budget takes the same ceiling the child's own reactor would, and a consumer that moved that
52
52
  // ceiling moves both (budget.ts, BudgetPolicy).
53
53
  export interface SpawnOptions {
54
- readonly agentOf?: () => string | undefined
54
+ readonly actorNameOf?: () => string | undefined
55
55
  readonly reserve?: (callId: string, want: number) => Promise<number>
56
56
  readonly shadowOf?: () => boolean
57
57
  // The parent's explicit world label, when its own fire named a shared world instead of taking
@@ -71,7 +71,7 @@ export const agentsPackage = (
71
71
  options: SpawnOptions & { readonly place?: (callId: string, self: string) => string } = {}
72
72
  ): Package<Router | Self | Facets> => {
73
73
  const place = options.place ?? sibling
74
- const agentOf = options.agentOf ?? (() => undefined)
74
+ const actorNameOf = options.actorNameOf ?? (() => undefined)
75
75
  const reserve = options.reserve ?? (async (_callId: string, want: number) => want)
76
76
  const shadowOf = options.shadowOf ?? (() => false)
77
77
  const worldOf = options.worldOf ?? (() => undefined)
@@ -156,7 +156,7 @@ export const agentsPackage = (
156
156
  if (budget <= 0) return { error: "the run's budget is exhausted; no budget to spawn this agent" }
157
157
  // The child works as the same member the parent does: the actor rides every brief in the
158
158
  // family, so a run's whole tree resolves connections identically.
159
- const actor = agentOf()
159
+ const actor = actorNameOf()
160
160
  // The parent's own shadow reading, never the tool args: an agent cannot set or unset it, so
161
161
  // a whole run family is shadow by construction from the fire alone. `world` rides along
162
162
  // the same way, when the fire named an explicit shared one.
package/src/agent/turn.ts CHANGED
@@ -5,28 +5,28 @@ import { send, type Actor } from "tardie/core/actor"
5
5
  import type { Router } from "tardie/core/router"
6
6
  import type { Self } from "tardie/core/actor"
7
7
  import type { Facets } from "tardie/core/facets"
8
- import type { Infer, InferPolicy } from "./infer"
9
- import type { BudgetPolicy } from "./budget"
10
- import type { ContextPolicy } from "./compaction"
8
+ import type { Infer, InferPolicy } from "./runtime/infer"
9
+ import type { BudgetPolicy } from "./components/budget"
10
+ import type { ContextPolicy } from "./components/compaction"
11
11
  import type { CodePolicy } from "tardie/code/execute"
12
12
  import type { WorkspacePolicy } from "tardie/code/workspace"
13
13
 
14
- export { Infer } from "./infer"
14
+ export { Infer } from "./runtime/infer"
15
15
 
16
16
  // AgentR is the runtime's needs: Infer for the model, EventLog for settle, Router, Self, and
17
17
  // Facets for reply and spawn (the deliver, identity, and observe privileges; core/facets.ts), and
18
18
  // KeyValueStore for the spill store code mode writes bounded results to
19
- // (packages/code/src/spill.ts). Capabilities add their own on top (capability.ts,
20
- // RequirementsOf).
19
+ // (packages/code/src/spill.ts). Components add their own on top (core/component.ts,
20
+ // ComponentRequirements).
21
21
  export type AgentR = Infer | EventLog | Router | Self | Facets | KeyValueStore.KeyValueStore
22
22
  export type RlmR = AgentR
23
23
 
24
24
  // AgentPolicy is every policy value an assembled agent applies, one field per part that applies
25
25
  // one, so a caller sets a single number without listing reactors. Each field is itself partial
26
26
  // and fills from its own exported default (infer.ts, budget.ts, compaction.ts,
27
- // packages/code/src/execute.ts). `infer` is the runtime's policy (agentOf takes it); `workspace`
27
+ // packages/code/src/execute.ts). `infer` is the runtime's policy (agentRuntime takes it); `workspace`
28
28
  // bounds the workspace package's own read and grep answers (packages/code/src/workspace.ts); the
29
- // rest ride their capabilities (budgetFor, compactionFor, codeModeFor).
29
+ // rest ride their components (budgetFor, compactionFor, codeModeFor).
30
30
  export interface AgentPolicy {
31
31
  readonly infer: Partial<InferPolicy>
32
32
  readonly budget: Partial<BudgetPolicy>
@@ -323,7 +323,7 @@ export const projection = <Params extends Schema.Struct.Fields, Result extends S
323
323
 
324
324
  // The two names a projection may not claim. The log is not a projection of itself: `events` is the
325
325
  // log read back and `stream` is the same log followed, and both are the platform's own guarantee
326
- // rather than anything an actor derives (projectionsOf refuses either, the way agentOf refuses a
326
+ // rather than anything an actor derives (projectionsOf refuses either, the way agentRuntime refuses a
327
327
  // duplicate tool).
328
328
  export const RESERVED_PROJECTIONS: ReadonlyArray<string> = ["events", "stream"]
329
329
 
@@ -288,11 +288,11 @@ export interface CodePolicy {
288
288
  // The packages arrive as values, and what they need arrives with them: the reactor's environment
289
289
  // is the spill store plus the union of the packages' own requirements, so a lane assembled with a
290
290
  // service-needing package cannot be run where that service is missing (execute.test.ts, "a
291
- // package's requirements ride its type"). Which packages are passed is the capability scope: the
291
+ // package's requirements ride its type"). Which packages are passed is the component scope: the
292
292
  // code can only name these, and the empty array is the powerless lane (packages.ts, Package).
293
293
  // Two packages under one name would make `pkg.name` ambiguous in the body's scope, so a duplicate
294
- // is a construction-time error, the same reading agentOf takes of two capabilities claiming one
295
- // tool name (packages/agent/src/capability.ts, agentOf).
294
+ // is a construction-time error, the same reading agentRuntime takes of two components claiming one
295
+ // tool name (packages/agent/src/runtime/agent.ts, agentRuntime).
296
296
  export const codeReactorFor = <const P extends ReadonlyArray<Package<never>> | ReadonlyArray<Package<unknown>>>(
297
297
  policy: Partial<CodePolicy>,
298
298
  packages: P
package/src/code/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- // The package root: the reactor and the key fragment a caller assembles an agent from. The
2
- // subpath exports stay for everything else.
1
+ // The package root exposes the code reactor and its event keys. Agent assemblies adapt these
2
+ // through codeModeFor in packages/agent/src/components/code.ts.
3
3
  export { codeReactor } from "./execute"
4
4
  export { codeKeys } from "./events"
@@ -91,8 +91,8 @@ export const annotationsOf = (pkg: Package<unknown>, method: string): Required<M
91
91
  ...pkg.annotations?.[method]
92
92
  })
93
93
 
94
- // Package is one named unit of an actor's world face, the dual of a Capability on its model
95
- // face (packages/agent/src/capability.ts). A capability declares what the model is offered
94
+ // Package is one named unit of an actor's world face, the dual of an AgentComponent on its model
95
+ // face (packages/agent/src/runtime/agent.ts). A component derives what the model is offered
96
96
  // (`tools`, `system`) and what settles its calls (`serve`); a package declares what code is
97
97
  // offered (`name`, `description`, `docs`) and what settles its calls (`methods`). Code calls
98
98
  // methods as `zohorecruit.insert_record(args)`. The platform binds the methods to providers;
@@ -107,11 +107,11 @@ export const annotationsOf = (pkg: Package<unknown>, method: string): Required<M
107
107
  // deliberately outside Package: it belongs to the host boundary and gets its own concept once a
108
108
  // consumer exists.
109
109
  //
110
- // Which packages an assembly passes is capability scoping: a task that must never send messages
110
+ // Which packages an assembly passes is component scoping: a task that must never send messages
111
111
  // is given no sending package, and the code cannot name what the assembly did not pass. The
112
112
  // powerless default is the empty array (execute.ts, codeReactor).
113
113
  //
114
- // `R` is what a package's methods need from the environment, the dual of `Capability<R>` on the
114
+ // `R` is what a package's methods need from the environment, the dual of `AgentComponent<R>` on the
115
115
  // model face. A package that reaches for a service names it in its type, and the reactor that
116
116
  // runs the package declares the union of what its packages need (execute.ts, codeReactorFor); a
117
117
  // package that reaches for nothing is `Package<never>` and runs anywhere.
@@ -136,7 +136,7 @@ export interface Package<R = never> {
136
136
 
137
137
  // PackageRequirements extracts one package's R, so a mixed list infers the union of what its
138
138
  // members need rather than collapsing on the first element's R. It is the package-face twin of
139
- // RequirementsOf (packages/agent/src/capability.ts), and what makes the environment a code
139
+ // ComponentRequirements (packages/core/src/component.ts), and what makes the environment a code
140
140
  // reactor demands a function of the packages it was passed (execute.test.ts, "a package's
141
141
  // requirements ride its type").
142
142
  export type PackageRequirements<T> = T extends Package<infer R> ? R : never
@@ -0,0 +1,102 @@
1
+ import { actor, type Actor, type Reactor, type Transition } from "./actor"
2
+ import type { Event } from "./event"
3
+ import { composeKeys, type KeyFragment } from "./event-log"
4
+
5
+ // Derivation is one component's reading of a log: information for consumers and transitions
6
+ // for the actor runtime. The information follows tla/Projection.tla, ViewFaithful. The
7
+ // transitions follow tla/Reconcile.tla, NoVoid.
8
+ export interface Derivation<I, R = never> {
9
+ readonly info: I
10
+ readonly transitions: ReadonlyArray<Transition<never, R>>
11
+ }
12
+
13
+ // Component derives information and owed work from the same log. Its key fragment declares how
14
+ // the events its transitions append prove commitment to the actor runtime.
15
+ export interface Component<I, R = never> {
16
+ readonly name: string
17
+ readonly keys?: KeyFragment
18
+ readonly derive: (log: ReadonlyArray<Event>) => Derivation<I, R>
19
+ }
20
+
21
+ // InfoAlgebra states how independently derived information composes. Callers supply the empty
22
+ // value and combination rule because ordering, collisions, and overrides belong to the
23
+ // information's consumer.
24
+ export interface InfoAlgebra<I> {
25
+ readonly empty: I
26
+ readonly combine: (left: I, right: I) => I
27
+ }
28
+
29
+ // ComponentRuntime interprets composed information as reactors and supplies the key fragments
30
+ // for its protocol. The reactor factory is polymorphic in component requirements because an
31
+ // interpreter may route work whose environment is declared by the component that contributed it.
32
+ export interface ComponentRuntime<I, R = never> {
33
+ readonly name: string
34
+ readonly algebra: InfoAlgebra<I>
35
+ readonly keys: ReadonlyArray<KeyFragment>
36
+ readonly reactors: <C>(infoOf: (log: ReadonlyArray<Event>) => I) => ReadonlyArray<Reactor<R | C>>
37
+ }
38
+
39
+ // ComponentRequirements extracts a component's environment so composition carries the union of every
40
+ // member's requirements to the actor that hosts it.
41
+ export type ComponentRequirements<C> = C extends Component<unknown, infer R> ? R : never
42
+
43
+ // composeComponents derives the algebraic sum of each component's information and concatenates
44
+ // its transitions in component order. The name is explicit because traces and collision errors
45
+ // identify the resulting component with it.
46
+ export const composeComponents = <
47
+ I,
48
+ const Cs extends ReadonlyArray<Component<I, never> | Component<I, unknown>>
49
+ >(
50
+ name: string,
51
+ algebra: InfoAlgebra<I>,
52
+ components: Cs
53
+ ): Component<I, ComponentRequirements<Cs[number]>> => {
54
+ const members = components as ReadonlyArray<Component<I, ComponentRequirements<Cs[number]>>>
55
+ const fragments = members.flatMap((component) => component.keys === undefined ? [] : [component.keys])
56
+ const keys = fragments.length === 0
57
+ ? undefined
58
+ : {
59
+ prefixes: fragments.flatMap((fragment) => fragment.prefixes),
60
+ keyOf: composeKeys(...fragments)
61
+ }
62
+ return {
63
+ name,
64
+ ...(keys === undefined ? {} : { keys }),
65
+ derive: (log) => {
66
+ let info = algebra.empty
67
+ const transitions: Array<Transition<never, ComponentRequirements<Cs[number]>>> = []
68
+ for (const component of members) {
69
+ const derived = component.derive(log)
70
+ info = algebra.combine(info, derived.info)
71
+ transitions.push(...derived.transitions)
72
+ }
73
+ return { info, transitions }
74
+ }
75
+ }
76
+ }
77
+
78
+ // reactorOf exposes a component's owed-work projection to the existing actor reconciler.
79
+ export const reactorOf = <I, R>(component: Component<I, R>): Reactor<R> =>
80
+ (log) => component.derive(log).transitions
81
+
82
+ // actorOf composes components under one information runtime and adapts their transitions to the
83
+ // existing actor reconciler. Runtime keys compose beside component keys with the same collision
84
+ // rule as composeKeys.
85
+ export const actorOf = <
86
+ I,
87
+ RuntimeR,
88
+ const Cs extends ReadonlyArray<Component<I, never> | Component<I, unknown>>
89
+ >(
90
+ runtime: ComponentRuntime<I, RuntimeR>,
91
+ components: Cs
92
+ ): Actor<RuntimeR | ComponentRequirements<Cs[number]>> => {
93
+ type ComponentR = ComponentRequirements<Cs[number]>
94
+ type R = RuntimeR | ComponentR
95
+ const combined = composeComponents(runtime.name, runtime.algebra, components) as Component<I, R>
96
+ const infoOf = (log: ReadonlyArray<Event>): I => combined.derive(log).info
97
+ const reactors = runtime.reactors<ComponentR>(infoOf) as ReadonlyArray<Reactor<R>>
98
+ return actor(
99
+ [...reactors, reactorOf(combined)],
100
+ composeKeys(...runtime.keys, ...(combined.keys === undefined ? [] : [combined.keys]))
101
+ )
102
+ }
@@ -4,7 +4,7 @@ import { openaiCompatibleText } from "@tanstack/ai-openai/compatible"
4
4
  import * as BedrockRuntime from "@aws-sdk/client-bedrock-runtime"
5
5
  import { FetchHttpHandler } from "@smithy/fetch-http-handler"
6
6
  import { BedrockConverseTextAdapter, type BEDROCK_CONVERSE_MODELS } from "@tanstack/ai-bedrock"
7
- import { Infer, type InferRequest } from "tardie/infer"
7
+ import { Infer, type InferRequest } from "tardie"
8
8
  import type { Action } from "tardie/events"
9
9
  import type { Event } from "tardie/core/event"
10
10
  import { assertSupportedBun } from "tardie/core/runtime"
@@ -540,7 +540,7 @@ export const infer = (config: ModelConfig) => {
540
540
  fetch: fetcher
541
541
  })
542
542
  // The actor decides the request, render included; the platform maps it to the wire and
543
- // streams it, holding no opinion about tools (tardie, capability.ts).
543
+ // streams it, holding no opinion about tools (tardie, runtime/agent.ts).
544
544
  const req = modelRequest(request.trajectory, request, request.context ?? {})
545
545
  const schema = outputSchemaOf(request.trajectory) // the answer parser needs the turn's declared shape
546
546
  const stream = adapter.chatStream({
@@ -2,7 +2,8 @@ import { Schema } from "effect"
2
2
  import type { Event } from "tardie/core/event"
3
3
  import type { Actor } from "tardie/core/actor"
4
4
  import {
5
- agentOf,
5
+ actorOf,
6
+ agentRuntime,
6
7
  agentsPackage,
7
8
  budget,
8
9
  codeModeFor,
@@ -24,7 +25,7 @@ import { inboundOf } from "./projections"
24
25
  // mounts what is declared here by name (packages/client/src/contract.ts, apiOf).
25
26
 
26
27
  // The assembly, one for every lane: code mode with four packages in scope, plus the three policy
27
- // capabilities. v1 runs this one assembly and forking is the customization path (apps-server-spec.md,
28
+ // components. v1 runs this one assembly and forking is the customization path (apps-server-spec.md,
28
29
  // "Explicitly out of scope for v1").
29
30
  //
30
31
  // What the four packages add up to is what this actor can reach. `agents` fans work out to children
@@ -33,12 +34,15 @@ import { inboundOf } from "./projections"
33
34
  // requests to any host. There is no shell: a shell cannot be scoped the way a root or an origin can,
34
35
  // and this build has no place to ask an operator whether one command is allowed.
35
36
  export const assemblyOf = () =>
36
- agentOf([
37
- codeModeFor({ packages: [agentsPackage(), workspacePackage(), filesPackage(), fetchPackage()] }),
38
- reply,
39
- budget,
40
- compaction
41
- ])
37
+ actorOf(
38
+ agentRuntime(),
39
+ [
40
+ codeModeFor({ packages: [agentsPackage(), workspacePackage(), filesPackage(), fetchPackage()] }),
41
+ reply,
42
+ budget,
43
+ compaction
44
+ ]
45
+ )
42
46
 
43
47
  // ServerR is what this assembly needs bound. It is read off the assembly rather than restated, so a
44
48
  // package added above lands in the host's obligation and a host that binds nothing for it fails to