Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 36 additions & 36 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
# Copy this file to `.env` for local development.
# Do not commit real credentials.

# Core LLM toggle
CHAINCOPILOT_LLM_ENABLED=true
CHAINCOPILOT_LLM_TIMEOUT_S=60
CHAINCOPILOT_LLM_RETRY_ATTEMPTS=1
CHAINCOPILOT_LLM_MODEL=gemma-4-31B-it

# Planner mode
# - hybrid: use LLM for candidate drafts, then deterministic scoring/selection
# - deterministic: skip planner LLM calls entirely
CHAINCOPILOT_PLANNER_MODE=hybrid

# Provider selection
# - gemini: Gemini Developer API
# - vertex: Vertex AI Gemini via aiplatform.googleapis.com
CHAINCOPILOT_LLM_PROVIDER=fpt

# Gemini Developer API
CHAINCOPILOT_LLM_API_KEY=

# Vertex AI Gemini
# Switch CHAINCOPILOT_LLM_PROVIDER=vertex when these are set correctly.
# VERTEX_AI_API_KEY=REPLACE_WITH_VERTEX_API_KEY
# VERTEX_AI_PROJECT_ID=REPLACE_WITH_GCP_PROJECT_ID
# VERTEX_AI_REGION=global

# Optional fallbacks recognized by the runtime
# GEMINI_API_KEY=REPLACE_WITH_GEMINI_API_KEY
# GOOGLE_CLOUD_PROJECT=REPLACE_WITH_GCP_PROJECT_ID
# GOOGLE_CLOUD_LOCATION=global




# Copy this file to `.env` for local development.
# Do not commit real credentials.
# Core LLM toggle
CHAINCOPILOT_LLM_ENABLED=true
CHAINCOPILOT_LLM_TIMEOUT_S=60
CHAINCOPILOT_LLM_RETRY_ATTEMPTS=1
CHAINCOPILOT_LLM_MODEL=gemma-4-31B-it
# Planner mode
# - hybrid: use LLM for candidate drafts, then deterministic scoring/selection
# - deterministic: skip planner LLM calls entirely
CHAINCOPILOT_PLANNER_MODE=hybrid
# Provider selection
# - gemini: Gemini Developer API
# - vertex: Vertex AI Gemini via aiplatform.googleapis.com
CHAINCOPILOT_LLM_PROVIDER=fpt
# Gemini Developer API
CHAINCOPILOT_LLM_API_KEY=
# Vertex AI Gemini
# Switch CHAINCOPILOT_LLM_PROVIDER=vertex when these are set correctly.
# VERTEX_AI_API_KEY=REPLACE_WITH_VERTEX_API_KEY
# VERTEX_AI_PROJECT_ID=REPLACE_WITH_GCP_PROJECT_ID
# VERTEX_AI_REGION=global
# Optional fallbacks recognized by the runtime
# GEMINI_API_KEY=REPLACE_WITH_GEMINI_API_KEY
# GOOGLE_CLOUD_PROJECT=REPLACE_WITH_GCP_PROJECT_ID
# GOOGLE_CLOUD_LOCATION=global
20 changes: 10 additions & 10 deletions .streamlit/config.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[theme]
primaryColor = "#4F46E5"
backgroundColor = "#FFFFFF"
secondaryBackgroundColor = "#F8FAFC"
textColor = "#1E293B"
font = "sans serif"
base = "light"

[server]
headless = true
[theme]
primaryColor = "#4F46E5"
backgroundColor = "#FFFFFF"
secondaryBackgroundColor = "#F8FAFC"
textColor = "#1E293B"
font = "sans serif"
base = "light"
[server]
headless = true
84 changes: 42 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
# ChainCopilot

ChainCopilot is a hackathon MVP for an autonomous resilient supply chain control tower.
It implements a stateful agentic loop:

- Sense
- Analyze
- Plan
- Act
- Learn

The MVP includes:

- 6-layer control-tower architecture
- Demand, Inventory, Supplier, Logistics, Risk, and Planner agents
- Normal and crisis modes with deterministic policy switching
- Decision logs with explainability
- What-if scenario simulation
- Streamlit dashboard and FastAPI service

## Quickstart

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pytest -q
streamlit run ui/dashboard.py
```

## API

```bash
python run_api.py
```

## Scenarios

- supplier_delay
- demand_spike
- route_blockage
- compound_disruption
# ChainCopilot
ChainCopilot is a hackathon MVP for an autonomous resilient supply chain control tower.
It implements a stateful agentic loop:
- Sense
- Analyze
- Plan
- Act
- Learn
The MVP includes:
- 6-layer control-tower architecture
- Demand, Inventory, Supplier, Logistics, Risk, and Planner agents
- Normal and crisis modes with deterministic policy switching
- Decision logs with explainability
- What-if scenario simulation
- Streamlit dashboard and FastAPI service
## Quickstart
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pytest -q
streamlit run ui/dashboard.py
```
## API
```bash
python run_api.py
```
## Scenarios
- supplier_delay
- demand_spike
- route_blockage
- compound_disruption
52 changes: 26 additions & 26 deletions actions/switch_supplier.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
from core.models import Action, SystemState


