aisoc 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. aisoc-0.1.0/.github/workflows/release.yml +62 -0
  2. aisoc-0.1.0/.gitignore +17 -0
  3. aisoc-0.1.0/CHANGELOG.md +67 -0
  4. aisoc-0.1.0/LICENSE +21 -0
  5. aisoc-0.1.0/PKG-INFO +306 -0
  6. aisoc-0.1.0/README.md +246 -0
  7. aisoc-0.1.0/SECURITY.md +36 -0
  8. aisoc-0.1.0/examples/README.md +37 -0
  9. aisoc-0.1.0/examples/backtest.py +68 -0
  10. aisoc-0.1.0/examples/demo.py +133 -0
  11. aisoc-0.1.0/examples/sample_alerts.jsonl +8 -0
  12. aisoc-0.1.0/examples/stub_model.py +149 -0
  13. aisoc-0.1.0/pyproject.toml +87 -0
  14. aisoc-0.1.0/src/aisoc/__init__.py +108 -0
  15. aisoc-0.1.0/src/aisoc/agents/__init__.py +60 -0
  16. aisoc-0.1.0/src/aisoc/agents/base.py +328 -0
  17. aisoc-0.1.0/src/aisoc/agents/campaign_detector.py +226 -0
  18. aisoc-0.1.0/src/aisoc/agents/detection_eng.py +380 -0
  19. aisoc-0.1.0/src/aisoc/agents/ir_lead.py +256 -0
  20. aisoc-0.1.0/src/aisoc/agents/soc_manager.py +354 -0
  21. aisoc-0.1.0/src/aisoc/agents/threat_hunter.py +435 -0
  22. aisoc-0.1.0/src/aisoc/agents/threat_intel.py +249 -0
  23. aisoc-0.1.0/src/aisoc/agents/tier2.py +249 -0
  24. aisoc-0.1.0/src/aisoc/agents/triage.py +175 -0
  25. aisoc-0.1.0/src/aisoc/backtest.py +174 -0
  26. aisoc-0.1.0/src/aisoc/bus.py +216 -0
  27. aisoc-0.1.0/src/aisoc/case_memory.py +1418 -0
  28. aisoc-0.1.0/src/aisoc/config.py +25 -0
  29. aisoc-0.1.0/src/aisoc/extract.py +63 -0
  30. aisoc-0.1.0/src/aisoc/hitl_store.py +260 -0
  31. aisoc-0.1.0/src/aisoc/py.typed +0 -0
  32. aisoc-0.1.0/src/aisoc/redis_bus.py +146 -0
  33. aisoc-0.1.0/src/aisoc/schemas.py +431 -0
  34. aisoc-0.1.0/src/aisoc/seams.py +108 -0
  35. aisoc-0.1.0/src/aisoc/verdict_store.py +126 -0
  36. aisoc-0.1.0/tests/_fakes.py +72 -0
  37. aisoc-0.1.0/tests/test_agents.py +267 -0
  38. aisoc-0.1.0/tests/test_backtest.py +139 -0
  39. aisoc-0.1.0/tests/test_bus.py +97 -0
  40. aisoc-0.1.0/tests/test_campaign_detector.py +153 -0
  41. aisoc-0.1.0/tests/test_case_memory.py +201 -0
  42. aisoc-0.1.0/tests/test_detection_eng.py +154 -0
  43. aisoc-0.1.0/tests/test_extract.py +41 -0
  44. aisoc-0.1.0/tests/test_ir_lead.py +154 -0
  45. aisoc-0.1.0/tests/test_schemas.py +73 -0
  46. aisoc-0.1.0/tests/test_soc_manager.py +115 -0
  47. aisoc-0.1.0/tests/test_threat_hunter.py +128 -0
  48. aisoc-0.1.0/tests/test_threat_intel.py +137 -0
  49. aisoc-0.1.0/tests/test_tier2.py +170 -0
