Strona główna / Artykuły

Wszystko to plugin: projekt Cordis stojący za systemem DeepSeek Harness

Jak DeepSeek Harness tworzy środowisko działania agenta na podstawie pluginów, wykorzystując koncepcje Cordis takie jak odwracalne efekty, reaktywne zależności oraz zastępowalny cykl agenta.

Wydawca: REACTAPP.TOP19 min czytania4403 słów

Źródła: DeepSeek Quietly Built a Different Kind of AI Agent Framework

Zwykły schemat i jego słabe punkty

Większość frameworki agentowych opiera się na tych samych składnikach: modelu językowym, kilku narzędziach, przechowywanej rozmowie oraz pętli obejmującej je wszystkie. Poniższy szkic pokazuje jego strukturę.

User asks something
      ↓
LLM thinks
      ↓
LLM calls a tool
      ↓
Tool returns data
      ↓
LLM continues
      ↓
Final answer

Z definicji to już stanowi agenta. Problemy pojawiają się, gdy system rośnie. Gromadzą się wymagania: liczni dostawcy modeli, narzędzia przeglądarkowe, wykonywanie w środowisku shell, serwery MCP, procesy zatwierdzania, pamięć, telemetryka, autoryzacja, różne interfejsy użytkownika, agenci w tle, spersonalizowane polecenia, uprawnienia oraz szybkie ładowanie. Wkrótce każda część zależy od wszystkich innych. Zmiana dostawcy modeli wpływa na pętlę agentów, usunięcie wtyczki pozostawia słuchacze zdarzeń, a ponowne załadowanie narzędzia może uszkodzić agenty działające w tym momencie. Wtyczki polegają również na usługach, które mogą pojawić się lub zniknąć w trakcie działania.

DeepSeek Harness podchodzi do tego zupełnie inaczej. Jego centralna idea brzmi niemal zbyt prosto: wszystko jest wtyczką, łącznie z elementami, które większość frameworków określa jako kluczowe.

Pod tym wszystkim znajduje się framework o nazwie Cordis, który powstał na podstawie artykułu badawczego zatytułowanego „A Programming Paradigm for Spatiotemporal Composability”. Ten artykuł jest interesujący, ponieważ projekt nie jest zwykłym sposobem organizacji modułów JavaScript. On adresuje głębsze pytanie inżynieryjne: jak komponenty mogą się łączyć, odchodzić, zastępować siebie oraz zmieniać zależności w działającym programie, bez tego, by środowisko wykonawcze popadło w chaos?

Czym jest harness

DeepSeek Harness, skracane jako dsh, to open-source harness dla agentów wydany przez DeepSeek. Harness stanowi infrastrukturę otaczającą model. Niezależnie od tego, czy chodzi o model DeepSeek, Claude, GPT, Gemini, czy lokalny, harness zajmuje się wszystkim, co jest z nim związane.

                    ┌───────────────┐
                    │      LLM      │
                    └───────┬───────┘
                            │
                 ┌──────────▼──────────┐
                 │    Agent Harness    │
                 │                     │
                 │ Prompt construction │
                 │ Tool execution      │
                 │ Session history     │
                 │ Permissions         │
                 │ Streaming           │
                 │ Agent lifecycle     │
                 │ Model adapters      │
                 └─────────────────────┘

Różnica ta ma ogromne znaczenie. Zazwyczaj zwraca się uwagę na to, jak inteligentny jest model, jednak stworzenie czegoś takiego jak Claude Code, Codex, Cursor czy długotrwały autonomiczny agent pokazuje, że czas działania aplikacji ma równie dużą wagę. DeepSeek Harness obsługuje ten czas działania poprzez zestaw dynamicznie komponowanych pluginów, a nie jako jedną monolityczną aplikację – decyzja ta pochodzi bezpośrednio z Cordis.

Zagadka zależności

Wyobraź sobie stworzenie agenta do programowania z trzech pluginów.

GitPlugin
TerminalPlugin
CodingAgent

Agent do kodowania wymaga Git i Terminala. Podczas uruchamiania najprostszym rozwiązaniem jest załadowanie ich po kolei.

load Git
load Terminal
load CodingAgent

