Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 31 additions & 3 deletions s16code/events/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,35 @@ async def process(self, event: EventEnvelope, *, llm: TextLLM, transport: Any =
record, fresh = self.store.ingest(event)
if not fresh:
return {"accepted": False, "duplicate": True, "record": record}
matching = [item for item in self.store.subscriptions()
if self._matches(item, event) and not self._self_caused(item, event)]
matching: list[Subscription] = []
ignored_now: list[dict[str, Any]] = []
now = datetime.now(UTC)
for item in self.store.subscriptions():
if not self._matches(item, event):
continue
if self._self_caused(item, event):
refusal = {
"subscription_id": item.id,
"relevant": False,
"reason": (
f"ignored actor {event.actor!r}: listed on subscription "
f"{item.id!r} ignore_actors"
),
"goal": "",
"ignored": True,
"decided_at": now.isoformat(),
}
self.store.record_refusal(
control="ignore_actors",
reason=refusal["reason"],
event=event.model_dump(mode="json"),
subscription_id=item.id,
detail={"actor": event.actor},
)
self.store.add_decision(event.source, event.id, refusal)
ignored_now.append(refusal)
continue
matching.append(item)

async def decide(subscription: Subscription) -> dict[str, Any]:
async with self._slots:
Expand Down Expand Up @@ -168,4 +195,5 @@ async def decide(subscription: Subscription) -> dict[str, Any]:
else:
clean.append(outcome)
return {"accepted": True, "duplicate": False, "sequence": record["sequence"],
"matched": len(matching), "decisions": clean}
"matched": len(matching), "ignored": len(ignored_now),
"decisions": ignored_now + clean}
20 changes: 20 additions & 0 deletions tests/test_autonomous_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,26 @@ async def irrelevant(_prompt, _system):
assert runtime.calls == []


@pytest.mark.asyncio
async def test_ignore_actors_are_recorded_and_do_not_start_agents(tmp_path):
store, runtime = EventStore(tmp_path), RecordingRuntime()
store.put_subscription(subscription(ignore_actors=["spam-bot"]))
engine = AutonomousEventEngine(store, runtime)

async def should_not_run(_prompt, _system):
raise AssertionError("ignored actors must not reach the relevance gate")

outcome = await engine.process(event(actor="spam-bot"), llm=should_not_run)
assert outcome["accepted"] is True
assert outcome["matched"] == 0
assert outcome["ignored"] == 1
assert outcome["decisions"][0]["relevant"] is False
assert "spam-bot" in outcome["decisions"][0]["reason"]
assert runtime.calls == []
refusals = store.refusals()
assert refusals and refusals[0]["control"] == "ignore_actors"


@pytest.mark.asyncio
async def test_one_event_can_launch_independent_subscriptions_concurrently(tmp_path):
store, runtime = EventStore(tmp_path), RecordingRuntime()
Expand Down