@@ -0,0 +1,62 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a version tag (e.g. v0.1.0) is pushed.
4
+ # Uses PyPI Trusted Publishing (OIDC) — no API token is stored anywhere.
5
+ on:
6
+ push:
7
+ tags:
8
+ - "v*"
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ name: Test (py${{ matrix.python-version }})
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ matrix:
19
+ # Floor and ceiling of the supported range, so a tag can't publish
20
+ # something that breaks on the requires-python lower bound.
21
+ python-version: ["3.10", "3.12"]
22
+ steps:
23
+ - uses: actions/checkout@v5
24
+ - uses: actions/setup-python@v6
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ - run: python -m pip install --upgrade pip
28
+ - run: pip install -e ".[dev]"
29
+ - run: python -m pytest -q
30
+
31
+ build:
32
+ name: Build distribution
33
+ needs: test
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v5
37
+ - uses: actions/setup-python@v6
38
+ with:
39
+ python-version: "3.x"
40
+ - run: python -m pip install --upgrade build
41
+ - run: python -m build
42
+ - run: python -m pip install --upgrade twine && python -m twine check dist/*
43
+ - uses: actions/upload-artifact@v7
44
+ with:
45
+ name: dist
46
+ path: dist/
47
+
48
+ publish:
49
+ name: Publish to PyPI
50
+ needs: build
51
+ runs-on: ubuntu-latest
52
+ environment:
53
+ name: pypi
54
+ url: https://pypi.org/project/aisoc/
55
+ permissions:
56
+ id-token: write # required for trusted publishing (OIDC)
57
+ steps:
58
+ - uses: actions/download-artifact@v8
59
+ with:
60
+ name: dist
61
+ path: dist/
62
+ - uses: pypa/gh-action-pypi-publish@release/v1
aisoc-0.1.0/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ *.sqlite
11
+ *.sqlite3
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .mypy_cache/
15
+ .hypothesis/
16
+ .coverage
17
+ htmlcov/
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ All notable changes to **aisoc** are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/), and the project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.0] — 2026-06-20
8
+
9
+ The first slices of the kernel: the event contract, the bus, the injection
10
+ seams, and case memory — the event-log read models. Enough to model a SOC as an
11
+ event-sourced team, replay it, and query its memory. The role agents land next.
12
+
13
+ ### Added
14
+ - **Event contract** (`aisoc.schemas`) — `BusEvent` envelope plus the 12 event
15
+ types the roles emit (`AlertReceived` … `ShiftSummary`), shared `Verdict` and
16
+ `Severity` vocabularies, a `parse_event()` dispatcher, and an `EVENT_TYPES`
17
+ registry. Pydantic-validated; forward-compatible (`extra="allow"`, unknown
18
+ `event_type` falls back to the base envelope).
19
+ - **Bus** (`aisoc.bus`) — the `Bus` Protocol plus `InMemoryBus`, a stdlib-only
20
+ implementation with faithful consumer-group delivery, ack/redelivery, and a
21
+ replayable audit mirror. Zero infrastructure.
22
+ - **Redis bus** (`aisoc.redis_bus`) — `RedisBus`, the durable multi-process path
23
+ over Redis Streams, behind the `redis` extra.
24
+ - **Injection seams** (`aisoc.seams`) — `ChatModel`, `StructuredChatModel`,
25
+ `AlertSource`, and `ToolProvider` Protocols. Bring your own LLM, alert source,
26
+ and tools; the kernel stays vendor-neutral.
27
+ - **Case memory** (`aisoc.case_memory` + `aisoc.verdict_store`,
28
+ `aisoc.hitl_store`) — read models over the event log: a case index built by
29
+ replaying the audit stream, `recall_similar_cases` / `recall_for_ticket`
30
+ (precedent by shared strong indicators), `get_case_reasoning` (a grounded,
31
+ recorded reasoning trace for interrogation), `find_campaign_clusters`
32
+ (cross-incident clustering with stable campaign ids + dedup state),
33
+ `compute_trends` (per-role cost/latency/accuracy-vs-ground-truth rollup), and
34
+ flag-gated precedent-injection blocks (`AISOC_CASE_RECALL=1`). The bus is
35
+ injected, so the whole layer runs offline on the in-memory bus.
36
+ - **Agent framework** (`aisoc.agents`, behind the `agent` extra) — the `Agent`
37
+ base over the three seams: a consumer-group run loop on the injected bus, a
38
+ generic bind-tools-and-iterate tool-call loop with per-event budget
39
+ enforcement, JSON-decision parsing, and graceful shutdown.
40
+ - **The role team** on that base. Per-ticket agents: `TriageAgent`
41
+ (`AlertReceived` → `AlertTriaged`), `Tier2Agent` (confirm/refine/escalate),
42
+ `IRLeadAgent` (containment plan + a human-gated `ActionProposed`), and
43
+ `ThreatIntelAgent` (actor attribution). Windowed `run_once` roles:
44
+ `detection_eng` (rule-tuning), `soc_manager` (shift summary), `threat_hunter`
45
+ (proactive hunts), and `campaign_detector` (cross-incident clustering). Each
46
+ reasons with the injected chat model, calls only the tools the provider allows
47
+ its role, and takes no real-world action without a human decision.
48
+ - **`Agent.notify` hook** — a no-op-by-default side-effect hook fired after each
49
+ successful `publish`, with `event.event_type` to dispatch on. The kernel still
50
+ notifies no one (results are just events on the bus); this is the seam an
51
+ integration overrides to *also* push an out-of-band notification — a chat card,
52
+ a page, a webhook — when a role publishes. Exceptions it raises are logged and
53
+ swallowed so a flaky notifier can't break the case.
54
+ - **Backtest harness** (`aisoc.backtest`) — `run_backtest` replays a recorded
55
+ alert log through the agent chain on an in-memory bus and reports each role's
56
+ verdict per ticket, read back from the bus audit log; `BacktestResult.accuracy`
57
+ scores against ground truth and `.coverage` reports per-role reach.
58
+ `load_alerts` reads a JSONL log. `Agent.drain()` runs a role to completion
59
+ without a blocking loop — the non-looping counterpart to `run()`.
60
+ - **Examples** (`examples/`) — two no-infrastructure runnable scripts: `demo.py`
61
+ (the full team works four cases end to end, then the windowed roles read the
62
+ log back) and `backtest.py` (replay + score, with a planted miss), both on a
63
+ deterministic offline stub model.
64
+ - **Helpers** — `aisoc.extract` (dependency-free IOC/timestamp parsing) and
65
+ `aisoc.config.data_dir` (sidecar SQLite location, `AISOC_DATA_DIR`).
66
+ - Test suite covering bus semantics, the event contract, the parsers, and an
67
+ end-to-end case-memory flow (index → recall → trace → campaign → trends).
aisoc-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vinay Vobbilichetty
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
aisoc-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,306 @@
1
+ Metadata-Version: 2.4
2
+ Name: aisoc
3
+ Version: 0.1.0
4
+ Summary: An AI SOC kernel: an event-sourced, multi-agent SOC where a role-based team of LLM agents works a case over a shared event bus — with case memory, human-in-the-loop gating, and a replayable audit log. Bring your own LLM, alert source, and tools.
5
+ Project-URL: Homepage, https://github.com/vinayvobbili/aisoc
6
+ Project-URL: Repository, https://github.com/vinayvobbili/aisoc
7
+ Project-URL: Issues, https://github.com/vinayvobbili/aisoc/issues
8
+ Author-email: Vinay Vobbilichetty <vinayvobbilichetty11@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Vinay Vobbilichetty
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: ai-soc,case-memory,dfir,event-sourcing,human-in-the-loop,incident-response,langgraph,llm-agents,multi-agent,security-operations,soar,soc,threat-intel
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: Intended Audience :: Information Technology
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Security
41
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
42
+ Requires-Python: >=3.10
43
+ Requires-Dist: pydantic>=2
44
+ Provides-Extra: agent
45
+ Requires-Dist: langchain-core>=0.3; extra == 'agent'
46
+ Requires-Dist: langgraph>=0.2; extra == 'agent'
47
+ Provides-Extra: dev
48
+ Requires-Dist: build>=1.0; extra == 'dev'
49
+ Requires-Dist: langchain-core>=0.3; extra == 'dev'
50
+ Requires-Dist: langgraph>=0.2; extra == 'dev'
51
+ Requires-Dist: mypy>=1.8; extra == 'dev'
52
+ Requires-Dist: pydantic>=2; extra == 'dev'
53
+ Requires-Dist: pytest>=7; extra == 'dev'
54
+ Requires-Dist: redis>=4.5; extra == 'dev'
55
+ Requires-Dist: ruff>=0.4; extra == 'dev'
56
+ Requires-Dist: twine>=5.0; extra == 'dev'
57
+ Provides-Extra: redis
58
+ Requires-Dist: redis>=4.5; extra == 'redis'
59
+ Description-Content-Type: text/markdown
60
+
61
+ # aisoc 🛡️🤖
62
+
63
+ **An AI SOC kernel** — a Security Operations Center modeled as a *team*, not a
64
+ classifier. A set of role-based LLM agents (triage, Tier 2, IR Lead, Threat
65
+ Intel, Threat Hunter, Detection Engineer, and a SOC Manager over the top) work a
66
+ case together over a shared, **event-sourced** bus, with a **human in the loop**
67
+ on every consequential action — and a **case memory** so the team gets better
68
+ over a campaign instead of re-deriving the same conclusion ticket by ticket.
69
+
70
+ > A single triage prompt can *label* an alert. It can't *run a case* — pull the
71
+ > context, weigh it against what the org has seen before, propose a containment,
72
+ > and leave a cited record of why. aisoc is the kernel for the second thing.
73
+
74
+ aisoc owns the **kernel**: the event contract, the bus, the case-memory read
75
+ models, and the agent framework. It owns **none of your environment**. You
76
+ inject three seams — an LLM, an alert source, and a tool registry — so the same
77
+ agent code runs in a unit test, a zero-infra demo, or production against your
78
+ real SOAR/EDR/SIEM.
79
+
80
+ ---
81
+
82
+ ## Why event-sourced?
83
+
84
+ Agents don't call each other directly. Each one **consumes** events off a shared
85
+ bus and **publishes** its own. That single decision buys a lot:
86
+
87
+ - **Auditable end to end.** Every verdict, escalation, and human decision is an
88
+ event on the log — nothing is implicit.
89
+ - **Replayable by construction.** Reconstruct any case from the log, or replay a
90
+ recorded log through the agents offline to backtest a prompt change.
91
+ - **Composable.** Add, pause, or swap a role without rewiring the others.
92
+
93
+ ---
94
+
95
+ ## Install
96
+
97
+ ```bash
98
+ pip install aisoc # core: event contract + in-memory bus
99
+ pip install "aisoc[redis]" # durable, multi-process bus (Redis Streams)
100
+ pip install "aisoc[agent]" # the LangGraph role agents (roadmap)
101
+ ```
102
+
103
+ Requires Python 3.10+.
104
+
105
+ ## Quick start — zero infrastructure
106
+
107
+ The in-memory bus needs no Redis, no daemon, nothing. It's the default for the
108
+ demo, the test suite, and the offline backtest.
109
+
110
+ ```python
111
+ from aisoc import InMemoryBus, AlertTriaged, STREAM_TRIAGE, parse_event
112
+
113
+ bus = InMemoryBus()
114
+
115
+ bus.publish(STREAM_TRIAGE, AlertTriaged(
116
+ correlation_id="TICKET-42", produced_by="triage", ticket_id="TICKET-42",
117
+ verdict="true_positive_malicious", confidence=0.93,
118
+ summary="beaconing to known-bad C2", priority_score=8,
119
+ ))
120
+
121
+ # Read the audit log back — every event is mirrored there.
122
+ for raw in bus.replay():
123
+ event = parse_event(raw) # typed model, dispatched on event_type
124
+ print(event.event_type, event.ticket_id, event.verdict)
125
+ ```
126
+
127
+ Consumer-group delivery (one event → one consumer per group, held pending until
128
+ acked, redelivered on failure) works the same on the in-memory bus as on Redis:
129
+
130
+ ```python
131
+ batch = bus.consume_batch([STREAM_TRIAGE], group="tier2", consumer="worker-1")
132
+ for stream, msg_id, event in batch:
133
+ ... # do the work
134
+ bus.ack(stream, "tier2", msg_id) # ack so it isn't redelivered
135
+ ```
136
+
137
+ ## The three seams
138
+
139
+ aisoc ships no integrations of its own — you inject them (see `aisoc.seams`).
140
+ They're `runtime_checkable` Protocols, so duck-typing is enough; you never
141
+ subclass anything.
142
+
143
+ - **`ChatModel`** — the LLM the agents reason with. Any object with an
144
+ `invoke()` satisfies it; a LangChain `BaseChatModel` drops in directly, so you
145
+ can point it at OpenAI, a local model, or a failover wrapper.
146
+ - **`AlertSource`** — where cases come from. Implement `poll()` to pull from
147
+ your SOAR/SIEM/ticketing system and yield normalized `AlertReceived` events.
148
+ - **`ToolProvider`** — the tools a role may call. `tools_for(role)` returns the
149
+ callables that role is allowed to use; aisoc fans them out per role and never
150
+ inspects them.
151
+
152
+ Swap a real model for a stub, a live SOAR for a fixture — the same agent code
153
+ runs in a test, a demo, or production.
154
+
155
+ ## The event contract
156
+
157
+ Every event subclasses `BusEvent` and is dispatched on a `Literal` `event_type`:
158
+
159
+ | Event | Emitted by | Meaning |
160
+ |---|---|---|
161
+ | `AlertReceived` | ingestion | a new ticket landed |
162
+ | `AlertTriaged` | triage | first-pass verdict + confidence |
163
+ | `CaseEscalated` | any role | handoff to a higher tier |
164
+ | `Tier2Analysis` | Tier 2 | refined verdict + escalation decision |
165
+ | `IRPlan` | IR Lead | written containment/eradication/recovery plan |
166
+ | `ActionProposed` | IR Lead | a real-system action awaiting human approval |
167
+ | `ActionDecision` | human | approve/reject (execution stays your integration) |
168
+ | `ThreatIntelReport` | Threat Intel | actor attribution + technique mapping |
169
+ | `DetectionTuningReport` | Detection Eng | rule-tuning opportunities over a window |
170
+ | `HuntingReport` | Threat Hunter | proactive findings (advisory) |
171
+ | `CampaignDetected` | Campaign Detector | cross-incident cluster (advisory) |
172
+ | `ShiftSummary` | SOC Manager | windowed readout of the shift |
173
+
174
+ Actions are **proposed, never auto-executed** — anything with real-world impact
175
+ (containing a host, blocking an indicator) is recorded as an `ActionProposed`
176
+ and gated on a human `ActionDecision`, captured with who approved it, when, and
177
+ why.
178
+
179
+ ## Case memory
180
+
181
+ Memory isn't a bolted-on vector store — it's a **read projection over the event
182
+ log** the SOC is already writing. Index the audit stream, then query it. The bus
183
+ is injected, so the whole layer runs offline on the in-memory bus:
184
+
185
+ ```python
186
+ from aisoc import InMemoryBus, case_memory
187
+
188
+ bus = InMemoryBus()
189
+ # ... agents publish events onto `bus` as they work cases ...
190
+
191
+ case_memory.backfill(bus=bus) # fold the audit log into a case index
192
+
193
+ case_memory.recall_for_ticket("4187", bus=bus) # prior cases sharing strong indicators
194
+ case_memory.get_case_reasoning("4187", bus=bus) # the recorded reasoning trace (for interrogation)
195
+ case_memory.find_campaign_clusters(window_days=14) # cross-incident campaigns
196
+ case_memory.compute_trends(window_days=30) # per-role cost / latency / accuracy-vs-ground-truth
197
+ ```
198
+
199
+ Recall is **retrieve mechanically, judge semantically** — it surfaces precedent
200
+ by shared hard indicators (actor, hash, domain, IP, CVE), and the relevance call
201
+ stays with the model. Interrogation is **deterministic recall + grounded
202
+ narration**: `get_case_reasoning` returns what actually happened from the record,
203
+ never a re-derived rationale. Precedent injection into agent prompts is
204
+ flag-gated (`AISOC_CASE_RECALL=1`) so you can A/B whether it actually helps.
205
+
206
+ ## The agents
207
+
208
+ A role agent is "which events do I care about, and what do I publish in
209
+ response." The base (`aisoc.agents.Agent`, in the `agent` extra) owns the rest:
210
+ the consumer-group run loop, a generic tool-call loop (bind the role's tools,
211
+ invoke, run the calls it asks for, feed results back, repeat until it answers or
212
+ the per-event budget runs out), and graceful shutdown. Everything is injected —
213
+ the bus, the chat model, the tool provider — so the same class runs in a unit
214
+ test against a stub, in the demo, or in production:
215
+
216
+ ```python
217
+ from aisoc import InMemoryBus
218
+ from aisoc.agents import TriageAgent
219
+
220
+ agent = TriageAgent(bus=bus, model=my_chat_model, tools=my_tool_provider)
221
+ agent.run() # consume soc.alerts, publish soc.triage verdicts
222
+ ```
223
+
224
+ There are two shapes of role. **Per-ticket roles** subclass `Agent` and run a
225
+ consume loop — the case moves down the chain as each publishes the next event:
226
+
227
+ - `TriageAgent` — alert → first-pass verdict (`AlertReceived` → `AlertTriaged`)
228
+ - `Tier2Agent` — deeper look, confirm/refine/escalate (`AlertTriaged` → `Tier2Analysis` / `CaseEscalated`)
229
+ - `IRLeadAgent` — containment plan + a human-gated `ActionProposed` (`CaseEscalated` → `IRPlan` + `ActionProposed`)
230
+ - `ThreatIntelAgent` — actor attribution + technique mapping (`IRPlan` → `ThreatIntelReport`)
231
+
232
+ **Windowed roles** are scheduled `run_once(*, bus, model=...)` functions that
233
+ replay the audit log over a window and publish a report — call them on a timer:
234
+
235
+ ```python
236
+ from aisoc.agents import detection_eng, soc_manager, threat_hunter, campaign_detector
237
+
238
+ soc_manager.run_once(bus=bus, model=model, window_hours=8) # ShiftSummary
239
+ detection_eng.run_once(bus=bus, model=model, window_hours=24) # DetectionTuningReport
240
+ threat_hunter.run_once(bus=bus, model=model, window_hours=24) # HuntingReport
241
+ campaign_detector.run_once(bus=bus, model=model, window_days=14) # CampaignDetected
242
+ ```
243
+
244
+ Every verdict is recorded for the case-memory trend rollup, and any real-world
245
+ action (containment, a block) is published as an `ActionProposed` gated on a
246
+ human `ActionDecision` — never auto-executed.
247
+
248
+ ## Backtest a change before it ships
249
+
250
+ Because every case is just events on a replayable log, you can run a recorded
251
+ alert log back through the *current* agents offline and measure how often they
252
+ get it right — no Redis, no live SOAR, and a stub model instead of a real LLM:
253
+
254
+ ```python
255
+ from aisoc.backtest import load_alerts, run_backtest
256
+
257
+ alerts = load_alerts("recorded_alerts.jsonl")
258
+ result = run_backtest(alerts, model=my_model, tools=my_tools)
259
+
260
+ result.accuracy(ground_truth, role="triage") # fraction matching known truth
261
+ result.verdict_for("5004", "triage") # what a given case resolved to
262
+ ```
263
+
264
+ Swap in a real model and rerun to see whether a prompt or model change moves
265
+ accuracy without regressing the rest. `examples/backtest.py` is a complete,
266
+ runnable version with a planted miss for the harness to catch.
267
+
268
+ ## Try it — no infrastructure
269
+
270
+ ```bash
271
+ pip install "aisoc[agent]"
272
+ python examples/demo.py # the whole SOC works four cases on an in-memory bus
273
+ python examples/backtest.py # replay a recorded log and score it against truth
274
+ ```
275
+
276
+ Both run with a deterministic stub model — no API key, no Redis, no real LLM.
277
+ See [`examples/`](examples/).
278
+
279
+ ## Roadmap
280
+
281
+ aisoc is being extracted from a production multi-agent SOC into a reusable,
282
+ vendor-neutral kernel. Landing in slices:
283
+
284
+ - ✅ **Kernel seams** — the three injection Protocols.
285
+ - ✅ **Bus + event contract** — in-memory + Redis Streams, replayable audit log.
286
+ - ✅ **Case memory** — event-log read models: recall similar prior cases as
287
+ precedent, score per-role accuracy against ground truth, reconstruct a cited
288
+ reasoning trace for any ticket, and cluster cross-incident campaigns. Runs
289
+ offline on the in-memory bus.
290
+ - ✅ **Agent framework + the role team** — the `Agent` base over the three
291
+ seams (consumer-group run loop, a generic bind-tools-and-iterate tool-call
292
+ loop, per-event budgets) plus the full team on top: triage, Tier 2, IR Lead,
293
+ and Threat Intel as per-ticket agents, and Detection Engineer, SOC Manager,
294
+ Threat Hunter, and Campaign Detector as windowed `run_once` roles.
295
+ - ✅ **Backtest harness + runnable demo** — `aisoc.backtest.run_backtest`
296
+ replays a recorded alert log through the agents offline and scores their
297
+ verdicts against ground truth; `Agent.drain()` runs a role to completion
298
+ without a blocking loop. Two no-infrastructure example scripts (`examples/`)
299
+ show the whole SOC and the backtest on the in-memory bus with a stub model.
300
+
301
+ The design story behind case memory is written up here:
302
+ [Giving an AI SOC a Memory](https://vinayvobbili.github.io/posts/soc-in-a-box-case-memory/).
303
+
304
+ ## License
305
+
306
+ MIT © Vinay Vobbilichetty