Latwo, a większość frameworki do iniekcji zależności radzi sobie z tym dobrze. Trudniejszym pytaniem jest to, co się stanie, jeśli podczas działania programu zniknie plugin do Git. Może zostać zaktualizowany, jego konfiguracja mogła ulec zmianie, użytkownik mógł go wyłączyć lub może zostać zastąpiona inna implementacja. W takim przypadku środowisko wykonawcze musi zatrzymać wszystko, co polega na tym usłudze.

CodingAgent
   ↓ -------->depends on
GitPlugin

Jeśli Git zniknie, agent kodujący nie powinien dalej udawać, że istnieje. A jeśli później pojawi się inny dostawca Git, być może agent powinien znów zacząć pracować. Kryją się tu dwa oddzielne problemy, które w artykule Cordis nazywane są kompozowalnością temporalną i kompozowalnością przestrzenną.

Kompozowalność temporalna: czyste wycofanie

Załóżmy, że plugin się ładuje i rejestruje słuchacz zdarzeń.

events.on("message", handler)

Później plugin jest wyłączany, a pytanie brzmi, co się dzieje z obsługą zdarzeń. Jeśli nic jej nie usunie, plugin przestaje istnieć pod względem nazwy, ale jego część nadal działa. Należy dodać, że plugin mógł uruchomić timery, nawiązać połączenia z bazą danych, zarejestrować polecenia, zmienić wspólny stan, dodać narzędzia, dołączyć obsługę zdarzeń oraz uruchomić procesy w tle. Wyłączenie go oznacza odwrócenie każdego z tych kroków.

To jest temporalna kompozycyjność: komponent, który dołącza do systemu, powinien pozwolić systemowi w pełni cofnąć to, co ten komponent zrobił. Cordis traktuje takie zmiany jako efekty możliwe do odwrócenia.

Efekty bez żargonu

Efekt to po prostu coś, co robi twój kod i co zmienia świat zewnętrzny lub stan w czasie wykonywania. Weźmy na przykład rejestrację narzędzia.

toolRegistry.add("search", searchTool)

Przed połączeniem rejestr wygląda w ten sposób.

tools = {}

Następnie przechowuje nowy narzędzie.

tools = {
    search: searchTool
}

Operacja przesunięcia to dodawanie.

add(searchTool)

Cordis pragnie jednak czegoś więcej. Chce, aby operacja miała swoją własną funkcję odwrotną. Koncepcyjnie efekt łączy działanie z jego odwołaniem.

Effect:
     do:   add search tool
     undo: remove search tool

Bez notacji matematycznej pomysł brzmi w następujący sposób.

state --effect--> new state
new state --inverse--> original state

To odwrotne działanie jest kluczowym elementem, a Cordis nazywa cały ten zestaw efektem odwracalnym.

Każda zmiana wymaga jej usunięcia

Cordis ujawnia to za pomocą czegoś w rodzaju poniższego wzorca, w którym funkcja wprowadza zmianę i zwraca funkcję, która ją cofa.

ctx.effect(() => {
    registerSomething()
    return () => {
        unregisterSomething()
    }
})

Czas wykonywania aplikuje efekt, przechowuje funkcję czyszczenia i wywołuje ją później, gdy wtyczka przestaje działać.

apply effect
      ↓
store cleanup function
      ↓
plugin runs
      ↓
plugin unloads
      ↓
run cleanup

Gdy wtyczka zawiera kilka efektów, są one stosowane w jednej kolejności.

effect A
effect B
effect C

Czyszczenie odbywa się następnie w odwrotnej kolejności, tak jak rozpakowuje się stos.

undo C
undo B
undo A

Dlaczego odwrotnie? Rozważmy plugin, który uruchamia się w ten sposób.

1. open database
2. start service using database
3. start agent using service

Zamykanie w tym samym porządku byłoby niebezpieczne, ponieważ baza danych zostaje zamknięta, gdy inne programy nadal ją używają.

close database
stop service
stop agent

Bezpieczny zamówienie jest jego przeciwieństwem.

stop agent
stop service
close database

To zasada „ostatni w, pierwszy wy”.

Last In
First Out

Cordis wbudowuje to zachowanie w zarządzanie cyklem życia komponentów.

Samodzielne usuwanie w odwrotnej kolejności jest niewystarczające

Tutaj artykuł staje się ciekawszy. Załóżmy, że Plugin A zmienia wspólną wartość, następnie Plugin B zmienia tę samą wartość, a potem A próbuje przywrócić stan początkowy. To może usunąć modyfikację dokonaną przez B.

