Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
44b540e
docs(automations): record Coven Automations v1 program status (coven#…
CompleteDotTech Aug 30, 2026
fb302f2
docs: plan cryptographic fresh-user and biometric assurance proofs (c…
CompleteDotTech Aug 30, 2026
fcc026c
docs: operationalize Coven Automations v1 tracker roadmap and drift c…
CompleteDotTech Aug 30, 2026
957715f
docs: consolidate Coven security policy, threat boundary, and support…
CompleteDotTech Aug 30, 2026
9847960
docs(pairing): plan TUI QR bootstrap and E2EE mobile pairing (refs #7…
CompleteDotTech Aug 30, 2026
d07bf61
docs: remove duplicate local public documentation (#870)
CompleteDotTech Aug 30, 2026
a781c9e
docs(807): record shipped reliability scorecard status decision (#863)
CompleteDotTech Aug 30, 2026
c703693
docs: record issue 670 docs program status on main
CompleteDotTech Aug 30, 2026
753f4bb
fix(agents): enforce target input-guardrail parity across handoffs
CompleteDotTech Aug 30, 2026
ba3a154
docs(cli): document the deterministic JSON help contract (#868)
CompleteDotTech Aug 30, 2026
c4c9ccb
docs(automations): specify coven.automations.v1 schemas, state machin…
CompleteDotTech Aug 30, 2026
39feb6d
refactor: extract route/version authority gate from coven-cli api
CompleteDotTech Aug 30, 2026
cb5c825
test: add the end-to-end certification matrix and machine-readable re…
CompleteDotTech Aug 30, 2026
95494ff
feat: derive certification applicability from candidate channel and s…
CompleteDotTech Aug 31, 2026
79df1a9
fix: stop macOS certification rows claiming release coverage that doe…
CompleteDotTech Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
- run: python3 scripts/check-ci-workflow-test.py
- run: node --test scripts/package-github-release-test.mjs
- run: node --test scripts/release-stress-test.mjs
- run: node --test scripts/certification-receipt-test.mjs
- run: scripts/check-workflows.sh

rust-lint-linux:
Expand Down
756 changes: 33 additions & 723 deletions README.md

Large diffs are not rendered by default.

319 changes: 215 additions & 104 deletions SECURITY.md

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions crates/coven-agents/src/guardrail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ impl GuardrailVerdict {
}

#[async_trait]
/// Checks the original user input before the starting agent runs.
/// Checks the bounded ingress of an agent before its first model turn.
///
/// Input guardrails attached only to handoff targets do not run in this MVP.
/// The runner evaluates the starting agent's input guardrails against the
/// original user input before the run begins, and a handoff target's input
/// guardrails against the same original user input before the target's first
/// model turn, so entering an agent through a handoff cannot grant access that
/// direct entry would reject. Input guardrails never inspect a serialized
/// transcript; the structured task/context manifest for delegated invocations
/// is a separate contract (see OpenCoven/coven#804).
pub trait InputGuardrail<C>: Send + Sync
where
C: Sync,
Expand Down
101 changes: 68 additions & 33 deletions crates/coven-agents/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,60 @@ where
error
}

/// Checks an agent's input guardrails against the run's original user
/// input.
///
/// This is the single ingress path shared by direct starts and handoffs.
/// The evaluated input is always the original user input that started the
/// run — the same bounded string a direct start would check — so entering
/// an agent through a handoff cannot grant access that direct entry would
/// reject. A `GuardrailChecked` event is emitted per guardrail with the
/// owning agent's identity, and a policy rejection or guardrail
/// implementation error fails the run before the agent's next model turn
/// or tool execution.
async fn check_input_guardrails(
&self,
agent: &Agent<C>,
input: &str,
context: &C,
) -> Result<(), RunError> {
for guardrail in &agent.input_guardrails {
let verdict = guardrail.check(input, context).await.map_err(|source| {
self.fail(
&agent.id,
RunFailureKind::InputGuardrail,
RunError::GuardrailFailed {
agent: agent.id.clone(),
guardrail: guardrail.name().to_owned(),
stage: GuardrailStage::Input,
source,
},
)
})?;
let allowed = verdict == GuardrailVerdict::Allow;
self.observer.on_event(&RunEvent::GuardrailChecked {
agent: agent.id.clone(),
guardrail: guardrail.name().to_owned(),
stage: GuardrailStage::Input,
allowed,
});
if let GuardrailVerdict::Reject { reason } = verdict {
return Err(self.fail(
&agent.id,
RunFailureKind::InputGuardrail,
RunError::GuardrailRejected {
agent: agent.id.clone(),
guardrail: guardrail.name().to_owned(),
stage: GuardrailStage::Input,
reason,
},
));
}
}

Ok(())
}

/// Runs `starting_agent` to a final output.
///
/// Every run emits exactly one `RunStarted` event followed by exactly one
Expand All @@ -139,6 +193,12 @@ where
/// user message, assistant message, and tool calls that preceded it, so
/// those items are handed back rather than dropped. The runner never
/// appends a failed run's items to the session store.
///
/// Input guardrails are enforced at every agent boundary: the starting
/// agent's before its first model turn, and each handoff target's against
/// the same original user input before that target's first model turn, so
/// a handoff cannot reach an agent that would have rejected the input as
/// the starting agent.
pub async fn run(
&self,
starting_agent: impl Into<AgentId>,
Expand Down Expand Up @@ -188,39 +248,8 @@ where
)
})?;

for guardrail in &current.input_guardrails {
let verdict = guardrail.check(&input, context).await.map_err(|source| {
self.fail(
&current.id,
RunFailureKind::InputGuardrail,
RunError::GuardrailFailed {
agent: current.id.clone(),
guardrail: guardrail.name().to_owned(),
stage: GuardrailStage::Input,
source,
},
)
})?;
let allowed = verdict == GuardrailVerdict::Allow;
self.observer.on_event(&RunEvent::GuardrailChecked {
agent: current.id.clone(),
guardrail: guardrail.name().to_owned(),
stage: GuardrailStage::Input,
allowed,
});
if let GuardrailVerdict::Reject { reason } = verdict {
return Err(self.fail(
&current.id,
RunFailureKind::InputGuardrail,
RunError::GuardrailRejected {
agent: current.id.clone(),
guardrail: guardrail.name().to_owned(),
stage: GuardrailStage::Input,
reason,
},
));
}
}
self.check_input_guardrails(&current, &input, context)
.await?;

let mut model_items = match (&options.session_id, &self.session) {
(Some(session_id), Some(session)) => {
Expand Down Expand Up @@ -460,6 +489,12 @@ where
name: handoff.name.clone(),
});
current = target;
// Ingress parity: the handoff target enforces the same input
// policy it would enforce as the starting agent, checked
// against the original user input, before its first model turn
// or tool execution.
self.check_input_guardrails(&current, &input, context)
.await?;
continue;
}

Expand Down
Loading