Niezmienny rejestr działań agentów
Dzienniki tylko do dopisywania, podpisy kryptograficzne i ścieżki audytowe wykrywające manipulacje.
Niezmienny rejestr działań agentów to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.
Dlaczego agenci potrzebują niezmiennych dzienników?
Agenci podejmują działania autonomicznie — często bez udziału człowieka w każdym kroku. Gdy coś pójdzie nie tak, trzeba móc odpowiedzieć na pytania: co agent zrobił, kiedy, w czyim imieniu i dlaczego?
Niezmienny dziennik odporny na niezauważalną manipulację uniemożliwia późniejsze modyfikowanie ścieżki audytowej.
Schemat wpisu dziennika
Każdy wpis dziennika zawiera: znacznik czasu, tożsamość agenta, tożsamość użytkownika, typ działania, parametry, wynik oraz hash łączący go z poprzednim wpisem. Taka struktura obsługuje zarówno wyszukiwanie, jak i wykrywanie manipulacji.
from dataclasses import dataclass
from typing import Any
@dataclass
class AuditEntry:
timestamp: float # Unix time (UTC)
agent_id: str
user_id: str
action_type: str
parameters: dict
result: dict
session_id: str
prev_hash: str # hash of previous entry
entry_hash: str = '' # computed after construction
if __name__ == '__main__':
entry = AuditEntry(
timestamp=1717000000.0, agent_id='agent-1', user_id='user-42',
action_type='send_email', parameters={'to': 'a@b.com'},
result={'status': 'sent'}, session_id='sess-1', prev_hash='0' * 64
)
print('Audit entry created:')
print(' agent_id:', entry.agent_id)
print(' action_type:', entry.action_type)
print(' prev_hash:', entry.prev_hash)
Łańcuchowanie hashy w celu wykrywania manipulacji
Hash każdego wpisu jest obliczany na podstawie jego treści oraz hasha poprzedniego wpisu. Jeśli po fakcie dowolny wpis zostanie zmodyfikowany, zmieni się jego hash, co unieważni wszystkie kolejne hashe. Dzięki temu można wykryć manipulację.
import hashlib, json, time
def compute_entry_hash(entry: dict, prev_hash: str) -> str:
content = json.dumps(entry, sort_keys=True, default=str)
payload = f'{prev_hash}:{content}'
return hashlib.sha256(payload.encode()).hexdigest()
def create_log_entry(agent_id: str, user_id: str, action_type: str,
parameters: dict, result: dict,
session_id: str, prev_hash: str) -> dict:
entry = {
'timestamp': time.time(),
'agent_id': agent_id,
'user_id': user_id,
'action_type': action_type,
'parameters': parameters,
'result': result,
'session_id': session_id,
'prev_hash': prev_hash
}
entry['entry_hash'] = compute_entry_hash(
{k: v for k, v in entry.items() if k != 'entry_hash'},
prev_hash
)
return entry
if __name__ == '__main__':
entry = create_log_entry('agent-1', 'user-42', 'send_email',
{'to': 'a@b.com'}, {'status': 'sent'},
'sess-1', prev_hash='0' * 64)
print('New log entry hash:', entry['entry_hash'])
print('Chained from prev_hash:', entry['prev_hash'])
Dziennik tylko do dopisywania w procesie
Najprostsza implementacja to lista w pamięci z metodą, która zezwala wyłącznie na dopisywanie. Nie są udostępniane metody usuwania ani aktualizowania wpisów. W środowisku produkcyjnym rozwiązanie to jest oparte na bazie danych lub magazynie obiektów.
class AppendOnlyLog:
def __init__(self):
self._entries: list[dict] = []
self._last_hash = 'GENESIS'
def append(self, agent_id: str, user_id: str, action_type: str,
parameters: dict, result: dict, session_id: str) -> dict:
entry = create_log_entry(
agent_id, user_id, action_type,
parameters, result, session_id,
self._last_hash
)
self._entries.append(entry)
self._last_hash = entry['entry_hash']
return entry
def verify_integrity(self) -> bool:
running_hash = 'GENESIS'
for e in self._entries:
expected = compute_entry_hash(
{k: v for k, v in e.items() if k != 'entry_hash'},
running_hash
)
if expected != e['entry_hash']:
return False
running_hash = e['entry_hash']
return True
@property
def entries(self):
return list(self._entries) # return copy, not referenceZapis do PostgreSQL z semantyką tylko dopisywania
W środowisku produkcyjnym należy zapisywać dzienniki w tabeli bazy danych, w której użytkownik aplikacji nie ma uprawnień UPDATE ani DELETE. Aby zapewnić rzeczywiste działanie tylko do dopisywania, należy odebrać te uprawnienia na poziomie bazy danych.
import psycopg2, json
# Table DDL (run once):
# CREATE TABLE agent_audit_log (
# id BIGSERIAL PRIMARY KEY,
# timestamp DOUBLE PRECISION NOT NULL,
# agent_id TEXT NOT NULL,
# user_id TEXT NOT NULL,
# action_type TEXT NOT NULL,
# parameters JSONB NOT NULL,
# result JSONB NOT NULL,
# session_id TEXT NOT NULL,
# prev_hash TEXT NOT NULL,
# entry_hash TEXT NOT NULL UNIQUE
# );
# REVOKE UPDATE, DELETE ON agent_audit_log FROM app_user;
def write_to_db(conn, entry: dict):
with conn.cursor() as cur:
cur.execute(
'INSERT INTO agent_audit_log '
'(timestamp,agent_id,user_id,action_type,parameters,result,session_id,prev_hash,entry_hash) '
'VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)',
(entry['timestamp'], entry['agent_id'], entry['user_id'],
entry['action_type'], json.dumps(entry['parameters']),
json.dumps(entry['result']), entry['session_id'],
entry['prev_hash'], entry['entry_hash'])
)
conn.commit()Zapis do S3 z Object Lock (WORM)
AWS S3 Object Lock w trybie COMPLIANCE uniemożliwia dowolnemu użytkownikowi — w tym administratorowi root konta — usuwanie lub zastępowanie obiektów przez okres przechowywania. Jest to rzeczywisty zapis jednokrotny dla dzienników audytowych.
import boto3, json, time
s3 = boto3.client('s3', region_name='us-east-1')
BUCKET = 'your-audit-log-bucket-worm'
def write_to_s3_worm(entry: dict):
key = f'audit/{entry["agent_id"]}/{int(entry["timestamp"])}/{entry["entry_hash"][:16]}.json'
s3.put_object(
Bucket=BUCKET,
Key=key,
Body=json.dumps(entry).encode(),
ContentType='application/json',
ObjectLockMode='COMPLIANCE',
ObjectLockRetainUntilDate='2030-01-01T00:00:00Z' # 4+ year retention
)
return keyOdpytywanie dziennika audytowego
Dziennik audytowy służy nie tylko do celów zgodności — jest również przydatny operacyjnie. Należy odpytywać go w celu debugowania zachowania agenta, odtworzenia sesji lub udzielenia odpowiedzi na pytanie „dlaczego agent wykonał X?”.
def query_session(log: AppendOnlyLog, session_id: str) -> list[dict]:
return [
e for e in log.entries
if e['session_id'] == session_id
]
def query_user_actions(log: AppendOnlyLog, user_id: str,
action_type: str = None) -> list[dict]:
entries = [e for e in log.entries if e['user_id'] == user_id]
if action_type:
entries = [e for e in entries if e['action_type'] == action_type]
return sorted(entries, key=lambda e: e['timestamp'])
def count_actions_by_type(log: AppendOnlyLog) -> dict:
from collections import Counter
return dict(Counter(e['action_type'] for e in log.entries))Oczyszczanie danych wrażliwych przed zapisaniem
Dzienniki audytowe powinny rejestrować działania agenta, a nie ujawniać dane wrażliwe. Przed zapisaniem należy usunąć dane PII (hasła, klucze API i numery kart kredytowych) z parametrów i wyników.
import re
SENSITIVE_KEYS = {'password', 'api_key', 'secret', 'token', 'credit_card', 'ssn'}
def sanitize(obj, depth: int = 0) -> dict | list | str:
if depth > 5:
return '[MAX_DEPTH]'
if isinstance(obj, dict):
return {
k: '[REDACTED]' if k.lower() in SENSITIVE_KEYS
else sanitize(v, depth + 1)
for k, v in obj.items()
}
elif isinstance(obj, list):
return [sanitize(i, depth + 1) for i in obj]
elif isinstance(obj, str):
# Redact anything that looks like an API key
return re.sub(r'(sk-|Bearer\s)[A-Za-z0-9_-]{16,}', '[REDACTED]', obj)
return obj
if __name__ == '__main__':
record = {'user': 'alice', 'password': 'hunter2', 'note': 'call me at sk-abcdefghijklmnopqrstuv'}
print('Sanitized record:', sanitize(record))
Rotacja i archiwizacja dzienników
Aktywne dzienniki stale rosną. Należy je rotować: przenosić wpisy starsze niż 90 dni do chłodnego magazynu (np. S3 Glacier) i kompresować je. Łańcuch musi pozostać spójny, dlatego należy zachować ostatni hash każdego rotowanego segmentu.
import time, json, gzip
def rotate_log(log: AppendOnlyLog, max_age_days: int = 90) -> dict:
cutoff = time.time() - max_age_days * 86400
archive = [e for e in log.entries if e['timestamp'] < cutoff]
remaining = [e for e in log.entries if e['timestamp'] >= cutoff]
if not archive:
return {'archived': 0, 'remaining': len(remaining)}
# Compress archive
archive_bytes = gzip.compress(json.dumps(archive).encode())
archive_file = f'/tmp/audit_archive_{int(time.time())}.json.gz'
with open(archive_file, 'wb') as f:
f.write(archive_bytes)
# Update in-memory log
log._entries = remaining
return {'archived': len(archive), 'remaining': len(remaining), 'file': archive_file}Alerty dotyczące podejrzanych wzorców
Należy monitorować dziennik audytowy w czasie rzeczywistym pod kątem nietypowych wzorców: nagłego wzrostu liczby działań wysokiego ryzyka, działań wykonywanych poza godzinami pracy lub powtórzenia tego samego działania więcej niż N razy w krótkim przedziale czasu.
import time
from collections import defaultdict
HIGH_RISK_ACTIONS = {'delete_user', 'send_mass_email',
'transfer_funds', 'export_all_data'}
BURST_LIMIT = 5
BURST_WINDOW = 60 # seconds
action_timestamps: dict[str, list] = defaultdict(list)
def check_suspicious(entry: dict) -> list[str]:
alerts = []
action = entry['action_type']
if action in HIGH_RISK_ACTIONS:
alerts.append(f'HIGH_RISK_ACTION: {action} by {entry["agent_id"]}')
now = time.time()
action_timestamps[action].append(now)
recent = [t for t in action_timestamps[action] if now - t < BURST_WINDOW]
action_timestamps[action] = recent
if len(recent) > BURST_LIMIT:
alerts.append(f'ACTION_BURST: {action} called {len(recent)}x in {BURST_WINDOW}s')
return alerts
if __name__ == '__main__':
entry = {'action_type': 'delete_user', 'agent_id': 'agent-9'}
for _ in range(6):
alerts = check_suspicious(entry)
print('Alerts on 6th call:', alerts)
Weryfikowanie integralności dziennika na żądanie
Należy uruchamiać sprawdzanie integralności jako część potoku monitorowania. Trzeba natychmiast wysłać alert po wykryciu niepowodzenia weryfikacji — oznacza ono błąd w kodzie rejestrowania albo aktywną próbę manipulacji.
def verify_and_alert(log: AppendOnlyLog) -> dict:
is_valid = log.verify_integrity()
total = len(log.entries)
result = {
'total_entries': total,
'integrity_ok': is_valid
}
if not is_valid:
import logging
alert_logger = logging.getLogger('agent.integrity_alert')
alert_logger.critical(
'AUDIT LOG INTEGRITY FAILURE: tampered or corrupted entries detected. '
'Total entries: %d. Initiating incident response.', total
)
result['alert_sent'] = True
return result
# Run periodically via cron or monitoring hook
# verify_and_alert(global_audit_log)Co wykrywa łańcuchowanie hashy w dzienniku audytowym?
Łańcuchowanie hashy to mechanizm kryptograficzny, który zapewnia dziennikowi audytowemu właściwość wykrywania manipulacji. Zrozumienie tego, co wykrywa, ma podstawowe znaczenie przy projektowaniu dzienników audytowych.
Podsumowanie niezmiennego rejestrowania działań
Niezmienne dzienniki agentów wykorzystują: łańcuchowanie hashy do wykrywania manipulacji, magazyn tylko do dopisywania (odebranie uprawnień UPDATE/DELETE na poziomie bazy danych), magazyn obiektów WORM (S3 Object Lock) do przechowywania danych zgodnie z wymogami regulacyjnymi, oczyszczanie danych PII przed zapisem oraz alerty o anomaliach w czasie rzeczywistym dotyczące podejrzanych wzorców działań.
Często zadawane pytania
Czy lekcja „Niezmienny rejestr działań agentów” jest bezpłatna?
Tak — pełny tekst „Niezmienny rejestr działań agentów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.
Co nauczysz się w „Niezmienny rejestr działań agentów”?
Dzienniki tylko do dopisywania, podpisy kryptograficzne i ścieżki audytowe wykrywające manipulacje. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Agents?
Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Niezmienny rejestr działań agentów”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?
Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Niezmienny rejestr działań agentów
- Egzekwowanie zasad dotyczących działań agentów
- Zgodność z regulacjami: GDPR i SOC2
- Bramki zatwierdzania z udziałem człowieka