Initial: x = 10

Plugin A: x = 20

Plugin B: x = 30

Plugin A unloads: restore x = 10

B jest nadal aktywne, ale jego stan zniknął. Dlatego efekty odwracalne są bezpieczne jedynie przy określonych założeniach niezależności, które w artykule sformalizowano jako niezależność efektów. Mówiąc potocznie, dwa efekty są niezależne, gdy ich zastosowanie lub odwrócenie nie unieważnia znaczenia drugiego. Rejestracja różnych narzędzi zazwyczaj spełnia ten warunek, ponieważ oddziałują one na różne miejsca.

Plugin A registers tool "search"
Plugin B registers tool "calculator"

Zastąpienie tej samej konfiguracji prawdopodobnie nie pomoże.

Plugin A replaces configuration X
Plugin B also replaces configuration X

To rozróżnienie pojawia się ponownie, gdy mowa jest o systemie kontekstowym Cordis.

Kompozycyjność przestrzenna: wiedza o tym, czego potrzebuje plugin

Kompozycyjność czasowa dotyczy pytania, czy komponent można bezpiecznie usunąć. Kompozycyjność przestrzenna dotyczy pytania, czy system rozumie, od czego zależy dany komponent. Wyobraźmy sobie ten plugin.

CodingAgent

Potrzebuje trzech rzeczy.

LLM
ToolRegistry
SessionStore

Klasyczny kod może korzystać z zmiennych globalnych.

global.llm
global.tools
global.sessions

W takim przypadku architektura nie posiada rzeczywistej wiedzy na temat zależności. Cordis czyni je jawnymi. Koncepcyjnie plugin deklaruje to, co wstrzykuje.

inject: [
    "llm",
    "tools",
    "sessions"
]

Teraz środowisko wykonywania może widzieć wymagania wtyczki jako drzewo.

CodingAgent
    ├── requires LLM
    ├── requires ToolRegistry
    └── requires SessionStore

To umożliwia reakcję w momencie zmian tych usług.

Zależności reaktywne

To może być najlepszy pomysł w Cordis. Załóżmy, że komponent potrzebuje LLM.

LLM

Początkowo nic nie istnieje.

LLM unavailable

Dlatego komponent nie może zostać aktywny. Później ładowany jest plugin dostawcy, który dostarcza jeden taki komponent.

OpenAI Plugin loads
      ↓
provides LLM
      ↓
dependency becomes satisfied
      ↓
component activates

Jeśli ten dostawca zostanie usunięty, jego funkcjonalność również zniknie.

OpenAI Plugin unloads
      ↓
LLM disappears
      ↓
dependency becomes unsatisfied
      ↓
dependent component unloads

Gdy załaduje się inny dostawca, funkcjonalność zostaje przywrócona.

DeepSeek Plugin
      ↓
provides LLM
      ↓
component becomes active again

Zależności nie są sprawdzane tylko raz podczas uruchamiania. Są reaktywne, dlatego w artykule mowa jest o reaktywnych koeffectach.

Efekty versus koeffecty

Terminologia może wydawać się skomplikowana, ale rozróżnienie jest proste. Efekt opisuje to, co komponent zmienia, przechodząc z wtyczki do środowiska.

Plugin → Environment

Przykładami są te działania.

register tool
open database
subscribe to event
start timer

Koeffect opisuje coś, czego komponent wymaga od swojego otoczenia, przepływając w drugą stronę.

Environment → Plugin

Typowymi przykładami są te potrzeby.

need an LLM
need a logger
need session storage
need a tool registry

Po połączeniu tych dwóch elementów wymagania płyną w jednym kierunku.

requirements
Environment ───────────────────> Plugin

podczas gdy efekty wracają.

Environment <─────────────────── Plugin
                    effects

To jest podstawowa idea stojąca za Cordis. Komponent określa, czego potrzebuje, co dostarcza oraz jakie odwracalne efekty wywołuje.

I NEED these services.
I PROVIDE these services.
I PERFORM these reversible effects.

Wtedy silnik wykonawczy koordynuje cykl życia aplikacji.

Pochodzenie terminu „spatiotemporalny”

Teraz tytuł artykułu nabiera sensu. Kompozycyjność przestrzenna dotyczy relacji pomiędzy komponentami.

