Skip to content
Merged
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
42 changes: 42 additions & 0 deletions BRANCH_TRIAGE.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
branch ahead behind disposition
feat/state-integrity-graph-reasoning-stubs 2 1 INTEGRATED (427745d: tui.py + agent_machine_adapter.py + cli wiring) into integ/agent-term-backlog-batch-1 (PR#90); 2nd commit captured by #89 (identical); backed up to backup/*
copilot/implement-git-workspace-state-classifier 0 207 DELETED captured (fully merged)
feat/network-byom-native-assistant-events 0 217 DELETED captured (fully merged)
feat/office-commands 0 221 DELETED captured (fully merged)
ops-history-dry-run-contracts 0 213 DELETED captured (fully merged)
ops-history-dry-run-demo 0 206 DELETED captured (fully merged)
seed/agentterm-chatops-core 0 193 DELETED captured (fully merged)
sourceos/m1-repo-manifest 0 208 DELETED captured (fully merged)
work/agent-registry-adapter 0 230 DELETED captured (fully merged)
work/agent-registry-service-backend 0 209 DELETED captured (fully merged)
work/ci-smoke-runner 0 209 DELETED captured (fully merged)
work/cloudshell-agentplane-adapters 0 225 DELETED captured (fully merged)
work/dispatch-cli 0 216 DELETED captured (fully merged)
work/generated-interaction-types 0 202 DELETED captured (fully merged)
work/knowledge-adapters 0 226 DELETED captured (fully merged)
work/knowledge-adapters-current 0 227 DELETED captured (fully merged)
work/matrix-adapter-scaffold 0 229 DELETED captured (fully merged)
work/matrix-adapter-scaffold-rebased 0 228 DELETED captured (fully merged)
work/matrix-cli 0 214 DELETED captured (fully merged)
work/matrix-incremental-sync 0 212 DELETED captured (fully merged)
work/matrix-service-backend 0 214 DELETED captured (fully merged)
work/matrix-sync-state 0 96 DELETED captured (fully merged)
work/noetica-interaction-artifact-import 0 199 DELETED captured (fully merged)
work/operator-dispatch-pipeline 0 221 DELETED captured (fully merged)
work/operator-quickstart-docs 0 206 DELETED captured (fully merged)
work/operator-smoke-runner 0 207 DELETED captured (fully merged)
work/pipeline-config-runtime 0 217 DELETED captured (fully merged)
work/policy-fabric-admission 0 229 DELETED captured (fully merged)
work/policy-fabric-service-backend 0 208 DELETED captured (fully merged)
work/predispatch-boundary-v0 0 205 DELETED captured (fully merged)
work/predispatch-boundary-v1 0 203 DELETED captured (fully merged)
work/registered-participants 0 225 DELETED captured (fully merged)
work/release-polish 0 206 DELETED captured (fully merged)
work/service-health-check 0 206 DELETED captured (fully merged)
work/sourceos-contract-sync 0 202 DELETED captured (fully merged)
work/sourceos-interaction-reference-pointer 0 202 DELETED captured (fully merged)
work/sourceos-interaction-render-43 0 202 DELETED captured (fully merged)
work/tui-scaffold 0 226 DELETED captured (fully merged)
work/tui-scaffold-current 0 221 DELETED captured (fully merged)
work/workspace-adapters 0 229 DELETED captured (fully merged)
work/workspace-adapters-current 0 226 DELETED captured (fully merged)
74 changes: 73 additions & 1 deletion src/agent_term/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,34 @@ def build_parser() -> argparse.ArgumentParser:
sherlock_packet.add_argument("--topic")
sherlock_packet.add_argument("--thread-id")

office = subparsers.add_parser(
"office",
help="Record governed Office (prophet-workspace) artifact request events.",
)
office_sub = office.add_subparsers(dest="office_command", required=True)

office_deck = office_sub.add_parser("create-deck", help="Request Office slide-deck generation.")
office_deck.add_argument("channel")
office_deck.add_argument("--workroom", default="default")
office_deck.add_argument("--title", required=True)
office_deck.add_argument("--sender", default="@operator")
office_deck.add_argument("--thread-id")

office_inspect = office_sub.add_parser("inspect", help="Inspect an Office artifact (read-only).")
office_inspect.add_argument("channel")
office_inspect.add_argument("path")
office_inspect.add_argument("--workroom", default="default")
office_inspect.add_argument("--sender", default="@operator")
office_inspect.add_argument("--thread-id")

office_convert = office_sub.add_parser("convert", help="Request Office artifact conversion.")
office_convert.add_argument("channel")
office_convert.add_argument("path")
office_convert.add_argument("--to", dest="to_format", required=True)
office_convert.add_argument("--workroom", default="default")
office_convert.add_argument("--sender", default="@operator")
office_convert.add_argument("--thread-id")

subparsers.add_parser("shell", help="Open the minimal AgentTerm interactive shell.")

# --- state-integrity (#36) ---
Expand Down Expand Up @@ -342,6 +370,49 @@ def cmd_sherlock_packet(store: EventStore, args: argparse.Namespace) -> int:
return append_and_print(store, event)


def cmd_office(store: EventStore, args: argparse.Namespace) -> int:
if args.office_command == "create-deck":
event = make_plane_event(
plane="prophet-workspace",
kind="office_artifact_request",
channel=args.channel,
sender=args.sender,
body=f"Request Office slide-deck generation: {args.title}",
thread_id=args.thread_id,
metadata={"workroom": args.workroom, "artifact": "slide-deck", "title": args.title},
approval_required=True,
)
return append_and_print(store, event)

if args.office_command == "inspect":
event = make_plane_event(
plane="prophet-workspace",
kind="office_artifact_inspection",
channel=args.channel,
sender=args.sender,
body=f"Office artifact inspection: {args.path}",
thread_id=args.thread_id,
metadata={"workroom": args.workroom, "path": args.path},
approval_required=False,
)
return append_and_print(store, event)

if args.office_command == "convert":
event = make_plane_event(
plane="prophet-workspace",
kind="office_artifact_request",
channel=args.channel,
sender=args.sender,
body=f"Request Office conversion to {args.to_format}: {args.path}",
thread_id=args.thread_id,
metadata={"workroom": args.workroom, "path": args.path, "target_format": args.to_format},
approval_required=True,
)
return append_and_print(store, event)

raise SystemExit(f"unknown office command: {args.office_command}")


def cmd_shell(store: EventStore) -> int:
print("AgentTerm shell. Type /help for commands, /quit to exit.")
channel = DEFAULT_CHANNEL
Expand Down Expand Up @@ -550,7 +621,6 @@ def cmd_reasoning_run(args: argparse.Namespace) -> int:
def cmd_ops_history(args: argparse.Namespace) -> int:
"""OpsHistory control plane (issue #33)."""
sub = args.oh_command
json_mode = getattr(args, "json_mode", False)
if sub == "policy":
result = policy_explain(args.profile)
print(json.dumps(result, indent=2))
Expand Down Expand Up @@ -584,6 +654,8 @@ def main(argv: list[str] | None = None) -> int:
return cmd_request_shell(store, args)
if args.command == "sherlock-packet":
return cmd_sherlock_packet(store, args)
if args.command == "office":
return cmd_office(store, args)
if args.command == "shell":
return cmd_shell(store)
if args.command == "state-integrity":
Expand Down
30 changes: 30 additions & 0 deletions src/agent_term/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ class AgentRegistrationConfig:
fail_closed_when_registry_unavailable: bool = True
repository: str = "SocioProphet/agent-registry"
required_for: tuple[str, ...] = ()
endpoint_url: str | None = None
fixture_path: str | None = None
token_env: str = "AGENT_TERM_AGENT_REGISTRY_TOKEN"
timeout_seconds: float = 5.0


@dataclass(frozen=True)
class PolicyFabricConfig:
repository: str = "SocioProphet/policy-fabric"
endpoint_url: str | None = None
fixture_path: str | None = None
token_env: str = "AGENT_TERM_POLICY_FABRIC_TOKEN"
timeout_seconds: float = 5.0


@dataclass(frozen=True)
Expand Down Expand Up @@ -83,6 +96,7 @@ class AgentTermConfig:
event_store: EventStoreConfig = field(default_factory=EventStoreConfig)
matrix: MatrixConfig = field(default_factory=MatrixConfig)
agent_registration: AgentRegistrationConfig = field(default_factory=AgentRegistrationConfig)
policy_fabric: PolicyFabricConfig = field(default_factory=PolicyFabricConfig)
planes: dict[str, PlaneConfig] = field(default_factory=dict)
participants: dict[str, ParticipantConfig] = field(default_factory=dict)
local_runtime: LocalRuntimeFixture = field(default_factory=LocalRuntimeFixture)
Expand Down Expand Up @@ -120,6 +134,7 @@ def config_from_dict(raw: dict[str, Any]) -> AgentTermConfig:
event_store_raw = _dict(raw.get("eventStore"))
matrix_raw = _dict(raw.get("matrix"))
registration_raw = _dict(raw.get("agentRegistration"))
policy_fabric_raw = _dict(raw.get("policyFabric"))
participants_raw = _dict(raw.get("participants"))
planes_raw = _dict(raw.get("planes"))
local_runtime_raw = _dict(raw.get("localRuntime"))
Expand Down Expand Up @@ -158,6 +173,21 @@ def config_from_dict(raw: dict[str, Any]) -> AgentTermConfig:
),
repository=str(registration_raw.get("repository") or "SocioProphet/agent-registry"),
required_for=tuple(str(item) for item in _list(registration_raw.get("requiredFor"))),
endpoint_url=_optional_str(registration_raw.get("endpointUrl")),
fixture_path=_optional_str(registration_raw.get("fixturePath")),
token_env=str(
registration_raw.get("tokenEnv") or "AGENT_TERM_AGENT_REGISTRY_TOKEN"
),
timeout_seconds=float(registration_raw.get("timeoutSeconds") or 5.0),
),
policy_fabric=PolicyFabricConfig(
repository=str(policy_fabric_raw.get("repository") or "SocioProphet/policy-fabric"),
endpoint_url=_optional_str(policy_fabric_raw.get("endpointUrl")),
fixture_path=_optional_str(policy_fabric_raw.get("fixturePath")),
token_env=str(
policy_fabric_raw.get("tokenEnv") or "AGENT_TERM_POLICY_FABRIC_TOKEN"
),
timeout_seconds=float(policy_fabric_raw.get("timeoutSeconds") or 5.0),
),
planes=planes,
participants=participants,
Expand Down
19 changes: 14 additions & 5 deletions src/agent_term/dispatch_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from pathlib import Path

