{"id":20655498,"url":"https://github.com/postmannen/actress","last_synced_at":"2025-09-26T22:30:45.682Z","repository":{"id":218864829,"uuid":"747098168","full_name":"postmannen/actress","owner":"postmannen","description":"Concurrent Actor framework written in Go","archived":false,"fork":false,"pushed_at":"2025-09-18T21:20:13.000Z","size":300,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-18T23:00:08.783Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/postmannen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-01-23T09:05:41.000Z","updated_at":"2025-09-18T21:20:16.000Z","dependencies_parsed_at":"2024-02-10T15:26:31.079Z","dependency_job_id":"58c70367-3c35-4c10-8994-768e9e27f1a5","html_url":"https://github.com/postmannen/actress","commit_stats":null,"previous_names":["postmannen/actress"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/postmannen/actress","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Factress","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Factress/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Factress/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Factress/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/postmannen","download_url":"https://codeload.github.com/postmannen/actress/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Factress/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276151645,"owners_count":25594029,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-20T02:00:10.207Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T18:11:26.506Z","updated_at":"2025-09-26T22:30:45.676Z","avatar_url":"https://github.com/postmannen.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Actress\n\nA Concurrent Actor framework written in Go.\n\n**NB: This is still in the idea phase, so concepts are being tested out and things might/will change rapidly. \n\u003cu\u003eExpect breaking changes between commits\u003c/u\u003e**.\n\n## Overview\n\nCreate custom processes where what the processes do are either your own piece of code, or it can be a command called from the Operating system. The processes can communicate by sending events to pass the result from one processes to the next for further processing, or by chaining together process as workflows to create a series of Events that together will provide some end result.\n\n### Processes\n\nA process are like a module capable of performing a specific tasks. The nature of the process is determined by an Name and a Function attached to each process. A process have an InCh for receiving events, and an AddEvent function for sending Events. The processes can themselves spawn new processes. Processes can also send Event messages to other processes.\n\nA process can hold state within the Process Function.\n\n### Events\n\nTo initiate and trigger the execution of the process's function, we send events. Each process has its own unique event name. Events serve as the communication within the system. They can carry data, either with the result of something a process did to pass it on to the next process for further processing, instructions for what a process should do, or both. An event can contain a chain of events to create workflows of what do do and in what order by using the NextEvent feature (see examples for usage).\n\n```Go\ntype Event struct {\n    Nr int\n    // Name is a unique name to identify the type of the event.\n    Name EventName `json:\"name\" yaml:\"name\" cbor:\"name\"`\n    // Kind is a more general way to describe the event that can\n    // be used to destinguish if it is static, error or dynamic event.\n    Kind Kind `json:\"kind\" yaml:\"kind\" cbor:\"kind\"`\n    // Cmd is usually used for giving instructions or parameters for\n    // what an event shall do.\n    Cmd []string `json:\"cmd\" yaml:\"cmd\" cbor:\"cmd\"`\n    // Data usually carries the data from one process to the next. Example\n    // could be a file read on process1 is put in the Data field, and\n    // passed on to process2 to be unmarshaled.\n    Data []byte `json:\"data\" yaml:\"data\" cbor:\"data\"`\n    // Data to be transfered internally. Example is to send config directly via\n    // the channel between internal actors.\n    InternalCh chan chan []byte `json:\"-\" yaml:\"-\" cbor:\"-\"`\n    // Err is used for defining the error message when the event is used\n    // as an error event.\n    Err error `json:\"error\" yaml:\"error\" cbor:\"error\"`\n    // NextEvent defines a series of events to be executed like a workflow.\n    // The receiving process should check this field for what kind of event\n    // to create as the next step in the workflow.\n    NextEvent *Event `json:\"nextEvent\" yaml:\"nextEvent\" cbor:\"nextEvent\"`\n    // PreviousEvent allows for keeping information about the previous event if needed.\n    PreviousEvent *Event `json:\"previousEvent\" yaml:\"previousEvent\" cbor:\"previousEvent\"`\n    // Dst node.\n    DstNode Node `json:\"dst\" yaml:\"dst\" cbor:\"dst\"`\n    // Src node.\n    SrcNode Node `json:\"src\" yaml:\"src\" cbor:\"src\"`\n}\n```\n\n### Event Functions (ETFunc)\n\nEvent Functions holds the logic for what a process shall do when an event is received, and what to do with the data the event carries. The Event functions are callback functions that are executed when a process is created.\n\nThe programmer can decide if the Process Function should depend on the input from the input channel of the process, or just continously do some work on it's own. For an event function to be triggered to work on events it should hold a **for** loop that listens on the Process InCh for new Events.\n\n### Examples\n\nCheck out the test files for examples for how to define an Event and it's Process function, or for more complete examples check out the [examples](examples/) folder.\n\n### Quick start\n\n```Go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n    \"strings\"\n    \"time\"  \n    \"github.com/postmannen/actress\"\n)\n\nfunc main() {\n    ctx, cancel := context.WithCancel(context.Background())\n    defer cancel()\n\n    // Create a new root process.\n    cfg, _ := actress.NewConfig(\"debug\")\n    rootAct := actress.NewRootProcess(ctx, nil, cfg, nil)\n\n    // Start the root process/actor.\n    err := rootAct.Act()\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Create a test channel where we receive the end result.\n    testCh := make(chan string)\n\n    // Define two event's for two processes.\n    const ETTest1 actress.Name = \"ETTest1\"\n    const ETTest2 actress.Name = \"ETTest2\"\n\n    // Define the first ETFunc function that will be attached to the ETTest1 Name process.\n    test1Func := func(ctx context.Context, p *actress.Process) func() {\n        fn := func() {\n            for {\n                select {\n                case ev := \u003c-p.InCh:\n                    upper := strings.ToUpper(string(ev.Data))\n                    // Pass on the processing to the next process, and use the NextEvent we have specified in main\n                    // for the Name, and add the result of ToUpper to the data field.\n                    p.AddEvent(actress.Event{Name: ev.NextEvent.Name,\n                        \n                        Data:      []byte(upper)})\n                case \u003c-ctx.Done():\n                    return\n                }\n            }\n        }\n        return fn\n    }\n\n    // Define the second ETFunc function that will be attached to the ETTest2 Name process.\n    test2Func := func(ctx context.Context, p *actress.Process) func() {\n        fn := func() {\n            for {\n                select {\n                case result := \u003c-p.InCh:\n                    dots := string(result.Data) + \"...\"                 \n                    // Send the result on the testCh so we are able to to receive it in main().\n                    testCh \u003c- string(dots)\n\n                    // Also create an informational error message.\n                    p.AddEvent(actress.Event{Name: actress.ERDebug,\n                        \n                        Err:       fmt.Errorf(\"info: done with the acting\")})\n\n                case \u003c-ctx.Done():\n                    return\n                }\n            }\n        }\n        return fn\n    }\n\n    // Register the event names and event function as processes,\n    // and start them with the Act() method.\n    actress.NewProcess(ctx, rootAct, ETTest1,  test1Func).Act()\n    actress.NewProcess(ctx, rootAct, ETTest2,  test2Func).Act()\n\n    // Pass in an event destined for an ETTest1 Name process, and also specify\n    // the next event to be used when passing the result on from ETTest1 to the next\n    // process which here is ETTest2.\n    rootAct.AddEvent(actress.Event{Name: ETTest1,\n        \n        Data:      []byte(\"test\"),\n        NextEvent: \u0026actress.Event{Name: ETTest2,\n            \n    },\n    )\n\n    // Wait and receive the result from the ETTest2 process.\n    fmt.Printf(\"The result: %v\\n\", \u003c-testCh)\n\n    time.Sleep(time.Second * 2)\n}\n```\n\n## Details\n\nShort intro about the Events.\n\nThe events for all processes, both static, dynamic, error, and supervisor uses the same event type and structure.\nWhat makes an event for example a Static or an Error is the Kind field of the Event which can have values like **KindStatic**, **KindDynamic**, **KindCustom**, **KindError** or **KindSupervisor**.\n\nThe reason for splitting them up with the use of **Kind** are for **separation** and use of **mutex'es** , for example if the event routing logic hangs on static events, it will not affect the other event kinds, so we are able to for example send errors if any of the other routers are having trouble or have massive load.\n\n### Where to use an actor process of a specific kind ?\n\n**KindStatic processes**, should only be used for processes/actors defined at startup. Event lookups for finding the right actor **are not protected by a mutex**.  \n**KindDynamic processes**, Can be used both for startup and runtime defined actors, but prefer static at startup unless you have a really good reason to not do it :). Event lookups for finding the right actor **are protected by a mutex**.\n**KindError processes** For error logging and handling.\n**KindSupervisor processes** For control logic and information about the whole Actress system.\n\n### Custom Events Processes\n\nCustom Event Processes allows for dynimally adding new Names and Processes at runtime. This feature is enabled by setting the `CUSTOMEVENTS` env variable to `true`, and also the path for where to look for configs with `CUSTOMEVENTSPATH`. The folder is continously being watched for changes, so any updates to config JSON files will be activated immediately. For adding custom event, put files in the `CUSTOMEVENTSPATH` with the extension `.json`. The files should be in the same format as an **Event**.\n\n```json\n{\"Name\":\"ET1\",\"Cmd\":[\"/bin/bash\",\"-c\"]}\n```\n\nThe example above will automatically create a Process that have an Name of `ET1`. We can then send Events using `ET1` as the Name, and what we put in Event.Cmd will be appended to the existing values that already exist in the Custom Event. Example follows.\n\n#### Custom Event Example 1\n\nWe use the Custom Event from above, and add a new event using `ET1` like this:\n\n```go\np.AddEvent(Event{Name: Name(\"ET1\"), Cmd: []string{\"ls -l\"}})\n```\n\nWhen the Event is received at the ET1 process it's Cmd is appended what was defined earlier when creating the ET1 Process. The end result of the Cmd field will be `[]string{\"/bin/bash\",\"-c\",\"ls -l\"}` which is then executed.\n\n#### Custom Event Example 2\n\nWe can also add the whole command to be executed in the `.json` file likes this.\n\n```json\n{\"Name\":\"ETBleeping\",\"Cmd\":[\"/bin/bash\",\"-c\",\"curl -L https://bleepingcomputer.com\"]}\n```\n\nSince this Event specification is complete in itself we don't have to use the Cmd field when adding an Event to use it.\n\n```go\np.AddEvent(Event{Name: Name(\"ETBleeping\")})\n```\n\n## NextEvent\n\nNextEvent makes it possible to define an event as a chain of Events. An example could be that we want to get the content of a web page, and print the result to the screen. We could do that in the following way.\n\n```go\np.AddEvent(Event{Name: Name(\"ETBleeping\"), NextEvent: \u0026Event{Name: ETPrint}})\n```\n\n## Dynamic Processes\n\nThe purpose of dynamic processes is to have short lived processes that can be quickly started, and removed again when it's job is done. The only difference between a Static process and a Dynamic process are that the dynamic processes have a mutex in the DynamicProcesses map so that we can delete the processes when they are no longer needed at runtime withhout causing a datarace.\n\nA typical example could be that there is a processes that needs to communicate in some other way with another process that cant be done with the current process's event channel. We can then spawn a dynamic process to take care of that. Check out the test and files in the examples directory. A process can spawn as many dynamic processes as it needs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpostmannen%2Factress","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpostmannen%2Factress","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpostmannen%2Factress/lists"}