0Pricing
AI Agents · Lekcja

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 reference

Zapis 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 key

Odpytywanie 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

  1. Niezmienny rejestr działań agentów
  2. Egzekwowanie zasad dotyczących działań agentów
  3. Zgodność z regulacjami: GDPR i SOC2
  4. Bramki zatwierdzania z udziałem człowieka
← Powrót do AI Agents