from agent_term.agent_registry import AgentRegistration, AgentRegistryAdapter
from agent_term.agent_registry import InMemoryAgentRegistryBackend, ToolGrant
from agent_term.agent_registry import AgentRegistryBackend, InMemoryAgentRegistryBackend, ToolGrant
from agent_term.agent_registry_service import build_agent_registry_backend_from_config
from agent_term.agentplane import AgentPlaneAdapter, InMemoryAgentPlaneBackend
from agent_term.cloudshell_fog import CloudShellFogAdapter, InMemoryCloudShellFogBackend
from agent_term.config import AgentTermConfig, load_config
Expand Down Expand Up @@ -38,8 +39,10 @@
InMemoryPolicyFabricBackend,
PolicyDecision,
PolicyFabricAdapter,
PolicyFabricBackend,
)
from agent_term.policy_fabric import action_for_event
from agent_term.policy_fabric_service import build_policy_fabric_backend_from_config
from agent_term.store import DEFAULT_DB_PATH, EventStore
from agent_term.workspace import (
InMemoryProphetWorkspaceBackend,
Expand Down Expand Up @@ -115,7 +118,7 @@ def build_event(args: argparse.Namespace, config: AgentTermConfig) -> AgentTermE
)


def build_registry_backend(args: argparse.Namespace, config: AgentTermConfig) -> InMemoryAgentRegistryBackend:
def build_registry_backend(args: argparse.Namespace, config: AgentTermConfig) -> AgentRegistryBackend:
agent_ids = set(config.local_runtime.registered_agents)
agent_ids.update(args.register_agent)
agent_id = args.agent_id or config.participant_agent_id(args.source)
Expand All @@ -132,7 +135,10 @@ def build_registry_backend(args: argparse.Namespace, config: AgentTermConfig) ->
for agent_id in sorted(agent_ids)
]
grants = [_parse_grant(raw) for raw in (*config.local_runtime.tool_grants, *args.grant)]
return InMemoryAgentRegistryBackend(agents=agents, grants=grants)
# A config-declared Agent Registry (file-backed or HTTP) is authoritative;
# the CLI-flag / local-runtime agents+grants serve as the fallback.
in_memory = InMemoryAgentRegistryBackend(agents=agents, grants=grants)
return build_agent_registry_backend_from_config(config, fallback=in_memory)


