SMOODEV-590: TS server honors per-agent config + conversation workflows#126
Merged
Conversation
Agents served by the TypeScript operator ignored their per-agent config and always used one generic org-level customer-support persona. This ports the monorepo general-agent behavior into the TS server: - AgentConfigResolver seam resolves a session's agentId -> AgentConfig (instructions, conversationWorkflow, greeting, personality, tool allow-list). Server-side because create_conversation_session carries only an agentId. Un-configured agents fall back to the default prompt + tools (unchanged). - conversationWorkflow: current step's intent + criteria rendered into the system prompt; a cheap, failure-tolerant post-turn judge decides whether the criteria were met and advances the pointer (explicit next or array order), tracked as currentStepId on the session. - Tolerant parsing: malformed config degrades to the default flow, never crashes a session. Judge errors stay on the current step. Mirrors the Rust agent-config-instructions-workflow design. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011va1JyN3rTsfd2xuNGALed
🦋 Changeset detectedLatest commit: 9fd15a7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Compute is_first_turn server-side (no prior messages) in the dispatcher and drop the <GreetingAwareness> section entirely on later turns, matching the Python server's is_first_turn semantics instead of relying on the model to self-gate a conditional instruction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011va1JyN3rTsfd2xuNGALed
tool_config is { enabledTools: [{ toolId, enabled, authLevel, config? }] }
(defaults to []), not a flat string list. Parse it per the monorepo
AgentToolConfig schema: enabled defaults true, authLevel "none", authLevel/
config preserved on the parsed type; skip malformed entries. Empty/missing
enabledTools → full tool set; non-empty → restrict to enabled=true entries
matched by snake_case toolId; unknown toolIds ignored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011va1JyN3rTsfd2xuNGALed
Close the parsed-but-unenforced gaps, mirroring the monorepo general-agent
tool-execution gate:
- authLevel enforcement at execution time (new src/toolGating.ts, wraps each
tool's execute — no engine fork). Gates ONLY when the enabledTools entry sets
authLevel != 'none' AND the tool declares supportsAuthRequirement (new opt-in
flag on ServerTool, default false). admin + public → blocked message;
visibility 'internal' → auto-satisfied; public + end_user → consult
SessionAuthenticator (new seam, fail-closed) → blocked identity-verification
message when unauthenticated. Adds AgentConfig.visibility ('public' default).
- Per-tool config delivered to execute's 2nd arg at execution (was only
preserved on the parsed type).
- Judge default aligned to the cross-lane haiku tier (claude-haiku-4-5).
OTP issuance stays host wiring behind SessionAuthenticator — the server only
gates and leaves the hook point.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011va1JyN3rTsfd2xuNGALed
brentrager
added a commit
that referenced
this pull request
Jul 2, 2026
PRs touching only python/, typescript/, or go/ got ZERO checks — only rust/dotnet paths triggered a lane, so SMOODEV-590's port PRs (#125/#126/#128) merged with no CI. Add one path-filtered lane per language, mirroring the existing .NET lane's shape (triggers, path filters, ubuntu-latest, timeout) plus concurrency + read-only permissions from the kind-smoke lane: - Python: uv sync + ruff check + ruff format --check + pytest, matrix over python/ and python/server/. - TypeScript: pnpm install + typecheck + test (vitest), scoped to the two workspace packages (@smooai/smooth-operator + …-server). - Go: gofmt check + go vet + go test ./... -race, matrix over go/ and go/server/. Each lane also triggers on spec/** because all three conformance suites validate against spec/conformance/fixtures.json at runtime. Live-gateway E2E tests self-skip without SMOOTH_AGENT_E2E + SMOOAI_GATEWAY_KEY, so no secrets needed. Also ran `ruff format` on the 7 unformatted files under python/ so the new format gate is green (pre-existing drift from the port PRs). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Problem
Agents served by the TypeScript smooth-operator server ignored their per-agent config and always used one generic org-level customer-support persona. The server resolved behavior only from a single static
systemPromptset at construction — so every agent behaved identically, per-agentinstructionswere never applied, andconversation_workflowwas unimplemented.In the SmooAI monorepo
agentstable each agent hasinstructions(jsonb{prompt}) andconversation_workflow(jsonb); the TS server applied neither.Approach
Ports the monorepo general-agent behavior (
packages/backend/src/ai/graphs/general-agent/) into the TS server, mirroring the Rustagent-config-instructions-workflowdesign.Config-delivery seam —
AgentConfigResolver.resolve(agentId)(newsrc/agentConfig.ts), following the server's existing pluggable-seam pattern (AuthVerifier.resolve,AccessKnowledge.forAccess). Resolution is server-side because thecreate_conversation_sessionpayload carries only anagentId(perspec/actions/create-conversation-session.schema.json). The referenceStaticAgentConfigResolveris in-memory; a real deployment plugs in one backed by theagentstable. An un-configured agent (no resolver, or resolver returns/throws) falls back to the server/org default prompt + full tool set — behavior unchanged.Per-agent instructions —
assembleSystemPrompt(base, config, currentStepId)makes the agent's owninstructionsthe primary persona body, keeps the base org rules, and folds in optionalgreeting/personality.tool_configfilters the server's tool set to an allow-list.Conversation workflow (new
src/workflow.ts) — the current step's intent + criteria render into the system prompt (renderWorkflowPromptSection); after each turn a cheap, failure-tolerant judge LLM call (judgeStep) decides whether the criteria were met and advances the pointer (explicitnextor array order).currentStepIdis tracked on the session (SessionStore.setCurrentStep).Tolerance —
parseWorkflow/parseAgentConfigdegrade malformed config to the default flow and never throw; a judge error (or unparseable verdict) stays on the current step. A broken workflow doesn't discard a validinstructions.prompt.Verification
pnpm test— 98 passing (41 pre-existing + 57 new acrossworkflow.test.ts,agentConfig.test.ts,agent-config-turn.test.ts): parse tolerance + hostile input, step resolve/next/render/advance, all judge verdict paths (yes/no/maybe/unparseable/thrown/empty-reply), prompt assembly with/without instructions/workflow/greeting/personality, and end-to-end over a real WebSocket — per-agent isolation, instructions honored, workflow advancement across turns, malformed-config + throwing-resolver fallback.pnpm typecheckclean (src + tests).@smooai/smooth-operator-serverminor).Notes
agent-config-instructions-workflow(the canonical reference). That branch was not yet on origin when this was built; the only surface to reconcile is the resolver interface — the workflow render/judge/step-tracking semantics follow the shared monorepo reference. Flagged for a re-check once the Rust branch lands.eventual_response(content tokens have already streamed) so the advancedcurrentStepIdis persisted before any next turn — no race.🤖 Generated with Claude Code
https://claude.ai/code/session_011va1JyN3rTsfd2xuNGALed