A depends on B
B provides X
C depends on X

Kompozowalność temporalna dotyczy sposobu, w jaki komponenty zmieniają się w czasie.

load
activate
replace
deactivate
unload
reload

Razem tworzą całą filozofię.

Spatiotemporal composability = dependency-aware composition + reversible lifecycle changes

Obiekt kontekstu

DeepSeek Harness nie przekazuje ręcznie dziesiątek usług za pośrednictwem konstruktorów. Wtyczki wchodzą ze sobą w interakcję poprzez wspólny kontekst, zwykle nazywany w ten sposób.

ctx

Koncepcyjnie przedstawia główne usługi.

ctx
├── llm
├── tools
├── sessions
├── agents
├── agentLoop
├── logger
└── ...

Jednak ctx to coś więcej niż tylko obiekt JavaScript wypełniony zmiennymi globalnymi. Dzięki niemu pluginy mogą znajdować swoje zależności, oferować usługi, rejestrować efekty, przysłuchiwać się wydarzeniom, izolować zasoby, przechwytywać dostęp do usług oraz koordynować cykle życia, dlatego Cordis przedstawia ten kontekst jako jednolitą abstrakcję.

Od teorii do rzeczywistego projektu

DeepSeek Harness udostępnia swoje główne podsystemy poprzez ten wspólny kontekst.

ctx.sessions
ctx.systemPrompt
ctx.tools
ctx.agents
ctx.agentLoop
ctx.llm

Zauważ coś niezwykłego: nawet pętla agentów to po prostu usługa.

ctx.agentLoop

Zatem ten pętla może zostać zastąpiona, a nie jest to święty kod frameworka. To prowadzi do kluczowej zasady projektu: nie istnieje uprzywilejowana część, którą każda funkcja musiałaby naprawiać.

Dlaczego pętla jako plugin ma znaczenie

Większość frameworków agentów zawiera centralną pętlę przypominającą tę tutaj.

while not finished:
response = model(history)
    if response.tool_call:
        result = execute_tool(response.tool_call)
        history.append(result)
    else:
        return response

Następnie każda zaawansowana funkcja zaczyna ją modyfikować. Zatwierdzenie przed uruchomieniem poleceń? Popraw loopa. Śledzenie? Popraw loopa. Próby ponowne, delegacja między wieloma agentami, inny backend do inferencji? Popraw loopa. W końcu staje się hubem, od którego zależą narzędzia, modele i pamięć.

                    Agent Loop
                   /    |    \
                  /     |     \
             tools    models   memory
             /          |        \
        approvals     retries    tracing

Pętla staje się „bóstwem” aplikacji. DeepSeek Harness unika tego, pozwalając pętli uczestniczyć w tej samej architekturze pluginów co wszystkie inne elementy.

Kształt jednej rundy agenta

W rzeczywistości interakcja przechodzi przez etapy mniej więcej w takiej kolejności.

turn/start
    ↓
claim incoming user input
    ↓
assemble prompt + available tools
    ↓
agent/pre-step
    ↓
step/start
    ↓
record user message
    ↓
reconstruct conversation history
    ↓
send model request
    ↓
stream assistant response
    ↓
tool call?
   │
   ├── yes → execute tool
   │          record result
   │          continue another step
   │
   └── no  → finish
    ↓
step/end
    ↓
turn/end

Ważne jest to, że każdy etap ujawnia wydarzenia oraz punkty łączeniowe usług, co daje pluginom możliwość uczestnictwa bez konieczności przepisywania całego środowiska działania.

Wydarzenia typu waterfall jako middleware

Cordis obsługuje kilka trybów uruchamiania wydarzeń. Jeden z przydatnych trybów, waterfall, funkcjonuje jak middleware. Wyobraź sobie żądanie przechodzące przez łańcuch operacji.

request
   ↓
Plugin A
   ↓
Plugin B
   ↓
Plugin C
   ↓
  LLM

Wtyczka B może otoczyć wykonywanie zadania obsługą, która przyjmuje następny krok.

async function handler(next) {
    console.log("before")
    const result = await next()
    console.log("after")
    return result
}

Wykonywanie jest następnie nawarstwiane, przy czym każdy plugin działa przed i po wewnętrznym wywołaniu.

