Scrydon
Security

Agent Control Standard (ACS)

Signed, fail-closed control of Scrydon agent lifecycles.

Scrydon implements the Agent Control Standard (ACS) v0.1.0 wire contract. ACS separates an Observed Agent, which reports a step before acting, from a Guardian Agent, which returns allow, deny, modify, ask, or defer.

In the default deployment, Agentic is the Observed Agent and API Platform is the Guardian. Their traffic is organization-scoped, signed, replay-protected, and persisted through Dapr so a workflow can resume on another replica without starting a second control session.

Active conformance profiles

Profiles are negotiated for each session. Scrydon only declares a profile when the running composition has the required methods, algorithms, and sinks.

ProfileAgentic Observed AgentAPI Platform Guardian
acs-coreNot claimed by the lifecycle-only sessionActive
acs-auditActiveActive
acs-inspectNot emittedNot enabled by default; requires a composed, actively maintained AgBOM inventory surface (the canonical projection, not a CycloneDX export — none exists)
acs-inspect-dynamicNot emittedNot enabled by default; also requires the live-change surface
acs-provenanceNot declaredActive by default; accepted only from deterministic producers
acs-traceNot enabled by defaultDeclared only when an OTel/OCSF sink is installed; there is no acs-trace Dapr component
acs-cryptoNot declared; HMAC-SHA256 baseline is usedNot declared; HMAC-SHA256 baseline is used

The ACS package contains Trace, canonical AgBOM, and provenance primitives for compositions that install those capabilities. It ships one signature implementation, HMAC-SHA256, so acs-crypto is refused at policy construction rather than offered. Package support alone is not a deployment conformance claim.

Governed Agentic lifecycle

One Agent block execution maps to one durable ACS session. Agentic currently emits these signed methods:

MethodControl point
handshake/helloNegotiates the exact methods, profiles, transport, and failure posture
steps/sessionStartOpens the execution-scoped session
steps/agentTriggerRecords how the agent was activated
steps/turnStartGates each provider/tool-loop turn before it runs
steps/agentResponseGates completed turn output before the workflow receives it
steps/turnEndRecords the terminal turn outcome
steps/sessionEndCloses the session and commits the final chain head

The lifecycle session deliberately does not advertise acs-core: Core requires tool request and result hooks, while Scrydon governs those operations in API Platform's separate execution session. Its methods_implemented list therefore contains only methods this session can actually emit. The Guardian still requires acs-audit, so the lifecycle remains signed, chained, and fail-closed.

Platform model, retrieval, memory, and tool operations are also governed at API Platform's extension-runtime chokepoint. That path uses the same ordered organization guardrail snapshot and ACS step vocabulary without adding a second policy pass: the in-process ACS hook records the verdict the kernel guardrail pipeline already reached onto the durable session chain — it does not evaluate a second time. Guardian ingress for agents Scrydon does not host (below) resolves that same guardrail snapshot but evaluates it through its own deterministic policy layer, not the kernel's runner.

Failure and replay posture

Scrydon negotiates on_decision_failure: deny, and this posture is a floor the counterparty cannot lower: the Guardian always returns its own configured posture in the negotiated ServerHello, regardless of what a client requests, so a peer cannot ask its way into proceed. A timeout, invalid signature, malformed response, missing Dapr sidecar, or unconfirmed state write therefore stops the action. An unguarded outcome is recorded distinctly and is never treated as permission.

Every request has a unique identity, a timestamp, and a nonce. The Guardian rejects duplicate request IDs, duplicate nonces, requests outside the negotiated skew window, and client chain heads that disagree with its durable session state.