def _parse_grant(raw: str) -> ToolGrant:
Expand All @@ -148,7 +154,7 @@ def build_policy_backend(
args: argparse.Namespace,
event: AgentTermEvent,
config: AgentTermConfig,
) -> InMemoryPolicyFabricBackend:
) -> PolicyFabricBackend:
decisions: list[PolicyDecision] = []
for action in (*config.local_runtime.allow_policies, *args.allow_policy):
decisions.append(_decision(action, ALLOW, args.policy_ref))
Expand All @@ -162,7 +168,10 @@ def build_policy_backend(
elif args.sensitive_context and not decisions:
decisions.append(_decision(action_for_event(event), ALLOW, args.policy_ref))

return InMemoryPolicyFabricBackend(decisions)
# A config-declared Policy Fabric service (file-backed or HTTP) is authoritative;
# the CLI-flag / local-runtime decisions serve as the fallback when none is configured.
in_memory = InMemoryPolicyFabricBackend(decisions)
return build_policy_fabric_backend_from_config(config, fallback=in_memory)


def _decision(action: str, status: str, policy_ref: str, reason: str | None = None) -> PolicyDecision:
Expand Down
19 changes: 11 additions & 8 deletions src/agent_term/graph_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ def to_dict(self) -> dict[str, Any]:
],
"active_leases": [
{
"lease_ref": l.lease_ref,
"agent_ref": l.agent_ref,
"capability": l.capability,
"decision": l.decision,
"lease_ref": lease.lease_ref,
"agent_ref": lease.agent_ref,
"capability": lease.capability,
"decision": lease.decision,
}
for l in self.active_leases
for lease in self.active_leases
],
"recent_decisions_count": len(self.recent_decisions),
"memory_mesh_connected": self.memory_mesh_connected,
Expand Down Expand Up @@ -151,12 +151,15 @@ def format_agent_list(agents: list[AgentRegistryEntry], json_mode: bool = False)
def format_leases(leases: list[CapabilityLease], json_mode: bool = False) -> str:
if json_mode:
return json.dumps(
[{"lease_ref": l.lease_ref, "capability": l.capability, "decision": l.decision} for l in leases],
[
{"lease_ref": lease.lease_ref, "capability": lease.capability, "decision": lease.decision}
for lease in leases
],
indent=2,
)
if not leases:
return f"No active capability leases. (note: {STUB_NOTE})"
lines = ["Active capability leases:"]
for l in leases:
lines.append(f" {l.capability} {l.decision} {l.lease_ref}")
for lease in leases:
lines.append(f" {lease.capability} {lease.decision} {lease.lease_ref}")
return "\n".join(lines)
35 changes: 33 additions & 2 deletions src/agent_term/matrix_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
from agent_term.dispatch_cli import build_pipeline
from agent_term.events import AgentTermEvent
from agent_term.matrix_service import normalize_sync_payload
from agent_term.matrix_state import (
MatrixStateStore,
MatrixSyncState,
resolve_matrix_room,
rooms_from_sync_payload,
)
from agent_term.store import DEFAULT_DB_PATH, EventStore


