feat(core): agent-runtime types β Phase 1 of architecture unification - #253
Merged
Conversation
Introduces the canonical shapes from the architecture unification plan
without changing runtime behaviour. No production caller imports any of
this yet β the adapter exists so Phase 3+ work can begin while the
existing QueryBackend implementations stay untouched.
What lands
src/core/agent-runtime/
events.ts AgentEvent / AgentError / AgentResult / UsageSnapshot
plus emptyUsage / addUsage / isAgentEventOf /
isAgentRunTerminator helpers.
model-ref.ts BackendId / ModelRef / CacheSupport / ModelSource
plus isBackendId / sameModelRef / makeBareModelRef
and the BACKEND_IDS constant (single literal source
for the five current backends).
run-policy.ts RunKind / RunPolicy + sub-policies (ToolPolicy,
DeliveryPolicy, TimeoutPolicy, LoggingPolicy,
SessionPolicy, PermissionPolicy) plus
defaultRunPolicyFor / allowsDelivery /
requiresAmbientChat.
tool-descriptor.ts ToolDescriptor / ToolFilter / applyToolFilter.
capabilities.ts ChatBackend / BackgroundRunner / ModelCatalog /
SessionBackend / ToolRuntime / UsageTelemetry plus
a composed Backend object with explicit capability
flags (deriveCapabilities).
adapter.ts adaptQueryBackend(legacy, id, label, opts) β wraps
the existing QueryBackend as a Backend by
synthesising minimal AgentEvent streams around
query() and runOneShotAgent(), translating
UnifiedModelInfo β ModelRef, and proxying
session / tools / usage methods. Optional logSink
receives appendLog calls so callers can keep the
legacy markdown stream alive during migration.
index.ts Barrel.
Tests
agent-runtime-types.test.ts (32 cases) β pure helpers across events,
model-ref, run-policy, tool-descriptor, capabilities.
agent-runtime-adapter.test.ts (18 cases) β every adapter translation
rule pinned: chat happy path / empty text / thrown query / abort
classification; background appendLog β logSink routing and error
classification; ModelCatalog kinds (exact / ambiguous / missing);
selectableOnly + query filter; getDefaultModel with and without
getModelInfo; cacheSupport propagation; sessions / tools / usage
proxies; capability flag derivation.
50 new cases, 2900 / 2912 in the full suite (12 pre-existing skips on
the live-integration tier). typecheck clean, prettier clean, no new
lint warnings.
Design notes lifted from the plan
- AgentEvent is structurally typed (switch on event.type); no
instanceof, no class hierarchy.
- ModelRef.{backend,id} is the identity pair; everything else is
metadata for UIs and telemetry.
- RunPolicy lifts the loose "contextLabel" string into a real policy
object: defaultRunPolicyFor(kind) returns the right shape for
chat / heartbeat / dream / trigger / test.
- BACKEND_IDS is the single literal source for the BackendId union;
config.ts zod enums remain duplicated for now and get reconciled
in a later phase (noted in model-ref.ts).
- Adapter behaviour is deliberately less rich than native Phase 3
backends will be (no per-token streaming, no tool-call events) so
the bridge can't lie about events it cannot observe.
Refs docs/talon-architecture-unification-plan.md
dylanneve1
approved these changes
May 24, 2026
dylanneve1
enabled auto-merge (squash)
May 24, 2026 11:53
4 tasks
dylanneve1
pushed a commit
that referenced
this pull request
May 24, 2026
Introduces the ModelRef-shaped counterpart to
core/active-model.ts's string-side resolveActiveModelForChat. Phase
2.1 contract: add the function + tests, no caller migration. The
existing 5-step string-shaped chain stays the single chain-of-truth β
this module wraps it and enriches the returned id into a ModelRef
carrying the metadata downstream consumers (/status, /model menu,
telemetry) currently re-derive on the spot.
What lands
src/core/agent-runtime/
resolver.ts resolveActiveModelRefForChat(chatId, backend,
backendId, config) β { ref: ModelRef | null,
source: ActiveModelSource } plus the
getActiveModelRefForChat() convenience wrapper.
index.ts Re-exports the new symbols.
Enrichment strategy (deliberate fall-through)
1. backend.getModelInfo(id) β preferred, full metadata
2. backend.resolveModel(id) β exact β fallback, also full metadata
3. makeBareModelRef(backend, id) β last resort, identity-only
(with cacheSupport stamped)
cacheSupport propagation
Each ref carries the backend's cacheMetrics field β backends
report caching uniformly across their catalog today, so this is
the right place to stamp it.
ActiveModelSource β ModelSource mapping
override-valid β "chat"
override-invalid-fallback β "fallback"
backend-canonical β "backend-default"
config-backend-defaults β "config"
config-legacy-global β "config"
none β (ref is null)
Invariants
- ref === null iff the 5-step chain returned model === null OR the
supplied backendId is not a known BackendId.
- source is returned verbatim from the underlying chain so callers
keep using identical toast wording.
Tests
agent-runtime-resolver.test.ts (17 cases):
- Enrichment via getModelInfo (preferred)
- Enrichment via resolveModel (fallback)
- Bare ref fallback when neither catalog method matches
- cacheSupport propagation (read / readwrite / undefined β "none")
- ref returned for trusted overrides when no resolveModel exists
- Source mapping β every ActiveModelSource β ModelSource branch
- Null edge cases (chain exhausts, unknown backendId, null backend)
- Survives getModelInfo throwing (falls through to resolveModel)
- Survives resolveModel throwing (falls through to bare ref)
- getActiveModelRefForChat convenience wrapper
Full suite: 2917 / 2929 (12 pre-existing skips). 17 net new tests
on top of Phase 1's 50. Typecheck clean. Prettier clean. No new
lint warnings.
Out of scope
- No caller migration. /status, /model, chat query, heartbeat,
dream all keep calling resolveActiveModelForChat. Phase 2.2+
converts them one at a time, each in its own PR.
- No changes to core/active-model.ts itself. The new module wraps,
doesn't fork.
- The lossy "config-backend-defaults" + "config-legacy-global" β
"config" collapse is intentional β ModelRef.source captures
provenance (chat / config / backend), the ActiveModelSource tag
returned alongside keeps the fine-grained reason for toast
wording.
Stacked on top of feat/agent-runtime-types-phase1 (#253).
Refs docs/talon-architecture-unification-plan.md
dylanneve1
pushed a commit
that referenced
this pull request
May 24, 2026
#254) * feat(core): agent-runtime types β Phase 1 of architecture unification Introduces the canonical shapes from the architecture unification plan without changing runtime behaviour. No production caller imports any of this yet β the adapter exists so Phase 3+ work can begin while the existing QueryBackend implementations stay untouched. What lands src/core/agent-runtime/ events.ts AgentEvent / AgentError / AgentResult / UsageSnapshot plus emptyUsage / addUsage / isAgentEventOf / isAgentRunTerminator helpers. model-ref.ts BackendId / ModelRef / CacheSupport / ModelSource plus isBackendId / sameModelRef / makeBareModelRef and the BACKEND_IDS constant (single literal source for the five current backends). run-policy.ts RunKind / RunPolicy + sub-policies (ToolPolicy, DeliveryPolicy, TimeoutPolicy, LoggingPolicy, SessionPolicy, PermissionPolicy) plus defaultRunPolicyFor / allowsDelivery / requiresAmbientChat. tool-descriptor.ts ToolDescriptor / ToolFilter / applyToolFilter. capabilities.ts ChatBackend / BackgroundRunner / ModelCatalog / SessionBackend / ToolRuntime / UsageTelemetry plus a composed Backend object with explicit capability flags (deriveCapabilities). adapter.ts adaptQueryBackend(legacy, id, label, opts) β wraps the existing QueryBackend as a Backend by synthesising minimal AgentEvent streams around query() and runOneShotAgent(), translating UnifiedModelInfo β ModelRef, and proxying session / tools / usage methods. Optional logSink receives appendLog calls so callers can keep the legacy markdown stream alive during migration. index.ts Barrel. Tests agent-runtime-types.test.ts (32 cases) β pure helpers across events, model-ref, run-policy, tool-descriptor, capabilities. agent-runtime-adapter.test.ts (18 cases) β every adapter translation rule pinned: chat happy path / empty text / thrown query / abort classification; background appendLog β logSink routing and error classification; ModelCatalog kinds (exact / ambiguous / missing); selectableOnly + query filter; getDefaultModel with and without getModelInfo; cacheSupport propagation; sessions / tools / usage proxies; capability flag derivation. 50 new cases, 2900 / 2912 in the full suite (12 pre-existing skips on the live-integration tier). typecheck clean, prettier clean, no new lint warnings. Design notes lifted from the plan - AgentEvent is structurally typed (switch on event.type); no instanceof, no class hierarchy. - ModelRef.{backend,id} is the identity pair; everything else is metadata for UIs and telemetry. - RunPolicy lifts the loose "contextLabel" string into a real policy object: defaultRunPolicyFor(kind) returns the right shape for chat / heartbeat / dream / trigger / test. - BACKEND_IDS is the single literal source for the BackendId union; config.ts zod enums remain duplicated for now and get reconciled in a later phase (noted in model-ref.ts). - Adapter behaviour is deliberately less rich than native Phase 3 backends will be (no per-token streaming, no tool-call events) so the bridge can't lie about events it cannot observe. Refs docs/talon-architecture-unification-plan.md * feat(core): resolveActiveModelRefForChat β Phase 2.1 Introduces the ModelRef-shaped counterpart to core/active-model.ts's string-side resolveActiveModelForChat. Phase 2.1 contract: add the function + tests, no caller migration. The existing 5-step string-shaped chain stays the single chain-of-truth β this module wraps it and enriches the returned id into a ModelRef carrying the metadata downstream consumers (/status, /model menu, telemetry) currently re-derive on the spot. What lands src/core/agent-runtime/ resolver.ts resolveActiveModelRefForChat(chatId, backend, backendId, config) β { ref: ModelRef | null, source: ActiveModelSource } plus the getActiveModelRefForChat() convenience wrapper. index.ts Re-exports the new symbols. Enrichment strategy (deliberate fall-through) 1. backend.getModelInfo(id) β preferred, full metadata 2. backend.resolveModel(id) β exact β fallback, also full metadata 3. makeBareModelRef(backend, id) β last resort, identity-only (with cacheSupport stamped) cacheSupport propagation Each ref carries the backend's cacheMetrics field β backends report caching uniformly across their catalog today, so this is the right place to stamp it. ActiveModelSource β ModelSource mapping override-valid β "chat" override-invalid-fallback β "fallback" backend-canonical β "backend-default" config-backend-defaults β "config" config-legacy-global β "config" none β (ref is null) Invariants - ref === null iff the 5-step chain returned model === null OR the supplied backendId is not a known BackendId. - source is returned verbatim from the underlying chain so callers keep using identical toast wording. Tests agent-runtime-resolver.test.ts (17 cases): - Enrichment via getModelInfo (preferred) - Enrichment via resolveModel (fallback) - Bare ref fallback when neither catalog method matches - cacheSupport propagation (read / readwrite / undefined β "none") - ref returned for trusted overrides when no resolveModel exists - Source mapping β every ActiveModelSource β ModelSource branch - Null edge cases (chain exhausts, unknown backendId, null backend) - Survives getModelInfo throwing (falls through to resolveModel) - Survives resolveModel throwing (falls through to bare ref) - getActiveModelRefForChat convenience wrapper Full suite: 2917 / 2929 (12 pre-existing skips). 17 net new tests on top of Phase 1's 50. Typecheck clean. Prettier clean. No new lint warnings. Out of scope - No caller migration. /status, /model, chat query, heartbeat, dream all keep calling resolveActiveModelForChat. Phase 2.2+ converts them one at a time, each in its own PR. - No changes to core/active-model.ts itself. The new module wraps, doesn't fork. - The lossy "config-backend-defaults" + "config-legacy-global" β "config" collapse is intentional β ModelRef.source captures provenance (chat / config / backend), the ActiveModelSource tag returned alongside keeps the fine-grained reason for toast wording. Stacked on top of feat/agent-runtime-types-phase1 (#253). Refs docs/talon-architecture-unification-plan.md --------- Co-authored-by: claudiusthebot <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
First step of the architecture unification plan
(
docs/talon-architecture-unification-plan.md). Adds the canonicalshapes β
AgentEvent,RunPolicy,ModelRef,ToolDescriptor,and the split capability interfaces β under
src/core/agent-runtime/, plus an adapter that wraps the existingQueryBackendas aBackendcomposed object so Phase 3+ work canbegin without churning every concrete backend in this same PR.
No production caller imports any of this yet. Phase 1 contract:
types-first, no behaviour change. The full test suite (2900 cases)
stays green and the only code added under
src/is the newagent-runtime/directory.Why
Talon has accreted five backends under time pressure. Behaviour
drifts per backend β event parsing, usage extraction, MCP setup,
abort handling, and logging are all re-implemented in slightly
different shapes. The plan replaces that with single sources of
truth (events, policies, model identity, tool registry, store
abstraction). This PR is the smallest reviewable opener:
The second PR will plumb
resolveActiveModel() β ModelRefthrough/status,/model, chat query, heartbeat, and dream (Phase 2 inthe plan).
What lands
Design notes (lifted from the plan)
AgentEventis structurally typed β switch onevent.type;no
instanceof, no class hierarchy.ModelRef.{backend, id}is the identity pair; everything elseis metadata for UIs and telemetry.
RunPolicylifts the loosecontextLabelstring into a realpolicy object.
defaultRunPolicyFor(kind)returns the right shapefor
chat/heartbeat/dream/trigger/test.BACKEND_IDSis the single literal source for theBackendIdunion. The
config.tszod enums stay duplicated for now and getreconciled in a later phase (noted in
model-ref.ts).backends will be β no per-token streaming, no tool-call events.
It can't lie about events it cannot observe; Phase 3 backends emit
the full event surface natively.
Testing
50 new cases across two files:
agent-runtime-types.test.ts(32) β pure helpers across events,model-ref, run-policy, tool-descriptor, capabilities.
agent-runtime-adapter.test.ts(18) β every adapter translationrule pinned: chat happy path / empty text / thrown query / abort
classification; background
appendLog β logSinkrouting and errorclassification;
ModelCatalogkinds (exact / ambiguous / missing);selectableOnly+ query filter;getDefaultModelwith andwithout
getModelInfo;cacheSupportpropagation;sessions/tools/usageproxies; capability-flagderivation.
Tests pass (
npm test) β 2900 / 2912 (12 pre-existing skips onthe live-integration tier).
Type check passes (
npm run typecheck)No new lint warnings (
npm run lintβ 18 pre-existing warnings,none in new code).
Prettier clean on the new files.
Out of scope
/status//modelchanges.config.tsbackend zod enums stay duplicated until Phase 2 orlater. Note in
model-ref.tsflags the link.core/errors.tsclassifier is not yet wired through theadapter β Phase 3 native backends will use it directly. The adapter
ships a small inline classifier that maps the common cases.
Refs
docs/talon-architecture-unification-plan.md