You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Build the static plugin runtime and z.ai conversational adapter
Status: pending
Tags: enhancement, assistant, backend, infra, testing, P1
Depends on: #123
Blocks: #121 and the todo/Typefully children of #122
Parent: #122
Architecture: _docs/CONVERSATIONAL_AGENT_PLUGIN_ARCHITECTURE.md at 4e5402c
Scope
Build the channel-independent model/runtime boundary used by later Telegram and
domain slices:
a trusted, build-time TypeScript plugin registry;
a compact plugin catalog and progressive disclosure;
the exact two-model-turn skill_load → skill_invoke protocol;
deterministic plugin/build/schema identity and stale-turn rejection;
bounded context assembly with a persisted context receipt;
a reusable z.ai client for the Anthropic-compatible Messages API;
disabled-by-default production configuration and least-privilege z.ai secret
wiring.
This child proves the framework with injected fake plugins and executors. The
production registry may remain empty until the todo child registers the first
domain plugin. Nothing in this issue writes a task, Typefully draft, document,
or other domain resource.
The existing socialDraftAssistant.ts z.ai code is evidence for the provider
contract, not the reusable runtime boundary. The shared adapter must not depend
on social-post types or call Typefully. Later work may migrate that caller; this
issue must not change the current social workflow's product behavior.
Product decisions
These choices keep the MVP small and deterministic:
Registry discovery is a static TypeScript list. There is no package scanning,
runtime installation, marketplace, semantic catalog search, or flow DSL.
At most one mutation plugin is active. Multi-plugin mutation planning is
rejected.
The core owns conversation/session actions, permissions, validation
sequencing, proposal boundaries, and model tool definitions. A plugin cannot
add approval/cancellation/session transitions or grant itself authority.
The initial core permission allowlist is exactly todo:create:self and typefully:create-saved-draft. They are contracts for
later children, not executable capabilities in this issue.
admin and operator remain the only recognized roles. Registry
configuration may narrow a plugin by role and channel, but cannot widen a
core permission. Disabled, wrong-role, and wrong-channel plugins are omitted
from the catalog and cannot be loaded by name.
No automatic provider retry is performed. A new operator event may safely
start a new turn after a transient failure.
A model turn may make at most one framework tool call. Invalid or malformed
output does not trigger an unbounded correction loop.
Plugin contract and registry
Define a typed plugin contract containing:
id
version
displayName
summary
activationHints
skillInstructions
actions[]
name
description
strict input schema
effect: read | proposal
corePermission
reconciliationMode (required for a future external proposal effect)
validator
proposal renderer (required for a proposal effect)
executor/reconciler declarations (validated as metadata here; not invoked here)
buildDigest
schemaDigest
Registry construction/startup must fail with a safe configuration error for:
duplicate or non-canonical plugin/action IDs;
a missing required field or handler declaration;
a schema that permits unknown properties;
duplicate action names;
an unknown core permission;
a read action that declares a mutation executor;
a proposal action without a renderer or permission;
an external proposal action without one of provider_idempotency, correlation_lookup, or operator_reconciliation_only;
an invalid build/schema digest;
an enabled role or channel outside the core allowlists.
The compact catalog exposes only id, displayName, one short summary, and
bounded activation hints for plugins available to the current actor/channel.
It never exposes full instructions, schemas, handlers, permissions, secrets, or
runtime configuration.
Build and schema digests
Both digests use lowercase sha256:<64 hex characters>.
schemaDigest is calculated at build/startup from canonical JSON containing
the ordered action names, effects, core permissions, reconciliation modes,
and strict input schemas.
buildDigest is generated, not hand-maintained. A deterministic build helper
hashes the compiled plugin module plus its canonical manifest/contract and
emits registry metadata. Re-running it over identical input must produce the
same digest; changing instructions, schema, renderer, validator, executor, or
reconciler code must change it.
Registry startup recomputes/verifies generated metadata and fails closed on a
mismatch.
A loaded skill and every later proposal record the exact build and schema
digests. If the build disappears or changes, unclaimed work is stale and must
be regenerated; this issue does not build the executor-version retention
mechanism owned by the approval/execution child.
Exact two-turn protocol
The following is the only plugin-selection protocol in the MVP.
If the model returns ordinary assistant text because it cannot yet select a
plugin, the runtime returns that clarification and makes no second provider
call. If it calls skill_load, the runtime requires exactly one tool call,
validates catalog availability, role, channel, and the current conversation
revision, then stores a SkillLoadReceipt containing:
The opaque nonce expires after five minutes and is invalidated by any newly
committed conversation event or revision. The successful skill_load ends
turn 1; its provider response cannot also invoke the plugin.
Turn 2: invoke the loaded skill
The runtime starts one separate z.ai Messages call containing:
the same higher-priority core rules;
the selected plugin's instructions and action schemas;
the bounded conversation context;
the exact plugin/build/schema identity and opaque load nonce;
The runtime accepts exactly one skill_invoke call. Before plugin validation
it verifies the authenticated actor/channel policy again, conversation ID and
revision, plugin ID, build digest, schema digest, nonce status/expiry, and action
effect/permission. The input is then validated against the strict registered
schema before any plugin handler is reached.
A valid invocation atomically consumes the load receipt through #123's
conditional persistence primitive. It returns only a typed, non-executing
result such as missing-field clarification, validated draft state, or a pure
proposal candidate. M2 tests use fakes for all three. It never claims approval
or invokes an executor.
An invalid schema/tool/action/nonce or a provider response containing text plus
a tool call is rejected. There is no automatic third provider call. The user
gets a safe retry/clarification interaction; a later input starts with a fresh
revision/load receipt. Duplicate delivery returns the stored consumed result
when available and never invokes a fake handler twice.
Bounded context
Implement a deterministic ContextAssembler with an injectable token counter.
Production uses a documented conservative estimator ceil(UTF-8 byte length / 3) because z.ai does not expose a compatible
pre-flight tokenizer. Tests use an exact fake counter.
Defaults:
maximum estimated input: 16,384 tokens;
maximum provider output: 4,096 tokens;
summary: at most 2,048 input tokens;
recent events: at most 8,192 input tokens, selected newest-first but emitted
in chronological order and never split mid-event;
source excerpts: at most 4,096 input tokens (the M2 runtime supplies none);
proposal-state reference: at most 512 input tokens.
Core rules, the filtered compact catalog, and—during turn 2—the one active
plugin contract are mandatory. If mandatory content alone exceeds the total,
fail closed as configuration error instead of silently truncating policy or
schemas. Optional layers are dropped/truncated in this order: oldest recent
events, source excerpts, then summary. Complete history, raw proposal payloads,
approval records, authentication records, secrets, and unrelated plugin
schemas never enter context.
For every provider call, persist through #123 a small ContextReceipt with:
algorithmVersion
conversationId and source revision
summary checkpoint ID/hash, if used
included event sequence ranges/hashes
source reference/revision hashes, if used
plugin ID/build/schema digests, if loaded
estimated input count and configured limits
truncation flags
provider/model identifier
The receipt contains no prompt/completion bodies or secret values.
z.ai Anthropic-compatible client
Provide an injected conversational model interface and one production z.ai
implementation.
at most two provider calls per incoming operator event.
Configuration accepts the base URL, model, timeout, and limits, but production
startup rejects non-HTTPS URLs and any host other than api.z.ai. Tests may
inject a local fake transport without weakening production validation.
The client must:
serialize Anthropic-compatible system, messages, tools, and tool_choice payloads;
accept only expected tool_use blocks and expected stop reasons;
expose typed safe error codes such as model_timeout, model_rate_limited, model_unavailable, model_invalid_output, model_policy_rejected, and model_config_error;
never return raw provider error bodies to a user.
There is no retry inside the client. Timeout, HTTP 429/5xx, malformed output,
and network ambiguity create no draft/proposal and return a recoverable,
operator-readable message. Provider output remains untrusted and cannot
authorize, approve, or execute an action.
Secrets, logging, and production wiring
Add a dedicated out-of-band Secrets Manager reference for the conversational
z.ai key. The managed secret value is JSON:
{ "apiKey": "<z.ai key>" }
Use a SAM parameter such as ZaiConversationalApiKeySecretArn and expose only
that reference to the backend (for example, ZAI_CONVERSATIONAL_API_KEY_SECRET_ARN). Grant secretsmanager:GetSecretValue only for that exact ARN. Resolve/cache the key
inside the production client; do not copy it into a CloudFormation plaintext
parameter, GitHub secret value, ordinary Lambda environment value, model
context, or generated registry metadata.
The conversational runtime remains disabled by default until rollout. Empty
secret configuration is allowed only while disabled. Enabling with no valid
secret ARN must fail closed. The existing OIDC deploy workflow may pass the
non-secret ARN as deployment configuration, but must never retrieve or echo the
key.
Redaction rules:
do not log/store prompt bodies, completion bodies, plugin input, conversation
text, provider bodies, API keys, authorization headers, or secret ARNs;
logs/metrics may contain correlation ID, safe error code, duration, HTTP
status class, model, estimated/input/output token counts, plugin ID, digests,
and hashes;
recursively redact keys matching secret/token/password/credential/cookie/
authorization/API-key patterns and values matching bearer/key/signature
patterns before any error crosses the client boundary;
cap all safe summaries and reject overlarge model/provider fields.
Automated tests use fake transports and fake Secrets Manager only. They must not
read a developer shell key or make a network request.
Acceptance criteria
A typed static registry and compact actor/channel-filtered catalog exist;
startup rejects every invalid registry case listed above.
The only initial core permissions are todo:create:self and typefully:create-saved-draft; plugins cannot define permissions or core
transitions.
Plugin build and schema digests are deterministic, verified, and change
when their covered inputs change.
skill_load and skill_invoke follow the exact separate-turn protocol;
load expiry, revision, nonce, plugin, action, role, channel, build, and
schema bindings are enforced before a fake handler runs.
One model response cannot both load and invoke; a turn has at most one
framework tool call and an input event makes at most two provider calls.
A valid invocation produces only a typed non-executing result. No runtime
path in this issue invokes an executor or mutates a domain resource.
Duplicate invocation delivery is idempotent; stale, expired, consumed,
mismatched, malformed, or unauthorized invocation executes no fake
handler.
Context assembly enforces the declared budgets and ordering, excludes
forbidden state/unrelated plugins, and stores a body-free context receipt.
The reusable z.ai client uses the declared Anthropic-compatible endpoint,
model/header/tool contracts, timeout, output limit, and injected transport.
Provider/network/output/config failures use bounded safe codes, create no
proposal, and leak no prompt, completion, plugin input, raw provider body,
key, token, secret reference, or authorization header.
SAM adds disabled-by-default z.ai configuration, an exact Secrets Manager
reference, and least-privilege read permission without embedding the key.
Existing unrelated APIs and the current social-draft behavior remain
compatible while the new runtime is disabled.
Automated tests make zero live z.ai calls and require no real secret.
[HUMAN] A credentialed AWS operator pre-creates/confirms the production
z.ai secret, enables the sandbox runtime, and runs one redacted glm-5.2 skill_load → fake skill_invoke smoke through the deployed backend.
Logs show only safe metadata and the runtime is disabled again if later
slices are not ready.
Test scenarios
Registry validation and catalog filtering
Given valid fake plugins plus duplicate IDs/actions, permissive schemas, unknown
permissions, missing handlers, invalid digests/reconciliation modes, disabled
plugins, and role/channel restrictions
When the registry starts and catalogs are requested
Then invalid registries fail safely and each actor sees only the bounded public
metadata for permitted plugins.
Deterministic digests
Given identical and individually changed fake plugin source/manifest/schema
fixtures
When generated metadata is built and verified twice
Then identical input has identical digests, each covered semantic/code change
changes the correct digest, and stale/tampered metadata fails startup.
Separate model turns
Given a fake z.ai transport that records requests
When turn 1 calls skill_load
Then it receives only the catalog/load tool, the response ends the call, and
turn 2 is a new request containing only the selected plugin contract and skill_invoke; unrelated schemas are absent.
Stale and replayed loads
Given concurrent input that advances the conversation revision, a five-minute
expiry, a consumed nonce, a duplicate delivery, a changed plugin build/schema,
and wrong actor/channel/action values
When skill_invoke is validated
Then stale or mismatched calls produce no handler invocation, while exact
duplicate delivery returns the recorded result and the valid path runs once.
Invalid model output
Given unknown/multiple/mixed tool calls, extra schema fields, malformed blocks,
oversized bodies, wrong stop reasons, and missing required input
When either model response is parsed
Then no automatic third call occurs, no proposal or executor is reached, and a
safe recoverable interaction is returned.
Context budgeting
Given oversized summaries, old/recent events, source excerpts, proposal state,
unrelated plugin schemas, secret-like text, and an exact fake token counter
When each turn is assembled
Then mandatory policy/schema remains intact, optional content is reduced in the
specified order, whole recent events remain chronological, the hard budget is
not exceeded, forbidden content is absent, and the context receipt exactly
describes the included revisions/hashes and truncation.
Provider contract and failures
Given fake success, timeout, cancellation, network failure, 401/403, 429, 5xx,
invalid JSON, malicious error bodies, and secret-like nested values
When the z.ai client runs
Then URL/headers/body/tool choice are exact, there is no retry, errors map to
safe codes, and captured logs/results contain none of the sensitive values or
prompt/completion bodies.
Secret and infrastructure boundary
Given the SAM template, disabled/enabled runtime configurations, a fake secret,
and missing/malformed secret values
When infrastructure validation and client startup run
Then disabled deployment needs no key, enabled runtime fails closed without
one, only the backend role can read the exact ARN, and no plaintext key appears
in templates, environment, build output, tests, or workflow logs.
Backward compatibility
Given the new runtime is disabled
When the existing backend assistant/Telegram/API tests run
Then existing behavior and routes are unchanged and no new z.ai secret lookup
or model call occurs.
Required verification
Run:
npm --prefix backend test
npm --prefix backend run typecheck
npm --prefix backend run build
make sam-validate
make sam-build
Tester must additionally inspect generated SAM for the exact secret ARN
permission/environment reference and run focused race/replay, context-budget,
digest, provider-contract, and recursive-redaction tests. No Playwright run or
screenshots are required because this issue changes no portal UI.
Assistant Engineer reviews the runtime/provider boundary before Tester
acceptance. After shipping, On-Call monitors the existing OIDC deployment. The
real secret/prod call remains the [HUMAN] gate; if all agent-verifiable work is
complete first, commit with Refs #125, add human, and leave the issue open
per process.
The production z.ai key is provisioned out of band. A valid development key
has already passed a minimal direct smoke, but it is not production
deployment wiring.
The public repository may contain runtime code, schemas, infrastructure, and
sanitized fixtures only. Operational prompts/private knowledge do not belong
here.
Out of scope
Todo, Typefully, SOP, podcast, workflow, recurring-work, document, memory, or
any other production domain plugin.
Any executor invocation, approval/presentation token, durable execution
worker, provider reconciliation, or domain mutation.
Telegram/web channel adapters, voice, photos, uploads, group behavior, or
cross-channel continuation.
Dynamic plugins, package discovery, plugin marketplace, semantic catalog
search, general flow DSL, arbitrary MCP/tool proxying, or multi-plugin
mutation plans.
History-wide retrieval, durable personal memory, embeddings, or source
document ingestion.
Migrating the existing social-draft flow to the new runtime or changing its
current Typefully behavior.
Build the static plugin runtime and z.ai conversational adapter
Status: pending
Tags:
enhancement,assistant,backend,infra,testing,P1Depends on: #123
Blocks: #121 and the todo/Typefully children of #122
Parent: #122
Architecture:
_docs/CONVERSATIONAL_AGENT_PLUGIN_ARCHITECTURE.mdat4e5402cScope
Build the channel-independent model/runtime boundary used by later Telegram and
domain slices:
skill_load→skill_invokeprotocol;wiring.
This child proves the framework with injected fake plugins and executors. The
production registry may remain empty until the todo child registers the first
domain plugin. Nothing in this issue writes a task, Typefully draft, document,
or other domain resource.
The existing
socialDraftAssistant.tsz.ai code is evidence for the providercontract, not the reusable runtime boundary. The shared adapter must not depend
on social-post types or call Typefully. Later work may migrate that caller; this
issue must not change the current social workflow's product behavior.
Product decisions
These choices keep the MVP small and deterministic:
runtime installation, marketplace, semantic catalog search, or flow DSL.
rejected.
sequencing, proposal boundaries, and model tool definitions. A plugin cannot
add approval/cancellation/session transitions or grant itself authority.
todo:create:selfandtypefully:create-saved-draft. They are contracts forlater children, not executable capabilities in this issue.
adminandoperatorremain the only recognized roles. Registryconfiguration may narrow a plugin by role and channel, but cannot widen a
core permission. Disabled, wrong-role, and wrong-channel plugins are omitted
from the catalog and cannot be loaded by name.
start a new turn after a transient failure.
output does not trigger an unbounded correction loop.
Plugin contract and registry
Define a typed plugin contract containing:
Registry construction/startup must fail with a safe configuration error for:
readaction that declares a mutation executor;proposalaction without a renderer or permission;provider_idempotency,correlation_lookup, oroperator_reconciliation_only;The compact catalog exposes only
id,displayName, one shortsummary, andbounded activation hints for plugins available to the current actor/channel.
It never exposes full instructions, schemas, handlers, permissions, secrets, or
runtime configuration.
Build and schema digests
sha256:<64 hex characters>.schemaDigestis calculated at build/startup from canonical JSON containingthe ordered action names, effects, core permissions, reconciliation modes,
and strict input schemas.
buildDigestis generated, not hand-maintained. A deterministic build helperhashes the compiled plugin module plus its canonical manifest/contract and
emits registry metadata. Re-running it over identical input must produce the
same digest; changing instructions, schema, renderer, validator, executor, or
reconciler code must change it.
mismatch.
digests. If the build disappears or changes, unclaimed work is stale and must
be regenerated; this issue does not build the executor-version retention
mechanism owned by the approval/execution child.
Exact two-turn protocol
The following is the only plugin-selection protocol in the MVP.
Turn 1: select and load
The runtime assembles:
skill_loadframework tool:{ "plugin": "<canonical plugin id>" }If the model returns ordinary assistant text because it cannot yet select a
plugin, the runtime returns that clarification and makes no second provider
call. If it calls
skill_load, the runtime requires exactly one tool call,validates catalog availability, role, channel, and the current conversation
revision, then stores a
SkillLoadReceiptcontaining:The opaque nonce expires after five minutes and is invalidated by any newly
committed conversation event or revision. The successful
skill_loadendsturn 1; its provider response cannot also invoke the plugin.
Turn 2: invoke the loaded skill
The runtime starts one separate z.ai Messages call containing:
skill_invoketool:{ "plugin": "<same plugin id>", "action": "<registered action>", "input": {}, "load_nonce": "<opaque nonce>" }The runtime accepts exactly one
skill_invokecall. Before plugin validationit verifies the authenticated actor/channel policy again, conversation ID and
revision, plugin ID, build digest, schema digest, nonce status/expiry, and action
effect/permission. The input is then validated against the strict registered
schema before any plugin handler is reached.
A valid invocation atomically consumes the load receipt through #123's
conditional persistence primitive. It returns only a typed, non-executing
result such as missing-field clarification, validated draft state, or a pure
proposal candidate. M2 tests use fakes for all three. It never claims approval
or invokes an executor.
An invalid schema/tool/action/nonce or a provider response containing text plus
a tool call is rejected. There is no automatic third provider call. The user
gets a safe retry/clarification interaction; a later input starts with a fresh
revision/load receipt. Duplicate delivery returns the stored consumed result
when available and never invokes a fake handler twice.
Bounded context
Implement a deterministic
ContextAssemblerwith an injectable token counter.Production uses a documented conservative estimator
ceil(UTF-8 byte length / 3)because z.ai does not expose a compatiblepre-flight tokenizer. Tests use an exact fake counter.
Defaults:
in chronological order and never split mid-event;
Core rules, the filtered compact catalog, and—during turn 2—the one active
plugin contract are mandatory. If mandatory content alone exceeds the total,
fail closed as configuration error instead of silently truncating policy or
schemas. Optional layers are dropped/truncated in this order: oldest recent
events, source excerpts, then summary. Complete history, raw proposal payloads,
approval records, authentication records, secrets, and unrelated plugin
schemas never enter context.
For every provider call, persist through #123 a small
ContextReceiptwith:The receipt contains no prompt/completion bodies or secret values.
z.ai Anthropic-compatible client
Provide an injected conversational model interface and one production z.ai
implementation.
Defaults:
https://api.z.ai/api/anthropic;https://api.z.ai/api/anthropic/v1/messages;glm-5.2;anthropic-version: 2023-06-01;x-api-key;Configuration accepts the base URL, model, timeout, and limits, but production
startup rejects non-HTTPS URLs and any host other than
api.z.ai. Tests mayinject a local fake transport without weakening production validation.
The client must:
system,messages,tools, andtool_choicepayloads;tool_useblocks and expected stop reasons;output, malformed JSON, oversized provider payloads, and limit violations;
AbortControllerfor the deadline;model_timeout,model_rate_limited,model_unavailable,model_invalid_output,model_policy_rejected, andmodel_config_error;There is no retry inside the client. Timeout, HTTP 429/5xx, malformed output,
and network ambiguity create no draft/proposal and return a recoverable,
operator-readable message. Provider output remains untrusted and cannot
authorize, approve, or execute an action.
Secrets, logging, and production wiring
Add a dedicated out-of-band Secrets Manager reference for the conversational
z.ai key. The managed secret value is JSON:
{ "apiKey": "<z.ai key>" }Use a SAM parameter such as
ZaiConversationalApiKeySecretArnand expose onlythat reference to the backend (for example,
ZAI_CONVERSATIONAL_API_KEY_SECRET_ARN). Grantsecretsmanager:GetSecretValueonly for that exact ARN. Resolve/cache the keyinside the production client; do not copy it into a CloudFormation plaintext
parameter, GitHub secret value, ordinary Lambda environment value, model
context, or generated registry metadata.
The conversational runtime remains disabled by default until rollout. Empty
secret configuration is allowed only while disabled. Enabling with no valid
secret ARN must fail closed. The existing OIDC deploy workflow may pass the
non-secret ARN as deployment configuration, but must never retrieve or echo the
key.
Redaction rules:
text, provider bodies, API keys, authorization headers, or secret ARNs;
status class, model, estimated/input/output token counts, plugin ID, digests,
and hashes;
authorization/API-key patterns and values matching bearer/key/signature
patterns before any error crosses the client boundary;
Automated tests use fake transports and fake Secrets Manager only. They must not
read a developer shell key or make a network request.
Acceptance criteria
startup rejects every invalid registry case listed above.
todo:create:selfandtypefully:create-saved-draft; plugins cannot define permissions or coretransitions.
when their covered inputs change.
skill_loadandskill_invokefollow the exact separate-turn protocol;load expiry, revision, nonce, plugin, action, role, channel, build, and
schema bindings are enforced before a fake handler runs.
framework tool call and an input event makes at most two provider calls.
path in this issue invokes an executor or mutates a domain resource.
mismatched, malformed, or unauthorized invocation executes no fake
handler.
forbidden state/unrelated plugins, and stores a body-free context receipt.
model/header/tool contracts, timeout, output limit, and injected transport.
proposal, and leak no prompt, completion, plugin input, raw provider body,
key, token, secret reference, or authorization header.
reference, and least-privilege read permission without embedding the key.
compatible while the new runtime is disabled.
z.ai secret, enables the sandbox runtime, and runs one redacted
glm-5.2skill_load→ fakeskill_invokesmoke through the deployed backend.Logs show only safe metadata and the runtime is disabled again if later
slices are not ready.
Test scenarios
Registry validation and catalog filtering
Given valid fake plugins plus duplicate IDs/actions, permissive schemas, unknown
permissions, missing handlers, invalid digests/reconciliation modes, disabled
plugins, and role/channel restrictions
When the registry starts and catalogs are requested
Then invalid registries fail safely and each actor sees only the bounded public
metadata for permitted plugins.
Deterministic digests
Given identical and individually changed fake plugin source/manifest/schema
fixtures
When generated metadata is built and verified twice
Then identical input has identical digests, each covered semantic/code change
changes the correct digest, and stale/tampered metadata fails startup.
Separate model turns
Given a fake z.ai transport that records requests
When turn 1 calls
skill_loadThen it receives only the catalog/load tool, the response ends the call, and
turn 2 is a new request containing only the selected plugin contract and
skill_invoke; unrelated schemas are absent.Stale and replayed loads
Given concurrent input that advances the conversation revision, a five-minute
expiry, a consumed nonce, a duplicate delivery, a changed plugin build/schema,
and wrong actor/channel/action values
When
skill_invokeis validatedThen stale or mismatched calls produce no handler invocation, while exact
duplicate delivery returns the recorded result and the valid path runs once.
Invalid model output
Given unknown/multiple/mixed tool calls, extra schema fields, malformed blocks,
oversized bodies, wrong stop reasons, and missing required input
When either model response is parsed
Then no automatic third call occurs, no proposal or executor is reached, and a
safe recoverable interaction is returned.
Context budgeting
Given oversized summaries, old/recent events, source excerpts, proposal state,
unrelated plugin schemas, secret-like text, and an exact fake token counter
When each turn is assembled
Then mandatory policy/schema remains intact, optional content is reduced in the
specified order, whole recent events remain chronological, the hard budget is
not exceeded, forbidden content is absent, and the context receipt exactly
describes the included revisions/hashes and truncation.
Provider contract and failures
Given fake success, timeout, cancellation, network failure, 401/403, 429, 5xx,
invalid JSON, malicious error bodies, and secret-like nested values
When the z.ai client runs
Then URL/headers/body/tool choice are exact, there is no retry, errors map to
safe codes, and captured logs/results contain none of the sensitive values or
prompt/completion bodies.
Secret and infrastructure boundary
Given the SAM template, disabled/enabled runtime configurations, a fake secret,
and missing/malformed secret values
When infrastructure validation and client startup run
Then disabled deployment needs no key, enabled runtime fails closed without
one, only the backend role can read the exact ARN, and no plaintext key appears
in templates, environment, build output, tests, or workflow logs.
Backward compatibility
Given the new runtime is disabled
When the existing backend assistant/Telegram/API tests run
Then existing behavior and routes are unchanged and no new z.ai secret lookup
or model call occurs.
Required verification
Run:
npm --prefix backend test npm --prefix backend run typecheck npm --prefix backend run build make sam-validate make sam-buildTester must additionally inspect generated SAM for the exact secret ARN
permission/environment reference and run focused race/replay, context-budget,
digest, provider-contract, and recursive-redaction tests. No Playwright run or
screenshots are required because this issue changes no portal UI.
Assistant Engineer reviews the runtime/provider boundary before Tester
acceptance. After shipping, On-Call monitors the existing OIDC deployment. The
real secret/prod call remains the
[HUMAN]gate; if all agent-verifiable work iscomplete first, commit with
Refs #125, addhuman, and leave the issue openper process.
Dependencies
conditional persistence, summary checkpoints, and the storage hook for
SkillLoadReceipt/ContextReceipt.has already passed a minimal direct smoke, but it is not production
deployment wiring.
sanitized fixtures only. Operational prompts/private knowledge do not belong
here.
Out of scope
any other production domain plugin.
worker, provider reconciliation, or domain mutation.
cross-channel continuation.
search, general flow DSL, arbitrary MCP/tool proxying, or multi-plugin
mutation plans.
document ingestion.
current Typefully behavior.
../dtc-operations,../datatasks, or../podcast-assistant.