Expand All @@ -22,6 +28,7 @@ def build_parser() -> argparse.ArgumentParser:
)
parser.add_argument("--config", help="Optional AgentTerm JSON config path.")
parser.add_argument("--db", help="Path to local AgentTerm SQLite event log.")
parser.add_argument("--state", help="Path to a Matrix sync state JSON file (room map + next_batch).")
subparsers = parser.add_subparsers(dest="command", required=True)

send = subparsers.add_parser("send", help="Send a Matrix message through the AgentTerm dispatch pipeline.")
Expand All @@ -41,6 +48,11 @@ def build_parser() -> argparse.ArgumentParser:
sync = subparsers.add_parser("normalize-sync", help="Normalize a Matrix /sync JSON payload.")
sync.add_argument("payload", help="Path to a Matrix sync payload JSON file, or '-' for stdin.")
sync.add_argument("--persist", action="store_true", help="Persist normalized events into EventStore.")
sync.add_argument(
"--save-state",
action="store_true",
help="Record the room map + next_batch cursor into the --state file.",
)
sync.add_argument("--sender", default="@agent-term")
sync.add_argument("--channel", default="!matrix-sync")

Expand All @@ -50,8 +62,14 @@ def build_parser() -> argparse.ArgumentParser:
def cmd_send(args: argparse.Namespace) -> int:
config = load_config(args.config)
db_path = Path(args.db or config.event_store.path or DEFAULT_DB_PATH)
state = (
MatrixStateStore(Path(args.state)).load()
if getattr(args, "state", None)
else MatrixSyncState.from_dict({})
)
room_id = resolve_matrix_room(args.room, config, state)
metadata: dict[str, object] = {
"matrix_room_id": args.room,
"matrix_room_id": room_id,
"msgtype": args.msgtype,
"policy_action": args.policy_action,
}
Expand All @@ -64,7 +82,7 @@ def cmd_send(args: argparse.Namespace) -> int:
metadata["matrix_e2ee_verified"] = bool(args.matrix_verified)

event = AgentTermEvent(
channel=args.room,
channel=room_id,
sender=args.sender,
kind="matrix_service_send",
source="matrix-service",
Expand Down Expand Up @@ -120,6 +138,19 @@ def cmd_normalize_sync(args: argparse.Namespace) -> int:
store.close()
print(f"persisted_events={len(events)}")

if args.save_state:
if not args.state:
raise SystemExit("--save-state requires --state <path>")
store_state = MatrixStateStore(Path(args.state))
state = store_state.load()
rooms = rooms_from_sync_payload(payload)
if rooms:
state = state.with_rooms(rooms)
if batch.next_batch:
state = state.with_next_batch(batch.next_batch)
store_state.save(state)
print(f"matrix_state_saved={args.state}")

for event in events:
print(f"{event.channel}\t{event.sender}\t{event.kind}\t{event.body}")
return 0
Expand Down
Loading
Loading