Plugin A before
    Plugin B before
        Plugin C before
            LLM
        Plugin C after
    Plugin B after
Plugin A after

To obejmuje uprawnienia, przepisywanie żądań, telemetrię, buforowanie, routowanie modeli, sprawdzanie bezpieczeństwa oraz iniekcję kontekstu – żadna z tych funkcji nie musi być bezpośrednio włączona do adaptera modelu.

Model znajduje się za granicą możliwości

Pakiet ten abstrahuje również LLM, dzięki czemu agent nie musi znać każdego SDK dostawcy. Komunikuje się z jedną usługą.

Agent
  ↓
ctx.llm
  ↓
Model Adapter
  ↓
Provider API

Jeden dostawca może to zaimplementować dla API DeepSeek.

DeepSeek API

Kolejny dotyczy OpenAI.

OpenAI

Trzeci dotyczy Anthropic.

Anthropic

Pozostała część architektury agenta obejmuje tę samą funkcjonalność LLM. Dokumentacja Harness czasami określa to jako „szew funkcjonalności”, składający się z definicji usługi i dostawcy usług.

Service Definition
   ↓
Service Provider
   ↓
Consumer

Dla modelu językowego ta łańcuchowa struktura biegnie od interfejsu do konkretnego dostawcy.

LLM interface
     ↓
OpenAI provider
     ↓
Agent loop

Dlatego dodanie dostawcy modeli nie oznacza koniecznie przepisywania silnika wykonawczego. Wystarczy dostarczyć inną implementację tej samej funkcjonalności.

Sesje oparte na zdarzeniach

Rozmowy nie są przechowywane przez system jako jeden duży, modyfikowalny tabliczka, który stanowi źródło prawdy. Zamiast tego sesje są przechowywane jako strumień zdarzeń, do którego można jedynie dodawać nowe elementy.

turn/start
user/message
assistant/chunk
assistant/chunk
assistant/message
tool/call
tool/result
assistant/message
turn/end

Historię modelu można odbudować na podstawie tych zdarzeń, dzięki czemu journal sesji stanowi źródło prawdy.

Session log = source of truth

To zastępuje powszechną alternatywę.

Mutable chat history = source of truth

Dlaczego agenci czerpią korzyści z event sourcing

Agenci stały się na tyle złożone, że przechowywanie zwykłej listy wiadomości często nie jest wystarczające.

[
  {"role": "user", "content": "..."},
  {"role": "assistant", "content": "..."}
]

Debugowanie może wymagać znajomości tego, który narzędzie zostało użyte i które wywołano, który model napisał odpowiedź, jaki prompt systemu został zastosowany, gdzie rozpoczęła się prośba, gdzie zakończył się krok rozumowania, jaka konfiguracja była aktywna, czy próba połączenia została powtórzona oraz jaki wynik w formie strumienia wrócił. Narzędzie to rejestruje ustrukturyzowane zdarzenia sesji, dzięki czemu można odtworzyć wszystko, co model mógł zobaczyć. Jedna z zasad jego projektu stanowi, że wszystko, co jest widoczne dla modelu, powinno dać się odzyskać z dziennika. Zamiast spekulować na temat tego, dlaczego agent postąpił w określony sposób, można odtworzyć prośbę, która za tym stała.

Fibry i stan cyklu życia

Cordis reprezentuje działające komponenty za pomocą struktur typu Fiber. Nie ma to nic wspólnego z fibrami systemu operacyjnego. Fiber w Cordis to instancja komponentu w czasie wykonywania, która przechodzi przez różne stany.

PENDING
   ↓
LOADING
   ↓
ACTIVE
   ↓
UNLOADING
   ↓
DISPOSED

Istnieją również stany awarii. Co ważniejsze, włókno rejestruje kilka informacji na temat samego siebie.

what dependencies I need
what providers currently satisfy them
what effects I have created
what cleanup functions belong to me

Gdy zmieniają się zależności, fiber może odświeżyć swój cykl życia.

Dlaczego ważna jest tożsamość dostawcy

Załóżmy, że komponent zależy od usługi bazy danych.

database

W tej chwili jest dostarczany przez jednego dostawcę.

DatabaseProvider A

Podczas gorącego ładowania jest zastępowane innym.

DatabaseProvider B

Nawet gdy oba zwracają równoważne obiekty JavaScript, Cordis potrafi odróżnić tożsamości dostawców. Powodem jest to, że znaczenie zależności nie polega wyłącznie na tym, iż dana wartość istnieje.

"value exists"

Może to również oznaczać, że wartość pochodzi z innej instancji komponentu.

"this value came from a different component instance"

Zmiana dostawcy może więc sprawić, że elementy zależne zostaną prawidłowo ponownie załadowane, co zapobiega wielu błędom związanym ze starymi referencjami.

Wyłączanie w kolejności zależności

Rozważmy łańcuch, w którym jeden plugin dostarcza bazę danych.

Plugin A provides database

Drugi plugin zależy od bazy danych, a trzeci od drugiego.

Plugin B depends on databasePlugin C depends on Plugin B

Łańcuch zależności wygląda w ten sposób.

A
↓
B
↓
C

Teraz usunięto pierwszy. Prosty silnik wykonywania może natychmiast zamknąć bazę danych, mimo że pozostałe dwa mogą nadal być w trakcie wykonywania. Zamiast tego Cordis koordynuje graf zależności. Rozpoczyna się rozładowywanie.

A begins unloading

Następnie zależne elementy są usuwane po kolei, zanim oryginał zakończy swoją czynność oczyszczania.

↓ mark A unavailableB notices dependency disappeared↓ B unloadsC notices B disappeared↓ C unloadsthenA completes cleanup

Kolejność ma ogromne znaczenie, ponieważ zapobiega to osobom zależnym od używania czegoś, co jest już na wpół zniszczone.

Izolacja: jedna nazwa, oddzielne światy

Jedno globalne nazewnictwo usług może okazać się ograniczające. Wyobraźmy sobie dwa agenty, które potrzebują różnych baz danych.

Agent A → Database A
Agent B → Database B

Oboje nadal proszą o to samo.

database

Cordis obsługuje izolację, więc rozwiązywanie problemów z usługami działa w parach.

service name + isolation realm

i nie tylko na podstawie imienia.

service name

Koncepcyjnie każda domena przypisuje to samo nazwę do swojej własnej instancji.

Realm A: database → DB_A
Realm B: database → DB_B

Ta sama zależność logiczna może więc być rozwiązywana inaczej w zależności od kontekstu, co jest przydatne w systemach wielu użytkowników, wielu agentach, środowiskach testowych, pluginach w izolacji oraz oddzielnych przestrzeniach roboczych.

Przechwytywanie

Cordis może również kształtować dostęp do usług za pomocą metadanych. Rozważmy rejestr narzędzi, w którym agent administracyjny może je widzieć.

shell
browser
filesystem
deploy

podczas gdy agent o ograniczonych uprawnieniach widzi mniejszy zbiór.

browser
filesystem-readonly

Zamiast tworzyć całkowicie oddzielny rejestr, dostęp można przechwytywać w zależności od kontekstu.

ctx + metadata
      ↓
service resolution
      ↓
modified view of capability

To silny wzorzec dla systemów polityk.

Bezpieczniejsza zamiana modułów w czasie rzeczywistym

Hot reloading wydaje się proste.

file changed
    ↓
reload module

W czasie działania wtyczki sytuacja staje się skomplikowana. Co jeśli nowy moduł zawiera błąd, a co się stanie z dawną, sprawną wersją? Ładowarka Cordis obsługuje zachowanie ponownego załadunku, które funkcjonuje jak transakcja.

  working plugin
        ↓
    code changes
        ↓
  attempt new import
        ↓
     success?
 ┌───────┴───────┐
yes               no
 ↓                 ↓
replace         rollback
plugin          old module

To naturalnie pasuje do efektów odwracalnych. Wtyczka o jasnym cyklu życia, określonym zestawie zależności oraz procedurze czyszczenia jest znacznie mniej ryzykowna pod względem wymiany.

Przypadek z praktyki

Cordis nie został stworzony wyłącznie dla DeepSeek Harness. Artykuł omawia Koishi – frameworka do chatbotów otwartego oprogramowania zbudowanego na jego bazie, który posiada ekosystem społeczności składający się z tysięcy wtyczek. Prawdziwy ekosystem sprawia, że dynamiczna kompozycja jest o wiele bardziej skuteczna niż dziesięć ręcznie budowanych modułów. Artykuł zachowuje jednak ostrożność i nie twierdzi, że kontrolowane testy wykazały duże wzrosty wydajności lub produktywności. Potrzebne są dalsze oceny.