def apply_switch_supplier(state: SystemState, action: Action) -> None:
item = state.inventory.get(action.target_id)
if item is None:
return

supplier_id = action.parameters.get("supplier_id")
if not supplier_id:
return

supplier = _resolve_supplier(state, supplier_id, item.sku)
if supplier is None:
return

item.preferred_supplier_id = supplier_id
item.unit_cost = supplier.unit_cost
state.extra_cost += max(action.estimated_cost_delta, 0.0)


def _resolve_supplier(state: SystemState, supplier_id: str, sku: str):
return (
state.suppliers.get(f"{supplier_id}_{sku}")
or state.suppliers.get(supplier_id)
)
from core.models import Action, SystemState
def apply_switch_supplier(state: SystemState, action: Action) -> None:
item = state.inventory.get(action.target_id)
if item is None:
return
supplier_id = action.parameters.get("supplier_id")
if not supplier_id:
return
supplier = _resolve_supplier(state, supplier_id, item.sku)
if supplier is None:
return
item.preferred_supplier_id = supplier_id
item.unit_cost = supplier.unit_cost
state.extra_cost += max(action.estimated_cost_delta, 0.0)
def _resolve_supplier(state: SystemState, supplier_id: str, sku: str):
return (
state.suppliers.get(f"{supplier_id}_{sku}")
or state.suppliers.get(supplier_id)
)
68 changes: 34 additions & 34 deletions agents/base.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
from __future__ import annotations

from abc import ABC, abstractmethod

from core.models import AgentProposal, Event, SystemState


class BaseAgent(ABC):
name: str
custom_system_prompt: str | None = None

@abstractmethod
def run(self, state: SystemState, event: Event | None = None) -> AgentProposal:
raise NotImplementedError

def enrich_with_llm(
self,
*,
state: SystemState,
event: Event | None,
proposal: AgentProposal,
state_slice: dict,
) -> AgentProposal:
from llm.service import enrich_specialist_proposal

enrich_specialist_proposal(
agent_name=self.name,
state=state,
event=event,
proposal=proposal,
state_slice=state_slice,
custom_prompt=self.custom_system_prompt,
)
return proposal
from __future__ import annotations
from abc import ABC, abstractmethod
from core.models import AgentProposal, Event, SystemState
class BaseAgent(ABC):
name: str
custom_system_prompt: str | None = None
@abstractmethod
def run(self, state: SystemState, event: Event | None = None) -> AgentProposal:
raise NotImplementedError
def enrich_with_llm(
self,
*,
state: SystemState,
event: Event | None,
proposal: AgentProposal,
state_slice: dict,
) -> AgentProposal:
from llm.service import enrich_specialist_proposal
enrich_specialist_proposal(
agent_name=self.name,
state=state,
event=event,
proposal=proposal,
state_slice=state_slice,
custom_prompt=self.custom_system_prompt,
)
return proposal
11 changes: 9 additions & 2 deletions agents/critic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from agents.base import BaseAgent
from core.models import AgentProposal, Event, SystemState
from llm.service import critique_candidate_plans
from policies.explainability import build_critic_review


class CriticAgent(BaseAgent):
Expand All @@ -21,6 +22,11 @@ def run(self, state: SystemState, event: Event | None = None) -> AgentProposal:
selected_plan=state.latest_plan,
evaluations=decision_log.candidate_evaluations,
)
if not summary and not findings:
summary, findings = build_critic_review(
state.latest_plan,
decision_log.candidate_evaluations,
)
state.latest_plan.critic_summary = summary
decision_log.critic_summary = summary
decision_log.critic_findings = findings
Expand All @@ -32,11 +38,12 @@ def run(self, state: SystemState, event: Event | None = None) -> AgentProposal:
proposal.notes_for_planner = summary
if findings:
proposal.downstream_impacts = findings
proposal.tradeoffs = findings[:2]
proposal.llm_used = used
proposal.llm_error = error
proposal.observations.append(
"critic reviewed candidate plans"
"Critic reviewed candidate plans"
if used or summary or findings
else "critic unavailable; deterministic selection retained"
else "Critic unavailable; deterministic selection retained"
)
return proposal
Loading
Loading