Skip to content

feat(arcan-ergon): kernel-side adapter running ergon::Workflow as tick body (BRO-1001)#1192

Merged
broomva merged 2 commits into
mainfrom
feature/bro-1001-arcan-ergon-tick-body
May 8, 2026
Merged

feat(arcan-ergon): kernel-side adapter running ergon::Workflow as tick body (BRO-1001)#1192
broomva merged 2 commits into
mainfrom
feature/bro-1001-arcan-ergon-tick-body

Conversation

@broomva
Copy link
Copy Markdown
Owner

@broomva broomva commented May 8, 2026

Summary

Closes BRO-1001 — ships
the kernel-side adapter that runs an ergon::Workflow as the body of
one aios_runtime::KernelRuntime tick, per the corrected
architectural framing in
docs/superpowers/specs/2026-05-08-bro-1001-ergon-tick-body.md
(§6 + §10 of the original ergon spec are SUPERSEDED, see PR #1190).

  • Kernel side: TickInput.kind: TickKind (Direct vs Workflow) +
    WorkflowTickDispatcher trait + KernelRuntime::with_workflow_dispatcher
    builder + dispatch logic in execute_turn. Direct ticks behave
    exactly as before; TickKind::Workflow ticks delegate through a
    registered dispatcher.
  • arcan-ergon (new crate): WorkflowRegistry (string-keyed,
    type-erased), ModelProviderAdapter (kernel ModelProviderPort
    ergon::Provider), ToolHarnessAdapter (ToolHarnessPort
    ergon::ToolRegistry), ModeRuntimeHandle,
    KernelCapabilityResolver (real PolicyGatePort-backed
    capability gate), 3 noop adapter-trait impls (budget / score /
    attest — real wiring lands in follow-up), and
    run_workflow_as_tick that composes a full ergon::StepCtx and
    drives the workflow body. ErgonWorkflowDispatcher registers on
    the kernel.
  • arcan binary: installs an empty-registry dispatcher at
    startup; adopting daemons override to register concrete
    ergon::Workflow impls before the runtime serves.

Test plan

  • Unit tests on every module: cargo test -p arcan-ergon --lib
    (16 tests pass).
  • End-to-end integration tests on a real KernelRuntime over
    file-backed event storage: cargo test -p arcan-ergon --tests
    (4 tests pass — workflow runs end-to-end, output lands in
    journal as ergon.workflow_output Custom event, direct ticks
    still work alongside the dispatcher, unknown workflows error
    cleanly).
  • Existing arcand / arcan / aios-kernel tests still green
    (cargo test -p arcand -p aios-kernel).
  • No new clippy warnings: cargo clippy -p arcan-ergon -p aios-runtime -p aios-kernel -p arcand -p arcan --all-targets -- -D warnings.
  • Workspace formatted: cargo fmt --all.

Spec / docs

  • Spec: docs/superpowers/specs/2026-05-08-bro-1001-ergon-tick-body.md
  • Architecture: docs/architecture/agent-harness.md
  • Crate-local: crates/arcan/arcan-ergon/CLAUDE.md (full deviation
    list + invariants for follow-up agents)
  • CHANGELOG: Added + Changed entries against Unreleased.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added workflow tick execution support to the kernel runtime, enabling workflows to run as tick bodies with built-in routing and dispatch.
    • Added workflow registry and registration mechanism for managing available workflows.
    • Introduced TickKind classification to distinguish direct ticks from workflow-routed ticks.
    • Integrated workflow capability gating, tool execution, and model provider integration.
  • Chores

    • Added arcan-ergon as a new workspace crate for workflow-kernel integration.

…k body (BRO-1001)

New `arcan-ergon` crate at `crates/arcan/arcan-ergon/` that runs an
`ergon::Workflow` as the body of one `aios_runtime::KernelRuntime`
tick. Closes BRO-1001's "ergon tick-body adapter" deliverable per
the corrected architectural framing in
`docs/superpowers/specs/2026-05-08-bro-1001-ergon-tick-body.md`
(§6, §10 of the original ergon spec are SUPERSEDED — see PR #1190).

Kernel side (aios-runtime):
- `TickKind` enum on `TickInput` — `Direct` (default, kernel-owned
  single model call) vs `Workflow { name, input }` (delegated).
- `WorkflowTickDispatcher` trait + `WorkflowTickInvocation<'a>` +
  `WorkflowTickOutcome` types so workflow runners (arcan-ergon
  today, future shapes later) plug in without forcing the kernel
  to take on substrate dependencies.
- `KernelRuntime::with_workflow_dispatcher` builder method;
  dispatch logic in `execute_turn` after the
  `Perceive`/`Deliberate`/`StateEstimated` boundary.
- Existing `TickInput` call sites updated to `kind: TickKind::Direct`
  for behavior parity.

arcan-ergon modules:
- `dispatcher` — `ErgonWorkflowDispatcher` implements
  `WorkflowTickDispatcher` against a `WorkflowRegistry`.
- `registry` — type-erased boxed-executor registry keyed by name
  (`BoxedWorkflowExecutor::run_json` does the JSON boundary).
- `provider` — `ModelProviderAdapter` wraps `ModelProviderPort` as
  `ergon::Provider`, translating the kernel's flat shape and
  synthesizing `StreamEvent` sequences from the directives.
- `tools` — `ToolHarnessAdapter` wraps `ToolHarnessPort` as
  `ergon::ToolRegistry`. No capability evaluation here — that lives
  on the dedicated capability hook to avoid double-firing.
- `runtime_handle` — `ModeRuntimeHandle` exposes per-tick
  `OperatingMode` as `ergon::RuntimeHandle`.
- `hooks` — `KernelCapabilityResolver` (real `PolicyGatePort`-backed
  adapter for `CapabilityResolver`, with `ToolCapabilityMap`
  declaring per-tool caps and fail-closed on unknown tools), plus
  `NoopBudgetGate` / `NoopResponseScorer` / `NoopSoulAttester`
  permissive stand-ins for the other three adapter traits — real
  autonomic / nous / anima impls land in follow-up.
- `runner::run_workflow_as_tick` — composes a fully-built
  `ergon::StepCtx` from a `WorkflowTickInvocation`, drives the
  workflow body, returns `WorkflowTickOutcome` with JSON output +
  emitted-event count.

arcan binary now installs an `ErgonWorkflowDispatcher` (with an empty
registry by default) on the kernel runtime at startup. Adopting
daemons override the registry to register their concrete
`ergon::Workflow` impls before the runtime starts serving.

Tests: 16 unit + 4 end-to-end integration tests
(`tests/workflow_tick_e2e.rs`) verifying the workflow tick path
against a real `KernelRuntime` over file-backed event storage:
workflow runs, JSON output ends up in an `ergon.workflow_output`
`Custom` event in the journal, direct ticks still work alongside
the dispatcher, unknown workflows surface clear errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear Bot commented May 8, 2026

BRO-1001

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

Warning

Rate limit exceeded

@broomva has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 24 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 967083f1-ae37-4725-9558-a50c0c25d4db

📥 Commits

Reviewing files that changed from the base of the PR and between 8a52cc0 and 03d2e01.

📒 Files selected for processing (6)
  • Cargo.toml
  • crates/aios/aios-runtime/src/lib.rs
  • crates/arcan/arcan-ergon/src/hooks.rs
  • crates/arcan/arcan-ergon/src/lib.rs
  • crates/arcan/arcan-ergon/src/provider.rs
  • crates/arcan/arcan-ergon/tests/workflow_tick_e2e.rs
📝 Walkthrough

Walkthrough

This PR introduces workflow tick support to the kernel runtime by adding a new arcan-ergon adapter crate that executes ergon workflows as tick bodies. It extends TickInput with a TickKind enum (Direct or Workflow), introduces WorkflowTickDispatcher trait for workflow execution, and implements comprehensive adapter types bridging kernel ports to ergon traits. The changes update existing code to explicitly set TickKind::Direct and wire the dispatcher into the main runtime.

Changes

Workflow Tick Infrastructure

Layer / File(s) Summary
Kernel API Contract
crates/aios/aios-runtime/src/lib.rs
Adds TickKind enum with Direct and Workflow { name, input } variants; extends TickInput to include kind: TickKind field; introduces WorkflowTickInvocation, WorkflowTickOutcome, and WorkflowTickDispatcher trait.
Kernel Execution Logic
crates/aios/aios-runtime/src/lib.rs
Updates KernelRuntime to register optional WorkflowTickDispatcher via builder method and modifies execute_turn to route TickKind::Workflow ticks through dispatcher, emit RunStarted event, append Custom output event, then run Commit phase.
Adapter Error Handling
crates/arcan/arcan-ergon/src/error.rs
Defines AdapterError enum for boundary translation: unknown workflow, JSON serde failures, workflow execution, and port call failures with helper constructors.
Workflow Registry
crates/arcan/arcan-ergon/src/registry.rs
Implements type-erased BoxedWorkflowExecutor trait and WorkflowRegistry for JSON-in/JSON-out workflow storage and lookup with duplicate-name panic guard.
Kernel Port Adapters
crates/arcan/arcan-ergon/src/provider.rs, src/tools.rs, src/runtime_handle.rs
Implements ModelProviderAdapter translating kernel ModelProviderPort to ergon Provider; ToolHarnessAdapter dispatching tool calls to kernel; ModeRuntimeHandle exposing kernel OperatingMode.
Capability Hooks
crates/arcan/arcan-ergon/src/hooks.rs
Implements KernelCapabilityResolver with fail-closed capability gating via PolicyGatePort; provides noop adapters for budget gate, response scorer, and soul attester.
Workflow Runner
crates/arcan/arcan-ergon/src/runner.rs
Implements run_workflow_as_tick orchestrating workflow execution: resolves from registry, constructs port adapters, builds hook registry, executes in StepCtx with in-memory BufferSink.
Dispatcher
crates/arcan/arcan-ergon/src/dispatcher.rs
Implements WorkflowTickDispatcher by delegating to run_workflow_as_tick and mapping adapter errors to anyhow::Error; includes unit tests for registered and unknown workflows.
Crate Public API
crates/arcan/arcan-ergon/src/lib.rs
Declares public modules and re-exports dispatcher, error types, hook implementations, adapters, registry, runner, and runtime handle types.
Crate Metadata & Spec
crates/arcan/arcan-ergon/Cargo.toml, CLAUDE.md
Defines arcan-ergon manifest with kernel/ergon dependencies; documents module roles, critical invariants, error mapping taxonomy, and spec deviations from design.

Integration and Updates

Layer / File(s) Summary
Arcan Runtime Integration
crates/arcan/arcan/Cargo.toml, src/main.rs
Adds arcan-ergon dependency to arcan crate; wires ErgonWorkflowDispatcher into KernelRuntime with empty WorkflowRegistry during startup.
Kernel Adapter Updates
crates/aios/aios-kernel/src/lib.rs
Updates AiosKernel::tick_on_branch to explicitly set kind: TickKind::Direct in TickInput.
Daemon Executor Updates
crates/arcan/arcand/src/canonical.rs, consciousness.rs
Sets kind: TickKind::Direct explicitly in agent execution loops for both initial and continuation ticks.
E2E Integration Tests
crates/arcan/arcan-ergon/tests/workflow_tick_e2e.rs
Comprehensive test suite validating workflow tick execution with real runtime: registry integration, output event emission, direct tick compatibility, error handling.
Workspace & Changelog
Cargo.toml, CHANGELOG.md
Adds arcan-ergon as workspace member and dependency; documents new workflow tick feature and kernel runtime API changes.

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related Issues

  • broomva/life#1191: Daemon-side arcand wiring to route workflow ticks through the new TickKind::Workflow variant and construct TickBodyCtx for the dispatcher integration.

Possibly Related PRs

  • broomva/life#1151: Integrates the ergon crate's types and traits that arcan-ergon directly depends on and adapts for kernel compatibility.
  • broomva/life#1190: Documents and specifies the workflow-tick design that this PR implements end-to-end.

Poem

🐰 A workflow hops through kernel tides,
Where ticks now choose their graceful guides—
Direct or Work, a gentle fold,
Adapters weave what stories told,
In ergon's dance and protocol's cheer!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: introducing a kernel-side adapter (arcan-ergon) that runs ergon::Workflow as a tick body, with clear reference to the tracking issue (BRO-1001).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/bro-1001-arcan-ergon-tick-body

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
crates/arcan/arcan-ergon/src/hooks.rs (1)

185-319: ⚡ Quick win

Add coverage for the requires_approval branch.

This test module exercises allow/deny/unknown-tool flows, but the BRO-1001-specific path that turns requires_approval into a fail-closed error is still untested. A regression there would silently weaken approval gating.

As per coding guidelines "All new Rust code requires tests; cargo test --workspace must pass before commit".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/arcan/arcan-ergon/src/hooks.rs` around lines 185 - 319, Add a test
covering the requires_approval branch: implement a test PolicyGatePort (e.g.,
AlwaysRequiresApproval) that returns a PolicyGateDecision with requires_approval
populated, then construct a KernelCapabilityResolver (use
KernelCapabilityResolver::new) with that port and a ToolCapabilityMap entry for
the tool, call KernelCapabilityResolver::can_invoke("tool",
&serde_json::Value::Null).await and assert it returns an Err (expect_err) and
that the error message indicates an approval/blocked state (contains the tool
name or "requires_approval") so the BRO-1001 fail-closed behavior is exercised.
crates/arcan/arcan-ergon/src/lib.rs (1)

56-63: 💤 Low value

Re-exports are consistent with the crate's primary integration surface — LGTM.

Minor note: WorkflowRunInputs (used alongside ErgonWorkflowDispatcher::new in every construction site) is not re-exported at the crate root. Callers currently reach it via arcan_ergon::runner::WorkflowRunInputs. Adding it to the re-export list would make the construction API fully self-contained at the crate root, but it compiles correctly as-is.

♻️ One-line re-export addition
 pub use runner::run_workflow_as_tick;
+pub use runner::WorkflowRunInputs;
 pub use runtime_handle::ModeRuntimeHandle;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/arcan/arcan-ergon/src/lib.rs` around lines 56 - 63, Add a re-export
for WorkflowRunInputs at the crate root so callers can access it alongside
ErgonWorkflowDispatcher::new without referencing the runner module; locate the
runner module re-export (currently exposing run_workflow_as_tick) and add
WorkflowRunInputs to the pub use list (so users can import WorkflowRunInputs
directly from the crate root instead of arcan_ergon::runner::WorkflowRunInputs).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Cargo.toml`:
- Around line 19-23: The workspace.members list in Cargo.toml is not
alphabetized: "crates/arcan/arcan-ergon" is incorrectly placed between
"crates/arcan/arcan-commands" and "crates/arcan/arcan-console"; move
"crates/arcan/arcan-ergon" so the entries are sorted lexicographically (e.g.,
place "crates/arcan/arcan-ergon" after "crates/arcan/arcan-core") to restore
alphabetical order.

In `@crates/aios/aios-runtime/src/lib.rs`:
- Around line 50-61: The doc references non-existent convenience APIs; add them
to match the docs: implement Default for TickInput (impl Default for TickInput {
fn default() -> Self { Self { kind: TickKind::Direct, /* fill other fields with
sensible defaults */ } } }) and add a pub fn direct() -> Self that returns
Self::default() (or explicitly sets kind: TickKind::Direct) so callers can use
..Default::default() or TickInput::direct(); alternatively, if you prefer not to
add APIs, remove the misleading sentences from the TickInput doc that mention
Default and TickInput::direct.
- Around line 702-785: The workflow branch returns early after
dispatcher.dispatch() and emitting "ergon.workflow_output", skipping the normal
terminal run lifecycle and error bookkeeping; change the TickKind::Workflow
handling (around dispatch(), outcome, and the emitted ergon.workflow_output) so
that on success it emits the same terminal events and runs the same
commit/finalize logic as the Direct path (use append_event with
EventKind::RunCompleted/RunSucceeded as appropriate, then call emit_phase and
finalize_tick and set ctx.mode before returning to current_tick_output), and on
dispatch failure ensure you append EventKind::RunErrored and invoke the same
error-streak / circuit-breaker bookkeeping used elsewhere (i.e., don't return
early — forward errors through the existing RunErrored handling path so workflow
ticks match Direct ticks); touch symbols: workflow_dispatcher,
WorkflowTickInvocation, dispatch(), outcome, append_event(), emit_phase(),
finalize_tick(), current_tick_output().

In `@crates/arcan/arcan-ergon/src/provider.rs`:
- Around line 125-137: ModelDirective::Message currently emits
StreamEvent::TextStart/TextDelta/TextEnd with a constant id "message", causing
ID collisions when multiple Message directives are emitted; fix this by
introducing a local counter (e.g., message_idx) before the directive-processing
loop in provider.rs and incrementing it for each ModelDirective::Message, then
construct the id using that index (matching how TextDelta IDs are generated)
when emitting StreamEvent::TextStart, StreamEvent::TextDelta, and
StreamEvent::TextEnd and when pushing ContentBlock::text so each message gets a
unique id.

In `@crates/arcan/arcan-ergon/tests/workflow_tick_e2e.rs`:
- Around line 242-247: The current if-let chain using workflow_output_event and
EventKind::Custom can silently skip the inner assertions if the pattern stops
matching; change the code to explicitly unwrap or expect the Option (use
workflow_output_event.expect(...) or .unwrap()) and then match or destructure
the EventKind::Custom (e.g., let EventKind::Custom { data, .. } = event.kind
else { panic!(...) }) so the test fails loudly if the event is missing or the
kind/shape changed, then keep the assert_eq! checks on data["workflow"] and
data["output"]["message"] to ensure payload verification.

---

Nitpick comments:
In `@crates/arcan/arcan-ergon/src/hooks.rs`:
- Around line 185-319: Add a test covering the requires_approval branch:
implement a test PolicyGatePort (e.g., AlwaysRequiresApproval) that returns a
PolicyGateDecision with requires_approval populated, then construct a
KernelCapabilityResolver (use KernelCapabilityResolver::new) with that port and
a ToolCapabilityMap entry for the tool, call
KernelCapabilityResolver::can_invoke("tool", &serde_json::Value::Null).await and
assert it returns an Err (expect_err) and that the error message indicates an
approval/blocked state (contains the tool name or "requires_approval") so the
BRO-1001 fail-closed behavior is exercised.

In `@crates/arcan/arcan-ergon/src/lib.rs`:
- Around line 56-63: Add a re-export for WorkflowRunInputs at the crate root so
callers can access it alongside ErgonWorkflowDispatcher::new without referencing
the runner module; locate the runner module re-export (currently exposing
run_workflow_as_tick) and add WorkflowRunInputs to the pub use list (so users
can import WorkflowRunInputs directly from the crate root instead of
arcan_ergon::runner::WorkflowRunInputs).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14163222-08a3-4935-b02f-089bfcb47e78

📥 Commits

Reviewing files that changed from the base of the PR and between 9f2116f and 8a52cc0.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • CHANGELOG.md
  • Cargo.toml
  • crates/aios/aios-kernel/src/lib.rs
  • crates/aios/aios-runtime/src/lib.rs
  • crates/arcan/arcan-ergon/CLAUDE.md
  • crates/arcan/arcan-ergon/Cargo.toml
  • crates/arcan/arcan-ergon/src/dispatcher.rs
  • crates/arcan/arcan-ergon/src/error.rs
  • crates/arcan/arcan-ergon/src/hooks.rs
  • crates/arcan/arcan-ergon/src/lib.rs
  • crates/arcan/arcan-ergon/src/provider.rs
  • crates/arcan/arcan-ergon/src/registry.rs
  • crates/arcan/arcan-ergon/src/runner.rs
  • crates/arcan/arcan-ergon/src/runtime_handle.rs
  • crates/arcan/arcan-ergon/src/tools.rs
  • crates/arcan/arcan-ergon/tests/workflow_tick_e2e.rs
  • crates/arcan/arcan/Cargo.toml
  • crates/arcan/arcan/src/main.rs
  • crates/arcan/arcand/src/canonical.rs
  • crates/arcan/arcand/src/consciousness.rs

Comment thread Cargo.toml
Comment thread crates/aios/aios-runtime/src/lib.rs
Comment thread crates/aios/aios-runtime/src/lib.rs
Comment thread crates/arcan/arcan-ergon/src/provider.rs
Comment thread crates/arcan/arcan-ergon/tests/workflow_tick_e2e.rs Outdated
Five actionable items + two nitpicks from CodeRabbit's review:

1. **Workflow tick lifecycle parity** (`aios-runtime/src/lib.rs`):
   the workflow branch was returning early after dispatch, skipping
   the standard terminal lifecycle (StepFinished + RunFinished) and
   bubbling dispatch failures out as bare anyhow errors. Now mirrors
   the Direct path: emits StepStarted before dispatch, then on
   success appends ergon.workflow_output + StepFinished + RunFinished
   and runs Commit/Reflect/Sleep finalize; on failure appends
   RunErrored, increments error_streak / uncertainty / error_budget,
   drops the runtime into Recover mode, and still finalizes the tick
   so the journal is coherent. The integration test
   `unknown_workflow_routes_to_run_errored_and_recover_mode` (renamed
   + rewritten from `unknown_workflow_yields_error`) asserts the new
   shape: tick returns Ok, mode=Recover, error_streak >= 1, and an
   `RunErrored` event in the journal mentioning the workflow name.

2. **Workspace.members alphabetization** (`Cargo.toml`):
   `crates/arcan/arcan-ergon` was wedged between arcan-commands and
   arcan-console. Moved to the alphabetically correct slot after
   arcan-core.

3. **TickInput doc accuracy** (`aios-runtime/src/lib.rs`): the
   doc-comment referenced `Default::default()` and a `direct()`
   helper that don't exist. Removed the misleading sentence — the
   field is just a normal struct field today and call sites set
   `kind: TickKind::Direct` explicitly.

4. **Provider Message id collision** (`arcan-ergon/src/provider.rs`):
   `ModelDirective::Message` was emitting `StreamEvent::TextStart` /
   `TextDelta` / `TextEnd` with a constant `id="message"`, so a
   completion with multiple Message directives would fail downstream
   sink pairing. Now uses a directive-local `message_idx` counter
   so each Message gets a unique `message-N` id.

5. **Test assertion hardening**
   (`tests/workflow_tick_e2e.rs::workflow_tick_emits_output_event`):
   was using `if let Some && let Custom` chain that would silently
   pass if the event went missing or its kind changed. Now uses
   `.expect()` + `let-else` so a regression fails the test loudly.

Nitpicks:

- **`requires_approval` test** (`arcan-ergon/src/hooks.rs`): added
  `approval_required_tool_fails_until_flow_lands` — exercises the
  BRO-1001 fail-closed path when `PolicyGateDecision.requires_approval`
  is non-empty (the kernel approval flow isn't bridged into ergon
  hooks yet; this regression-tests the temporary fail-closed behavior
  so a future bridge implementation can update the test deliberately).

- **`WorkflowRunInputs` re-export** (`arcan-ergon/src/lib.rs`): now
  re-exported at the crate root alongside `run_workflow_as_tick` so
  callers can construct `ErgonWorkflowDispatcher::new(registry,
  inputs)` purely through `arcan_ergon::*` without reaching into
  the `runner` module.

Test counts: 17 unit (was 16) + 4 e2e (4) all green.
Workspace: cargo clippy --all-targets -- -D warnings clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@broomva broomva merged commit 8cf6d79 into main May 8, 2026
15 checks passed
@broomva broomva deleted the feature/bro-1001-arcan-ergon-tick-body branch May 8, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant