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.
- aisoc-0.1.0/.github/workflows/release.yml +62 -0
- aisoc-0.1.0/.gitignore +17 -0
- aisoc-0.1.0/CHANGELOG.md +67 -0
- aisoc-0.1.0/LICENSE +21 -0
- aisoc-0.1.0/PKG-INFO +306 -0
- aisoc-0.1.0/README.md +246 -0
- aisoc-0.1.0/SECURITY.md +36 -0
- aisoc-0.1.0/examples/README.md +37 -0
- aisoc-0.1.0/examples/backtest.py +68 -0
- aisoc-0.1.0/examples/demo.py +133 -0
- aisoc-0.1.0/examples/sample_alerts.jsonl +8 -0
- aisoc-0.1.0/examples/stub_model.py +149 -0
- aisoc-0.1.0/pyproject.toml +87 -0
- aisoc-0.1.0/src/aisoc/__init__.py +108 -0
- aisoc-0.1.0/src/aisoc/agents/__init__.py +60 -0
- aisoc-0.1.0/src/aisoc/agents/base.py +328 -0
- aisoc-0.1.0/src/aisoc/agents/campaign_detector.py +226 -0
- aisoc-0.1.0/src/aisoc/agents/detection_eng.py +380 -0
- aisoc-0.1.0/src/aisoc/agents/ir_lead.py +256 -0
- aisoc-0.1.0/src/aisoc/agents/soc_manager.py +354 -0
- aisoc-0.1.0/src/aisoc/agents/threat_hunter.py +435 -0
- aisoc-0.1.0/src/aisoc/agents/threat_intel.py +249 -0
- aisoc-0.1.0/src/aisoc/agents/tier2.py +249 -0
- aisoc-0.1.0/src/aisoc/agents/triage.py +175 -0
- aisoc-0.1.0/src/aisoc/backtest.py +174 -0
- aisoc-0.1.0/src/aisoc/bus.py +216 -0
- aisoc-0.1.0/src/aisoc/case_memory.py +1418 -0
- aisoc-0.1.0/src/aisoc/config.py +25 -0
- aisoc-0.1.0/src/aisoc/extract.py +63 -0
- aisoc-0.1.0/src/aisoc/hitl_store.py +260 -0
- aisoc-0.1.0/src/aisoc/py.typed +0 -0
- aisoc-0.1.0/src/aisoc/redis_bus.py +146 -0
- aisoc-0.1.0/src/aisoc/schemas.py +431 -0
- aisoc-0.1.0/src/aisoc/seams.py +108 -0
- aisoc-0.1.0/src/aisoc/verdict_store.py +126 -0
- aisoc-0.1.0/tests/_fakes.py +72 -0
- aisoc-0.1.0/tests/test_agents.py +267 -0
- aisoc-0.1.0/tests/test_backtest.py +139 -0
- aisoc-0.1.0/tests/test_bus.py +97 -0
- aisoc-0.1.0/tests/test_campaign_detector.py +153 -0
- aisoc-0.1.0/tests/test_case_memory.py +201 -0
- aisoc-0.1.0/tests/test_detection_eng.py +154 -0
- aisoc-0.1.0/tests/test_extract.py +41 -0
- aisoc-0.1.0/tests/test_ir_lead.py +154 -0
- aisoc-0.1.0/tests/test_schemas.py +73 -0
- aisoc-0.1.0/tests/test_soc_manager.py +115 -0
- aisoc-0.1.0/tests/test_threat_hunter.py +128 -0
- aisoc-0.1.0/tests/test_threat_intel.py +137 -0
- 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
aisoc-0.1.0/CHANGELOG.md
ADDED
|
@@ -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
|