Checkpoints use strong reads and optimistic concurrency, but Agentic and the Guardian do not share one keyspace. Agentic's own lifecycle state stays in the exec-security-state Dapr store. The Guardian's session state — the negotiated handshake, the replay guard, and the audit chain its verdicts are computed from — lives in its own component, acs-guardian-state, scoped to API Platform alone and sealed at rest with AES-256-GCM under a key only API Platform holds: the workload a Guardian judges must not be able to forge the rows deciding its own verdicts. In the shipped chart that store still lives in the Agentic database; a dedicated database role for it is tracked as a follow-up, and until then a session deleted at the SQL level fails closed rather than being replayed. If delivery succeeds but the client cannot confirm its checkpoint, the session fails closed instead of replaying a request whose outcome is uncertain.

Signatures

The default deployment derives a separate HMAC-SHA256 session key from the shared ACS_GUARDIAN_HMAC_SECRET, organization, workflow, and session IDs. Requests and responses are signed over the canonical envelope; the root secret never travels on the wire.

Because every caller authenticates with that same deployment-wide secret, this control boundary works for Scrydon-hosted agents only — an agent your organization operates outside Scrydon's own deployment cannot safely hold the same shared root key. Extending signed control to an independently hosted, customer-operated agent needs asymmetric signing, which this deployment does not yet offer.

Configure one key (ACS_GUARDIAN_HMAC_SECRET, at least 32 characters) for both API Platform and Agentic. The Helm chart generates and preserves a key on first installation. Under global.requireExplicitSecrets: true — the mode for GitOps/lookup-free rendering (helm template, ArgoCD, offline air-gap manifests), where a generate-on-render fallback would silently rotate the credential on every sync — the chart instead fails the render and names the value the operator must set explicitly: auth.secrets.ACS_GUARDIAN_HMAC_SECRET. The same applies to the Guardian store's own sealing key, auth.secrets.ACS_GUARDIAN_STATE_ENCRYPTION_KEY.

Environment variables

VariableApplies toMeaning
ACS_REQUIREDAPI Platform, AgenticWhether a process that cannot reach the Guardian may boot at all. The Helm chart renders true in every environment (auth.acs.required); a raw process with no flag defaults to true only when NODE_ENV=production. With it off, a missing input disables ACS with a warning naming it, and the gauges scrydon_acs_guardian_enabled / scrydon_acs_observed_agent_enabled read 0.
ACS_GUARDIAN_HMAC_SECRETAPI Platform, AgenticRoot HMAC key. Required while ACS_REQUIRED is on — startup throws and the process does not boot without it, a reachable Dapr sidecar, and (API Platform only) the sealer key below.
ACS_GUARDIAN_MAX_CONCURRENTAPI PlatformGlobal cap on signed ACS ingress evaluations this pod runs at once, across all tenants. Default 64. The per-organization cap (16) is not an env knob — it exists specifically so one tenant cannot consume the whole budget.
ACS_VERDICT_AUDITAPI PlatformWhich ACS verdicts become Audit Log rows: all (default), denials, or off. See Audit and observability.
ACS_GUARDIAN_STATE_ENCRYPTION_KEYAPI PlatformAES-256-GCM key that seals the Guardian's acs-guardian-state values at rest. Held by API Platform only — deliberately not the EXEC_SECURITY_STATE_ENCRYPTION_KEY Agentic also holds, since Agentic runs the agents those rows judge. Required while ACS_REQUIRED is on; there is no "run unsealed" fallback. An unpadded 32-byte base64url value (43 characters); the chart generates and preserves one.

Audit and observability

Content-bearing ACS requests advance a tamper-evident per-session chain. The chain commits to canonical request parameters, including the previous digest as raw bytes as required by ACS. turnEnd and sessionEnd are audit facts and cannot retroactively veto completed work.

The extension runtime exposes one effects plane with separate durability contracts:

  • audit and billing projections remain blocking;
  • ACS Trace is best-effort and cannot change or delay a verdict;
  • decision observations contain correlation and policy metadata, not modified content or policy-private payloads.

