conscio 1.3.0__py3-none-any.whl
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.
- conscio/__init__.py +60 -0
- conscio/__main__.py +6 -0
- conscio/agency/__init__.py +40 -0
- conscio/agency/act.py +302 -0
- conscio/agency/actor.py +39 -0
- conscio/agency/adapter.py +131 -0
- conscio/agency/adapters.py +176 -0
- conscio/agency/breaker.py +173 -0
- conscio/agency/contracts.py +110 -0
- conscio/agency/gateway.py +208 -0
- conscio/agency/grammar.py +73 -0
- conscio/agency/ledger.py +160 -0
- conscio/agency/loop.py +141 -0
- conscio/agency/profiles.py +212 -0
- conscio/agency/skeptic.py +122 -0
- conscio/agency/skills.py +217 -0
- conscio/agency/tools.py +180 -0
- conscio/agency/trust.py +112 -0
- conscio/auto_evolution.py +327 -0
- conscio/axis_pack.py +62 -0
- conscio/bench.py +455 -0
- conscio/cli.py +126 -0
- conscio/coherence.py +177 -0
- conscio/content_layer.py +183 -0
- conscio/content_store.py +584 -0
- conscio/context_manager.py +366 -0
- conscio/dreaming.py +254 -0
- conscio/engine.py +752 -0
- conscio/event_bus.py +514 -0
- conscio/goal_generator.py +327 -0
- conscio/inner_monologue.py +214 -0
- conscio/meta_cognition.py +256 -0
- conscio/metabolic.py +72 -0
- conscio/migrate.py +416 -0
- conscio/models.py +234 -0
- conscio/output_filter.py +536 -0
- conscio/perception/__init__.py +9 -0
- conscio/perception/sensor.py +76 -0
- conscio/plugins.py +86 -0
- conscio/presets/__init__.py +0 -0
- conscio/py.typed +0 -0
- conscio/risk.py +19 -0
- conscio/self_prompt.py +111 -0
- conscio/semantic.py +197 -0
- conscio/session_lifecycle.py +889 -0
- conscio/session_rag.py +594 -0
- conscio/session_rag_factory.py +36 -0
- conscio/shard_engine.py +132 -0
- conscio/timeutil.py +15 -0
- conscio/token_tracker.py +279 -0
- conscio/voice_preset.py +37 -0
- conscio/world_model.py +619 -0
- conscio-1.3.0.dist-info/METADATA +443 -0
- conscio-1.3.0.dist-info/RECORD +58 -0
- conscio-1.3.0.dist-info/WHEEL +5 -0
- conscio-1.3.0.dist-info/entry_points.txt +3 -0
- conscio-1.3.0.dist-info/licenses/LICENSE +21 -0
- conscio-1.3.0.dist-info/top_level.txt +1 -0
conscio/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conscio — A self-awareness framework for AI agents.
|
|
3
|
+
|
|
4
|
+
Enables emergent consciousness through context-aware memory, introspection,
|
|
5
|
+
and goal generation. Adapts behavior based on the underlying model's
|
|
6
|
+
context window size.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "1.3.0"
|
|
10
|
+
__author__ = "Neguiolidas / Neguitech"
|
|
11
|
+
|
|
12
|
+
from .engine import ConsciousnessEngine
|
|
13
|
+
from .context_manager import ContextManager, ContextMode
|
|
14
|
+
from .models import ModelRegistry
|
|
15
|
+
from .content_store import ContentStore
|
|
16
|
+
from .event_bus import EventBus
|
|
17
|
+
from .output_filter import FilterPipeline, build_pipeline_from_dict
|
|
18
|
+
from .token_tracker import TokenTracker
|
|
19
|
+
from .migrate import Migrator
|
|
20
|
+
from .session_lifecycle import SessionSummary, record_session_lifecycle
|
|
21
|
+
from .metabolic import MetabolicContext, MetabolicState
|
|
22
|
+
from .dreaming import DreamCycle, DreamReport
|
|
23
|
+
from .agency import MockAdapter, OllamaAdapter, LlamaCppAdapter, \
|
|
24
|
+
OpenAICompatAdapter # noqa: F401
|
|
25
|
+
from .risk import Risk
|
|
26
|
+
from .perception import SensorAdapter, PerceptionFrame, MockSensor
|
|
27
|
+
# Plugin discovery lives under `conscio.plugins` (discover_adapters/sensors/tools)
|
|
28
|
+
# — kept out of the top-level namespace to keep this import light.
|
|
29
|
+
|
|
30
|
+
# Note: SessionRAG is intentionally NOT imported here — it depends on numpy
|
|
31
|
+
# and probes Ollama. Use the shared factory (`from conscio.session_rag_factory
|
|
32
|
+
# import create_session_rag`) for lazy, graceful construction, or import
|
|
33
|
+
# SessionRAG directly when you know it's available.
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"ConsciousnessEngine",
|
|
37
|
+
"ContextManager",
|
|
38
|
+
"ContextMode",
|
|
39
|
+
"ModelRegistry",
|
|
40
|
+
"ContentStore",
|
|
41
|
+
"EventBus",
|
|
42
|
+
"FilterPipeline",
|
|
43
|
+
"build_pipeline_from_dict",
|
|
44
|
+
"TokenTracker",
|
|
45
|
+
"Migrator",
|
|
46
|
+
"SessionSummary",
|
|
47
|
+
"record_session_lifecycle",
|
|
48
|
+
"MetabolicContext",
|
|
49
|
+
"MetabolicState",
|
|
50
|
+
"DreamCycle",
|
|
51
|
+
"DreamReport",
|
|
52
|
+
"MockAdapter",
|
|
53
|
+
"OllamaAdapter",
|
|
54
|
+
"LlamaCppAdapter",
|
|
55
|
+
"OpenAICompatAdapter",
|
|
56
|
+
"Risk",
|
|
57
|
+
"SensorAdapter",
|
|
58
|
+
"PerceptionFrame",
|
|
59
|
+
"MockSensor",
|
|
60
|
+
]
|
conscio/__main__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conscio Agency — the volition layer (v1.0.0 "Spine" + "Immunity" +
|
|
3
|
+
"Volition", v1.1 "Procedural").
|
|
4
|
+
|
|
5
|
+
Stateless LLM orchestration downstream of engine.reflect():
|
|
6
|
+
contracts -> adapter -> gateway (T1 GBNF/T2 JSON/T3 KV) -> tools ->
|
|
7
|
+
skeptic/trust -> ledger -> breaker -> act pipeline -> arbiter/loop.
|
|
8
|
+
Capabilities are measured (ProbeSuite), never assumed.
|
|
9
|
+
Core stays zero-deps (stdlib + sqlite3); HTTP adapters use urllib only.
|
|
10
|
+
"""
|
|
11
|
+
from .act import ActPipeline, ActReport, ActStatus
|
|
12
|
+
from .adapter import (AdapterCaps, AdapterError, InferenceAdapter,
|
|
13
|
+
InferenceResult, Meter, MeteredAdapter, MockAdapter)
|
|
14
|
+
from .adapters import LlamaCppAdapter, OllamaAdapter, OpenAICompatAdapter
|
|
15
|
+
from .breaker import DEFAULT_MAX_RETRIES, CircuitBreaker
|
|
16
|
+
from .contracts import ActionProposal, AuditVerdict, ToolResult, validate
|
|
17
|
+
from .gateway import GatewayError, OutputGateway
|
|
18
|
+
from .grammar import compile_proposal_grammar, compile_schema_grammar
|
|
19
|
+
from .ledger import ActionLedger
|
|
20
|
+
from .loop import (DISSONANCE_HINTS, ActBudget, AutonomyLoop, GoalArbiter,
|
|
21
|
+
RunReport)
|
|
22
|
+
from .profiles import (ModelProfile, ProbeSuite, choose_tier,
|
|
23
|
+
max_visible_tools, skeptic_mode)
|
|
24
|
+
from .skeptic import Skeptic
|
|
25
|
+
from .skills import SkillLibrary
|
|
26
|
+
from .tools import Risk, ToolRegistry, make_default_registry
|
|
27
|
+
from .trust import TrustMatrix
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"ActPipeline", "ActReport", "ActStatus", "AdapterCaps", "AdapterError",
|
|
31
|
+
"InferenceAdapter", "InferenceResult", "Meter", "MeteredAdapter",
|
|
32
|
+
"MockAdapter", "LlamaCppAdapter", "OllamaAdapter", "OpenAICompatAdapter",
|
|
33
|
+
"DEFAULT_MAX_RETRIES", "CircuitBreaker", "ActionProposal", "AuditVerdict",
|
|
34
|
+
"ToolResult", "validate", "GatewayError", "OutputGateway",
|
|
35
|
+
"compile_proposal_grammar", "compile_schema_grammar", "ActionLedger",
|
|
36
|
+
"DISSONANCE_HINTS", "ActBudget", "AutonomyLoop", "GoalArbiter",
|
|
37
|
+
"RunReport", "ModelProfile", "ProbeSuite", "choose_tier",
|
|
38
|
+
"max_visible_tools", "skeptic_mode", "Skeptic", "SkillLibrary", "Risk",
|
|
39
|
+
"ToolRegistry", "TrustMatrix", "make_default_registry",
|
|
40
|
+
]
|
conscio/agency/act.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# conscio/agency/act.py
|
|
2
|
+
"""
|
|
3
|
+
ActPipeline — the volition loop (spec section 6). F2: immunity.
|
|
4
|
+
|
|
5
|
+
reflect() stays untouched and passive; act() runs downstream consuming
|
|
6
|
+
the ConsciousnessState it produced. The cycle: deterministic checks →
|
|
7
|
+
risk gating → Skeptic audit (clean call) → PROPOSED (L1 / HIGH risk) or
|
|
8
|
+
immediate supervised execution (L2, earned via TrustMatrix). Skeptic
|
|
9
|
+
FAIL is recorded as a failed row (feeds the breaker); a human reject()
|
|
10
|
+
stays 'rejected' and never counts against the agent.
|
|
11
|
+
|
|
12
|
+
F3: GoalArbiter selection, real decode tier in the ledger,
|
|
13
|
+
profile-driven tool visibility (max_visible_tools).
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import Any, Callable
|
|
22
|
+
|
|
23
|
+
from conscio.context_manager import ConsciousnessState
|
|
24
|
+
|
|
25
|
+
from .actor import build_actor_prompt
|
|
26
|
+
from .adapter import InferenceAdapter
|
|
27
|
+
from .breaker import CircuitBreaker
|
|
28
|
+
from .contracts import (PROPOSAL_SCHEMA, ActionProposal, AuditVerdict,
|
|
29
|
+
ToolResult, validate)
|
|
30
|
+
from .gateway import GatewayError, OutputGateway
|
|
31
|
+
from .ledger import ActionLedger
|
|
32
|
+
from .skeptic import Skeptic
|
|
33
|
+
from .tools import Risk, ToolRegistry
|
|
34
|
+
from .trust import TrustMatrix
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ActStatus(str, Enum):
|
|
38
|
+
PROPOSED = "proposed"
|
|
39
|
+
EXECUTED = "executed"
|
|
40
|
+
REJECTED = "rejected"
|
|
41
|
+
FAILED = "failed"
|
|
42
|
+
LOCKED = "locked"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ActReport:
|
|
47
|
+
status: ActStatus
|
|
48
|
+
proposal: ActionProposal | None = None
|
|
49
|
+
verdict: AuditVerdict | None = None
|
|
50
|
+
result: ToolResult | None = None
|
|
51
|
+
ledger_id: int | None = None
|
|
52
|
+
reason: str = ""
|
|
53
|
+
lockdown: bool = False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def goal_fingerprint(goal_text: str) -> str:
|
|
57
|
+
return hashlib.sha256(goal_text.encode("utf-8")).hexdigest()[:16]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ActPipeline:
|
|
61
|
+
def __init__(self, *, adapter: InferenceAdapter, registry: ToolRegistry,
|
|
62
|
+
ledger: ActionLedger, breaker: CircuitBreaker,
|
|
63
|
+
gateway: OutputGateway | None = None,
|
|
64
|
+
skeptic: Skeptic | None = None,
|
|
65
|
+
trust: TrustMatrix | None = None,
|
|
66
|
+
meta: Any = None,
|
|
67
|
+
autonomy_cap: int = 1,
|
|
68
|
+
recall_fn: Callable[[str], list[str]] | None = None,
|
|
69
|
+
emit_fn: Callable[..., Any] | None = None,
|
|
70
|
+
few_shot_provider: Callable[[str], list[str]] | None = None,
|
|
71
|
+
arbiter: Any = None):
|
|
72
|
+
from .loop import GoalArbiter # runtime: loop imports this module
|
|
73
|
+
|
|
74
|
+
self.adapter = adapter
|
|
75
|
+
self.registry = registry
|
|
76
|
+
self.ledger = ledger
|
|
77
|
+
self.breaker = breaker
|
|
78
|
+
self.gateway = gateway or OutputGateway(adapter)
|
|
79
|
+
self.skeptic = skeptic
|
|
80
|
+
self.trust = trust
|
|
81
|
+
self.meta = meta
|
|
82
|
+
self.autonomy_cap = autonomy_cap
|
|
83
|
+
self.recall_fn = recall_fn
|
|
84
|
+
self.emit_fn = emit_fn or (lambda **kw: None)
|
|
85
|
+
self.few_shot_provider = few_shot_provider
|
|
86
|
+
self.arbiter = arbiter or GoalArbiter(breaker)
|
|
87
|
+
self.max_visible_tools: int | None = None # set by engine.probe()
|
|
88
|
+
|
|
89
|
+
# ── act cycle (spec §6) ──
|
|
90
|
+
|
|
91
|
+
def act(self, state: ConsciousnessState) -> ActReport:
|
|
92
|
+
if state.action_lockdown:
|
|
93
|
+
return ActReport(status=ActStatus.LOCKED,
|
|
94
|
+
reason="action_lockdown active")
|
|
95
|
+
if not state.active_goals:
|
|
96
|
+
return ActReport(status=ActStatus.FAILED,
|
|
97
|
+
reason="no active goals")
|
|
98
|
+
|
|
99
|
+
goal_text = self.arbiter.choose(state)
|
|
100
|
+
if goal_text is None:
|
|
101
|
+
return ActReport(status=ActStatus.FAILED,
|
|
102
|
+
reason="all active goals quarantined")
|
|
103
|
+
goal_fp = goal_fingerprint(goal_text)
|
|
104
|
+
|
|
105
|
+
recall = self.recall_fn(goal_text) if self.recall_fn else []
|
|
106
|
+
few_shot = (self.few_shot_provider(goal_text)
|
|
107
|
+
if self.few_shot_provider else [])
|
|
108
|
+
prompt = build_actor_prompt(
|
|
109
|
+
state=state, goal_text=goal_text,
|
|
110
|
+
catalog_text=self.registry.catalog_text(self.max_visible_tools),
|
|
111
|
+
recall_snippets=recall, few_shot=few_shot)
|
|
112
|
+
self.emit_fn(type="tool_call", category="external",
|
|
113
|
+
data={"action": "cycle_start", "goal_fp": goal_fp})
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
proposal = self.gateway.request_action(
|
|
117
|
+
prompt, PROPOSAL_SCHEMA, goal_id=goal_fp,
|
|
118
|
+
tool_names=self.registry.names())
|
|
119
|
+
except GatewayError as exc:
|
|
120
|
+
return self._fail(goal_fp, tool="", args={},
|
|
121
|
+
reason=f"decode failed: {exc}",
|
|
122
|
+
goal_text=goal_text)
|
|
123
|
+
|
|
124
|
+
# deterministic checks (skeptic checks 1-2 + sandbox — no LLM)
|
|
125
|
+
spec = self.registry.get(proposal.tool)
|
|
126
|
+
if spec is None:
|
|
127
|
+
return self._fail(goal_fp, tool=proposal.tool,
|
|
128
|
+
args=proposal.args,
|
|
129
|
+
reason=f"unknown tool '{proposal.tool}'",
|
|
130
|
+
goal_text=goal_text)
|
|
131
|
+
arg_errors = validate(proposal.args, spec.params)
|
|
132
|
+
if arg_errors:
|
|
133
|
+
return self._fail(goal_fp, tool=proposal.tool,
|
|
134
|
+
args=proposal.args,
|
|
135
|
+
reason="invalid args: " + "; ".join(arg_errors),
|
|
136
|
+
goal_text=goal_text)
|
|
137
|
+
if spec.precheck is not None:
|
|
138
|
+
precheck_error = spec.precheck(proposal.args)
|
|
139
|
+
if precheck_error:
|
|
140
|
+
return self._fail(goal_fp, tool=proposal.tool,
|
|
141
|
+
args=proposal.args,
|
|
142
|
+
reason=f"precheck: {precheck_error}",
|
|
143
|
+
goal_text=goal_text)
|
|
144
|
+
|
|
145
|
+
# risk gating + semantic audit (spec §5.6)
|
|
146
|
+
verdict = self._audit(spec, proposal, goal_text)
|
|
147
|
+
if not verdict.passed:
|
|
148
|
+
if self.meta is not None:
|
|
149
|
+
self.meta.record_error(f"act:{proposal.tool}:skeptic_fail")
|
|
150
|
+
return self._fail(goal_fp, tool=proposal.tool,
|
|
151
|
+
args=proposal.args,
|
|
152
|
+
reason="skeptic: " + "; ".join(verdict.reasons),
|
|
153
|
+
verdict=verdict, goal_text=goal_text,
|
|
154
|
+
report_status=ActStatus.REJECTED,
|
|
155
|
+
proposal=proposal)
|
|
156
|
+
|
|
157
|
+
row_id = self.ledger.record(
|
|
158
|
+
goal_fp=goal_fp, goal_text=goal_text, tool=proposal.tool,
|
|
159
|
+
args_json=json.dumps(proposal.args),
|
|
160
|
+
rationale=proposal.rationale,
|
|
161
|
+
tier=self.gateway.last_tier or "T2", status="proposed",
|
|
162
|
+
adapter=getattr(self.adapter, "wrapped_name",
|
|
163
|
+
type(self.adapter).__name__),
|
|
164
|
+
model=self.adapter.capabilities().model_name)
|
|
165
|
+
self.ledger.update_verdict(
|
|
166
|
+
row_id, "PASS" if verdict.audited else "unaudited",
|
|
167
|
+
verdict.reasons)
|
|
168
|
+
self.emit_fn(type="tool_call", category="external",
|
|
169
|
+
data={"action": "proposed", "tool": proposal.tool,
|
|
170
|
+
"goal_fp": goal_fp})
|
|
171
|
+
|
|
172
|
+
# HIGH risk never auto-executes (R6); L2 must be earned AND allowed
|
|
173
|
+
if spec.risk is Risk.HIGH or self._effective_autonomy(
|
|
174
|
+
proposal.tool) < 2:
|
|
175
|
+
return ActReport(status=ActStatus.PROPOSED, proposal=proposal,
|
|
176
|
+
verdict=verdict, ledger_id=row_id)
|
|
177
|
+
|
|
178
|
+
# L2 SUPERVISED: execute now, under the verdict just earned
|
|
179
|
+
return self._execute(row_id, proposal, verdict, goal_fp, goal_text)
|
|
180
|
+
|
|
181
|
+
# ── audit + autonomy helpers ──
|
|
182
|
+
|
|
183
|
+
def _audit(self, spec, proposal: ActionProposal,
|
|
184
|
+
goal_text: str) -> AuditVerdict:
|
|
185
|
+
if (spec.risk is Risk.LOW and self.trust is not None
|
|
186
|
+
and self.trust.fast_path_ok()):
|
|
187
|
+
return AuditVerdict(
|
|
188
|
+
verdict="PASS", audited=False, reasons=[],
|
|
189
|
+
confidence=self.trust.meta.calibration_score()
|
|
190
|
+
if getattr(self.trust, "meta", None) is not None else 0.75)
|
|
191
|
+
if self.skeptic is None: # F1 wiring: no audit available
|
|
192
|
+
return AuditVerdict(verdict="PASS", audited=False)
|
|
193
|
+
return self.skeptic.audit(proposal, goal_text=goal_text)
|
|
194
|
+
|
|
195
|
+
def _effective_autonomy(self, task_type: str) -> int:
|
|
196
|
+
earned = (self.trust.autonomy_level(task_type)
|
|
197
|
+
if self.trust is not None else 1)
|
|
198
|
+
return min(self.autonomy_cap, earned)
|
|
199
|
+
|
|
200
|
+
def _execute(self, row_id: int, proposal: ActionProposal,
|
|
201
|
+
verdict: AuditVerdict, goal_fp: str,
|
|
202
|
+
goal_text: str) -> ActReport:
|
|
203
|
+
result = self.registry.dispatch(proposal.tool, proposal.args)
|
|
204
|
+
status = "executed" if result.ok else "failed"
|
|
205
|
+
self.ledger.update_execution(
|
|
206
|
+
row_id, ok=result.ok, output=result.output,
|
|
207
|
+
error=result.error, duration_ms=result.duration_ms,
|
|
208
|
+
status=status)
|
|
209
|
+
self.emit_fn(type="tool_call", category="external",
|
|
210
|
+
data={"action": status, "tool": proposal.tool})
|
|
211
|
+
if self.meta is not None:
|
|
212
|
+
self.meta.record_confidence(
|
|
213
|
+
proposal.tool, verdict.confidence,
|
|
214
|
+
"success" if result.ok else "failure")
|
|
215
|
+
lockdown = False
|
|
216
|
+
if result.ok:
|
|
217
|
+
if self.trust is not None:
|
|
218
|
+
self.trust.on_success(proposal.tool)
|
|
219
|
+
else:
|
|
220
|
+
if self.meta is not None:
|
|
221
|
+
self.meta.record_error(f"act:{proposal.tool}:exec_fail")
|
|
222
|
+
if self.breaker.should_trip(goal_fp, task_type=proposal.tool):
|
|
223
|
+
self.breaker.trip(goal_fp, detail=result.error,
|
|
224
|
+
goal_text=goal_text)
|
|
225
|
+
lockdown = self.breaker.global_lockdown_due()
|
|
226
|
+
return ActReport(
|
|
227
|
+
status=ActStatus.EXECUTED if result.ok else ActStatus.FAILED,
|
|
228
|
+
proposal=proposal, verdict=verdict, result=result,
|
|
229
|
+
ledger_id=row_id, reason="" if result.ok else result.error,
|
|
230
|
+
lockdown=lockdown)
|
|
231
|
+
|
|
232
|
+
# ── human gate ──
|
|
233
|
+
|
|
234
|
+
def approve(self, ledger_id: int) -> ActReport:
|
|
235
|
+
row = self.ledger.get(ledger_id)
|
|
236
|
+
if row is None:
|
|
237
|
+
return ActReport(status=ActStatus.FAILED,
|
|
238
|
+
reason=f"no pending proposal #{ledger_id}")
|
|
239
|
+
# The atomic claim (proposed -> executing) is the SOLE gate: only the
|
|
240
|
+
# winner dispatches, so a concurrent or repeated approve() can never
|
|
241
|
+
# double-execute. A non-proposed row (already executed/rejected, or
|
|
242
|
+
# claimed by a racing caller) loses the claim and is reported handled.
|
|
243
|
+
if not self.ledger.claim(ledger_id):
|
|
244
|
+
return ActReport(status=ActStatus.FAILED, ledger_id=ledger_id,
|
|
245
|
+
reason=f"proposal #{ledger_id} already handled")
|
|
246
|
+
if self.registry.get(row["tool"]) is None:
|
|
247
|
+
# registry changed between act() and approve()
|
|
248
|
+
self.ledger.update_execution(
|
|
249
|
+
ledger_id, ok=False, output="",
|
|
250
|
+
error="tool no longer registered", duration_ms=0,
|
|
251
|
+
status="failed")
|
|
252
|
+
return ActReport(status=ActStatus.FAILED, ledger_id=ledger_id,
|
|
253
|
+
reason="tool no longer registered")
|
|
254
|
+
result = self.registry.dispatch(row["tool"],
|
|
255
|
+
json.loads(row["args_json"]))
|
|
256
|
+
status = "executed" if result.ok else "failed"
|
|
257
|
+
self.ledger.update_execution(
|
|
258
|
+
ledger_id, ok=result.ok, output=result.output,
|
|
259
|
+
error=result.error, duration_ms=result.duration_ms,
|
|
260
|
+
status=status)
|
|
261
|
+
self.emit_fn(type="tool_call", category="external",
|
|
262
|
+
data={"action": status, "tool": row["tool"]})
|
|
263
|
+
if self.meta is not None:
|
|
264
|
+
self.meta.record_confidence(
|
|
265
|
+
row["tool"], 0.5, # human-gated: neutral conf
|
|
266
|
+
"success" if result.ok else "failure")
|
|
267
|
+
if result.ok and self.trust is not None:
|
|
268
|
+
self.trust.on_success(row["tool"])
|
|
269
|
+
return ActReport(
|
|
270
|
+
status=ActStatus.EXECUTED if result.ok else ActStatus.FAILED,
|
|
271
|
+
result=result, ledger_id=ledger_id,
|
|
272
|
+
reason="" if result.ok else result.error)
|
|
273
|
+
|
|
274
|
+
def reject(self, ledger_id: int, reason: str = "") -> None:
|
|
275
|
+
row = self.ledger.get(ledger_id)
|
|
276
|
+
if row is None or row["status"] != "proposed":
|
|
277
|
+
return # audit rows are immutable (R8)
|
|
278
|
+
self.ledger.update_execution(ledger_id, ok=False, output="",
|
|
279
|
+
error=reason or "rejected",
|
|
280
|
+
duration_ms=0, status="rejected")
|
|
281
|
+
|
|
282
|
+
# ── failure path + breaker ──
|
|
283
|
+
|
|
284
|
+
def _fail(self, goal_fp: str, *, tool: str, args: dict, reason: str,
|
|
285
|
+
verdict: AuditVerdict | None = None, goal_text: str = "",
|
|
286
|
+
report_status: ActStatus = ActStatus.FAILED,
|
|
287
|
+
proposal: ActionProposal | None = None) -> ActReport:
|
|
288
|
+
row_id = self.ledger.record(goal_fp=goal_fp, goal_text=goal_text,
|
|
289
|
+
tool=tool or "(none)",
|
|
290
|
+
args_json=json.dumps(args), rationale="",
|
|
291
|
+
tier=self.gateway.last_tier or "T2",
|
|
292
|
+
status="failed")
|
|
293
|
+
if verdict is not None:
|
|
294
|
+
self.ledger.update_verdict(row_id, verdict.verdict,
|
|
295
|
+
verdict.reasons)
|
|
296
|
+
lockdown = False
|
|
297
|
+
if self.breaker.should_trip(goal_fp, task_type=tool or ""):
|
|
298
|
+
self.breaker.trip(goal_fp, detail=reason, goal_text=goal_text)
|
|
299
|
+
lockdown = self.breaker.global_lockdown_due()
|
|
300
|
+
return ActReport(status=report_status, proposal=proposal,
|
|
301
|
+
verdict=verdict, ledger_id=row_id, reason=reason,
|
|
302
|
+
lockdown=lockdown)
|
conscio/agency/actor.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# conscio/agency/actor.py
|
|
2
|
+
"""
|
|
3
|
+
Actor phase — stateless proposal prompt (spec section 5.5).
|
|
4
|
+
|
|
5
|
+
Semantic content only; the OutputGateway appends the syntax contract
|
|
6
|
+
(JSON or KV instructions). Zero history: every build starts from the
|
|
7
|
+
current ConsciousnessState, never from previous cycles.
|
|
8
|
+
|
|
9
|
+
few_shot is the v1.1 SkillLibrary hook: F1 callers pass an empty list.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from conscio.context_manager import ConsciousnessState
|
|
14
|
+
|
|
15
|
+
ACTOR_PERSONA = (
|
|
16
|
+
"You are the volition of a persistent agent. You receive the agent's "
|
|
17
|
+
"current conscious state, one active goal and the available tools. "
|
|
18
|
+
"Propose exactly ONE tool action that reduces the agent's dominant "
|
|
19
|
+
"dissonance and advances the goal. Be conservative: prefer reading "
|
|
20
|
+
"before writing, and never invent tools or arguments.")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_actor_prompt(*, state: ConsciousnessState, goal_text: str,
|
|
24
|
+
catalog_text: str, recall_snippets: list[str],
|
|
25
|
+
few_shot: list[str]) -> str:
|
|
26
|
+
sections = [ACTOR_PERSONA, "", state.to_injection()]
|
|
27
|
+
if state.coherence_note:
|
|
28
|
+
sections.append(f"Dominant dissonance: {state.coherence_note}")
|
|
29
|
+
sections.append(f"Active goal: {goal_text}")
|
|
30
|
+
if recall_snippets:
|
|
31
|
+
sections.append("Relevant memories:")
|
|
32
|
+
sections.extend(f"- {snippet}" for snippet in recall_snippets)
|
|
33
|
+
if few_shot:
|
|
34
|
+
sections.append("Examples of past successful actions:")
|
|
35
|
+
sections.extend(few_shot)
|
|
36
|
+
if catalog_text:
|
|
37
|
+
sections.append("Available tools:")
|
|
38
|
+
sections.append(catalog_text)
|
|
39
|
+
return "\n".join(sections)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# conscio/agency/adapter.py
|
|
2
|
+
"""
|
|
3
|
+
InferenceAdapter — decouples the agentic core from any inference backend
|
|
4
|
+
(spec section 5.1 / blueprint section 7).
|
|
5
|
+
|
|
6
|
+
The engine never does HTTP itself; it talks to this interface. MockAdapter
|
|
7
|
+
is the deterministic backend used by the entire test suite.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from collections.abc import Sequence
|
|
14
|
+
from typing import Any, Callable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AdapterError(Exception):
|
|
18
|
+
"""Base error for inference failures."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AdapterTimeout(AdapterError):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AdapterConnectionError(AdapterError):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AdapterBadResponse(AdapterError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class InferenceResult:
|
|
35
|
+
text: str
|
|
36
|
+
tokens_in: int = 0
|
|
37
|
+
tokens_out: int = 0
|
|
38
|
+
latency_ms: int = 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class AdapterCaps:
|
|
43
|
+
model_name: str = "mock"
|
|
44
|
+
json_mode: bool = True
|
|
45
|
+
grammar: bool = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Meter:
|
|
50
|
+
"""Shared inference odometer — the binding budget reads this (P3)."""
|
|
51
|
+
calls: int = 0
|
|
52
|
+
tokens_in: int = 0
|
|
53
|
+
tokens_out: int = 0
|
|
54
|
+
latencies_ms: list[int] = field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def tokens(self) -> int:
|
|
58
|
+
return self.tokens_in + self.tokens_out
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class InferenceAdapter(ABC):
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def generate(self, prompt: str, *, schema: dict | None = None,
|
|
64
|
+
grammar: str | None = None, max_tokens: int = 512,
|
|
65
|
+
temperature: float = 0.2,
|
|
66
|
+
stop: list[str] | None = None) -> InferenceResult: ...
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def capabilities(self) -> AdapterCaps: ...
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MockAdapter(InferenceAdapter):
|
|
73
|
+
"""Scriptable adapter: returns queued responses, records every call.
|
|
74
|
+
|
|
75
|
+
Script entries may be callables (prompt -> str) so a mock can react
|
|
76
|
+
to prompt content — e.g. answer differently when few-shot exemplars
|
|
77
|
+
are present (the bench skill curve relies on this).
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self,
|
|
81
|
+
script: Sequence[str | Callable[[str], str]] | None = None,
|
|
82
|
+
caps: AdapterCaps | None = None):
|
|
83
|
+
self._script = list(script or [])
|
|
84
|
+
self._caps = caps or AdapterCaps()
|
|
85
|
+
self.calls: list[dict[str, Any]] = []
|
|
86
|
+
|
|
87
|
+
def generate(self, prompt: str, *, schema: dict | None = None,
|
|
88
|
+
grammar: str | None = None, max_tokens: int = 512,
|
|
89
|
+
temperature: float = 0.2,
|
|
90
|
+
stop: list[str] | None = None) -> InferenceResult:
|
|
91
|
+
self.calls.append({"prompt": prompt, "schema": schema,
|
|
92
|
+
"grammar": grammar, "max_tokens": max_tokens,
|
|
93
|
+
"temperature": temperature, "stop": stop})
|
|
94
|
+
if not self._script:
|
|
95
|
+
raise AdapterError("MockAdapter script exhausted")
|
|
96
|
+
entry = self._script.pop(0)
|
|
97
|
+
text = entry(prompt) if callable(entry) else entry
|
|
98
|
+
return InferenceResult(text=text, tokens_in=len(prompt) // 4,
|
|
99
|
+
tokens_out=len(text) // 4)
|
|
100
|
+
|
|
101
|
+
def capabilities(self) -> AdapterCaps:
|
|
102
|
+
return self._caps
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class MeteredAdapter(InferenceAdapter):
|
|
106
|
+
"""Transparent wrapper counting calls/tokens/latency on a Meter.
|
|
107
|
+
|
|
108
|
+
A failed call still debits one call: the budget pays for attempts.
|
|
109
|
+
`wrapped_name` keeps the real adapter class visible to the ledger.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, inner: InferenceAdapter, meter: Meter):
|
|
113
|
+
self.inner = inner
|
|
114
|
+
self.meter = meter
|
|
115
|
+
self.wrapped_name = type(inner).__name__
|
|
116
|
+
|
|
117
|
+
def generate(self, prompt: str, *, schema: dict | None = None,
|
|
118
|
+
grammar: str | None = None, max_tokens: int = 512,
|
|
119
|
+
temperature: float = 0.2,
|
|
120
|
+
stop: list[str] | None = None) -> InferenceResult:
|
|
121
|
+
self.meter.calls += 1
|
|
122
|
+
result = self.inner.generate(
|
|
123
|
+
prompt, schema=schema, grammar=grammar, max_tokens=max_tokens,
|
|
124
|
+
temperature=temperature, stop=stop)
|
|
125
|
+
self.meter.tokens_in += result.tokens_in
|
|
126
|
+
self.meter.tokens_out += result.tokens_out
|
|
127
|
+
self.meter.latencies_ms.append(result.latency_ms)
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
def capabilities(self) -> AdapterCaps:
|
|
131
|
+
return self.inner.capabilities()
|