Nie wszystko da się cofnąć

Tutaj teoria spotyka się z rzeczywistością. Niektóre efekty w czasie wykonywania są rzeczywiście odwracalne, np. otwieranie i zamykanie pliku.

open file
→ close file

Inne pary łączą się równie dobrze.

allocate memory
→ free memorystart worker
→ terminate workerregister event listener
→ unregister listener

Następnie jest wysyłanie e-maila.

send email

Jakie byłoby jego odwrotność?

unsend email?

Zazwyczaj jest to niemożliwe, i to samo dotyczy grupy innych działań.

send HTTP request
transfer money
publish message
delete external data

Gdy coś wpływa na świat zewnętrzny, prawdziwe odwrócenie sytuacji może nie istnieć. Najlepszą dostępną opcją jest odszkodowanie.

payment → refund
message → follow-up correction
database mutation → compensating transaction

Kompensacja nie jest tym samym co matematyczna korekta. Artykuł wyraźnie wskazuje na tę różnicę, a Cordis najlepiej sprawdza się w przypadku efektów, które czas wykonywania programu może faktycznie kontrolować i naprawiać.

Cordis polega na twojej obsłudze końcowa

Załóżmy, że plugin uruchamia serwer wewnątrz jakiegoś efektu i zwraca funkcję do jego usunięcia.

ctx.effect(() => {
    startServer()
    return () => {
        // supposedly cleanup
    }
}))

Cordis nie ma sposobu, aby udowodnić, że operacja czyszczenia rzeczywiście odwraca proces uruchamiania. Formalny model stanowi, że operacja odwrotna musi przywrócić środowisko do jego wcześniejszego stanu, ale implementacja zależy od programisty, który musi napisać poprawny mechanizm usuwania. Dlatego łączenie efektu z operacją czyszczenia wymaga określonej dyscypliny.

effect + cleanup

To nie jest automatyczna weryfikacja formalna, a to istotna różnica.

Dlaczego to pasuje do agentów AI

Klasyczne aplikacje pozostają dość stabilne po uruchomieniu. Agenci rozwijają się w przeciwnym kierunku. Przyszłe systemy agentów mogą nieprzerwanie wykonywać takie operacje.

install tool
remove tool
change model
spawn agent
kill agent
load skill
reload code
switch permissions
connect service
disconnect service
modify environment

Załóżmy agenta, który decyduje, że potrzebuje wsparcia GitHuba, i ładuje odpowiednią funkcjonalność, a następnie odrzuca ją później, gdy przestaje być przydatna, lub zastępuje nieudaną implementację. Taki system wymaga znacznie silniejszych semantyk kompozycji w czasie wykonywania niż te, które oferuje tradycyjny injektor zależności. Bez nich agent działający przez długi czas mógłby gromadzić niepotrzebne elementy.

orphan listeners
stale tools
dangling processes
broken dependencies
zombie services
duplicated handlers

Cordis stara się uczynić kompozycję w czasie wykonywania koncepcją o pierwszorzędnym znaczeniu w oprogramowaniu.

Dlaczego projekt wydaje się elegancki

Gdy Cordis zajmuje się kompozycją, DeepSeek Harness może modelować niemal wszystko w ten sam sposób.

Model provider     → plugin
Tool               → plugin
Agent loop         → plugin
Session store      → plugin
UI                 → plugin
Telemetry          → plugin
Approval system    → plugin
Sandbox            → plugin
Credential store   → plugin

Nowe funkcje nie stają się specjalnymi przypadkami w ramach frameworka. Stają się jedynie dodatkowymi elementami w obrębie jednego systemu kompozycji.

Profilowie i pakiety

Jeśli wszystko jest plikiem wtyczkowym, nikt nie chce za każdym razem ręcznie składać pięćdziesięciu z nich. Dlatego framework oferuje bardziej zaawansowane struktury kompozycji. Profil opisuje nazwaną konfigurację systemu, a pakiet rozprowadza grupy wtyczek wraz z ich konfiguracjami. Podstawowy pakiet może wyglądać w ten sposób.

Base Bundle
├── model adapters
├── tools
├── sessions
├── sandbox
├── approvals
├── credentials
├── settings
└── telemetry

