igniter 0.4.5 → 0.5.0
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.
- checksums.yaml +4 -4
- data/README.md +217 -0
- data/docs/APPLICATION_V1.md +253 -0
- data/docs/CAPABILITIES_V1.md +207 -0
- data/docs/CONSENSUS_V1.md +477 -0
- data/docs/CONTENT_ADDRESSING_V1.md +221 -0
- data/docs/DATAFLOW_V1.md +274 -0
- data/docs/MESH_V1.md +732 -0
- data/docs/NODE_CACHE_V1.md +324 -0
- data/docs/PROACTIVE_AGENTS_V1.md +293 -0
- data/docs/SERVER_V1.md +200 -1
- data/docs/SKILLS_V1.md +213 -0
- data/docs/STORE_ADAPTERS.md +41 -13
- data/docs/TEMPORAL_V1.md +174 -0
- data/docs/TOOLS_V1.md +347 -0
- data/docs/TRANSCRIPTION_V1.md +403 -0
- data/examples/README.md +37 -0
- data/examples/consensus.rb +239 -0
- data/examples/dataflow.rb +308 -0
- data/examples/elocal_webhook.rb +1 -0
- data/examples/llm_tools.rb +237 -0
- data/examples/mesh.rb +239 -0
- data/examples/mesh_discovery.rb +267 -0
- data/examples/mesh_gossip.rb +162 -0
- data/examples/ringcentral_routing.rb +1 -1
- data/lib/igniter/agents/ai/alert_agent.rb +111 -0
- data/lib/igniter/agents/ai/chain_agent.rb +127 -0
- data/lib/igniter/agents/ai/critic_agent.rb +163 -0
- data/lib/igniter/agents/ai/evaluator_agent.rb +193 -0
- data/lib/igniter/agents/ai/evolution_agent.rb +286 -0
- data/lib/igniter/agents/ai/health_check_agent.rb +122 -0
- data/lib/igniter/agents/ai/observer_agent.rb +184 -0
- data/lib/igniter/agents/ai/planner_agent.rb +210 -0
- data/lib/igniter/agents/ai/router_agent.rb +131 -0
- data/lib/igniter/agents/ai/self_reflection_agent.rb +175 -0
- data/lib/igniter/agents/observability/metrics_agent.rb +130 -0
- data/lib/igniter/agents/pipeline/batch_processor_agent.rb +131 -0
- data/lib/igniter/agents/proactive_agent.rb +208 -0
- data/lib/igniter/agents/reliability/retry_agent.rb +99 -0
- data/lib/igniter/agents/scheduling/cron_agent.rb +110 -0
- data/lib/igniter/agents.rb +56 -0
- data/lib/igniter/application/app_config.rb +32 -0
- data/lib/igniter/application/autoloader.rb +18 -0
- data/lib/igniter/application/generator.rb +157 -0
- data/lib/igniter/application/scheduler.rb +109 -0
- data/lib/igniter/application/yml_loader.rb +39 -0
- data/lib/igniter/application.rb +174 -0
- data/lib/igniter/capabilities.rb +68 -0
- data/lib/igniter/compiler/validators/dependencies_validator.rb +50 -2
- data/lib/igniter/compiler/validators/remote_validator.rb +2 -0
- data/lib/igniter/consensus/cluster.rb +183 -0
- data/lib/igniter/consensus/errors.rb +14 -0
- data/lib/igniter/consensus/executors.rb +43 -0
- data/lib/igniter/consensus/node.rb +320 -0
- data/lib/igniter/consensus/read_query.rb +30 -0
- data/lib/igniter/consensus/state_machine.rb +58 -0
- data/lib/igniter/consensus.rb +58 -0
- data/lib/igniter/content_addressing.rb +133 -0
- data/lib/igniter/contract.rb +12 -0
- data/lib/igniter/dataflow/aggregate_operators.rb +147 -0
- data/lib/igniter/dataflow/aggregate_state.rb +77 -0
- data/lib/igniter/dataflow/diff.rb +37 -0
- data/lib/igniter/dataflow/diff_state.rb +81 -0
- data/lib/igniter/dataflow/incremental_collection_result.rb +39 -0
- data/lib/igniter/dataflow/window_filter.rb +48 -0
- data/lib/igniter/dataflow.rb +65 -0
- data/lib/igniter/dsl/contract_builder.rb +71 -7
- data/lib/igniter/executor.rb +60 -0
- data/lib/igniter/extensions/capabilities.rb +39 -0
- data/lib/igniter/extensions/content_addressing.rb +5 -0
- data/lib/igniter/extensions/dataflow.rb +117 -0
- data/lib/igniter/extensions/mesh.rb +31 -0
- data/lib/igniter/fingerprint.rb +43 -0
- data/lib/igniter/integrations/llm/config.rb +48 -4
- data/lib/igniter/integrations/llm/executor.rb +221 -28
- data/lib/igniter/integrations/llm/providers/anthropic.rb +37 -4
- data/lib/igniter/integrations/llm/providers/openai.rb +34 -5
- data/lib/igniter/integrations/llm/transcription/providers/assemblyai.rb +200 -0
- data/lib/igniter/integrations/llm/transcription/providers/base.rb +122 -0
- data/lib/igniter/integrations/llm/transcription/providers/deepgram.rb +162 -0
- data/lib/igniter/integrations/llm/transcription/providers/openai.rb +102 -0
- data/lib/igniter/integrations/llm/transcription/transcriber.rb +145 -0
- data/lib/igniter/integrations/llm/transcription/transcript_result.rb +29 -0
- data/lib/igniter/integrations/llm.rb +37 -1
- data/lib/igniter/memory/agent_memory.rb +104 -0
- data/lib/igniter/memory/episode.rb +29 -0
- data/lib/igniter/memory/fact.rb +27 -0
- data/lib/igniter/memory/memorable.rb +90 -0
- data/lib/igniter/memory/reflection_cycle.rb +96 -0
- data/lib/igniter/memory/reflection_record.rb +28 -0
- data/lib/igniter/memory/store.rb +115 -0
- data/lib/igniter/memory/stores/in_memory.rb +136 -0
- data/lib/igniter/memory/stores/sqlite.rb +284 -0
- data/lib/igniter/memory.rb +80 -0
- data/lib/igniter/mesh/announcer.rb +55 -0
- data/lib/igniter/mesh/config.rb +45 -0
- data/lib/igniter/mesh/discovery.rb +39 -0
- data/lib/igniter/mesh/errors.rb +31 -0
- data/lib/igniter/mesh/gossip.rb +47 -0
- data/lib/igniter/mesh/peer.rb +21 -0
- data/lib/igniter/mesh/peer_registry.rb +51 -0
- data/lib/igniter/mesh/poller.rb +77 -0
- data/lib/igniter/mesh/router.rb +109 -0
- data/lib/igniter/mesh.rb +85 -0
- data/lib/igniter/metrics/collector.rb +131 -0
- data/lib/igniter/metrics/prometheus_exporter.rb +104 -0
- data/lib/igniter/metrics/snapshot.rb +8 -0
- data/lib/igniter/metrics.rb +37 -0
- data/lib/igniter/model/aggregate_node.rb +34 -0
- data/lib/igniter/model/collection_node.rb +3 -2
- data/lib/igniter/model/compute_node.rb +13 -0
- data/lib/igniter/model/remote_node.rb +18 -2
- data/lib/igniter/node_cache.rb +231 -0
- data/lib/igniter/replication/bootstrapper.rb +61 -0
- data/lib/igniter/replication/bootstrappers/gem.rb +32 -0
- data/lib/igniter/replication/bootstrappers/git.rb +39 -0
- data/lib/igniter/replication/bootstrappers/tarball.rb +56 -0
- data/lib/igniter/replication/expansion_plan.rb +38 -0
- data/lib/igniter/replication/expansion_planner.rb +142 -0
- data/lib/igniter/replication/manifest.rb +45 -0
- data/lib/igniter/replication/network_topology.rb +123 -0
- data/lib/igniter/replication/node_role.rb +42 -0
- data/lib/igniter/replication/reflective_replication_agent.rb +238 -0
- data/lib/igniter/replication/replication_agent.rb +87 -0
- data/lib/igniter/replication/role_registry.rb +73 -0
- data/lib/igniter/replication/ssh_session.rb +77 -0
- data/lib/igniter/replication.rb +54 -0
- data/lib/igniter/runtime/execution.rb +18 -0
- data/lib/igniter/runtime/input_validator.rb +6 -2
- data/lib/igniter/runtime/resolver.rb +254 -16
- data/lib/igniter/runtime/stores/redis_store.rb +41 -4
- data/lib/igniter/server/client.rb +44 -1
- data/lib/igniter/server/config.rb +13 -6
- data/lib/igniter/server/handlers/event_handler.rb +4 -0
- data/lib/igniter/server/handlers/execute_handler.rb +6 -0
- data/lib/igniter/server/handlers/liveness_handler.rb +20 -0
- data/lib/igniter/server/handlers/manifest_handler.rb +34 -0
- data/lib/igniter/server/handlers/metrics_handler.rb +51 -0
- data/lib/igniter/server/handlers/peers_handler.rb +115 -0
- data/lib/igniter/server/handlers/readiness_handler.rb +47 -0
- data/lib/igniter/server/http_server.rb +54 -17
- data/lib/igniter/server/router.rb +54 -21
- data/lib/igniter/server/server_logger.rb +52 -0
- data/lib/igniter/server.rb +6 -0
- data/lib/igniter/skill/feedback.rb +116 -0
- data/lib/igniter/skill/output_schema.rb +110 -0
- data/lib/igniter/skill.rb +218 -0
- data/lib/igniter/temporal.rb +84 -0
- data/lib/igniter/tool/discoverable.rb +151 -0
- data/lib/igniter/tool.rb +52 -0
- data/lib/igniter/tool_registry.rb +144 -0
- data/lib/igniter/version.rb +1 -1
- data/lib/igniter.rb +17 -0
- metadata +122 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72a53df8eec907ddc95b8ab3b17890be79b00acebc04a94a92b6426573829f35
|
|
4
|
+
data.tar.gz: 9facff923baee1ef67e72c194fdbf390f79b516dcf3377ebc69dc74222bc6603
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 000ef1ec945e8d4e2ea392708326927be8f9e8d6f6293b537c5b26f4a37ac5c81d0dec3da4c4b0603f5e1e5d78949f558aaadbbb6e062bb8029d130bfd2e4256
|
|
7
|
+
data.tar.gz: d79723b7f2a0938fcd70af41af44d4d3057834c366195ae7201ef003e9d36818137e1c8483a2d8ee9c48c517b988a658576d1b604cff22a49a21b32fe96b4ca0
|
data/README.md
CHANGED
|
@@ -13,6 +13,10 @@ Igniter is a Ruby gem for expressing business logic as a validated dependency gr
|
|
|
13
13
|
- runtime auditing, diagnostics reports, and reactive side effects
|
|
14
14
|
- graph and runtime introspection (text, Mermaid)
|
|
15
15
|
- ergonomic DSL helpers: `const`, `lookup`, `map`, `project`, `aggregate`, `guard`, `export`, `expose`, `effect`, `on_success`, `scope`, `namespace`
|
|
16
|
+
- `Igniter::Application` — application scaffold with YAML config, autoloading, scheduler, and `igniter-server new` generator
|
|
17
|
+
- capability-based security: declare executor resource requirements, enforce `Policy` at runtime
|
|
18
|
+
- temporal contracts: reproducible historical execution via an explicit `as_of` time input
|
|
19
|
+
- content-addressed computation: `pure` executors cached by input fingerprint across executions and processes
|
|
16
20
|
|
|
17
21
|
## Installation
|
|
18
22
|
|
|
@@ -69,6 +73,10 @@ contract.diagnostics_text # compact execution summary
|
|
|
69
73
|
- **Diagnostics**: compact text, Markdown, or structured reports for triage.
|
|
70
74
|
- **Reactive**: subscribe declaratively to runtime events with `effect`, `on_success`, `on_failure`.
|
|
71
75
|
- **Introspection**: render graphs as text or Mermaid; inspect runtime state.
|
|
76
|
+
- **Capabilities**: executors declare what resources they need (`:network`, `:database`, …); `Policy` denies them at runtime.
|
|
77
|
+
- **Temporal contracts**: inject `as_of` time input automatically; replay any historical computation with the original timestamp.
|
|
78
|
+
- **Content addressing**: `pure` executors get a universal cache key — identical inputs return a cached result across executions, processes, and deployments.
|
|
79
|
+
- **Incremental dataflow**: `mode: :incremental` on collection nodes — only added/changed items run, unchanged items reuse cached results, removed items are retracted. O(change) not O(total).
|
|
72
80
|
|
|
73
81
|
## Quick Start Recipes
|
|
74
82
|
|
|
@@ -363,6 +371,10 @@ Igniter::Server.start
|
|
|
363
371
|
**CLI:**
|
|
364
372
|
|
|
365
373
|
```bash
|
|
374
|
+
# Generate a new application scaffold
|
|
375
|
+
igniter-server new my_app
|
|
376
|
+
|
|
377
|
+
# Start a server directly
|
|
366
378
|
igniter-server start --port 4568 --require ./contracts.rb
|
|
367
379
|
```
|
|
368
380
|
|
|
@@ -448,6 +460,199 @@ end
|
|
|
448
460
|
|
|
449
461
|
See [`examples/llm/research_agent.rb`](examples/llm/research_agent.rb), [`examples/llm/tool_use.rb`](examples/llm/tool_use.rb), and [`docs/LLM_V1.md`](docs/LLM_V1.md).
|
|
450
462
|
|
|
463
|
+
### 12. Igniter::Application
|
|
464
|
+
|
|
465
|
+
Package contracts, executors, scheduler, and server config into a single coherent entry point:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
# Scaffold a new application
|
|
469
|
+
igniter-server new my_app
|
|
470
|
+
cd my_app && bundle install && bin/start
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
```ruby
|
|
474
|
+
require "igniter/application"
|
|
475
|
+
|
|
476
|
+
class MyApp < Igniter::Application
|
|
477
|
+
config_file "application.yml" # optional YAML base config
|
|
478
|
+
|
|
479
|
+
configure do |c|
|
|
480
|
+
c.port = ENV.fetch("PORT", 4567).to_i
|
|
481
|
+
c.store = Igniter::Runtime::Stores::MemoryStore.new
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
executors_path "executors/" # eager-require all executors
|
|
485
|
+
contracts_path "contracts/" # eager-require all contracts
|
|
486
|
+
|
|
487
|
+
register "OrderContract", OrderContract
|
|
488
|
+
|
|
489
|
+
schedule :cleanup, every: "1h" do
|
|
490
|
+
puts "[cleanup] #{Time.now.strftime("%H:%M")}"
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
schedule :report, every: "1d", at: "09:00" do
|
|
494
|
+
DailyReportContract.new.resolve_all(date: Date.today)
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
MyApp.start # blocking built-in HTTP server
|
|
499
|
+
# or
|
|
500
|
+
MyApp.rack_app # Rack app for Puma / Unicorn
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**`application.yml`** — base config loaded before the `configure` block (block always wins):
|
|
504
|
+
|
|
505
|
+
```yaml
|
|
506
|
+
server:
|
|
507
|
+
port: 4567
|
|
508
|
+
host: "0.0.0.0"
|
|
509
|
+
log_format: json # text (default) or json
|
|
510
|
+
drain_timeout: 30
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Scheduler interval formats:** `30` (seconds), `"30s"`, `"5m"`, `"2h"`, `"1d"`, `{ hours: 1, minutes: 30 }`
|
|
514
|
+
|
|
515
|
+
See [`docs/APPLICATION_V1.md`](docs/APPLICATION_V1.md) for the full reference and companion app example.
|
|
516
|
+
|
|
517
|
+
### 13. Capability-Based Security
|
|
518
|
+
|
|
519
|
+
Declare what external resources an executor needs, then deny specific capabilities at the
|
|
520
|
+
policy level — without touching the executors themselves:
|
|
521
|
+
|
|
522
|
+
```ruby
|
|
523
|
+
require "igniter/capabilities"
|
|
524
|
+
|
|
525
|
+
class DbLookup < Igniter::Executor
|
|
526
|
+
capabilities :database
|
|
527
|
+
|
|
528
|
+
def call(id:)
|
|
529
|
+
DB.find(id)
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
class PureCalc < Igniter::Executor
|
|
534
|
+
pure # shorthand for capabilities(:pure)
|
|
535
|
+
|
|
536
|
+
def call(x:, y:) = x + y
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Inspect the graph's surface area before deploying
|
|
540
|
+
MyContract.compiled_graph.required_capabilities
|
|
541
|
+
# => { fetch: [:database], total: [:pure] }
|
|
542
|
+
|
|
543
|
+
# Enforce policy at boot time
|
|
544
|
+
Igniter::Capabilities.policy = Igniter::Capabilities::Policy.new(denied: [:database])
|
|
545
|
+
|
|
546
|
+
MyContract.new(id: 1).resolve_all
|
|
547
|
+
# => CapabilityViolationError: Node 'fetch' uses denied capabilities: database
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
See [`docs/CAPABILITIES_V1.md`](docs/CAPABILITIES_V1.md).
|
|
551
|
+
|
|
552
|
+
### 14. Temporal Contracts
|
|
553
|
+
|
|
554
|
+
Make time an explicit input so every execution is fully reproducible:
|
|
555
|
+
|
|
556
|
+
```ruby
|
|
557
|
+
require "igniter/temporal"
|
|
558
|
+
|
|
559
|
+
class TaxRateContract < Igniter::Contract
|
|
560
|
+
include Igniter::Temporal
|
|
561
|
+
|
|
562
|
+
define do
|
|
563
|
+
input :country
|
|
564
|
+
# `as_of` is injected automatically (default: Time.now)
|
|
565
|
+
|
|
566
|
+
temporal_compute :rate, depends_on: :country do |country:, as_of:|
|
|
567
|
+
HISTORICAL_RATES.dig(country, as_of.year) || 0.0
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
output :rate
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Current rate
|
|
575
|
+
TaxRateContract.new(country: "UA").result.rate
|
|
576
|
+
# => 0.22
|
|
577
|
+
|
|
578
|
+
# Reproduce the exact 2024 result
|
|
579
|
+
TaxRateContract.new(country: "UA", as_of: Time.new(2024, 1, 1)).result.rate
|
|
580
|
+
# => 0.20
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
See [`docs/TEMPORAL_V1.md`](docs/TEMPORAL_V1.md).
|
|
584
|
+
|
|
585
|
+
### 15. Content-Addressed Computation
|
|
586
|
+
|
|
587
|
+
`pure` executors are cached by a fingerprint of their logic + inputs. Identical computation
|
|
588
|
+
is never repeated — within an execution, across executions, or across processes:
|
|
589
|
+
|
|
590
|
+
```ruby
|
|
591
|
+
require "igniter/extensions/content_addressing"
|
|
592
|
+
|
|
593
|
+
class TaxCalculator < Igniter::Executor
|
|
594
|
+
pure
|
|
595
|
+
fingerprint "tax_calc_v1" # bump to invalidate the cache when logic changes
|
|
596
|
+
|
|
597
|
+
def call(country:, amount:)
|
|
598
|
+
TAX_RATES[country] * amount
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# First execution — computes and caches
|
|
603
|
+
InvoiceContract.new(country: "UA", amount: 1000).result.tax # computed
|
|
604
|
+
|
|
605
|
+
# Second execution — served from cache; TaxCalculator is never called
|
|
606
|
+
InvoiceContract.new(country: "UA", amount: 1000).result.tax # cache hit
|
|
607
|
+
|
|
608
|
+
# Distributed cache (Redis) — shared across all nodes
|
|
609
|
+
Igniter::ContentAddressing.cache = RedisContentCache.new(Redis.new)
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
See [`docs/CONTENT_ADDRESSING_V1.md`](docs/CONTENT_ADDRESSING_V1.md).
|
|
613
|
+
|
|
614
|
+
### 16. Incremental Dataflow — O(change) Collection Processing
|
|
615
|
+
|
|
616
|
+
`mode: :incremental` on a collection node makes the runtime diff the input array on
|
|
617
|
+
every `resolve_all`. Only added/changed items have their child contract re-run;
|
|
618
|
+
unchanged items reuse the cached result; removed items are retracted automatically.
|
|
619
|
+
|
|
620
|
+
```ruby
|
|
621
|
+
require "igniter/extensions/dataflow"
|
|
622
|
+
|
|
623
|
+
class SensorPipeline < Igniter::Contract
|
|
624
|
+
define do
|
|
625
|
+
input :readings, type: :array
|
|
626
|
+
|
|
627
|
+
collection :processed,
|
|
628
|
+
with: :readings,
|
|
629
|
+
each: SensorAnalysis,
|
|
630
|
+
key: :sensor_id,
|
|
631
|
+
mode: :incremental,
|
|
632
|
+
window: { last: 1000 } # bounded memory
|
|
633
|
+
|
|
634
|
+
output :processed
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
pipeline = SensorPipeline.new(readings: initial_batch)
|
|
639
|
+
pipeline.resolve_all # all N items run once
|
|
640
|
+
|
|
641
|
+
# Push only the delta — no full-array replacement needed
|
|
642
|
+
pipeline.feed_diff(:readings,
|
|
643
|
+
add: [{ sensor_id: "new-1", value: 10 }],
|
|
644
|
+
update: [{ sensor_id: "tmp-2", value: 90 }],
|
|
645
|
+
remove: ["hum-1"]
|
|
646
|
+
)
|
|
647
|
+
pipeline.resolve_all # only 2 child contracts run (new-1 + tmp-2)
|
|
648
|
+
|
|
649
|
+
diff = pipeline.collection_diff(:processed)
|
|
650
|
+
diff.processed_count # => 2
|
|
651
|
+
diff.unchanged.size # => N - 2
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
See [`docs/DATAFLOW_V1.md`](docs/DATAFLOW_V1.md).
|
|
655
|
+
|
|
451
656
|
## Examples
|
|
452
657
|
|
|
453
658
|
| Example | Run | Shows |
|
|
@@ -465,6 +670,8 @@ See [`examples/llm/research_agent.rb`](examples/llm/research_agent.rb), [`exampl
|
|
|
465
670
|
| `server/node1.rb` + `node2.rb` | run both, then curl | Two-node igniter-server with `remote:` DSL |
|
|
466
671
|
| `llm/research_agent.rb` | `ruby examples/llm/research_agent.rb` | Multi-step LLM pipeline with Ollama |
|
|
467
672
|
| `llm/tool_use.rb` | `ruby examples/llm/tool_use.rb` | LLM tool declarations, chained LLM nodes, `Context` |
|
|
673
|
+
| `companion/demo.rb` | `ruby examples/companion/demo.rb` | End-to-end voice AI pipeline using `Igniter::Application` |
|
|
674
|
+
| `dataflow.rb` | `ruby examples/dataflow.rb` | Incremental sensor pipeline: `mode: :incremental`, `feed_diff`, sliding window |
|
|
468
675
|
|
|
469
676
|
## Design Docs
|
|
470
677
|
|
|
@@ -478,6 +685,11 @@ See [`examples/llm/research_agent.rb`](examples/llm/research_agent.rb), [`exampl
|
|
|
478
685
|
- [Store Adapters](docs/STORE_ADAPTERS.md)
|
|
479
686
|
- [igniter-server v1](docs/SERVER_V1.md)
|
|
480
687
|
- [LLM Integration v1](docs/LLM_V1.md)
|
|
688
|
+
- [Application scaffold v1](docs/APPLICATION_V1.md)
|
|
689
|
+
- [Capabilities v1](docs/CAPABILITIES_V1.md)
|
|
690
|
+
- [Temporal Contracts v1](docs/TEMPORAL_V1.md)
|
|
691
|
+
- [Content Addressing v1](docs/CONTENT_ADDRESSING_V1.md)
|
|
692
|
+
- [Incremental Dataflow v1](docs/DATAFLOW_V1.md)
|
|
481
693
|
- [Concepts and Principles](docs/IGNITER_CONCEPTS.md)
|
|
482
694
|
|
|
483
695
|
## Development
|
|
@@ -500,7 +712,12 @@ Current feature baseline:
|
|
|
500
712
|
- igniter-server: TCP server, Rack adapter, CLI, `remote:` DSL
|
|
501
713
|
- LLM compute nodes: Ollama, Anthropic, OpenAI providers
|
|
502
714
|
- Rails integration: Railtie, ActiveJob, ActionCable, webhook controller mixin
|
|
715
|
+
- `Igniter::Application`: YAML config, autoloading, scheduler, generator (`igniter-server new`)
|
|
503
716
|
- auditing, diagnostics, reactive subscriptions, graph introspection
|
|
717
|
+
- capability-based security: `capabilities`, `pure`, `Policy`, `CapabilityViolationError`
|
|
718
|
+
- temporal contracts: `include Igniter::Temporal`, `temporal_compute`, `as_of` input, historical reproduction
|
|
719
|
+
- content-addressed computation: `pure`, `fingerprint`, universal `ContentKey`, pluggable `ContentCache`
|
|
720
|
+
- incremental dataflow: `mode: :incremental`, `window:`, `feed_diff`, `collection_diff`, `DiffState`, `IncrementalCollectionResult`
|
|
504
721
|
|
|
505
722
|
## License
|
|
506
723
|
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Igniter::Application v1
|
|
2
|
+
|
|
3
|
+
`Igniter::Application` is an optional base class that packages contracts, executors, YAML config, a background scheduler, and server startup into a single coherent entry point. It replaces the raw `Igniter::Server.configure` boilerplate and provides a conventional project layout.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
### 1. Generate a scaffold
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
igniter-server new my_app
|
|
13
|
+
cd my_app
|
|
14
|
+
bundle install
|
|
15
|
+
bin/start
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This creates:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
my_app/
|
|
22
|
+
├── application.rb ← Application class (entry point)
|
|
23
|
+
├── application.yml ← base config (port, log_format, etc.)
|
|
24
|
+
├── Gemfile
|
|
25
|
+
├── config.ru ← Rack entry point for Puma / Unicorn
|
|
26
|
+
├── bin/start ← convenience start script
|
|
27
|
+
├── contracts/ ← put Contract subclasses here
|
|
28
|
+
└── executors/ ← put Executor subclasses here
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Define your Application
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# application.rb
|
|
35
|
+
require "igniter"
|
|
36
|
+
require "igniter/server"
|
|
37
|
+
require "igniter/application"
|
|
38
|
+
|
|
39
|
+
Dir[File.join(__dir__, "executors/**/*.rb")].sort.each { |f| require f }
|
|
40
|
+
Dir[File.join(__dir__, "contracts/**/*.rb")].sort.each { |f| require f }
|
|
41
|
+
|
|
42
|
+
class MyApp < Igniter::Application
|
|
43
|
+
config_file File.join(__dir__, "application.yml") # optional
|
|
44
|
+
|
|
45
|
+
configure do |c|
|
|
46
|
+
c.port = ENV.fetch("PORT", 4567).to_i
|
|
47
|
+
c.store = Igniter::Runtime::Stores::MemoryStore.new
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
register "OrderContract", OrderContract
|
|
51
|
+
register "InvoiceContract", InvoiceContract
|
|
52
|
+
|
|
53
|
+
schedule :cleanup, every: "1h" do
|
|
54
|
+
puts "[cleanup] #{Time.now.strftime("%H:%M")}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
schedule :daily_report, every: "1d", at: "09:00" do
|
|
58
|
+
DailyReportContract.new.resolve_all(date: Date.today)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
MyApp.start if $PROGRAM_NAME == __FILE__
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. Run
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Built-in HTTP server (blocking)
|
|
69
|
+
ruby application.rb
|
|
70
|
+
|
|
71
|
+
# Rack / Puma
|
|
72
|
+
bundle exec puma config.ru
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## DSL Reference
|
|
78
|
+
|
|
79
|
+
### `config_file(path)`
|
|
80
|
+
|
|
81
|
+
Load a YAML file as the base configuration. Applied **before** the `configure` block — values in the block always win.
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
config_file File.join(__dir__, "application.yml")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `configure { |c| ... }`
|
|
88
|
+
|
|
89
|
+
Block receives an `AppConfig` instance. May be called multiple times; blocks are applied in order.
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
configure do |c|
|
|
93
|
+
c.host = "0.0.0.0"
|
|
94
|
+
c.port = 4567
|
|
95
|
+
c.log_format = :json # :text (default) or :json
|
|
96
|
+
c.drain_timeout = 30 # seconds for SIGTERM drain
|
|
97
|
+
c.store = my_store # any store adapter
|
|
98
|
+
c.metrics_collector = Igniter::Metrics::Collector.new
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `executors_path(path)` / `contracts_path(path)`
|
|
103
|
+
|
|
104
|
+
Eagerly require all `.rb` files under the given directory on startup.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
executors_path "executors/"
|
|
108
|
+
contracts_path "contracts/"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `register(name, contract_class)`
|
|
112
|
+
|
|
113
|
+
Register a contract for HTTP dispatch.
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
register "OrderContract", OrderContract
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `schedule(name, every:, at: nil) { ... }`
|
|
120
|
+
|
|
121
|
+
Define a recurring background job. Starts automatically with `start` / `rack_app`.
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
schedule :heartbeat, every: "30s" do
|
|
125
|
+
HealthCheckContract.new.resolve_all
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
schedule :daily_report, every: "1d", at: "09:00" do
|
|
129
|
+
DailyReportContract.new.resolve_all(date: Date.today)
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Interval formats:**
|
|
134
|
+
|
|
135
|
+
| Format | Meaning |
|
|
136
|
+
|--------|---------|
|
|
137
|
+
| `30` | 30 seconds (Integer) |
|
|
138
|
+
| `"30s"` | 30 seconds |
|
|
139
|
+
| `"5m"` | 5 minutes |
|
|
140
|
+
| `"2h"` | 2 hours |
|
|
141
|
+
| `"1d"` | 1 day |
|
|
142
|
+
| `{ hours: 1, minutes: 30 }` | 90 minutes |
|
|
143
|
+
|
|
144
|
+
`at: "HH:MM"` delays the first run until the next occurrence of that wall-clock time, then repeats with `every:`.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## application.yml Reference
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
server:
|
|
152
|
+
port: 4567
|
|
153
|
+
host: "0.0.0.0"
|
|
154
|
+
log_format: text # "text" (default) or "json"
|
|
155
|
+
drain_timeout: 30 # seconds for graceful SIGTERM shutdown
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Keys under `server:` map 1-to-1 to `AppConfig` attributes. Values from YAML are applied first; the `configure` block runs afterwards and overrides anything.
|
|
159
|
+
|
|
160
|
+
ENV variables are not expanded in YAML — read them in the `configure` block:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
configure do |c|
|
|
164
|
+
c.port = ENV.fetch("PORT", 4567).to_i
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Lifecycle
|
|
171
|
+
|
|
172
|
+
### `MyApp.start`
|
|
173
|
+
|
|
174
|
+
Builds the config, registers contracts, starts background jobs, then starts the built-in pure-Ruby HTTP server (blocking). Registers `at_exit` to stop the scheduler.
|
|
175
|
+
|
|
176
|
+
### `MyApp.rack_app`
|
|
177
|
+
|
|
178
|
+
Same as `start` but returns a Rack-compatible application instead of blocking. Use with Puma, Unicorn, or any Rack server:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
# config.ru
|
|
182
|
+
require_relative "application"
|
|
183
|
+
run MyApp.rack_app
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
bundle exec puma config.ru -p 4567
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `MyApp.config`
|
|
191
|
+
|
|
192
|
+
Returns the `AppConfig` instance (populated after the first call to `start` or `rack_app`).
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Build Order
|
|
197
|
+
|
|
198
|
+
1. YAML file loaded → values applied to `AppConfig`
|
|
199
|
+
2. `executors_path` / `contracts_path` directories required
|
|
200
|
+
3. `configure` blocks run in declaration order (override YAML)
|
|
201
|
+
4. `Server::Config` built from `AppConfig`
|
|
202
|
+
5. Contracts registered on the server registry
|
|
203
|
+
6. Scheduler started (one thread per job)
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Subclass Isolation
|
|
208
|
+
|
|
209
|
+
Each `Igniter::Application` subclass gets its own isolated set of registered contracts, scheduled jobs, and configure blocks via the `inherited` hook. Subclasses do not share state:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
class AppA < Igniter::Application
|
|
213
|
+
register "ContractA", ContractA
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
class AppB < Igniter::Application
|
|
217
|
+
register "ContractB", ContractB
|
|
218
|
+
end
|
|
219
|
+
# AppA and AppB have completely independent registries
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Companion App Example
|
|
225
|
+
|
|
226
|
+
`examples/companion/` is a full production-style application built with `Igniter::Application`. It implements a distributed voice AI assistant pipeline:
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
ESP32 microphone → ASR → Intent → Chat (LLM) → TTS → ESP32 speaker
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Single-process demo (mock executors, no hardware):**
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
ruby examples/companion/demo.rb
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Orchestrator node (HP t740, real Ollama):**
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Requires: ollama serve (llama3.1:8b pulled)
|
|
242
|
+
bundle exec ruby examples/companion/application.rb
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**See also:** [`examples/companion/README.md`](../examples/companion/README.md)
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Integration with igniter-server
|
|
250
|
+
|
|
251
|
+
`Igniter::Application` is a thin wrapper around `Igniter::Server`. The underlying `Server::Config` and `HttpServer` are the same — you can still use `Igniter::Server.configure` directly when you don't need the Application scaffold.
|
|
252
|
+
|
|
253
|
+
The two approaches are compatible in the same process: `Igniter::Application#start` calls `Igniter::Server::HttpServer.new(config).start` internally.
|