ACS_VERDICT_AUDIT (default all) selects which verdicts become Audit Log rows:

  • a governance row (acs.decision.denied), written immediately for every denying verdict (deny, ask, defer), carrying the OCSF Detection Finding;
  • one telemetry row per execution (acs.decision.recorded), written when the execution ends, containing that execution's whole decision list — allowed steps included. A streamed answer produces a verdict per governed window and per governed chunk; recording each one as its own row would have multiplied a governed call's audit volume by the length of its answer, so this row aggregates them instead.
  • denials writes only the governance row; off writes neither — spans still trace.

There is no separate OCSF-to-SIEM forwarder: the governance row rides the Audit Log's existing HMAC-signed SIEM delivery, so a Log Receiver already configured for the Audit Log picks up ACS denials with no additional configuration. There is no acs-trace Dapr component.

When a Trace sink is installed, each governed step uses the ACS OpenTelemetry span name, its verdict is attached as an acs.decision span event, and the same step and decision are emitted as idempotent OCSF events. Without an active sink, the handshake does not claim acs-trace.

Streaming

An enforce-mode interceptor that declares a governable window (streamWindow) evaluates and releases a streamed answer in windows, so it reaches the caller as it is produced. That only holds when every enforcing interceptor governing the stream declares a window: if even one does not, the whole stream is held back and evaluated as a single buffer before the first byte is released — this is the guardrail pipeline's windowed-streaming design, not an outage. Audit-only interceptors and windowed interceptors observe every governed chunk and window as it happens.

Whichever mode applies, the ACS session chain itself is not multiplied by stream length: the chain records one aggregate evidence step for the governed operation, built from the same windows and chunks the guardrail pipeline observed, rather than one chain entry per token.

Scrydon as a Guardian

Authenticated, organization-scoped workload identities can submit official signed ACS JSON-RPC envelopes through the Platform operation agent.control.evaluate. Its scope, agent-control:write, is the one Platform scope a user session or API key never satisfies: only a service workload the internal roster grants it (Agentic today) can reach the Guardian or occupy its admission budget. API Platform resolves the organization's current DLP, moderation, and classification policy for each request; it does not cache one tenant's policy for another tenant. Signed ACS ingress is bounded per organization (16 concurrent requests, 64 globally, a 2-second maximum wait); a saturated organization is answered HTTP 429 with reason: rate_limited rather than starving every other tenant's agent traffic.

Authentication and attribution answer two different questions. The Guardian authenticates the calling workload through the Platform gateway's own transport identity — never a value read out of the request body — and attributes the step to an agent from the signed envelope's metadata.agent_id. A well-formed envelope names the actual observed workflow agent; a malformed one falls back to the authenticated workload identity, so the Guardian returns the protocol's own INVALID_REQUEST instead of trusting an unverified claim.

The default Guardian supports deterministic provenance validation, all five dispositions, replay protection, and the request-content audit chain. The ACS kernel can validate canonical AgBOM snapshots and changes, but the Guardian only advertises Inspect after those pieces are composed into an active inventory surface — the shipped deployment does not compose it today. acs-crypto is not offered by the ACS envelope layer either: this package ships one working signature implementation, HMAC-SHA256. Scrydon separately signs its Better Auth audit-log checkpoints with ML-DSA-65, but that is a different system with a different audit chain, and it does not make this envelope layer's acs-crypto claim true. Malformed or unavailable policy state fails closed.

Admin visibility

Organization admins reach this control at Settings → Governance → Agent Control. The panel exposes the one ACS limit that is actually admin- configurable — maxAgentTurnsPerExecution, the Guardian's per-execution turn ceiling — plus read-only prose describing what the control plane already enforces automatically. It is not a live handshake or posture probe: the rest of ACS (audit, DLP, replay protection, Trace) is either enforced without a toggle or not yet composed in this deployment, and the panel says so rather than rendering a status badge with nothing behind it.

Current boundaries

The default deployment does not send Scrydon Agentic hooks to a customer-operated external Guardian, does not emit Agentic compaction, subagent, skill, or AgBOM lifecycle hooks, and does not claim acs-inspect, acs-inspect-dynamic, or acs-crypto. Those capabilities must be composed and negotiated before they can be advertised.

On this page

On this page