Kolejny pakiet mógłby zawierać aplikację internetową.

Web Application

Ktoś inny mógłby stworzyć bezgłowego agenta.

Headless Agent

Architektura pozostaje modułowa, nie utrudniając codziennego użytkowania.

Pięć pomysłów wartych zapamiętania

Gdy wszystko inne zaniknie, to te pięć punktów należy pamiętać.

Najważniejsze są efekty odwracalne: każda zmiana wprowadzona przez komponent powinna mieć możliwość odwrócenia.

change + inverse

Drugi typ to reaktywne współdziałania. Komponenty deklarują, czego potrzebują, a system w czasie wykonywania reaguje, gdy zależności pojawiają się, znikają lub zmieniają.

dependency available → activate
dependency gone      → deactivate

Trzecim elementem jest kontekst. Efekty, zależności oraz dostęp do usług istnieją w ramach jednego, zintegrowanego kontekstu działania.

ctx

Czwartym elementem jest dynamiczny rachunek komponentów, który opisuje komponent poprzez to, czego potrzebuje, co dostarcza oraz co zmienia, zapewniając w czasie wykonywania wystarczającą strukturę do rozumienia procesów ładowania i rozładowywania.

Dependencies + Provisions + Effects

Piątą jest kompozycyjność przestrzenno-czasowa, która łączy aspekty przestrzenne i czasowe.

spatial:
who depends on whom?
+
temporal:
how can components safely enter and leave?

W skrócie chodzi o zmianę odwracalną w połączeniu z zależnościami reaktywnymi.

Dlaczego ma to znaczenie poza pojedynczym projektem

Większość obecnych frameworki agentów nadal zakłada dość statyczną aplikację: uruchom program, załaduj narzędzia, załaduj model, uruchom agenta, zatrzymaj. Autonomiczne agenty dążą do środowisk uruchomieniowych, które mogą się modyfikować podczas działania. Wyobraź sobie agenta, który może postępować zgodnie z taką sekwencją.

discover a capability
install it
use it
upgrade it
replace it
remove it
continue running

W tym momencie zarządzanie cyklem życia pluginu przestaje być przyjemną cechą architektury i staje się podstawową infrastrukturą. To właśnie sprawia, że projekt jest interesujący. Model nie jest najbardziej innowacyjną częścią – to środowisko działania wokół niego jest takie.

Głębsza lekcja

Architektura oprogramowania zazwyczaj dotyczy kompozycji w określonym momencie czasu – łączy się elementy i uruchamia program.

A + B + C

Cordis zadaje trudniejsze pytania.

What if A disappears?
What if B is replaced?
What if C suddenly gains a dependency?
What if another implementation of A appears?
What if all of this happens while the system is running?

To całkowicie zmienia charakter problemu. Komponenty przestają być statycznymi blokami i zachowują się jak żywe uczestnicy: przybywają, nawiązują relacje, zmieniają swoje otoczenie, są zależne od innych i w końcu odchodzą.

Główny wniosek

DeepSeek Harness może wyglądać jak kolejna ramowa struktura dla agentów, a jej zaletą nie jest to, że potrafi wywoływać modele językowe lub uruchamiać narzędzia, co potrafią wiele innych ram. Kluczową koncepcją jest to, że sam system kompozycji stanowi architekturę. Każda funkcjonalność to komponent, każda zależność jest jawna, każdy kontrolowalny efekt wie, jak się sam usunąć, komponenty reagują na zmiany w zależnościach, a nawet pętla agenta może zostać zastąpiona – nie jest to niezmienialny kod ramy. Podstawowym założeniem jest uczynienie z każdej funkcjonalności komponentu, każdej zależności czymś jawnym oraz zapewnienie, by każdy efekt miał własną funkcję odwracającą.

W zwykłych aplikacjach jest to uporządkowana architektura wtyczek. W długotrwałych, samomodificujących się agentach sztucznej inteligencji może to mieć znacznie większe znaczenie. Jeśli agenci w końcu zmienią swój własny środowisko działania, oprogramowanie będzie potrzebowało zasadniczej metody zmian bez stopniowego niszczenia środowiska, w którym działa. To właśnie problem, który ma na celu rozwiązanie DeepSeek Harness oraz badania Cordis stojące za nim.